Skip to content

Commit e106eb0

Browse files
committed
PANA-8500: Map captured trees to wire models
1 parent 5621e00 commit e106eb0

2 files changed

Lines changed: 693 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sessionreplay.internal.composition
8+
9+
import com.datadog.android.sessionreplay.model.MobileSegment
10+
11+
internal sealed interface CaptureWireMappingResult<out T> {
12+
data class Success<T>(val value: T) : CaptureWireMappingResult<T>
13+
14+
data class Invalid(
15+
val failures: List<CaptureValidationFailure>
16+
) : CaptureWireMappingResult<Nothing>
17+
}
18+
19+
internal interface CapturedTreeWireMapper {
20+
fun mapFullSnapshot(
21+
snapshot: CapturedFullSnapshot
22+
): CaptureWireMappingResult<MobileSegment.MobileRecord.MobileFullSnapshotRecord>
23+
24+
fun mapMutation(
25+
mutation: CapturedMutationSet,
26+
base: CapturedFullSnapshot
27+
): CaptureWireMappingResult<MobileSegment.MobileRecord.MobileIncrementalSnapshotRecord>
28+
}
29+
30+
internal class DefaultCapturedTreeWireMapper(
31+
private val validator: CapturedTreeValidator = DefaultCapturedTreeValidator()
32+
) : CapturedTreeWireMapper {
33+
34+
override fun mapFullSnapshot(
35+
snapshot: CapturedFullSnapshot
36+
): CaptureWireMappingResult<MobileSegment.MobileRecord.MobileFullSnapshotRecord> {
37+
val validation = validator.validate(snapshot)
38+
if (validation is CaptureValidationResult.Invalid) {
39+
return CaptureWireMappingResult.Invalid(validation.failures)
40+
}
41+
42+
val root = checkNotNull(snapshot.root)
43+
val wireframes = snapshot.wireframes.map { captured ->
44+
captured.toWireframeOrNull() ?: return CaptureWireMappingResult.Invalid(
45+
listOf(
46+
CaptureValidationFailure(
47+
code = CaptureValidationErrorCode.UNRESOLVED_PIXEL_RESOURCE,
48+
identity = captured.identity
49+
)
50+
)
51+
)
52+
}
53+
val tree = MobileSegment.CompositionTree(
54+
root = root.toWireLayer(),
55+
layers = snapshot.layers.map { it.toWireLayer() }.ifEmpty { null }
56+
)
57+
return CaptureWireMappingResult.Success(
58+
MobileSegment.MobileRecord.MobileFullSnapshotRecord(
59+
timestamp = snapshot.timestamp,
60+
data = MobileSegment.Data(
61+
wireframes = wireframes,
62+
compositionTree = tree
63+
)
64+
)
65+
)
66+
}
67+
68+
override fun mapMutation(
69+
mutation: CapturedMutationSet,
70+
base: CapturedFullSnapshot
71+
): CaptureWireMappingResult<MobileSegment.MobileRecord.MobileIncrementalSnapshotRecord> {
72+
val validation = validator.validate(mutation, base)
73+
if (validation is CaptureValidationResult.Invalid) {
74+
return CaptureWireMappingResult.Invalid(validation.failures)
75+
}
76+
77+
val data = MobileSegment.MobileIncrementalData.CompositionTreeMutationData(
78+
root = mutation.root.mapOrNull { it.toWireLayer() },
79+
adds = mutation.adds.mapOrNull { layers -> layers.map { it.toWireLayer() } },
80+
removes = mutation.removes.mapOrNull { identities -> identities.map(CapturedIdentity::wireId) },
81+
updates = mutation.updates.mapOrNull { updates -> updates.map { it.toWireUpdate() } }
82+
)
83+
return CaptureWireMappingResult.Success(
84+
MobileSegment.MobileRecord.MobileIncrementalSnapshotRecord(
85+
timestamp = mutation.timestamp,
86+
data = data
87+
)
88+
)
89+
}
90+
91+
private fun CapturedLayer.toWireLayer(): MobileSegment.CompositionLayer =
92+
MobileSegment.CompositionLayer(
93+
id = identity.wireId,
94+
x = bounds.x,
95+
y = bounds.y,
96+
width = bounds.width,
97+
height = bounds.height,
98+
children = children.map { it.toWireChild() },
99+
modifiers = modifiers.map { it.toWireModifier() }.ifEmpty { null },
100+
compositeOperation = compositeOperation?.toWireCompositeOperation()
101+
)
102+
103+
private fun CapturedLayerUpdate.toWireUpdate(): MobileSegment.CompositionLayerUpdate =
104+
MobileSegment.CompositionLayerUpdate(
105+
id = identity.wireId,
106+
x = x.mapOrNull { it },
107+
y = y.mapOrNull { it },
108+
width = width.mapOrNull { it },
109+
height = height.mapOrNull { it },
110+
children = children.mapOrNull { children -> children.map { it.toWireChild() } },
111+
modifiers = modifiers.mapOrNull { modifiers -> modifiers.map { it.toWireModifier() } },
112+
compositeOperation = compositeOperation.mapOrNull {
113+
it?.toWireCompositeOperation() ?: MobileSegment.CompositeOperation.SOURCEOVER
114+
}
115+
)
116+
117+
private fun CapturedWireframe.toWireframeOrNull(): MobileSegment.Wireframe? = when (this) {
118+
is CapturedWireframe.Shape -> MobileSegment.Wireframe.ShapeWireframe(
119+
id = identity.wireId,
120+
x = bounds.x,
121+
y = bounds.y,
122+
width = bounds.width,
123+
height = bounds.height,
124+
clip = clip?.toWireClip(),
125+
shapeStyle = style?.toWireShapeStyle(),
126+
border = border?.toWireBorder(),
127+
permanentId = permanentId
128+
)
129+
130+
is CapturedWireframe.Text -> MobileSegment.Wireframe.TextWireframe(
131+
id = identity.wireId,
132+
x = bounds.x,
133+
y = bounds.y,
134+
width = bounds.width,
135+
height = bounds.height,
136+
clip = clip?.toWireClip(),
137+
shapeStyle = style?.toWireShapeStyle(),
138+
border = border?.toWireBorder(),
139+
text = text,
140+
textStyle = textStyle.toWireTextStyle(),
141+
textPosition = textPosition?.toWireTextPosition(),
142+
permanentId = permanentId
143+
)
144+
145+
is CapturedWireframe.Pixel -> (resource as? PixelResource.Resolved)
146+
?.takeIf { it.resourceId.isNotBlank() }
147+
?.let { resolvedResource ->
148+
MobileSegment.Wireframe.ImageWireframe(
149+
id = identity.wireId,
150+
x = bounds.x,
151+
y = bounds.y,
152+
width = bounds.width,
153+
height = bounds.height,
154+
clip = clip?.toWireClip(),
155+
shapeStyle = style?.toWireShapeStyle(),
156+
border = border?.toWireBorder(),
157+
resourceId = resolvedResource.resourceId,
158+
mimeType = resolvedResource.mimeType,
159+
isEmpty = isEmpty,
160+
permanentId = permanentId
161+
)
162+
}
163+
164+
is CapturedWireframe.PrivacyPlaceholder -> MobileSegment.Wireframe.PlaceholderWireframe(
165+
id = identity.wireId,
166+
x = bounds.x,
167+
y = bounds.y,
168+
width = bounds.width,
169+
height = bounds.height,
170+
clip = clip?.toWireClip(),
171+
label = label,
172+
permanentId = permanentId
173+
)
174+
175+
is CapturedWireframe.WebView -> MobileSegment.Wireframe.WebviewWireframe(
176+
id = identity.wireId,
177+
x = bounds.x,
178+
y = bounds.y,
179+
width = bounds.width,
180+
height = bounds.height,
181+
clip = clip?.toWireClip(),
182+
shapeStyle = style?.toWireShapeStyle(),
183+
border = border?.toWireBorder(),
184+
slotId = slotId,
185+
isVisible = isVisible,
186+
permanentId = permanentId
187+
)
188+
189+
is CapturedWireframe.EmbeddedContent -> MobileSegment.Wireframe.EmbeddedContentWireframe(
190+
id = identity.wireId,
191+
x = bounds.x,
192+
y = bounds.y,
193+
width = bounds.width,
194+
height = bounds.height,
195+
clip = clip?.toWireClip(),
196+
shapeStyle = style?.toWireShapeStyle(),
197+
border = border?.toWireBorder(),
198+
slotId = slotId,
199+
isVisible = isVisible,
200+
permanentId = permanentId
201+
)
202+
}
203+
204+
private fun CapturedClip.toWireClip() = MobileSegment.WireframeClip(top, bottom, left, right)
205+
206+
private fun CapturedShapeStyle.toWireShapeStyle() = MobileSegment.ShapeStyle(
207+
backgroundColor = backgroundColor,
208+
backgroundGradient = backgroundGradient?.toWireGradient(),
209+
opacity = opacity,
210+
cornerRadius = cornerRadius
211+
)
212+
213+
private fun CapturedBackgroundGradient.toWireGradient() = MobileSegment.BackgroundGradient(
214+
stops = stops.map { MobileSegment.ShapeGradientStop(it.color, it.offset) },
215+
startPoint = MobileSegment.StartPoint(startPoint.x, startPoint.y),
216+
endPoint = MobileSegment.StartPoint(endPoint.x, endPoint.y)
217+
)
218+
219+
private fun CapturedShapeBorder.toWireBorder() = MobileSegment.ShapeBorder(color, width)
220+
221+
private fun CapturedTextStyle.toWireTextStyle() = MobileSegment.TextStyle(
222+
family = family,
223+
size = size,
224+
color = color,
225+
truncationMode = truncationMode?.toWireTruncationMode()
226+
)
227+
228+
private fun CapturedTruncationMode.toWireTruncationMode() = when (this) {
229+
CapturedTruncationMode.CLIP -> MobileSegment.TruncationMode.CLIP
230+
CapturedTruncationMode.HEAD -> MobileSegment.TruncationMode.HEAD
231+
CapturedTruncationMode.TAIL -> MobileSegment.TruncationMode.TAIL
232+
CapturedTruncationMode.MIDDLE -> MobileSegment.TruncationMode.MIDDLE
233+
}
234+
235+
private fun CapturedTextPosition.toWireTextPosition() = MobileSegment.TextPosition(
236+
padding = padding?.let { MobileSegment.Padding(it.top, it.bottom, it.left, it.right) },
237+
alignment = alignment?.let {
238+
MobileSegment.Alignment(
239+
horizontal = it.horizontal?.toWireHorizontal(),
240+
vertical = it.vertical?.toWireVertical()
241+
)
242+
}
243+
)
244+
245+
private fun CapturedHorizontalAlignment.toWireHorizontal() = when (this) {
246+
CapturedHorizontalAlignment.LEFT -> MobileSegment.Horizontal.LEFT
247+
CapturedHorizontalAlignment.CENTER -> MobileSegment.Horizontal.CENTER
248+
CapturedHorizontalAlignment.RIGHT -> MobileSegment.Horizontal.RIGHT
249+
}
250+
251+
private fun CapturedVerticalAlignment.toWireVertical() = when (this) {
252+
CapturedVerticalAlignment.TOP -> MobileSegment.Vertical.TOP
253+
CapturedVerticalAlignment.CENTER -> MobileSegment.Vertical.CENTER
254+
CapturedVerticalAlignment.BOTTOM -> MobileSegment.Vertical.BOTTOM
255+
}
256+
257+
private fun CapturedChild.toWireChild(): MobileSegment.CompositionLayerChild = when (this) {
258+
is CapturedChild.Layer -> MobileSegment.CompositionLayerChild(
259+
type = MobileSegment.Type.LAYER,
260+
id = identity.wireId
261+
)
262+
263+
is CapturedChild.Wireframe -> MobileSegment.CompositionLayerChild(
264+
type = MobileSegment.Type.WIREFRAME,
265+
id = identity.wireId
266+
)
267+
}
268+
269+
private fun CapturedModifier.toWireModifier(): MobileSegment.CompositionLayerModifier = when (this) {
270+
is CapturedModifier.BrightnessBias ->
271+
MobileSegment.CompositionLayerModifier.CompositionLayerBrightnessBiasModifier(value)
272+
273+
is CapturedModifier.Clip ->
274+
MobileSegment.CompositionLayerModifier.CompositionLayerClipModifier(
275+
path = path,
276+
fillRule = fillRule?.toWireFillRule()
277+
)
278+
279+
is CapturedModifier.ColorMatrix ->
280+
MobileSegment.CompositionLayerModifier.CompositionLayerColorMatrixModifier(values)
281+
282+
is CapturedModifier.GaussianBlur ->
283+
MobileSegment.CompositionLayerModifier.CompositionLayerGaussianBlurModifier(radius)
284+
285+
is CapturedModifier.MaskImage ->
286+
MobileSegment.CompositionLayerModifier.CompositionLayerMaskImageModifier(resourceId)
287+
288+
is CapturedModifier.Opacity ->
289+
MobileSegment.CompositionLayerModifier.CompositionLayerOpacityModifier(value)
290+
291+
is CapturedModifier.Saturate ->
292+
MobileSegment.CompositionLayerModifier.CompositionLayerSaturateModifier(value)
293+
294+
is CapturedModifier.Shadow ->
295+
MobileSegment.CompositionLayerModifier.CompositionLayerShadowModifier(
296+
color = color,
297+
offsetX = offsetX,
298+
offsetY = offsetY,
299+
radius = radius,
300+
path = path
301+
)
302+
}
303+
304+
private fun CapturedFillRule.toWireFillRule(): MobileSegment.FillRule = when (this) {
305+
CapturedFillRule.EVEN_ODD -> MobileSegment.FillRule.EVENODD
306+
CapturedFillRule.NON_ZERO -> MobileSegment.FillRule.NONZERO
307+
}
308+
309+
private fun CapturedCompositeOperation.toWireCompositeOperation(): MobileSegment.CompositeOperation =
310+
when (this) {
311+
CapturedCompositeOperation.DESTINATION_IN -> MobileSegment.CompositeOperation.DESTINATIONIN
312+
CapturedCompositeOperation.DESTINATION_OUT -> MobileSegment.CompositeOperation.DESTINATIONOUT
313+
CapturedCompositeOperation.PLUS_DARKER -> MobileSegment.CompositeOperation.PLUSDARKER
314+
CapturedCompositeOperation.SOURCE_OVER -> MobileSegment.CompositeOperation.SOURCEOVER
315+
}
316+
317+
private fun <T, R> CapturedChange<T>.mapOrNull(transform: (T) -> R): R? =
318+
when (this) {
319+
is CapturedChange.Set -> transform(value)
320+
CapturedChange.Unchanged -> null
321+
}
322+
}

0 commit comments

Comments
 (0)