From 4312745cb0f1abd2cde186465167fa48a2096d79 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 11 May 2026 18:09:11 +0200 Subject: [PATCH 1/9] chore(sr): slider_recorder.dart added --- .../material_widgets/slider_recorder.dart | 455 ++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart new file mode 100644 index 00000000..dbf7da31 --- /dev/null +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -0,0 +1,455 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-Present Datadog, Inc. + +import 'dart:math' as math; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../../extensions.dart'; +import '../../../sr_data_models.dart'; +import '../../capture_node.dart'; +import '../../recorder.dart'; +import '../../view_tree_snapshot.dart'; +import '../recording_extensions.dart'; + +enum _SliderThumbStyle { round, handle } + +typedef _SliderThumbGeometry = ({ + Rect rect, + BorderRadius borderRadius, + _SliderThumbStyle style, +}); + +typedef _SliderTrackSegmentGeometry = ({ + Rect rect, + BorderRadius borderRadius, +}); + +typedef _SliderGeometry = ({ + _SliderThumbGeometry thumb, + _SliderTrackSegmentGeometry inactiveTrack, + _SliderTrackSegmentGeometry activeTrack, + _SliderTrackSegmentGeometry? secondaryActiveTrack, +}); + +/// Detects 'Slider' widgets and places an slider +/// in SessionReplay. +class SliderRecorder implements ElementRecorder { + final KeyGenerator keyGenerator; + + const SliderRecorder(this.keyGenerator); + + @override + bool accepts(Widget widget) => widget is Slider; + + @override + CaptureNodeSemantics? captureSemantics( + Element element, + CapturedViewAttributes attributes, + TreeCapturePrivacy capturePrivacy, + ) { + // Check for cupertino slider style + { + bool isCupertinoAdaptive = false; + element.visitChildElements((child) { + if (child.widget is CupertinoSlider) isCupertinoAdaptive = true; + }); + if (isCupertinoAdaptive) return null; + } + + final widget = element.widget; + if (widget is! Slider) return null; + + // Resolves for privacy settings + final bool isMasked = capturePrivacy.shouldMaskInputs; + + // Resolve slider theme for colors + final ThemeData theme = Theme.of(element); + + final bool year2023 = + widget.year2023 ?? theme.sliderTheme.year2023 ?? true; + + final isEnabled = widget.onChanged != null; + + final Color activeColor = _getActiveColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + year2023: year2023 + ); + final Color inactiveColor = _getInactiveColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + year2023: year2023 + ); + final Color secondaryActiveColor = _getSecondaryActiveColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + year2023: year2023 + ); + final Color thumbColor = _getThumbColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + year2023: year2023 + ); + + final _SliderGeometry geometry = _getSliderGeometry( + widget: widget, + theme: theme, + year2023: year2023, + bounds: attributes.paintBounds, + scaleX: attributes.scaleX, + scaleY: attributes.scaleY, + ); + + final inactiveTrackKey = + keyGenerator.keyForElement(element, wireframeId: 0); + final secondaryActiveTrackKey = + keyGenerator.keyForElement(element, wireframeId: 1); + final activeTrackKey = + keyGenerator.keyForElement(element, wireframeId: 2); + final thumbKey = + keyGenerator.keyForElement(element, wireframeId: 3); + + final node = SliderNode( + attributes, + inactiveTrackWireframeId: inactiveTrackKey, + secondaryActiveTrackWireframeId: secondaryActiveTrackKey, + activeTrackWireframeId: activeTrackKey, + thumbWireframeId: thumbKey, + inactiveTrackRect: geometry.inactiveTrack.rect, + activeTrackRect: geometry.activeTrack.rect, + secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, + thumbRect: geometry.thumb.rect, + activeColor: activeColor, + inactiveColor: inactiveColor, + secondaryActiveColor: secondaryActiveColor, + thumbColor: thumbColor, + ); + + return SpecificElement( + subtreeStrategy: CaptureNodeSubtreeStrategy + .ignore, // Ignore subtree to prevent CustomPaintRecorder from capturing the inner CustomPaint + nodes: [node], + ); + } + + Color _getActiveColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required bool year2023, + }) { + if (isEnabled) return widget.activeColor ?? theme.sliderTheme.activeTrackColor ?? theme.colorScheme.primary; + Color? disabledColor = theme.sliderTheme.disabledActiveTrackColor; + if (disabledColor != null) return disabledColor; + if (theme.useMaterial3) return theme.colorScheme.onSurface.withValues(alpha: 0.38); + return theme.colorScheme.onSurface.withValues(alpha: 0.32); + } + + Color _getInactiveColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required bool year2023, + }) { + if (isEnabled) { + Color? inactiveColor = widget.inactiveColor ?? theme.sliderTheme.inactiveTrackColor; + if (inactiveColor != null) return inactiveColor; + if (theme.useMaterial3) { + if (year2023) return theme.colorScheme.surfaceContainerHighest; + return theme.colorScheme.secondaryContainer; + } + return theme.colorScheme.primary.withValues(alpha: 0.24); + } + return theme.colorScheme.onSurface.withValues(alpha: 0.12); + } + + Color _getSecondaryActiveColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required bool year2023, + }) { + if (isEnabled) { + return widget.secondaryActiveColor ?? + theme.sliderTheme.secondaryActiveTrackColor ?? + theme.colorScheme.primary.withValues(alpha: 0.54); + } + Color? disabledColor = theme.sliderTheme.disabledSecondaryActiveTrackColor; + if (disabledColor != null) return disabledColor; + if (theme.useMaterial3 && !year2023) { + return theme.colorScheme.onSurface.withValues(alpha: 0.38); + } + return theme.colorScheme.onSurface.withValues(alpha: 0.12); + } + + Color _getThumbColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required bool year2023, + }) { + if (isEnabled) { + return widget.thumbColor ?? + widget.activeColor ?? + theme.sliderTheme.thumbColor ?? + theme.colorScheme.primary; + } + Color? disabledColor = theme.sliderTheme.disabledThumbColor; + if (disabledColor != null) return disabledColor; + if (theme.useMaterial3 && !year2023) { + return theme.colorScheme.onSurface.withValues(alpha: 0.38); + } + return Color.alphaBlend( + theme.colorScheme.onSurface.withValues(alpha: 0.38), + theme.colorScheme.surface, + ); + } + + _SliderGeometry _getSliderGeometry({ + required Slider widget, + required ThemeData theme, + required bool year2023, + required Rect bounds, + required double scaleX, + required double scaleY, + }) { + final SliderThemeData sliderTheme = theme.sliderTheme; + final bool isGapped = theme.useMaterial3 && !year2023; + + // Uniform scale preserves circles/pills and ensures dimensions fit within + // anisotropic bounds. + final double scale = math.min(scaleX, scaleY); + + final double trackHeight = + (sliderTheme.trackHeight ?? (isGapped ? 16.0 : 4.0)) * scale; + + final _SliderThumbStyle thumbStyle; + final Size thumbSize; + if (isGapped) { + thumbStyle = _SliderThumbStyle.handle; + final Size logicalThumbSize = + sliderTheme.thumbSize?.resolve({}) ?? + const Size(4.0, 44.0); + thumbSize = Size( + logicalThumbSize.width * scale, + logicalThumbSize.height * scale, + ); + } else { + thumbStyle = _SliderThumbStyle.round; + thumbSize = Size(20.0 * scale, 20.0 * scale); + } + + // RoundSliderOverlayShape default radius is 24 → 48px diameter. + final double overlayWidth = 48.0 * scale; + final double horizontalInset = sliderTheme.padding != null + ? 0.0 + : math.max(thumbSize.width, overlayWidth) / 2; + + final double trackLeft = bounds.left + horizontalInset; + final double trackRight = bounds.right - horizontalInset; + final double trackTop = bounds.center.dy - trackHeight / 2; + final double trackBottom = trackTop + trackHeight; + final double trackWidth = trackRight - trackLeft; + + final double range = widget.max - widget.min; + final double valueRatio = range == 0 + ? 0.0 + : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble(); + final double thumbCenterX = trackLeft + trackWidth * valueRatio; + + final Radius trackEndRadius = Radius.circular(trackHeight / 2); + final Radius trackInsideRadius = Radius.circular(2.0 * scale); + final double trackGap = + isGapped ? (sliderTheme.trackGap ?? 6.0) * scale : 0.0; + + final _SliderTrackSegmentGeometry activeTrack; + if (isGapped) { + final double activeRight = + math.max(trackLeft, thumbCenterX - trackGap); + activeTrack = ( + rect: Rect.fromLTRB(trackLeft, trackTop, activeRight, trackBottom), + borderRadius: BorderRadius.only( + topLeft: trackEndRadius, + bottomLeft: trackEndRadius, + topRight: trackInsideRadius, + bottomRight: trackInsideRadius, + ), + ); + } else { + activeTrack = ( + rect: Rect.fromLTRB( + trackLeft, + trackTop, + thumbCenterX + trackHeight / 2, + trackBottom, + ), + borderRadius: BorderRadius.all(trackEndRadius), + ); + } + + final _SliderTrackSegmentGeometry inactiveTrack; + if (isGapped) { + final double inactiveLeft = + math.min(trackRight, thumbCenterX + trackGap); + inactiveTrack = ( + rect: Rect.fromLTRB(inactiveLeft, trackTop, trackRight, trackBottom), + borderRadius: BorderRadius.only( + topLeft: trackInsideRadius, + bottomLeft: trackInsideRadius, + topRight: trackEndRadius, + bottomRight: trackEndRadius, + ), + ); + } else { + inactiveTrack = ( + rect: Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), + borderRadius: BorderRadius.all(trackEndRadius), + ); + } + + _SliderTrackSegmentGeometry? secondaryActiveTrack; + final double? secValue = widget.secondaryTrackValue; + if (secValue != null) { + final double clampedSec = + secValue.clamp(widget.min, widget.max).toDouble(); + final double secRatio = + range == 0 ? 0.0 : (clampedSec - widget.min) / range; + final double secX = trackLeft + trackWidth * secRatio; + if (isGapped) { + final double secLeft = thumbCenterX + trackGap; + if (secX > secLeft) { + secondaryActiveTrack = ( + rect: Rect.fromLTRB(secLeft, trackTop, secX, trackBottom), + borderRadius: BorderRadius.only( + topLeft: trackInsideRadius, + bottomLeft: trackInsideRadius, + topRight: trackEndRadius, + bottomRight: trackEndRadius, + ), + ); + } + } else if (secX > thumbCenterX) { + secondaryActiveTrack = ( + rect: Rect.fromLTRB(thumbCenterX, trackTop, secX, trackBottom), + borderRadius: BorderRadius.only( + topRight: trackEndRadius, + bottomRight: trackEndRadius, + ), + ); + } + } + + final Rect thumbRect = Rect.fromCenter( + center: Offset(thumbCenterX, bounds.center.dy), + width: thumbSize.width, + height: thumbSize.height, + ); + final _SliderThumbGeometry thumb = ( + rect: thumbRect, + borderRadius: + BorderRadius.all(Radius.circular(thumbSize.shortestSide / 2)), + style: thumbStyle, + ); + + return ( + thumb: thumb, + inactiveTrack: inactiveTrack, + activeTrack: activeTrack, + secondaryActiveTrack: secondaryActiveTrack, + ); + } +} + +/// Holds the resolved visual properties of a [Slider] widget and builds the +/// corresponding [SRShapeWireframe]s: an inactive track (full background), +/// an optional secondary active segment, an active segment, and a thumb on +/// top. Each piece is rendered as a pill (cornerRadius = shortestSide / 2). +@immutable +class SliderNode extends CaptureNode { + final int inactiveTrackWireframeId; + final int secondaryActiveTrackWireframeId; + final int activeTrackWireframeId; + final int thumbWireframeId; + final Rect inactiveTrackRect; + final Rect activeTrackRect; + final Rect? secondaryActiveTrackRect; + final Rect thumbRect; + final Color activeColor; + final Color inactiveColor; + final Color secondaryActiveColor; + final Color thumbColor; + + const SliderNode( + super.attributes, { + required this.inactiveTrackWireframeId, + required this.secondaryActiveTrackWireframeId, + required this.activeTrackWireframeId, + required this.thumbWireframeId, + required this.inactiveTrackRect, + required this.activeTrackRect, + required this.secondaryActiveTrackRect, + required this.thumbRect, + required this.activeColor, + required this.inactiveColor, + required this.secondaryActiveColor, + required this.thumbColor, + }); + + @override + List buildWireframes() { + final wireframes = [ + _shape( + id: inactiveTrackWireframeId, + rect: inactiveTrackRect, + color: inactiveColor, + ), + ]; + + if (secondaryActiveTrackRect != null) { + wireframes.add(_shape( + id: secondaryActiveTrackWireframeId, + rect: secondaryActiveTrackRect!, + color: secondaryActiveColor, + )); + } + + wireframes.add(_shape( + id: activeTrackWireframeId, + rect: activeTrackRect, + color: activeColor, + )); + + wireframes.add(_shape( + id: thumbWireframeId, + rect: thumbRect, + color: thumbColor, + )); + + return wireframes; + } + + static SRShapeWireframe _shape({ + required int id, + required Rect rect, + required Color color, + }) { + return SRShapeWireframe( + id: id, + x: rect.left.round(), + y: rect.top.round(), + width: rect.width.round(), + height: rect.height.round(), + shapeStyle: SRShapeStyle( + backgroundColor: color.toHexString(), + cornerRadius: rect.shortestSide / 2, + ), + ); + } +} From 329d389cade47c71d11ed1787a197035b57ac3d0 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Fri, 15 May 2026 10:54:36 +0200 Subject: [PATCH 2/9] Material slider recorder version 1 --- .../material_widgets/slider_recorder.dart | 251 +++++++++++------- .../lib/src/capture/recorder.dart | 2 + 2 files changed, 158 insertions(+), 95 deletions(-) diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index dbf7da31..5f762364 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -12,7 +12,7 @@ import '../../../sr_data_models.dart'; import '../../capture_node.dart'; import '../../recorder.dart'; import '../../view_tree_snapshot.dart'; -import '../recording_extensions.dart'; +//import '../recording_extensions.dart'; enum _SliderThumbStyle { round, handle } @@ -32,6 +32,7 @@ typedef _SliderGeometry = ({ _SliderTrackSegmentGeometry inactiveTrack, _SliderTrackSegmentGeometry activeTrack, _SliderTrackSegmentGeometry? secondaryActiveTrack, + Rect? gap, }); /// Detects 'Slider' widgets and places an slider @@ -63,57 +64,75 @@ class SliderRecorder implements ElementRecorder { if (widget is! Slider) return null; // Resolves for privacy settings - final bool isMasked = capturePrivacy.shouldMaskInputs; + //final bool isMasked = capturePrivacy.shouldMaskInputs; - // Resolve slider theme for colors + // Resolve slider theme. SliderTheme.of honors a local SliderTheme widget + // (an InheritedWidget); ThemeData.sliderTheme is only the global default + // and misses widget-tree overrides like SliderTheme(data: ..., child: ...). final ThemeData theme = Theme.of(element); + final SliderThemeData sliderTheme = SliderTheme.of(element); - final bool year2023 = - widget.year2023 ?? theme.sliderTheme.year2023 ?? true; + final bool year2023 = switch (theme.useMaterial3) { + true => widget.year2023 ?? sliderTheme.year2023 ?? true, + false => false, + }; final isEnabled = widget.onChanged != null; final Color activeColor = _getActiveColor( - widget: widget, - isEnabled: isEnabled, - theme: theme, - year2023: year2023 + widget: widget, + isEnabled: isEnabled, + theme: theme, + sliderTheme: sliderTheme, + year2023: year2023, ); final Color inactiveColor = _getInactiveColor( widget: widget, isEnabled: isEnabled, theme: theme, - year2023: year2023 + sliderTheme: sliderTheme, + year2023: year2023, ); final Color secondaryActiveColor = _getSecondaryActiveColor( widget: widget, isEnabled: isEnabled, theme: theme, - year2023: year2023 + sliderTheme: sliderTheme, + year2023: year2023, ); final Color thumbColor = _getThumbColor( widget: widget, isEnabled: isEnabled, theme: theme, - year2023: year2023 + sliderTheme: sliderTheme, + year2023: year2023, ); final _SliderGeometry geometry = _getSliderGeometry( widget: widget, theme: theme, + sliderTheme: sliderTheme, year2023: year2023, bounds: attributes.paintBounds, scaleX: attributes.scaleX, scaleY: attributes.scaleY, ); + // Only walk the tree for a background color when we actually need to fake + // the gap (M3-2024 / year2023 == false). Round thumbs cover the seam + // themselves, so no overpaint is needed. + final Color? gapColor = + geometry.gap != null ? _findBackgroundColor(element, theme) : null; + final inactiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 0); final secondaryActiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 1); final activeTrackKey = keyGenerator.keyForElement(element, wireframeId: 2); - final thumbKey = + final gapKey = + keyGenerator.keyForElement(element, wireframeId: 4); + final thumbKey = keyGenerator.keyForElement(element, wireframeId: 3); final node = SliderNode( @@ -121,14 +140,17 @@ class SliderRecorder implements ElementRecorder { inactiveTrackWireframeId: inactiveTrackKey, secondaryActiveTrackWireframeId: secondaryActiveTrackKey, activeTrackWireframeId: activeTrackKey, + gapWireframeId: gapKey, thumbWireframeId: thumbKey, inactiveTrackRect: geometry.inactiveTrack.rect, activeTrackRect: geometry.activeTrack.rect, secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, + gapRect: geometry.gap, thumbRect: geometry.thumb.rect, activeColor: activeColor, inactiveColor: inactiveColor, secondaryActiveColor: secondaryActiveColor, + gapColor: gapColor, thumbColor: thumbColor, ); @@ -143,10 +165,11 @@ class SliderRecorder implements ElementRecorder { required Slider widget, required bool isEnabled, required ThemeData theme, + required SliderThemeData sliderTheme, required bool year2023, }) { - if (isEnabled) return widget.activeColor ?? theme.sliderTheme.activeTrackColor ?? theme.colorScheme.primary; - Color? disabledColor = theme.sliderTheme.disabledActiveTrackColor; + if (isEnabled) return widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary; + Color? disabledColor = sliderTheme.disabledActiveTrackColor; if (disabledColor != null) return disabledColor; if (theme.useMaterial3) return theme.colorScheme.onSurface.withValues(alpha: 0.38); return theme.colorScheme.onSurface.withValues(alpha: 0.32); @@ -156,10 +179,11 @@ class SliderRecorder implements ElementRecorder { required Slider widget, required bool isEnabled, required ThemeData theme, + required SliderThemeData sliderTheme, required bool year2023, }) { if (isEnabled) { - Color? inactiveColor = widget.inactiveColor ?? theme.sliderTheme.inactiveTrackColor; + Color? inactiveColor = widget.inactiveColor ?? sliderTheme.inactiveTrackColor; if (inactiveColor != null) return inactiveColor; if (theme.useMaterial3) { if (year2023) return theme.colorScheme.surfaceContainerHighest; @@ -174,14 +198,15 @@ class SliderRecorder implements ElementRecorder { required Slider widget, required bool isEnabled, required ThemeData theme, + required SliderThemeData sliderTheme, required bool year2023, }) { if (isEnabled) { return widget.secondaryActiveColor ?? - theme.sliderTheme.secondaryActiveTrackColor ?? + sliderTheme.secondaryActiveTrackColor ?? theme.colorScheme.primary.withValues(alpha: 0.54); } - Color? disabledColor = theme.sliderTheme.disabledSecondaryActiveTrackColor; + Color? disabledColor = sliderTheme.disabledSecondaryActiveTrackColor; if (disabledColor != null) return disabledColor; if (theme.useMaterial3 && !year2023) { return theme.colorScheme.onSurface.withValues(alpha: 0.38); @@ -193,15 +218,16 @@ class SliderRecorder implements ElementRecorder { required Slider widget, required bool isEnabled, required ThemeData theme, + required SliderThemeData sliderTheme, required bool year2023, }) { if (isEnabled) { return widget.thumbColor ?? widget.activeColor ?? - theme.sliderTheme.thumbColor ?? + sliderTheme.thumbColor ?? theme.colorScheme.primary; } - Color? disabledColor = theme.sliderTheme.disabledThumbColor; + Color? disabledColor = sliderTheme.disabledThumbColor; if (disabledColor != null) return disabledColor; if (theme.useMaterial3 && !year2023) { return theme.colorScheme.onSurface.withValues(alpha: 0.38); @@ -215,12 +241,12 @@ class SliderRecorder implements ElementRecorder { _SliderGeometry _getSliderGeometry({ required Slider widget, required ThemeData theme, + required SliderThemeData sliderTheme, required bool year2023, required Rect bounds, required double scaleX, required double scaleY, }) { - final SliderThemeData sliderTheme = theme.sliderTheme; final bool isGapped = theme.useMaterial3 && !year2023; // Uniform scale preserves circles/pills and ensures dimensions fit within @@ -246,11 +272,15 @@ class SliderRecorder implements ElementRecorder { thumbSize = Size(20.0 * scale, 20.0 * scale); } - // RoundSliderOverlayShape default radius is 24 → 48px diameter. + // Round thumbs reserve room for the 48px overlay halo; handle thumbs have + // no halo so only the thumb half-width is reserved. final double overlayWidth = 48.0 * scale; - final double horizontalInset = sliderTheme.padding != null - ? 0.0 - : math.max(thumbSize.width, overlayWidth) / 2; + final double horizontalInset; + if (sliderTheme.padding != null) { + horizontalInset = 0.0; + } else { + horizontalInset = math.max(thumbSize.width, overlayWidth) / 2; + } final double trackLeft = bounds.left + horizontalInset; final double trackRight = bounds.right - horizontalInset; @@ -258,61 +288,36 @@ class SliderRecorder implements ElementRecorder { final double trackBottom = trackTop + trackHeight; final double trackWidth = trackRight - trackLeft; + final Radius trackEndRadius = Radius.circular(trackHeight / 2); + final double range = widget.max - widget.min; final double valueRatio = range == 0 ? 0.0 : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble(); - final double thumbCenterX = trackLeft + trackWidth * valueRatio; - - final Radius trackEndRadius = Radius.circular(trackHeight / 2); - final Radius trackInsideRadius = Radius.circular(2.0 * scale); - final double trackGap = - isGapped ? (sliderTheme.trackGap ?? 6.0) * scale : 0.0; - - final _SliderTrackSegmentGeometry activeTrack; - if (isGapped) { - final double activeRight = - math.max(trackLeft, thumbCenterX - trackGap); - activeTrack = ( - rect: Rect.fromLTRB(trackLeft, trackTop, activeRight, trackBottom), - borderRadius: BorderRadius.only( - topLeft: trackEndRadius, - bottomLeft: trackEndRadius, - topRight: trackInsideRadius, - bottomRight: trackInsideRadius, - ), - ); - } else { - activeTrack = ( - rect: Rect.fromLTRB( - trackLeft, - trackTop, - thumbCenterX + trackHeight / 2, - trackBottom, - ), - borderRadius: BorderRadius.all(trackEndRadius), - ); - } + // The thumb travels across the straight portion of the track only: at min + // it sits at trackLeft + trackEndRadius, at max at trackRight - trackEndRadius. + final double thumbTravel = trackWidth - 2 * trackEndRadius.x; + final double thumbCenterX = + trackLeft + trackEndRadius.x + thumbTravel * valueRatio; + + // Overlay layout (bottom → top): inactive spans the full track, secondary + // overlays from trackLeft to its value, active overlays from trackLeft to + // the thumb center, then the thumb sits on top. Z-order alone produces the + // correct visible regions — no gap geometry needed. + final _SliderTrackSegmentGeometry inactiveTrack = ( + rect: Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), + borderRadius: BorderRadius.all(trackEndRadius), + ); - final _SliderTrackSegmentGeometry inactiveTrack; - if (isGapped) { - final double inactiveLeft = - math.min(trackRight, thumbCenterX + trackGap); - inactiveTrack = ( - rect: Rect.fromLTRB(inactiveLeft, trackTop, trackRight, trackBottom), - borderRadius: BorderRadius.only( - topLeft: trackInsideRadius, - bottomLeft: trackInsideRadius, - topRight: trackEndRadius, - bottomRight: trackEndRadius, - ), - ); - } else { - inactiveTrack = ( - rect: Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), - borderRadius: BorderRadius.all(trackEndRadius), - ); - } + final _SliderTrackSegmentGeometry activeTrack = ( + rect: Rect.fromLTRB( + trackLeft, + trackTop, + math.max(trackLeft, thumbCenterX), + trackBottom, + ), + borderRadius: BorderRadius.all(trackEndRadius), + ); _SliderTrackSegmentGeometry? secondaryActiveTrack; final double? secValue = widget.secondaryTrackValue; @@ -322,26 +327,10 @@ class SliderRecorder implements ElementRecorder { final double secRatio = range == 0 ? 0.0 : (clampedSec - widget.min) / range; final double secX = trackLeft + trackWidth * secRatio; - if (isGapped) { - final double secLeft = thumbCenterX + trackGap; - if (secX > secLeft) { - secondaryActiveTrack = ( - rect: Rect.fromLTRB(secLeft, trackTop, secX, trackBottom), - borderRadius: BorderRadius.only( - topLeft: trackInsideRadius, - bottomLeft: trackInsideRadius, - topRight: trackEndRadius, - bottomRight: trackEndRadius, - ), - ); - } - } else if (secX > thumbCenterX) { + if (secX > trackLeft) { secondaryActiveTrack = ( - rect: Rect.fromLTRB(thumbCenterX, trackTop, secX, trackBottom), - borderRadius: BorderRadius.only( - topRight: trackEndRadius, - bottomRight: trackEndRadius, - ), + rect: Rect.fromLTRB(trackLeft, trackTop, secX, trackBottom), + borderRadius: BorderRadius.all(trackEndRadius), ); } } @@ -358,13 +347,63 @@ class SliderRecorder implements ElementRecorder { style: thumbStyle, ); + // M3-2024 gap: a single bg-colored band centered on the thumb that + // overpaints the active/inactive tracks to simulate the visual gap. Sized + // as thumb.width + 2 * trackGap wide, trackHeight tall. Skipped for round + // thumbs (year2023) — the round thumb covers the seam itself. + Rect? gap; + if (isGapped) { + final double trackGap = (sliderTheme.trackGap ?? 6.0) * scale; + gap = Rect.fromCenter( + center: Offset(thumbCenterX, bounds.center.dy), + width: thumbSize.width + 2 * trackGap, + height: trackHeight, + ); + } + return ( thumb: thumb, inactiveTrack: inactiveTrack, activeTrack: activeTrack, secondaryActiveTrack: secondaryActiveTrack, + gap: gap, ); } + + /// Walks up the ancestor chain to find the nearest opaque background color. + /// Falls back to [ThemeData.colorScheme.surface] when nothing solid is found + /// (e.g., the slider sits on a gradient, image, or transparent stack). + Color _findBackgroundColor(Element element, ThemeData theme) { + Color? result; + element.visitAncestorElements((ancestor) { + final w = ancestor.widget; + Color? c; + if (w is Material && w.type != MaterialType.transparency) { + c = w.color ?? + (w.type == MaterialType.card + ? theme.cardColor + : theme.canvasColor); + } else if (w is ColoredBox) { + c = w.color; + } else if (w is Container) { + final dec = w.decoration; + c = dec is BoxDecoration ? dec.color : w.color; + } else if (w is DecoratedBox) { + final dec = w.decoration; + c = dec is BoxDecoration ? dec.color : null; + } else if (w is Card) { + c = w.color ?? theme.cardColor; + } else if (w is Scaffold) { + c = w.backgroundColor ?? theme.scaffoldBackgroundColor; + } + if (c != null && c.a == 1.0) { + result = c; + return false; + } + return true; + }); + return result ?? theme.colorScheme.surface; + } } /// Holds the resolved visual properties of a [Slider] widget and builds the @@ -376,14 +415,17 @@ class SliderNode extends CaptureNode { final int inactiveTrackWireframeId; final int secondaryActiveTrackWireframeId; final int activeTrackWireframeId; + final int gapWireframeId; final int thumbWireframeId; final Rect inactiveTrackRect; final Rect activeTrackRect; final Rect? secondaryActiveTrackRect; + final Rect? gapRect; final Rect thumbRect; final Color activeColor; final Color inactiveColor; final Color secondaryActiveColor; + final Color? gapColor; final Color thumbColor; const SliderNode( @@ -391,14 +433,17 @@ class SliderNode extends CaptureNode { required this.inactiveTrackWireframeId, required this.secondaryActiveTrackWireframeId, required this.activeTrackWireframeId, + required this.gapWireframeId, required this.thumbWireframeId, required this.inactiveTrackRect, required this.activeTrackRect, required this.secondaryActiveTrackRect, + required this.gapRect, required this.thumbRect, required this.activeColor, required this.inactiveColor, required this.secondaryActiveColor, + required this.gapColor, required this.thumbColor, }); @@ -426,6 +471,19 @@ class SliderNode extends CaptureNode { color: activeColor, )); + // M3-2024 gap: overpaints the tracks around the thumb in the background + // color. Sharp corners (cornerRadius: 0) so the cut against the rounded + // track edges produces a clean band. + if (gapRect != null && gapColor != null) { + wireframes.add(_shape( + id: gapWireframeId, + rect: gapRect!, + color: gapColor!, + cornerRadius: 0, + borderColor: gapColor!, + )); + } + wireframes.add(_shape( id: thumbWireframeId, rect: thumbRect, @@ -439,6 +497,8 @@ class SliderNode extends CaptureNode { required int id, required Rect rect, required Color color, + double? cornerRadius, + Color borderColor = Colors.transparent, }) { return SRShapeWireframe( id: id, @@ -448,8 +508,9 @@ class SliderNode extends CaptureNode { height: rect.height.round(), shapeStyle: SRShapeStyle( backgroundColor: color.toHexString(), - cornerRadius: rect.shortestSide / 2, + cornerRadius: cornerRadius ?? rect.shortestSide / 2, ), + border: SRShapeBorder(color: borderColor.toHexString(), width: 1), ); } } diff --git a/packages/datadog_session_replay/lib/src/capture/recorder.dart b/packages/datadog_session_replay/lib/src/capture/recorder.dart index cc13d7ce..3b986f8a 100644 --- a/packages/datadog_session_replay/lib/src/capture/recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/recorder.dart @@ -22,6 +22,7 @@ import 'element_recorders/editable_text_recorder.dart'; import 'element_recorders/image_recorder.dart'; import 'element_recorders/material_widgets/checkbox_recorder.dart'; import 'element_recorders/material_widgets/radio_recorder.dart'; +import 'element_recorders/material_widgets/slider_recorder.dart'; import 'element_recorders/material_widgets/switch_recorder.dart'; import 'element_recorders/privacy_recorder.dart'; import 'element_recorders/text_recorder.dart'; @@ -178,6 +179,7 @@ class SessionReplayRecorder { CupertinoRadioRecorder(keyGenerator), SwitchRecorder(keyGenerator), CupertinoSwitchRecorder(keyGenerator), + SliderRecorder(keyGenerator), ]); } From b490d8df37be00e0601c4b7827969d762b1c099e Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 11:23:53 +0200 Subject: [PATCH 3/9] Material Slider without divisions property --- .../material_widgets/slider_recorder.dart | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index 5f762364..6c659187 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -33,6 +33,7 @@ typedef _SliderGeometry = ({ _SliderTrackSegmentGeometry activeTrack, _SliderTrackSegmentGeometry? secondaryActiveTrack, Rect? gap, + Rect? stopIndicator, }); /// Detects 'Slider' widgets and places an slider @@ -132,6 +133,8 @@ class SliderRecorder implements ElementRecorder { keyGenerator.keyForElement(element, wireframeId: 2); final gapKey = keyGenerator.keyForElement(element, wireframeId: 4); + final stopIndicatorKey = + keyGenerator.keyForElement(element, wireframeId: 5); final thumbKey = keyGenerator.keyForElement(element, wireframeId: 3); @@ -141,11 +144,13 @@ class SliderRecorder implements ElementRecorder { secondaryActiveTrackWireframeId: secondaryActiveTrackKey, activeTrackWireframeId: activeTrackKey, gapWireframeId: gapKey, + stopIndicatorWireframeId: stopIndicatorKey, thumbWireframeId: thumbKey, inactiveTrackRect: geometry.inactiveTrack.rect, activeTrackRect: geometry.activeTrack.rect, secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, gapRect: geometry.gap, + stopIndicatorRect: geometry.stopIndicator, thumbRect: geometry.thumb.rect, activeColor: activeColor, inactiveColor: inactiveColor, @@ -352,6 +357,7 @@ class SliderRecorder implements ElementRecorder { // as thumb.width + 2 * trackGap wide, trackHeight tall. Skipped for round // thumbs (year2023) — the round thumb covers the seam itself. Rect? gap; + Rect? stopIndicator; if (isGapped) { final double trackGap = (sliderTheme.trackGap ?? 6.0) * scale; gap = Rect.fromCenter( @@ -359,6 +365,16 @@ class SliderRecorder implements ElementRecorder { width: thumbSize.width + 2 * trackGap, height: trackHeight, ); + + // Stop indicator: a small filled dot centered in the right end cap of + // the track (cap center = trackRight - trackEndRadius). Default radius + // 2.0. Gets overpainted by the gap when the thumb is near max. + final double stopRadius = 2.0 * scale; + stopIndicator = Rect.fromCenter( + center: Offset(trackRight - trackEndRadius.x, bounds.center.dy), + width: stopRadius * 2, + height: stopRadius * 2, + ); } return ( @@ -367,6 +383,7 @@ class SliderRecorder implements ElementRecorder { activeTrack: activeTrack, secondaryActiveTrack: secondaryActiveTrack, gap: gap, + stopIndicator: stopIndicator, ); } @@ -416,11 +433,13 @@ class SliderNode extends CaptureNode { final int secondaryActiveTrackWireframeId; final int activeTrackWireframeId; final int gapWireframeId; + final int stopIndicatorWireframeId; final int thumbWireframeId; final Rect inactiveTrackRect; final Rect activeTrackRect; final Rect? secondaryActiveTrackRect; final Rect? gapRect; + final Rect? stopIndicatorRect; final Rect thumbRect; final Color activeColor; final Color inactiveColor; @@ -434,11 +453,13 @@ class SliderNode extends CaptureNode { required this.secondaryActiveTrackWireframeId, required this.activeTrackWireframeId, required this.gapWireframeId, + required this.stopIndicatorWireframeId, required this.thumbWireframeId, required this.inactiveTrackRect, required this.activeTrackRect, required this.secondaryActiveTrackRect, required this.gapRect, + required this.stopIndicatorRect, required this.thumbRect, required this.activeColor, required this.inactiveColor, @@ -484,6 +505,17 @@ class SliderNode extends CaptureNode { )); } + // Stop indicator: drawn after the gap so it remains visible even when the + // thumb is near max. The thumb (drawn last) covers it when the thumb is + // exactly at max, which is the desired behavior. + if (stopIndicatorRect != null) { + wireframes.add(_shape( + id: stopIndicatorWireframeId, + rect: stopIndicatorRect!, + color: activeColor, + )); + } + wireframes.add(_shape( id: thumbWireframeId, rect: thumbRect, From b56d83d9398538fbaf25d5d06dadc107e2771ac7 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 12:03:25 +0200 Subject: [PATCH 4/9] Divisions rendering feature added --- .../material_widgets/slider_recorder.dart | 164 +++++++++++++++++- 1 file changed, 161 insertions(+), 3 deletions(-) diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index 6c659187..19db4764 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -34,6 +34,8 @@ typedef _SliderGeometry = ({ _SliderTrackSegmentGeometry? secondaryActiveTrack, Rect? gap, Rect? stopIndicator, + List activeTickMarks, + List inactiveTickMarks, }); /// Detects 'Slider' widgets and places an slider @@ -108,6 +110,20 @@ class SliderRecorder implements ElementRecorder { sliderTheme: sliderTheme, year2023: year2023, ); + final Color activeTickMarkColor = _getActiveTickMarkColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + sliderTheme: sliderTheme, + year2023: year2023, + ); + final Color inactiveTickMarkColor = _getInactiveTickMarkColor( + widget: widget, + isEnabled: isEnabled, + theme: theme, + sliderTheme: sliderTheme, + year2023: year2023, + ); final _SliderGeometry geometry = _getSliderGeometry( widget: widget, @@ -125,6 +141,8 @@ class SliderRecorder implements ElementRecorder { final Color? gapColor = geometry.gap != null ? _findBackgroundColor(element, theme) : null; + // Slots: 0 inactive, 1 secondary, 2 active, 3 gap, 4 stop indicator, + // 5 thumb, 6+ tick marks. final inactiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 0); final secondaryActiveTrackKey = @@ -132,11 +150,17 @@ class SliderRecorder implements ElementRecorder { final activeTrackKey = keyGenerator.keyForElement(element, wireframeId: 2); final gapKey = - keyGenerator.keyForElement(element, wireframeId: 4); + keyGenerator.keyForElement(element, wireframeId: 3); final stopIndicatorKey = - keyGenerator.keyForElement(element, wireframeId: 5); + keyGenerator.keyForElement(element, wireframeId: 4); final thumbKey = - keyGenerator.keyForElement(element, wireframeId: 3); + keyGenerator.keyForElement(element, wireframeId: 5); + final int tickCount = + geometry.activeTickMarks.length + geometry.inactiveTickMarks.length; + final List tickMarkKeys = [ + for (int i = 0; i < tickCount; i++) + keyGenerator.keyForElement(element, wireframeId: 6 + i), + ]; final node = SliderNode( attributes, @@ -146,16 +170,21 @@ class SliderRecorder implements ElementRecorder { gapWireframeId: gapKey, stopIndicatorWireframeId: stopIndicatorKey, thumbWireframeId: thumbKey, + tickMarkWireframeIds: tickMarkKeys, inactiveTrackRect: geometry.inactiveTrack.rect, activeTrackRect: geometry.activeTrack.rect, secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, gapRect: geometry.gap, stopIndicatorRect: geometry.stopIndicator, + activeTickMarkRects: geometry.activeTickMarks, + inactiveTickMarkRects: geometry.inactiveTickMarks, thumbRect: geometry.thumb.rect, activeColor: activeColor, inactiveColor: inactiveColor, secondaryActiveColor: secondaryActiveColor, gapColor: gapColor, + activeTickMarkColor: activeTickMarkColor, + inactiveTickMarkColor: inactiveTickMarkColor, thumbColor: thumbColor, ); @@ -377,6 +406,32 @@ class SliderRecorder implements ElementRecorder { ); } + // Tick marks for discrete (`divisions`) sliders. `divisions + 1` dots + // distributed along the thumb's travel range, so tick i sits exactly where + // the thumb sits at value = min + i/divisions * (max - min). Split into + // active (≤ thumbCenterX) and inactive (> thumbCenterX) so each gets the + // contrasting color of the track segment it overlays. + final List activeTickMarks = []; + final List inactiveTickMarks = []; + final int? divisions = widget.divisions; + if (divisions != null && divisions > 0) { + final double tickRadius = (year2023 ? 1.0 : 2.0) * scale; + for (int i = 0; i <= divisions; i++) { + final double tickX = + trackLeft + trackEndRadius.x + thumbTravel * (i / divisions); + final Rect tickRect = Rect.fromCenter( + center: Offset(tickX, bounds.center.dy), + width: tickRadius * 2, + height: tickRadius * 2, + ); + if (tickX <= thumbCenterX) { + activeTickMarks.add(tickRect); + } else { + inactiveTickMarks.add(tickRect); + } + } + } + return ( thumb: thumb, inactiveTrack: inactiveTrack, @@ -384,9 +439,81 @@ class SliderRecorder implements ElementRecorder { secondaryActiveTrack: secondaryActiveTrack, gap: gap, stopIndicator: stopIndicator, + activeTickMarks: activeTickMarks, + inactiveTickMarks: inactiveTickMarks, ); } + // Tick marks contrast with the track they sit on, so the widget-level + // fallbacks are swapped: active ticks (over the active track) default to the + // inactive color, and vice versa. Matches Flutter's Slider build: + // activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? default + // inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? default + // Defaults mirror Flutter's three tick mark default classes (M2, M3 + // year2023, M3 2024). See _SliderDefaultsM2 / _SliderDefaultsM3Year2023 / + // _SliderDefaultsM3 in flutter/material/slider.dart. + Color _getActiveTickMarkColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required SliderThemeData sliderTheme, + required bool year2023, + }) { + if (!isEnabled) { + final Color defaultColor; + if (!theme.useMaterial3) { + defaultColor = theme.colorScheme.onPrimary.withValues(alpha: 0.12); + } else if (year2023) { + defaultColor = theme.colorScheme.onSurface.withValues(alpha: 0.38); + } else { + defaultColor = theme.colorScheme.onInverseSurface; + } + return sliderTheme.disabledActiveTickMarkColor ?? defaultColor; + } + final Color defaultColor; + if (!theme.useMaterial3) { + defaultColor = theme.colorScheme.onPrimary.withValues(alpha: 0.54); + } else if (year2023) { + defaultColor = theme.colorScheme.onPrimary.withValues(alpha: 0.38); + } else { + defaultColor = theme.colorScheme.onPrimary; + } + return widget.inactiveColor ?? + sliderTheme.activeTickMarkColor ?? + defaultColor; + } + + Color _getInactiveTickMarkColor({ + required Slider widget, + required bool isEnabled, + required ThemeData theme, + required SliderThemeData sliderTheme, + required bool year2023, + }) { + if (!isEnabled) { + final Color defaultColor; + if (!theme.useMaterial3) { + defaultColor = theme.colorScheme.onSurface.withValues(alpha: 0.12); + } else if (year2023) { + defaultColor = theme.colorScheme.onSurface.withValues(alpha: 0.38); + } else { + defaultColor = theme.colorScheme.onSurface; + } + return sliderTheme.disabledInactiveTickMarkColor ?? defaultColor; + } + final Color defaultColor; + if (!theme.useMaterial3) { + defaultColor = theme.colorScheme.primary.withValues(alpha: 0.54); + } else if (year2023) { + defaultColor = theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.38); + } else { + defaultColor = theme.colorScheme.onSecondaryContainer; + } + return widget.activeColor ?? + sliderTheme.inactiveTickMarkColor ?? + defaultColor; + } + /// Walks up the ancestor chain to find the nearest opaque background color. /// Falls back to [ThemeData.colorScheme.surface] when nothing solid is found /// (e.g., the slider sits on a gradient, image, or transparent stack). @@ -435,16 +562,21 @@ class SliderNode extends CaptureNode { final int gapWireframeId; final int stopIndicatorWireframeId; final int thumbWireframeId; + final List tickMarkWireframeIds; final Rect inactiveTrackRect; final Rect activeTrackRect; final Rect? secondaryActiveTrackRect; final Rect? gapRect; final Rect? stopIndicatorRect; + final List activeTickMarkRects; + final List inactiveTickMarkRects; final Rect thumbRect; final Color activeColor; final Color inactiveColor; final Color secondaryActiveColor; final Color? gapColor; + final Color activeTickMarkColor; + final Color inactiveTickMarkColor; final Color thumbColor; const SliderNode( @@ -455,16 +587,21 @@ class SliderNode extends CaptureNode { required this.gapWireframeId, required this.stopIndicatorWireframeId, required this.thumbWireframeId, + required this.tickMarkWireframeIds, required this.inactiveTrackRect, required this.activeTrackRect, required this.secondaryActiveTrackRect, required this.gapRect, required this.stopIndicatorRect, + required this.activeTickMarkRects, + required this.inactiveTickMarkRects, required this.thumbRect, required this.activeColor, required this.inactiveColor, required this.secondaryActiveColor, required this.gapColor, + required this.activeTickMarkColor, + required this.inactiveTickMarkColor, required this.thumbColor, }); @@ -492,6 +629,27 @@ class SliderNode extends CaptureNode { color: activeColor, )); + // Tick marks for discrete sliders. Drawn before the gap so the gap + // overpaints any tick near the thumb. Active ticks (over the active + // track) use activeTickMarkColor; inactive ticks use the inactive color. + int tickIdx = 0; + for (final rect in activeTickMarkRects) { + wireframes.add(_shape( + id: tickMarkWireframeIds[tickIdx], + rect: rect, + color: activeTickMarkColor, + )); + tickIdx++; + } + for (final rect in inactiveTickMarkRects) { + wireframes.add(_shape( + id: tickMarkWireframeIds[tickIdx], + rect: rect, + color: inactiveTickMarkColor, + )); + tickIdx++; + } + // M3-2024 gap: overpaints the tracks around the thumb in the background // color. Sharp corners (cornerRadius: 0) so the cut against the rounded // track edges produces a clean band. From 4cac9e060b4f6723a14ee73a512404cbb0316242 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 13:42:33 +0200 Subject: [PATCH 5/9] feat(sr): Material Slider Recorder added --- .../material_widgets/slider_recorder.dart | 111 +++++++----------- 1 file changed, 42 insertions(+), 69 deletions(-) diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index 19db4764..8c7c6157 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -12,7 +12,7 @@ import '../../../sr_data_models.dart'; import '../../capture_node.dart'; import '../../recorder.dart'; import '../../view_tree_snapshot.dart'; -//import '../recording_extensions.dart'; +import '../recording_extensions.dart'; enum _SliderThumbStyle { round, handle } @@ -67,11 +67,9 @@ class SliderRecorder implements ElementRecorder { if (widget is! Slider) return null; // Resolves for privacy settings - //final bool isMasked = capturePrivacy.shouldMaskInputs; + final bool isMasked = capturePrivacy.shouldMaskInputs; - // Resolve slider theme. SliderTheme.of honors a local SliderTheme widget - // (an InheritedWidget); ThemeData.sliderTheme is only the global default - // and misses widget-tree overrides like SliderTheme(data: ..., child: ...). + // Resolve slider theme final ThemeData theme = Theme.of(element); final SliderThemeData sliderTheme = SliderTheme.of(element); @@ -130,31 +128,26 @@ class SliderRecorder implements ElementRecorder { theme: theme, sliderTheme: sliderTheme, year2023: year2023, + isMasked: isMasked, bounds: attributes.paintBounds, scaleX: attributes.scaleX, scaleY: attributes.scaleY, ); // Only walk the tree for a background color when we actually need to fake - // the gap (M3-2024 / year2023 == false). Round thumbs cover the seam - // themselves, so no overpaint is needed. + // the gap (M3-2024 / year2023 == false) final Color? gapColor = geometry.gap != null ? _findBackgroundColor(element, theme) : null; - // Slots: 0 inactive, 1 secondary, 2 active, 3 gap, 4 stop indicator, - // 5 thumb, 6+ tick marks. final inactiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 0); final secondaryActiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 1); - final activeTrackKey = - keyGenerator.keyForElement(element, wireframeId: 2); - final gapKey = - keyGenerator.keyForElement(element, wireframeId: 3); + final activeTrackKey = keyGenerator.keyForElement(element, wireframeId: 2); + final gapKey = keyGenerator.keyForElement(element, wireframeId: 3); final stopIndicatorKey = keyGenerator.keyForElement(element, wireframeId: 4); - final thumbKey = - keyGenerator.keyForElement(element, wireframeId: 5); + final thumbKey = keyGenerator.keyForElement(element, wireframeId: 5); final int tickCount = geometry.activeTickMarks.length + geometry.inactiveTickMarks.length; final List tickMarkKeys = [ @@ -172,20 +165,20 @@ class SliderRecorder implements ElementRecorder { thumbWireframeId: thumbKey, tickMarkWireframeIds: tickMarkKeys, inactiveTrackRect: geometry.inactiveTrack.rect, - activeTrackRect: geometry.activeTrack.rect, secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, + activeTrackRect: geometry.activeTrack.rect, gapRect: geometry.gap, stopIndicatorRect: geometry.stopIndicator, + thumbRect: geometry.thumb.rect, activeTickMarkRects: geometry.activeTickMarks, inactiveTickMarkRects: geometry.inactiveTickMarks, - thumbRect: geometry.thumb.rect, - activeColor: activeColor, inactiveColor: inactiveColor, secondaryActiveColor: secondaryActiveColor, + activeColor: activeColor, gapColor: gapColor, + thumbColor: thumbColor, activeTickMarkColor: activeTickMarkColor, inactiveTickMarkColor: inactiveTickMarkColor, - thumbColor: thumbColor, ); return SpecificElement( @@ -202,10 +195,16 @@ class SliderRecorder implements ElementRecorder { required SliderThemeData sliderTheme, required bool year2023, }) { - if (isEnabled) return widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary; + if (isEnabled) { + return widget.activeColor ?? + sliderTheme.activeTrackColor ?? + theme.colorScheme.primary; + } Color? disabledColor = sliderTheme.disabledActiveTrackColor; if (disabledColor != null) return disabledColor; - if (theme.useMaterial3) return theme.colorScheme.onSurface.withValues(alpha: 0.38); + if (theme.useMaterial3) { + return theme.colorScheme.onSurface.withValues(alpha: 0.38); + } return theme.colorScheme.onSurface.withValues(alpha: 0.32); } @@ -217,7 +216,8 @@ class SliderRecorder implements ElementRecorder { required bool year2023, }) { if (isEnabled) { - Color? inactiveColor = widget.inactiveColor ?? sliderTheme.inactiveTrackColor; + Color? inactiveColor = + widget.inactiveColor ?? sliderTheme.inactiveTrackColor; if (inactiveColor != null) return inactiveColor; if (theme.useMaterial3) { if (year2023) return theme.colorScheme.surfaceContainerHighest; @@ -277,6 +277,7 @@ class SliderRecorder implements ElementRecorder { required ThemeData theme, required SliderThemeData sliderTheme, required bool year2023, + required bool isMasked, required Rect bounds, required double scaleX, required double scaleY, @@ -306,8 +307,6 @@ class SliderRecorder implements ElementRecorder { thumbSize = Size(20.0 * scale, 20.0 * scale); } - // Round thumbs reserve room for the 48px overlay halo; handle thumbs have - // no halo so only the thumb half-width is reserved. final double overlayWidth = 48.0 * scale; final double horizontalInset; if (sliderTheme.padding != null) { @@ -325,19 +324,17 @@ class SliderRecorder implements ElementRecorder { final Radius trackEndRadius = Radius.circular(trackHeight / 2); final double range = widget.max - widget.min; - final double valueRatio = range == 0 - ? 0.0 - : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble(); - // The thumb travels across the straight portion of the track only: at min - // it sits at trackLeft + trackEndRadius, at max at trackRight - trackEndRadius. + // When inputs are masked, anchor the thumb at the center of the track so + // the recorded replay doesn't leak the actual value. + final double valueRatio = isMasked + ? 0.5 + : (range == 0 + ? 0.0 + : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble()); final double thumbTravel = trackWidth - 2 * trackEndRadius.x; final double thumbCenterX = trackLeft + trackEndRadius.x + thumbTravel * valueRatio; - // Overlay layout (bottom → top): inactive spans the full track, secondary - // overlays from trackLeft to its value, active overlays from trackLeft to - // the thumb center, then the thumb sits on top. Z-order alone produces the - // correct visible regions — no gap geometry needed. final _SliderTrackSegmentGeometry inactiveTrack = ( rect: Rect.fromLTRB(trackLeft, trackTop, trackRight, trackBottom), borderRadius: BorderRadius.all(trackEndRadius), @@ -382,9 +379,7 @@ class SliderRecorder implements ElementRecorder { ); // M3-2024 gap: a single bg-colored band centered on the thumb that - // overpaints the active/inactive tracks to simulate the visual gap. Sized - // as thumb.width + 2 * trackGap wide, trackHeight tall. Skipped for round - // thumbs (year2023) — the round thumb covers the seam itself. + // overpaints the active/inactive tracks to simulate the visual gap. Rect? gap; Rect? stopIndicator; if (isGapped) { @@ -395,9 +390,6 @@ class SliderRecorder implements ElementRecorder { height: trackHeight, ); - // Stop indicator: a small filled dot centered in the right end cap of - // the track (cap center = trackRight - trackEndRadius). Default radius - // 2.0. Gets overpainted by the gap when the thumb is near max. final double stopRadius = 2.0 * scale; stopIndicator = Rect.fromCenter( center: Offset(trackRight - trackEndRadius.x, bounds.center.dy), @@ -406,11 +398,7 @@ class SliderRecorder implements ElementRecorder { ); } - // Tick marks for discrete (`divisions`) sliders. `divisions + 1` dots - // distributed along the thumb's travel range, so tick i sits exactly where - // the thumb sits at value = min + i/divisions * (max - min). Split into - // active (≤ thumbCenterX) and inactive (> thumbCenterX) so each gets the - // contrasting color of the track segment it overlays. + // Tick marks for discrete (`divisions`) sliders final List activeTickMarks = []; final List inactiveTickMarks = []; final int? divisions = widget.divisions; @@ -444,14 +432,6 @@ class SliderRecorder implements ElementRecorder { ); } - // Tick marks contrast with the track they sit on, so the widget-level - // fallbacks are swapped: active ticks (over the active track) default to the - // inactive color, and vice versa. Matches Flutter's Slider build: - // activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? default - // inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? default - // Defaults mirror Flutter's three tick mark default classes (M2, M3 - // year2023, M3 2024). See _SliderDefaultsM2 / _SliderDefaultsM3Year2023 / - // _SliderDefaultsM3 in flutter/material/slider.dart. Color _getActiveTickMarkColor({ required Slider widget, required bool isEnabled, @@ -514,9 +494,7 @@ class SliderRecorder implements ElementRecorder { defaultColor; } - /// Walks up the ancestor chain to find the nearest opaque background color. - /// Falls back to [ThemeData.colorScheme.surface] when nothing solid is found - /// (e.g., the slider sits on a gradient, image, or transparent stack). + // Walks up the ancestor chain to find the nearest opaque background color. Color _findBackgroundColor(Element element, ThemeData theme) { Color? result; element.visitAncestorElements((ancestor) { @@ -524,9 +502,7 @@ class SliderRecorder implements ElementRecorder { Color? c; if (w is Material && w.type != MaterialType.transparency) { c = w.color ?? - (w.type == MaterialType.card - ? theme.cardColor - : theme.canvasColor); + (w.type == MaterialType.card ? theme.cardColor : theme.canvasColor); } else if (w is ColoredBox) { c = w.color; } else if (w is Container) { @@ -564,20 +540,20 @@ class SliderNode extends CaptureNode { final int thumbWireframeId; final List tickMarkWireframeIds; final Rect inactiveTrackRect; - final Rect activeTrackRect; final Rect? secondaryActiveTrackRect; + final Rect activeTrackRect; final Rect? gapRect; final Rect? stopIndicatorRect; + final Rect thumbRect; final List activeTickMarkRects; final List inactiveTickMarkRects; - final Rect thumbRect; - final Color activeColor; final Color inactiveColor; final Color secondaryActiveColor; + final Color activeColor; final Color? gapColor; + final Color thumbColor; final Color activeTickMarkColor; final Color inactiveTickMarkColor; - final Color thumbColor; const SliderNode( super.attributes, { @@ -589,20 +565,20 @@ class SliderNode extends CaptureNode { required this.thumbWireframeId, required this.tickMarkWireframeIds, required this.inactiveTrackRect, - required this.activeTrackRect, required this.secondaryActiveTrackRect, + required this.activeTrackRect, required this.gapRect, required this.stopIndicatorRect, + required this.thumbRect, required this.activeTickMarkRects, required this.inactiveTickMarkRects, - required this.thumbRect, - required this.activeColor, required this.inactiveColor, required this.secondaryActiveColor, + required this.activeColor, required this.gapColor, + required this.thumbColor, required this.activeTickMarkColor, required this.inactiveTickMarkColor, - required this.thumbColor, }); @override @@ -663,9 +639,6 @@ class SliderNode extends CaptureNode { )); } - // Stop indicator: drawn after the gap so it remains visible even when the - // thumb is near max. The thumb (drawn last) covers it when the thumb is - // exactly at max, which is the desired behavior. if (stopIndicatorRect != null) { wireframes.add(_shape( id: stopIndicatorWireframeId, From 20aef8eba86f927e435194254eb88cd77d49de57 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 14:33:21 +0200 Subject: [PATCH 6/9] feat(sr): Cupertino slider recorder added --- .../cupertino_widgets/cupertino_slider.dart | 229 ++++++++++++++++++ .../lib/src/capture/recorder.dart | 2 + 2 files changed, 231 insertions(+) create mode 100644 packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart new file mode 100644 index 00000000..88c1eecc --- /dev/null +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart @@ -0,0 +1,229 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-Present Datadog, Inc. + +import 'dart:math' as math; + +import 'package:flutter/cupertino.dart'; + +import '../../../extensions.dart'; +import '../../../sr_data_models.dart'; +import '../../capture_node.dart'; +import '../../recorder.dart'; +import '../../view_tree_snapshot.dart'; +import '../recording_extensions.dart'; +import 'cupertino_recording_extensions.dart'; + +const double _padding = 8.0; +const double _thumbRadius = 14.0; +const double _trackHalfHeight = 1.0; + +typedef _SliderGeometry = ({ + Rect inactiveTrack, + Rect activeTrack, + Rect thumb, +}); + +/// Detects [CupertinoSlider] widgets and renders them in Session Replay. +class CupertinoSliderRecorder implements ElementRecorder { + final KeyGenerator keyGenerator; + + const CupertinoSliderRecorder(this.keyGenerator); + + @override + bool accepts(Widget widget) => widget is CupertinoSlider; + + @override + CaptureNodeSemantics? captureSemantics( + Element element, + CapturedViewAttributes attributes, + TreeCapturePrivacy capturePrivacy, + ) { + final widget = element.widget; + if (widget is! CupertinoSlider) return null; + + // Resolves for privacy settings + final bool isMasked = capturePrivacy.shouldMaskInputs; + + final Color activeColor = _getActiveColor(element: element, widget: widget); + final Color trackColor = _getTrackColor(element: element); + final Color thumbColor = _getThumbColor(element: element, widget: widget); + + final _SliderGeometry geometry = _getSliderGeometry( + widget: widget, + isMasked: isMasked, + bounds: attributes.paintBounds, + scaleX: attributes.scaleX, + scaleY: attributes.scaleY, + ); + + final inactiveTrackKey = + keyGenerator.keyForElement(element, wireframeId: 0); + final activeTrackKey = keyGenerator.keyForElement(element, wireframeId: 1); + final thumbKey = keyGenerator.keyForElement(element, wireframeId: 2); + + final node = CupertinoSliderNode( + attributes, + inactiveTrackWireframeId: inactiveTrackKey, + activeTrackWireframeId: activeTrackKey, + thumbWireframeId: thumbKey, + inactiveTrackRect: geometry.inactiveTrack, + activeTrackRect: geometry.activeTrack, + thumbRect: geometry.thumb, + trackColor: trackColor, + activeColor: activeColor, + thumbColor: thumbColor, + ); + + return SpecificElement( + subtreeStrategy: CaptureNodeSubtreeStrategy + .ignore, // Ignore subtree to prevent CustomPaintRecorder from capturing the inner CustomPaint + nodes: [node], + ); + } + + Color _getActiveColor({ + required Element element, + required CupertinoSlider widget, + }) { + final base = widget.activeColor ?? CupertinoTheme.of(element).primaryColor; + return base.resolveColor(element); + } + + Color _getTrackColor({required Element element}) { + return CupertinoColors.systemFill.resolveColor(element); + } + + Color _getThumbColor({ + required Element element, + required CupertinoSlider widget, + }) { + return widget.thumbColor.resolveColor(element); + } + + _SliderGeometry _getSliderGeometry({ + required CupertinoSlider widget, + required bool isMasked, + required Rect bounds, + required double scaleX, + required double scaleY, + }) { + // Uniform scale preserves circles/pills and ensures dimensions fit within + // anisotropic bounds. + final double scale = math.min(scaleX, scaleY); + + final double padding = _padding * scale; + final double thumbRadius = _thumbRadius * scale; + final double trackHalfHeight = _trackHalfHeight * scale; + + final double trackLeft = bounds.left + padding; + final double trackRight = bounds.right - padding; + final double trackCenterY = bounds.center.dy; + final double trackTop = trackCenterY - trackHalfHeight; + final double trackBottom = trackCenterY + trackHalfHeight; + + final double range = widget.max - widget.min; + final double valueRatio = isMasked + ? 0.5 + : (range == 0 + ? 0.0 + : ((widget.value - widget.min) / range) + .clamp(0.0, 1.0) + .toDouble()); + + final double thumbTravel = (trackRight - trackLeft) - 2 * thumbRadius; + final double thumbCenterX = + trackLeft + thumbRadius + thumbTravel * valueRatio; + + final Rect inactiveTrack = Rect.fromLTRB( + trackLeft, + trackTop, + trackRight, + trackBottom, + ); + final Rect activeTrack = Rect.fromLTRB( + trackLeft, + trackTop, + math.max(trackLeft, thumbCenterX), + trackBottom, + ); + final Rect thumb = Rect.fromCircle( + center: Offset(thumbCenterX, trackCenterY), + radius: thumbRadius, + ); + + return ( + inactiveTrack: inactiveTrack, + activeTrack: activeTrack, + thumb: thumb, + ); + } +} + +/// Holds the resolved visual properties of a [CupertinoSlider] and builds the +/// corresponding [SRShapeWireframe]s: inactive track segment, active track +/// segment, then the circular thumb on top. +@immutable +class CupertinoSliderNode extends CaptureNode { + final int inactiveTrackWireframeId; + final int activeTrackWireframeId; + final int thumbWireframeId; + final Rect inactiveTrackRect; + final Rect activeTrackRect; + final Rect thumbRect; + final Color trackColor; + final Color activeColor; + final Color thumbColor; + + const CupertinoSliderNode( + super.attributes, { + required this.inactiveTrackWireframeId, + required this.activeTrackWireframeId, + required this.thumbWireframeId, + required this.inactiveTrackRect, + required this.activeTrackRect, + required this.thumbRect, + required this.trackColor, + required this.activeColor, + required this.thumbColor, + }); + + @override + List buildWireframes() { + return [ + _shape( + id: inactiveTrackWireframeId, + rect: inactiveTrackRect, + color: trackColor, + ), + _shape( + id: activeTrackWireframeId, + rect: activeTrackRect, + color: activeColor, + ), + _shape( + id: thumbWireframeId, + rect: thumbRect, + color: thumbColor, + ), + ]; + } + + static SRShapeWireframe _shape({ + required int id, + required Rect rect, + required Color color, + }) { + return SRShapeWireframe( + id: id, + x: rect.left.round(), + y: rect.top.round(), + width: rect.width.round(), + height: rect.height.round(), + shapeStyle: SRShapeStyle( + backgroundColor: color.toHexString(), + cornerRadius: rect.shortestSide / 2, + ), + ); + } +} \ No newline at end of file diff --git a/packages/datadog_session_replay/lib/src/capture/recorder.dart b/packages/datadog_session_replay/lib/src/capture/recorder.dart index 3b986f8a..594b3cb9 100644 --- a/packages/datadog_session_replay/lib/src/capture/recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/recorder.dart @@ -16,6 +16,7 @@ import 'capture_node.dart'; import 'element_recorders/container_recorder.dart'; import 'element_recorders/cupertino_widgets/cupertino_checkbox_recorder.dart'; import 'element_recorders/cupertino_widgets/cupertino_radio_recorder.dart'; +import 'element_recorders/cupertino_widgets/cupertino_slider.dart'; import 'element_recorders/cupertino_widgets/cupertino_switch_recorder.dart'; import 'element_recorders/custom_paint_recorder.dart'; import 'element_recorders/editable_text_recorder.dart'; @@ -180,6 +181,7 @@ class SessionReplayRecorder { SwitchRecorder(keyGenerator), CupertinoSwitchRecorder(keyGenerator), SliderRecorder(keyGenerator), + CupertinoSliderRecorder(keyGenerator), ]); } From f4f070f10528c459a58ba494dcaefd9531f77b20 Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 14:36:33 +0200 Subject: [PATCH 7/9] feat(sr): Material slider recorder and Cupertino Slider recorder added --- .../{cupertino_slider.dart => cupertino_slider_recorder.dart} | 0 packages/datadog_session_replay/lib/src/capture/recorder.dart | 2 +- .../test/capture/widgets/slider_recorder_test.dart | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/{cupertino_slider.dart => cupertino_slider_recorder.dart} (100%) create mode 100644 packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart similarity index 100% rename from packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider.dart rename to packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart diff --git a/packages/datadog_session_replay/lib/src/capture/recorder.dart b/packages/datadog_session_replay/lib/src/capture/recorder.dart index 594b3cb9..ef634249 100644 --- a/packages/datadog_session_replay/lib/src/capture/recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/recorder.dart @@ -16,7 +16,7 @@ import 'capture_node.dart'; import 'element_recorders/container_recorder.dart'; import 'element_recorders/cupertino_widgets/cupertino_checkbox_recorder.dart'; import 'element_recorders/cupertino_widgets/cupertino_radio_recorder.dart'; -import 'element_recorders/cupertino_widgets/cupertino_slider.dart'; +import 'element_recorders/cupertino_widgets/cupertino_slider_recorder.dart'; import 'element_recorders/cupertino_widgets/cupertino_switch_recorder.dart'; import 'element_recorders/custom_paint_recorder.dart'; import 'element_recorders/editable_text_recorder.dart'; diff --git a/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart b/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart new file mode 100644 index 00000000..e69de29b From 0f4182219584564a66387851fcbb4e599e94d38f Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Mon, 18 May 2026 16:45:56 +0200 Subject: [PATCH 8/9] feat(sr): Material and Cupertino Slider recorder added with tests --- .../goldens/masked_cupertino_sliders.png | Bin 0 -> 25724 bytes .../goldens/masked_material_sliders.png | Bin 0 -> 30239 bytes .../goldens/unmasked_cupertino_sliders.png | Bin 0 -> 28925 bytes .../goldens/unmasked_material_sliders.png | Bin 0 -> 35124 bytes .../simple_widget_golden_test.dart | 152 ++++++ .../cupertino_slider_recorder.dart | 26 +- .../material_widgets/slider_recorder.dart | 73 +-- .../capture/widgets/slider_recorder_test.dart | 442 ++++++++++++++++++ 8 files changed, 637 insertions(+), 56 deletions(-) create mode 100644 packages/datadog_session_replay/example/golden_test/goldens/masked_cupertino_sliders.png create mode 100644 packages/datadog_session_replay/example/golden_test/goldens/masked_material_sliders.png create mode 100644 packages/datadog_session_replay/example/golden_test/goldens/unmasked_cupertino_sliders.png create mode 100644 packages/datadog_session_replay/example/golden_test/goldens/unmasked_material_sliders.png diff --git a/packages/datadog_session_replay/example/golden_test/goldens/masked_cupertino_sliders.png b/packages/datadog_session_replay/example/golden_test/goldens/masked_cupertino_sliders.png new file mode 100644 index 0000000000000000000000000000000000000000..67d8ab79028c53a14b737c9e93ba30dec81d6316 GIT binary patch literal 25724 zcmeHQXH-?nOf+C6}Nj(N=B`GL4f-Qpr zDp^D_v>>7)StKaZh|r{#oYOm1b(qn0-&^ahb>F({-8X%HOxM}9t4`If9lqLCIM;sF z(_S=RWIl#ri*$BT_hT484a4T~@y*34X;q`M@t@iDJ9G~4;g1{N2?qW?!+yW^4lKSz zv<<^nV>;CB2b{w?>s74}?8Ceow*|_WZQWS=TkSQ**kZE)&$P=mvk#l?dV4`!_g#wO zl^JIO6$%t`&Re$n`uHFLVmm~A{= z#BO2>rpv3B&}K}RsWYXfi}YReIn(9E{E5>=jKb@DS{DB{u|x#b&U3!zU=|V@8d_U; z$JVU+q)G6})Q`n#Zx1*w@06=KnwbBhwzjssi(N1J$!FG3UbgesBMYaiesikK9C@8h zFw(qV2+Ye7!!Wkvz*u!}OZ~^%LX0*m$8+SlX;#i1ci+cZq2oo_cCwhWa#xg- z5XLMTnuTGa0lHIj8Yd?1*J@)liL4b{*`6LkTQPPUUc~5}UdNdjtEc9|Uy36a#M85B zZQ+^UUta0LrZHSB?|RxgIL+;xs?Otk9o*ej$M}$}PKV)I!`@{!nHJ^_JSqOvsCaHC zYBB8JW_SOBuUnfe*u8`^HqqNN;fwhRqwgqdWnx zlS2AA8ADNTeTQvHGbxRfmsdzacjS&<2w^R|^K||P*7_UE#gmpSmbh|*He>aDZYz;qumshzzP1f+9kIqLhciqY zlPz)2Q;&fYVU`*n%)vOPX)a2Lw=v|_4j$Oc0QY$Qthq6m^<-IgbXv5}V!UBCS9Kv? zixqa(_`d=L#iemi?um`?S00-b@b<752DEt{cLEAQp|$x|UZerv8(4mov;_PPRg|q>u^t(89Xc)uLm8lg?6ea0};@Sik^rl?NAPN0xhvr#B7&*)pEG~ z%ZOIA6`+2~D<^?pF`F+jMRmzsJ@CR-Mt7IQNTOyEyFg#;WoWSRR+f5xw$JW30U{Qn zxEofkzgf-=ww;U0sA*SvW1B$S6KPj?0M8O9R-@{ZM~YA?Tp7D-FoQu~pK8u6k%q}p zQwqFIAcUEZt7_OP@Y#jBh?3ZH zq4h;4xHmvM?eC5MU^}bA1h@rxo3DTu3Hz7eCY#*)^0LXAHF$}Yh?eicl11DAGuRW- z;(uhhY#s4OcG4ca5&axW^6XqA0$R}LZR=3Qe=(J-vQs^V6Qd!yaq3e&KBHZpp9jhF z1oBEfCYwe-59QXo`UZ95calcGBzShI&K14Vd~6?w!x`vpE;=MLE=I$<(7O*NPZu9= z7tVd$F!fC~yO4glw>?=nTU&8OQRM5{GO-GwqwND7ccUKX4Afd?7vl1uYjaNEHioxq z6ZGs*ohcd?(y)}qEjpIo30@S=e(om&u`#b8$^P2)InFfzCt#R1lq74hM|XY~&LmKh zo9kBZdL<~y#HW^>(bw1aSpw%`1wmz$yK;ZLzi+gh(-4>7+2qo-k4+ozxIfjQn)k*y zK_Oe4emTihs@+ER$om+hgo^<{Hu@1~j_4b7iGy!BU*wt?O1tWxqkJ%|%>1kgXl0jI z{*_!s`7-+UbYXYTPGE#IQ>ZNTeb>AO3uV9*yw*VVT7KKgieNGRi^a0i74(T+MB3|X zy+nX}^+)y4QR$~S0)Hg86{;oc^@r?viZc*No*+eyXug^R9eJ%qU%R)q|GaH3n9p)1 z5}{oX&kg^;3$~oK1zjcD^ zmh=V4E}kDW+%iD^!nc5~TfrgoGQlah7pt`nFLR*gJsT6zETq1oQ~@bYDvADksU zR5$NI1|#9qTwpXTX~NUl8W_fW*6iOL*Rl$9Fim%>NF-ITd!UVHq7|@536-v;?irSZdu|{WIzA%*4ZtEoK?_Y})04bH&youf%G))ABL#pkc}*YoE2( zlVl=d@R&*-X#hRboY8WU8!QAO4%*XG!Y;=akf$aGL`~D%pkk~=>0=fH9FBbM+48`5niY;^o-!k*8-VBq5=0reP`|BvGt`c$%$f|9ND zNz5x(x$xdWji0~H-+1)pY{|XL>(?aCiHg0nC2{`GJ9ou?rR?Mre=$?z%X}I2FJ9lC zn=Nz;tNG?wXQt*@8_m@24Sz81Ht7|qU5MG6S#SoqOXO}Ttgj9Xhh z+$XLvUa!2*cXDr<&%ZPQTsYN(J=xUQ8K;d>E)C(-+X1gItN`4XesW|1NUUTO%D zlU|eyY!?9(R}@KV0xk6XnC};5f~NFE{uQ9EMsibKg41Rwl8;EFZfvh;%ZH5j)nwoY z>3aw;xh8$0eKmn4FQZBFrJAoIAm?eq4`-C?9Jru}kf|J^u?J&9c00mIbse_DQq6BC zeiwcf#g#8X$SIE?r$$p?XxQ~y7{s8E=^vED*P|F-Y)=IuEqf~O_RDckFoR+gVxPSe zx>%U32}T@+P@qU~n82!H0WjOT7H5Dh!VTZFmHnbzx)@CC**nro{6`~(s@a6lqXlYr zXpM*nf}HXQa+cw!)EVNQa1%71oomX=tPsTvQ4U`7!aKv*R>^f>rCCaD0|5z53b~_a z#zz9oUwKa1o4bmp@u^Q0=zkegAJ*e;lx+OgsFpL{(O?rWL->!bjI``*KO7NK8%ESw zcn^CK;FKI64KaV&7CXBv;!k-M)DWsFf(Y+XpcraNhaaaGFt8G0YpX*n6^uc zXihEMWrh-zXLbudtZhDupeE5rL7U% z23^4_m(7To?(5oR0wE(92!m{dD@K zqQ>+b8-b9hZsuAs)_d)TwLjH+-S&=Ns;UG$1M4Q5n&?^5P2rfzsQ2b} zjN9HyoRoP$LbUa$*4I+)la#|L@!)QV<%AHJmdhDg@4S1W-6hXj{NU%bdmD`ILb2)) z*k8mYZIKO`WJYZ-!Lu$8Vop+}^vBNv7DBhM=v2{5k46^L1~hL}`3(=rda>W?Lg|E| z+mrOZxA{U>JMLEwuH}x8HMdWIuqy(+}$)hA-VLzs8JLzusO zcfWyG#)XGJJUI|@kS8o|(Vmr4T5?(spY?Qf=u@emhpE((ZMnfV-zOwX;(T_QQTtc^ zR%Kr?*yY||c%CXn>D@#LmM8&+ft4axWOotQdGC+$0)FvFC~O`u)| zK)J~3&N`HfOk0Wr$s;5D1`2N8dveo3pL8FG;=x$fJi46Hn}4Pl*y;rzcR5%*8LA{Yhb`QZFdX8w5>PG zJ+ebzfR;5VB(Y~HsHjS4JHVpx@51neJVBlyCS^VXsP5^zwUHlnfAR-~3z8-i7F)|YJvl9M& zN|`{uR!j*XQhIP?qvnb+M1fxkKglfH#)OF`d9>p%OZM>v``i zcku$DHvFW24WRuK5D;LE;~{{#m_RJ+uX*i66fUAX+bs`N8q*G~g4zC(#u!hxRk%ufX_v!*1dS$uvNsg9G z&A?ZNHVS1W-&mbHPVtz^%zaOuCG+;9Q-TeSz1(Ft{gzU2eua6iry6S1Tvx;SJxB8!tOGD%PI2lSJx!&#!VTjeV(K2S3WlrIr;J>$ zyfKyvPSK|OlslQZH4St)j}4yT?!&uhOI7r`mmmPh9=ch%;+_=ccjXTNjD3qvKE%06-ouuw`?zOMES=p$aFXKF9;({(w?LP##=8oT#==a2s$?bYyLGXznPM)}N55 zvkxQ7_}sy|1{Y z=@r0%V|(wS>dM#oXN>z8t(5&vQOj*snMq3=bNS*49VD@HP|R*#OfB}tV#F@R+P=*7 z+&J6FHUmh9?Te%nW<+NGVmcM}T>TV>Bj{-{{55MLnGJ#IUBvM|MQC5!WANED$>V0d z0-yw*GErQm0_#uWc830407S+##F46jS6&$jg(n9>4u25VXJuSBEPH7kCDPB_+>=7- zeYKWy{_{wR>2YVkNZ=n^9QaWp8u((FLC-zA9;gRP93$NfAti~PI|#U(iNs%J2W7wd zCr1aX3j<3|IaJ)<;S1a|%LVP775(I?MzZN`)(<_0_W7oC?-d7aqY1NfOBT;jwsao2 z)qgHHi@|Vdb^*0_fNJAUQ=3FZ=ghnqvr~8QM^9NhZJ%|*3^neV%;jjr!fh%wkG z4*8E01HXj>w~RW2tTN@7?GnB2+@qkgG>d9Sc?gUmcaho#-|FAVXxwHxT5JX?O=E4; z$N+PM2d}%jhd8T#M4VO223q7NUls~F5|;yVO;{G;-7iNPkv2B(Iu&saLcnOkvARgB zRw_htewu-^DyA>bI z7{L+`YZJkMuo?p6RVtTu7mQAyRChME0{=xX3>>yL$S~o;XiMZ+oT+xtG;hGEk`ZEl z{_uB$2WuVjVep_`et_Q}!m#Jpd!K?UV_f4rO`R{KV}5~Tiij;1j4lnMqqTGj=a1GqJ$udVFXlZ#k$x* zoDZ`TFrR4Z7%oomSm`<06w=c$^{JXXPbRO_vnObHQZUEB)mJCYH7!7PYIF!>16wm? za2VE99I8<6G$|;D_fQETM>v1(WV3M3Si?l*;BvctpL1Nza2(d^dXqm^I=EY$?v@ED3PfBD=qsYN(XT8$7L~$_@hV4Mi6wPA&r#a;K|0aUw%}J zlRahSh;jz;1WQ{B%Axb=GITzzQPkW02f7DAAiXkW{np#|V<0UvogwrqqC^}s$pWVH zXJ(N~Z6>xl$mS?5Df7~Hz>GPO9b+)0Brk5<2CRBDvX5B)>jIWW_&XMhyruE)+SGW} zmp{EG&Zi}inTVy;wz;75X-R@2{pr$xj!O+pBofwk`dogcha>2IdZ+ktnXSvkz-;h= z?%Akez}I>DB-=OndVokHa(-)@$LfSk1u`K`3^D2#p4KT$eKplLITp{c`wh0rZlo3!_8Yqit^}kYPPj%M86cB5*XNOX!j6duZpY;+LA>f?IZ|`$r z!1ZhD2lgG?hd>|>T)C`r6M^{241w7D9zL$M>Z(kq$DR=u4A+UpE`&#)pllitR{>Q}PoWJ24)=lw3dwAUT zs&k}P*!LCL`c>aqIjzxiryKnFGD;$mrd<~XxV<^6fBE_&G07!}o*yC;5Gd<}n)ZtVXdx^!*>Y zx_mN`h~eMYEV~g1tMXrM5QvGzIY7m;n^gCHRWcE#+H9)_b2a_{W@IKUS9b6@_*}44 zs@yUF79xQ_02X53+}zx>cGSD-T*gm5ja;(v=a{FA-%u^|3G_R_^@Zza`O2U>e%n86 z^Lr79;a=`RlU1)3)@`*xM1M3r__qOPT4$!yf;EHb%b8IKtP0HFj2W}Ss=y58zXqR7 zg!6aYz;qT&XTfw93<)qpXl4ivF9oJm{M#xnB;csjw`CRcvUJ5zAMU+25j%EidcRc` z2|u-0-|p1u4*zEvzp`=!CTePOMV54%70;{+C7N7Iulq-cmNT2}(Oosy^|S;o?fyMH zc6c-KjK)hgzkm4u>}2ye%%^6sR`Rcd=Bx6G^jU5t-sdYgs<&y>yrRbj^~9C08OFc9 zNhMzXCLwwJn-tRXZ}OK{zDZOb|0W~({F}TclRc2!V+sdY?o7=Aguq-@fDrhZMuBM* zpa6hr6qrT<(gdba0OY`o6hLM8Uor~+Xl~mDc3(PRabtTgfSL2Cb8x&Y=Zc45>U2oV ztIg44tgT(9Ar+6N@m-G|S=->~fo~4jiz@H{4Sl7u-<`vi7ST(aQ6(I@f91h8r;iEC0hwKQhYu22w-|Hu7wQAF)Xg z73;WVTll`)X(+UN@o7V>qYub;O%e*VwJ3u*X(K&+<*a_DO6*OFAsIRX&+G=dT?h1o zGP9@bN!1{=!4ed(OEys_n(=g0Xl@n{HweO3nE`yNRM*K)cw)s6jW9W7Q3G;hU7U7 zj~BvkX1AI#+4vc;E!5Oe5mMwXp^#sC;F-ECnUmWN2T()y~%xi2+qrc|R+oj+}cJ|4Tx^`ufv0iC321-oJ)cI;1ZD z3_i_me+B~dJ;B9uR7~OZRA|^fNV(-8)zx@ca>T*rKsnF!ggT*a#C(b!Y*nxizWbZY zh!LQ4R|!_78#Una(}$+)Oj7|yT0DWW^nR5h@Dw$lz%@F=Hz_vEr(9nmFJvGiRv5STnsw>gwvwt`Aj_g+d;g z9c@l9B+5OGS0~g|sI$Dl!H@Ji!K|{ z=*vC}t3G73%&y4E%XiiozAhS)JQ5!t+61&h7#tJ20{#`fW$UIr^^;aHhqpTTUN02Z z(!S1dd%x+~S^v39b6ZhjjCuu##XL<0HV8p%3^o4ahH&K73u{7U=ESH2DQTiTg|T8m_Db(W zfRACzEeq)|=i5xQ@r6xPaqY8)u4v4nPwxWP3^813h{t$f<|+~teBuEej#N%gjWAH4 zi3*L)jd&$`6Oyq+t%IAii~by<>hdEVqNV28aCkla$! zk(zkW!Rga!DxyXP0~T*WA4Ybs^mWz7Q%L@g8a(t@dcX!*JdluHkp}W3$eUrIHLnve zHXEp)L?ZoRVbw4nC7(Vl1Hl?&V_eo5$Q}&^06ZK#mo*zD$F(U_=`MAE+y4Aargru9djp>< zlt6n%G){obu31NLm{v_bk~dT)EY9{5t+->41;I<+yn*wq{U;h|_J`YHyfT2U!~~*z z7Sy=CX*!Zr$&8x;M@rqDKF;0O9NAScxX$J62f5E%);|5EqyeMCx{XKH-0 zUC3VE((|QgIhaxlres}VNwpYC-aE2fAJC?)*p>R0Yb1PrHuTW}TuK_)b;};PtBl!6 zTxn`Du?kod7BKg;5vZUH>=Ktjzg|8J67~9mkFjRIN>Zq6-e{g*qr^#|#v8jOES_L% zy3M4G4)ry3JNqg>AK<@y>?Z#;fgkTD4X9}K4cygD&nUXGHEH@bqeCx4&#*VQ*GLa* zbL__}r(*v)weQcJ;w|sb)rCGfvT#5?Gz2eE(MXGHHG${F29&&dcMI z?uyBRbob@e`5nHyshMJ%SqI4-Lp_zK;}stG&VC5f?1G#e$EZ4GHrO_d)SzH7_^gN& z!Z(mFL16%eeUQUZeh1tQLRUz|q1E02f4JonQ!(a2)*Q_~vE{TMxC%1l7 zIB_ev)jwx2PpS@yoPfOlMHq=W4pCfc>htRRB{*zsoCUny2%*quPMv~6k0qDc$@G$~ z3X~(k>eXHnTDne6$dLfbG|?AQy~aMAt8LiJDG$<7EeQn$E+40^RhS1q%)@cA%IX4r zAqb0-91vug4UJyqBS3-ZV+b3a6C0xUvfap$0eUI*z_AXkZxq2ijzBz|q{S6^2(-;V zNJq~s^%Gd|Yg1D&#X*>&h*bd9svi4cIpo~Ug()C3G7ESa_bRZpHLqXdGJlnD%wC^O%~`KI#~Ho*7P)M8 zyO9`Z=a8h70uPbMhDDu2?#z1ZKGN!i6=Dn+7h!RYp*f2&F$c}qLlBu}zs&J9DKJ9I zxp?l_M#iN3brhEKMFJ&^PkBV^mJhg66chchN+M5uXO-WM+z38)GyM17q@4+@R{K zsQcxBL1&_F9Ag?qi*BYL_QK(?sJ{|*`0OPe!}yynQIt5VFHtRq_6s_gJO4)E2Izf@ z90o!-@nzYj(ToXf8UHlq=&YC7syh=%x_ySieP~-9;;Z)(SC!oPDZ#2_^@K`k>7TQj zA$8rkz{|+yjE}Y*m{4;?N>PHNG9T&_fp_B&*uMzbt(b_=(8cGn?AFB8j@H&Mc?`hl zM7(kc6oHfUxe{s0pHYVUDh0v!O|Z3ZmM&PIy3WHh)?I?7A=g1QOmy$1+-Ee_>Kf1v z(dbNwK&v$vy`#syd8qFMtwUva9X9wWIk*G*y%a7@9{~AU9oSxfgT3tHZY#UN^*7wX zSu3^~o{=Aom(RhS0w~-mE1dY3s51?) zP@rJ{J4!Rj^fO#=Cy7ue$ERj;YSopbVgLf#AYZwl2h|#0q1T}m z`W&~^8#Ll2lS5Dt?4jD3@Wtj_P=ZaLCbVWB+hA?Ajp(5xs+!=L4yVR|CapSW(j zt06sCox0Qt^PfYMwy;8htr8V9wVG>WD%K1DONQy!HffKXzs!UyLupuNG|<{+V*KW?Y02cBa@iFu!@ub`HY%i_kDcU*W{cxhaEK1(#p= zZ0c8J>=ec9DjxE8W*hcApJ9NiOxU|Sry;d!LxouC7Fy`t=RMDz6taAt-L5<1e_&Up zh1nmrHvQ#t9#Iday+8wnDCdRLujc>@kH11$Sl#5LO5Blne>RKC^`oq*&kXYM#@Cg0n!GLhp)<5KR5*F6?l~)U&foizB%i~yoTh%lmk-^|0i|e zjUa_Fb4PgP4ubJkYioe4xE!lco%fQ2KvHoI2aZ-~x-~nWQ(GFOR|{Ck{&p}jF&MeJ zLRJ%`{0KIVifT_wHOEJY)dFVqziyp^EVnS>YlAWg!3p+CpP$dzA2HyO8SuDev|xxT z8f6?yU9g4>(rwa5F=P#e-la_*B~p_*kjYn5{G0US`DBLV4o;M(tk0>XS)*&iFIK{95j(K&XqxM=%R2o?Doi%typcez0dKJ zb!qt|z{L|I60j%t?CH8Y06woW8gjKQ&O?>dsWtjQpCP^UJytkUz^?Qjzuj6(UB5QS z&kC+0y|VPMmIe{Vk={}n%8M@Nv9iuuypor$v&+NL@6v!Rhm-MmM$q%iEKj|{*u%HR z#lI<1=W{~nkpr;X+Vcs!7vz53vt2v?seg`u!0P zYqx$K$*j!xAzb!c34IHB`j$Pg)A<-i>N(oSI}nYoD?ea6z@=1*LXjAO&fz!+a^3~* za#B1W$Wnsuf`42TAD;Uz7Epc;gyn1F`n+Lk$>Hd$@6?i6O>q_vf)g;!Q1bHec3@I5 zKj$A|5rIXCc6U#zY%XAUzK=V`=ek?99k#p(qnK7nmVSy&aLBb$D4yE^2ZhDx`CeX$ z_LY91c;n$AO&`ymgLx>!=whtpQLEeL7fvSt!l?6h^N7tUVPK>H$9ECu)h`kn;sTd#%Y;62&F`>Qp zpQ6AXz&H1^-L{*^g~W)3l<}NUh+0kPi9h5%XQ#2K0`g)Ulvai70M`ImbBv7$bXL$0 zBur;}yd+^!Cjl$7O~HA~N&G^A(kkxUAt5y(C%95kjU)w-DgfLED1~yYNOVK%t+#6W zZ$(NU6!tq^K3*l9DCmbDdEi5~+BEM9hg7q-tzLYq8?2mK;XH7>Y!MMTn@1D;D=G%x z=m=v`;z*N=2Fj)zOMbQbpst;9UaZl9!HdLsDfZ-K3n0!{1`q`ceeGp8^}}UIX>8}# z^2ji%u(I}$tHT2*6@)3fmuix!j!2M~6B3Q#?L&UI>DS8sHG6os5jQ?>ZG3d-0VY$>;1dzg2t%r`kx(mrVWoW^Z{;1ERKhoMr^ zD|INCkF&K}YJV*!_bJc8iJGtos-*H#!F>B~V53*rWogHvvy_AYjD@{RJAY#)8^&0V zV2l;b;xn=c==!*z?PeMHR4vE0*J630i21_welVTzK2yxUbsLCz-TRa}6*CpizWY6{r2 z@>G$b!n2C>sm!GOOhK6bPiT;-W%Fh7BZFnnh)s*5={kWWQ&EpfzFgvR`Y!Ef74YTt zF7j9(UqE{O%Pe{8fsVCNRCd6l_fof9!5MFX(+UNRfDQ88BBc?-6G)*Ovm=Vng!QjF zG|g8?*_DsIn>203y`g~VF0&~}5nCMnSVC%Xu6ALzVRN?Y4}Q`B{W6Bn(5~+=D!#9; zHx_)L+))_DNTN;M&XaIX=vi*XZouRBdz$on&M0RY7r#5v1-dw2eaL9$PF_oR7083+ z4gT_)mglr)rg^RLxN%x$Co9(;J$%)0Z+D6N!)4JKn+-jn6~avcQonax|J^VKa*7}i zL;&n%8E+0pGUUs+=KuC|81tGblW%q4hB^sUam0dkn8LF8&n$CcoZb$S0s`JMBf8>1 zwkg|#@)Vl}l2j>O&j^z_hE)g2l~ro;lG8?JeAW$Sd%Y*6wy1q#XbsO1QxkSn*6pIn zK9Fd*&~5jx;D!A$(+XO+k0h{xsJoDM-E(YJo#P=RskaUF?e`h_Y?fhZ zYC01XD^JBTxP(BK*G(G;MvG4*FzOt7P=vOS`iI&4Wer8p(;?nM)^%9xj*WBD_jKTG z1du&tJSy}0-%JkE9~FFSWu&CI2sT^yX1&KRbEDUFxcQETfn_skIbQ7u3G4(ob-S0I z+IwH1{JDF*<8?h0v^_o@Dex)srjBsRsc4NG=_)$%4L9aQ)q#{9Pmq>xp$a#xBYmwh zY&OIg$rs4!<8a*4($WbD25#W%)IWd>zDp`=zC=hRr8xhFFz%4~Lzl>8JrQ+mQr+!+ zKN#5B0o4r>ZfUA}W0({Z|8@eiomFZnIu_!eUhko))k*vu;7__2IeAMj!10!UM>EjO z+T8P`_d+}mc{nU#<@QCWJA|)lra;(0D&nq@NT)(MX&S2u+_vA}+%*H5Tw#L%r( zGw$=A0+5dc+W4M$+v+d-&{6uN&~&Shnv}4KMz;&VeSFGxFHvKQ_9Z4R2Er_02fme3 z67Bxs$?dI`?*udA$NHVD1a|0gt=F?)Lgqb*0dKmvAVdZdF9-ujB9t#SbdVkj;6+Em z>ni}`5g{cvX#j1!hSKREp&JhX0J_O9tt16X(JBUWRqmFy!#(kS#4;b;87SBX$;KyZ zCN(vY9ZrPApMdDuby+`cL~LvJbxiz<5+jxaXq3Z)LmO*mZ3HPlb6ob-H6x~_5q;=g z33cfa5no4k4hf7*O+7XF^?PB-9eR&w3ut*_%OfHb{DOV`Wo22)nucs1#k}A7WA46b zAcS92Qu2l(SC9a~F}Xa|WA)i7hd&aLnFvmVxVShRrh@nPe}1DLCpUIDD+w&AkS&OL zEAJ;DKm^`Syi$j3Qo{{?RCw~k&a9mtK)pj;LLwH5;y?neHY*j)Yi=xeSb_N7irVrB zs!@QqtbW^}Ck}jSxw-6oVXHp}3f#auiS?;z)2#~}A3jV08o%y7P2MFgpXmZtV!)Sy zX7wesMOWw(2QxyRzrR5~DcFr#*CBq+?P5Er)vEl^orf;xwbL&H(#`jozsvwPjD{L@ z!XBr6p`y#Yj+f+=CFvxqCcACOw#vW#$X;v2+Ho@322e!?< z+fQ4;(<|4e=h5_@Y6nUepM5XQ5*6!mnj-)}CZRY_s8j?J`=0nxkEhgBaGH8o6Xwul zs|)LEnACnJm!j4eGr4ZSxSY(L1sIhji2M#-n`*e&)1{rWKQ~Yfz+OW5uK`QYmuRt& zqS5@EG|~@QAE0s`*bKDO?2U^Rx4(J757>#7VvyA6wA7fKC@%oOmRI-x{XJbA)$IC} zg(nic6vo~mGOd7tv_q+Ske$I{d+J<=Lu+5NJCR^(!~?$5!lF{zUNqza$0B#PcMZie z;2TI>_1vL4N~^V%93~_kZ_Gm`D3)XIQ=E3{k)20*t2T|?S~J`xg$QGekRNQ2dpt|7 zOomQO@RkK2Rb4jEUSbu-f|WJTa^NU=H3Z0RFFZ~i%t@ZUkdnj-JXaO_gQUe;$HwMP zD8vPM82q+pkC>H*&5Cu(O%;7DUtP(HyvI|w^g@bF99^K89VC;yRhZ9x!k%B08^C(T z(j_PVmNep3}sx*;_v8Dv$@cWFU|MvQmx^H8cru>=agml3BRXbNib5!cT=s!JC zC7eqcTrMhENXq}J>ijo=u_`nGz&sZLETBxCPvo4ffijEQPe26(WB|Tm(ypc)ritJ@ zawma#=&DSTr*~A+;@{|aMnVJ#gz61p)#aQlHSFxb6Vy+Qk+shMc{*;! zIp|4c=wh#xkwc1J=M#*16Sl-;YP5>&0!h4Scedk=(VN1Ri=W;{qFmIlMxPY?IDoCJ z>DGnZd}}Sw){s7{@7LmI#6qst>Vx?W@Re%-j21H%Y4A`_e_1qBwo$d{5Z;kIQ=%Ea=dxGsdkksO z6PcmV{UqSNW^ZYL-Sq8F_L_q;C*KPv8jT*}KGgg4=eYfMlpoLiupp&~zJA8$j39n! zZEa$UJaa>C?1XWY+%DpCNQXZF>- z(@^w2>H;VD*=^_uV^zjNP^IH74a~QJzM1Y3-kxLnCJ*nh5&8~Q3mWR*WnhZ904TSh zODrl=OUj?w+WMH0Hh9#ve`$(+fZ=TU(u!mUtX}Xkre>>e35nyiNcJNZ>W8VS)CisJ zI}9)*_Rff%R$#10It!GW1_z}4-0_0>;P@0-IPk55qF+S1@!r*ha9M84~%RbN?T7UfM(LS zaO??X!hD`2V{Ws2d6rB5SiZ#m^scFN*Sgg&P2v~cNx97SbI%Z_Ojh55Z5JG%<(d^! zO=Kf4$hhJ~(1SMhGxmIg+%iMnx;E6Bx~OtRkaQD1<8+47>=PgCxAi5ZoA%+g)k@lp z!rL{?55z(Z?f62M#AYANL5Z+&C=q7$(lrdn7v|$?zd20ZIvSLww+impF6>@eN7iYX zRtRtGMe8r)z~yBi4-KZEra`QN)wFW!Wz0*ASZVTLAG_;NnaDHsqQl=+sB&j{sm%@K zZr9aV2zgGjfe!0s?=CJAdbYbg2 z`z;TK^Rk6DRaCtL(vhLm%GJBv=Vfnre8Tt2Ngkol-XCaH>NbQa6O)%gaUe46y-7MJ zMl${VLD5b4EZ%J-#0$ea+TnvvS+qIR%|!5^lk5dWRu;tt8~F4DT){$(p~Gf_I1}V` zJnEi@D5B3*P(D^bJN)0k+w$*=DY~BGUGFrQI(k3M^MU9p&ba_OB_Is#R#E=cxa{e< zWN+fHmVCqG>TO^f-d;X8=~Sf{czb;yT%lTAt40%XZ22juxI#8<39?w&>=`K44pZdb zo##a>Vca(HDJaocA-=6JS#c?C=$sEbD31qF3Qb8I#9B8jZ z)$?-qyTZWk%#}8Yr~wHiufNPM$S5f7y?xT@xzA=Di-kkp4Uf8ahDJ6uO0+l(jSJDy+d{k5S%;n&yo~=)Y;$es7rLf-9vLAj_Xa_*jPkus@f7K3!-P-#z(7cDYw2F7DMC1u&7DXd67o8#Y)`%jn-%x~sL7b->uT zReL^Q&;z&34fu2Rn5iXx~qwmBxJhLISvl%D2su zDcUahQ3#HeRovZEu8xH}3{d~4`a6}K;_e}I;`4v*?HVm>PZ3gXXl!b#U3>pT{Ji*4 zR@Pllc8M3*8alhWMq~CMv>c*#Tz|;t8r9T9z%MUrF0YpI+==`#S9siyz#~Vaorty% z)S5^@Z_G~^m;7qYQ`lWedHeee!tDOvouGw`b_A`iY|IeJ&3P<2xm&A!_UaaStolX{ z?ybg%7E#nugsy99zKtaS_oVDD8i?ETVgS}vbt_6s^L#5u5LtT7zacW3Z|#|z0reIb zv8LuV$-i%qG*La9A3p388?pJOiUx|?=}RZQeF(|BHH@Oul6iOec6_?EN4evCL!wuF zUzfJW&Vt6_Dl_e{9aYw2qPr5Oo1LQ`K6J@m5NM7XGN?83A>JcTizs1PCTFN&s=$zU zaiIkT%oo?3gZX4l+XtD!>JqNpY!V?-M%(8;|Xk)7%^?<)Hh9N8diQ`5GU?3AAu0 z90x6XOTm}$w<$se{kPYSGAz9!RK$2yglo$ArK6{ZRo!qKmX*Qm@7K7-;!NdAdK*xkYp~tSXY>SD=b}cF-tH1#?6Cz*Sy?#2 zE~v?HFT9e?Aul7^lQZD{(AoJjaL82J*m!27bEyB;KN&k|+9sBaeQJ|vtN5cKGH>Zs5Ogpv@dMOLT%_TAxt!w4%V||j`5dgQ9d2nFmIUl3 zP$_zuwF8x9B1JY_q6#VZIgKwIr1TeySQp-gj(O?8J09{oJ8)-7G#UYq^-@Sli)$Sw zybYt$7oGu@Lx0=FeGPe)(J`^c{*_yq6U3)!`2UCVL~oX%iLHKh=7pY%I+?2do_&9K zk~QSv!(;Hie5-)GRzdO#H6Z}o^E63*8fWZ4!Ihc2-PLGiXj*{~WT($aNQv`=c}j$~LEpK+nJ2@zBKvIFUl2HCyrL5=DB1#Us^szH z&AKfu+4;u@-g<%S3(*NX0+I3I2Y4b#?q#8FxVRzjVsf~+Uy<9;Zx8Seq+EaxK4g`! z3?>BOfZc_F?eCUxk+ei{+{|g$o)4a@jfkUDszRKy17a}1 zVsL0{OE@?J!5n67;ekrcvkDKJ$(#gw?~rNOYYmy&g^y8f&`u*FG}~a;h$}oPpmTv< zI^UM)z6}|_1;^i(EWWpg+id>DfQ-TRe;2Jz=LoV}Urc4+*fM6;FEC;4SFcpHi8ekn@x{Pf>F#RPY i38r~un#X@v^SJLvJ(<&77mFE5_$#W{RI-0Jd;A~4F9)Om literal 0 HcmV?d00001 diff --git a/packages/datadog_session_replay/example/golden_test/goldens/unmasked_cupertino_sliders.png b/packages/datadog_session_replay/example/golden_test/goldens/unmasked_cupertino_sliders.png new file mode 100644 index 0000000000000000000000000000000000000000..b77cce649b2673491c46f32c299a3463f6a1ebd0 GIT binary patch literal 28925 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 literal 0 HcmV?d00001 diff --git a/packages/datadog_session_replay/example/golden_test/goldens/unmasked_material_sliders.png b/packages/datadog_session_replay/example/golden_test/goldens/unmasked_material_sliders.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc86823a8551abfe114fe8c1edf02fd4f120ef8 GIT binary patch literal 35124 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 KCH1t~gZ~c>&*G~9 literal 0 HcmV?d00001 diff --git a/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart b/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart index c924eacf..1efa7c52 100644 --- a/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart +++ b/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart @@ -471,4 +471,156 @@ void main() { ); await snapshotTest(tester, recorder, fixture); }); + + testWidgets('unmasked material sliders', (tester) async { + final fixture = MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Unmasked Material Sliders')), + body: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // year2023 = true (default M3 round thumb) — min, mid, max. + Slider(value: 0.0, onChanged: (_) {}), + Slider(value: 0.5, onChanged: (_) {}), + Slider(value: 1.0, onChanged: (_) {}), + // Custom colors. + Slider( + value: 0.5, + onChanged: (_) {}, + activeColor: Colors.green, + inactiveColor: Colors.yellow, + thumbColor: Colors.red, + ), + // With secondary track value. + Slider( + value: 0.3, + secondaryTrackValue: 0.7, + onChanged: (_) {}, + ), + // Discrete slider with tick marks. + Slider(value: 0.4, divisions: 5, onChanged: (_) {}), + // M3-2024 (year2023 = false) — handle thumb + gap + stop indicator. + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.4, + onChanged: (_) {}, + ), + // Disabled. + Slider(value: 0.5, onChanged: null), + ], + ), + ), + ), + ), + ); + await snapshotTest(tester, recorder, fixture); + }); + + testWidgets('unmasked cupertino sliders', (tester) async { + final fixture = MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Unmasked Cupertino Sliders')), + body: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoSlider(value: 0.0, onChanged: (_) {}), + CupertinoSlider(value: 0.5, onChanged: (_) {}), + CupertinoSlider(value: 1.0, onChanged: (_) {}), + CupertinoSlider( + value: 0.5, + onChanged: (_) {}, + activeColor: CupertinoColors.systemPurple, + thumbColor: CupertinoColors.systemPurple, + ), + CupertinoSlider(value: 0.5, onChanged: null), + CupertinoSlider(value: 0.5, divisions: 5, onChanged: (_) {}), + ], + ), + ), + ), + ), + ); + await snapshotTest(tester, recorder, fixture); + }); + + testWidgets('masked material sliders', (tester) async { + recorder = SessionReplayRecorder( + defaultCapturePrivacy: TreeCapturePrivacy( + textAndInputPrivacyLevel: TextAndInputPrivacyLevel.maskAllInputs, + imagePrivacyLevel: ImagePrivacyLevel.maskNone, + ), + touchPrivacyLevel: TouchPrivacyLevel.show, + ); + recorder.updateContext(context); + + // With maskAllInputs every thumb should be anchored at the track midpoint + // regardless of the supplied value (0.0, 0.5, 1.0 should all look the same). + final fixture = MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Masked Material Sliders')), + body: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Slider(value: 0.0, onChanged: (_) {}), + Slider(value: 0.5, onChanged: (_) {}), + Slider(value: 1.0, onChanged: (_) {}), + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.9, + onChanged: (_) {}, + ), + Slider(value: 0.2, divisions: 5, onChanged: (_) {}), + ], + ), + ), + ), + ), + ); + await snapshotTest(tester, recorder, fixture); + }); + + testWidgets('masked cupertino sliders', (tester) async { + recorder = SessionReplayRecorder( + defaultCapturePrivacy: TreeCapturePrivacy( + textAndInputPrivacyLevel: TextAndInputPrivacyLevel.maskAllInputs, + imagePrivacyLevel: ImagePrivacyLevel.maskNone, + ), + touchPrivacyLevel: TouchPrivacyLevel.show, + ); + recorder.updateContext(context); + + // With maskAllInputs every thumb should be anchored at the track midpoint + // regardless of the supplied value (0.0, 0.5, 1.0 should all look the same). + final fixture = MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('Masked Cupertino Sliders')), + body: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoSlider(value: 0.0, onChanged: (_) {}), + CupertinoSlider(value: 0.5, onChanged: (_) {}), + CupertinoSlider(value: 1.0, onChanged: (_) {}), + CupertinoSlider(value: 0.5, divisions: 5, onChanged: (_) {}), + ], + ), + ), + ), + ), + ); + await snapshotTest(tester, recorder, fixture); + }); } diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart index 88c1eecc..9e30c9c2 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart @@ -6,11 +6,11 @@ import 'dart:math' as math; import 'package:flutter/cupertino.dart'; -import '../../../extensions.dart'; import '../../../sr_data_models.dart'; import '../../capture_node.dart'; import '../../recorder.dart'; import '../../view_tree_snapshot.dart'; +import '../material_widgets/slider_recorder.dart'; import '../recording_extensions.dart'; import 'cupertino_recording_extensions.dart'; @@ -191,39 +191,21 @@ class CupertinoSliderNode extends CaptureNode { @override List buildWireframes() { return [ - _shape( + ShapeWireframeBuilder.shape( id: inactiveTrackWireframeId, rect: inactiveTrackRect, color: trackColor, ), - _shape( + ShapeWireframeBuilder.shape( id: activeTrackWireframeId, rect: activeTrackRect, color: activeColor, ), - _shape( + ShapeWireframeBuilder.shape( id: thumbWireframeId, rect: thumbRect, color: thumbColor, ), ]; } - - static SRShapeWireframe _shape({ - required int id, - required Rect rect, - required Color color, - }) { - return SRShapeWireframe( - id: id, - x: rect.left.round(), - y: rect.top.round(), - width: rect.width.round(), - height: rect.height.round(), - shapeStyle: SRShapeStyle( - backgroundColor: color.toHexString(), - cornerRadius: rect.shortestSide / 2, - ), - ); - } } \ No newline at end of file diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index 8c7c6157..78849ea7 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -139,46 +139,47 @@ class SliderRecorder implements ElementRecorder { final Color? gapColor = geometry.gap != null ? _findBackgroundColor(element, theme) : null; + final int tickCount = + geometry.activeTickMarks.length + geometry.inactiveTickMarks.length; + final inactiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 0); final secondaryActiveTrackKey = keyGenerator.keyForElement(element, wireframeId: 1); final activeTrackKey = keyGenerator.keyForElement(element, wireframeId: 2); - final gapKey = keyGenerator.keyForElement(element, wireframeId: 3); - final stopIndicatorKey = - keyGenerator.keyForElement(element, wireframeId: 4); - final thumbKey = keyGenerator.keyForElement(element, wireframeId: 5); - final int tickCount = - geometry.activeTickMarks.length + geometry.inactiveTickMarks.length; final List tickMarkKeys = [ for (int i = 0; i < tickCount; i++) - keyGenerator.keyForElement(element, wireframeId: 6 + i), + keyGenerator.keyForElement(element, wireframeId: 3 + i), ]; + final gapKey = keyGenerator.keyForElement(element, wireframeId: 3 + tickCount); + final stopIndicatorKey = + keyGenerator.keyForElement(element, wireframeId: 4 + tickCount); + final thumbKey = keyGenerator.keyForElement(element, wireframeId: 5 + tickCount); final node = SliderNode( attributes, inactiveTrackWireframeId: inactiveTrackKey, secondaryActiveTrackWireframeId: secondaryActiveTrackKey, activeTrackWireframeId: activeTrackKey, + tickMarkWireframeIds: tickMarkKeys, gapWireframeId: gapKey, stopIndicatorWireframeId: stopIndicatorKey, thumbWireframeId: thumbKey, - tickMarkWireframeIds: tickMarkKeys, inactiveTrackRect: geometry.inactiveTrack.rect, secondaryActiveTrackRect: geometry.secondaryActiveTrack?.rect, activeTrackRect: geometry.activeTrack.rect, + activeTickMarkRects: geometry.activeTickMarks, + inactiveTickMarkRects: geometry.inactiveTickMarks, gapRect: geometry.gap, stopIndicatorRect: geometry.stopIndicator, thumbRect: geometry.thumb.rect, - activeTickMarkRects: geometry.activeTickMarks, - inactiveTickMarkRects: geometry.inactiveTickMarks, inactiveColor: inactiveColor, secondaryActiveColor: secondaryActiveColor, activeColor: activeColor, - gapColor: gapColor, - thumbColor: thumbColor, activeTickMarkColor: activeTickMarkColor, inactiveTickMarkColor: inactiveTickMarkColor, + gapColor: gapColor, + thumbColor: thumbColor, ); return SpecificElement( @@ -535,56 +536,56 @@ class SliderNode extends CaptureNode { final int inactiveTrackWireframeId; final int secondaryActiveTrackWireframeId; final int activeTrackWireframeId; + final List tickMarkWireframeIds; final int gapWireframeId; final int stopIndicatorWireframeId; final int thumbWireframeId; - final List tickMarkWireframeIds; final Rect inactiveTrackRect; final Rect? secondaryActiveTrackRect; final Rect activeTrackRect; + final List activeTickMarkRects; + final List inactiveTickMarkRects; final Rect? gapRect; final Rect? stopIndicatorRect; final Rect thumbRect; - final List activeTickMarkRects; - final List inactiveTickMarkRects; final Color inactiveColor; final Color secondaryActiveColor; final Color activeColor; - final Color? gapColor; - final Color thumbColor; final Color activeTickMarkColor; final Color inactiveTickMarkColor; + final Color? gapColor; + final Color thumbColor; const SliderNode( super.attributes, { required this.inactiveTrackWireframeId, required this.secondaryActiveTrackWireframeId, required this.activeTrackWireframeId, + required this.tickMarkWireframeIds, required this.gapWireframeId, required this.stopIndicatorWireframeId, required this.thumbWireframeId, - required this.tickMarkWireframeIds, required this.inactiveTrackRect, required this.secondaryActiveTrackRect, required this.activeTrackRect, + required this.activeTickMarkRects, + required this.inactiveTickMarkRects, required this.gapRect, required this.stopIndicatorRect, required this.thumbRect, - required this.activeTickMarkRects, - required this.inactiveTickMarkRects, required this.inactiveColor, required this.secondaryActiveColor, required this.activeColor, - required this.gapColor, - required this.thumbColor, required this.activeTickMarkColor, required this.inactiveTickMarkColor, + required this.gapColor, + required this.thumbColor, }); @override List buildWireframes() { final wireframes = [ - _shape( + ShapeWireframeBuilder.shape( id: inactiveTrackWireframeId, rect: inactiveTrackRect, color: inactiveColor, @@ -592,14 +593,14 @@ class SliderNode extends CaptureNode { ]; if (secondaryActiveTrackRect != null) { - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: secondaryActiveTrackWireframeId, rect: secondaryActiveTrackRect!, color: secondaryActiveColor, )); } - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: activeTrackWireframeId, rect: activeTrackRect, color: activeColor, @@ -610,7 +611,7 @@ class SliderNode extends CaptureNode { // track) use activeTickMarkColor; inactive ticks use the inactive color. int tickIdx = 0; for (final rect in activeTickMarkRects) { - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: tickMarkWireframeIds[tickIdx], rect: rect, color: activeTickMarkColor, @@ -618,7 +619,7 @@ class SliderNode extends CaptureNode { tickIdx++; } for (final rect in inactiveTickMarkRects) { - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: tickMarkWireframeIds[tickIdx], rect: rect, color: inactiveTickMarkColor, @@ -630,24 +631,23 @@ class SliderNode extends CaptureNode { // color. Sharp corners (cornerRadius: 0) so the cut against the rounded // track edges produces a clean band. if (gapRect != null && gapColor != null) { - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: gapWireframeId, rect: gapRect!, color: gapColor!, cornerRadius: 0, - borderColor: gapColor!, )); } if (stopIndicatorRect != null) { - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: stopIndicatorWireframeId, rect: stopIndicatorRect!, color: activeColor, )); } - wireframes.add(_shape( + wireframes.add(ShapeWireframeBuilder.shape( id: thumbWireframeId, rect: thumbRect, color: thumbColor, @@ -656,12 +656,18 @@ class SliderNode extends CaptureNode { return wireframes; } - static SRShapeWireframe _shape({ +} + +/// Builds [SRShapeWireframe] instances from a [Rect] + [Color]. Shared across +/// slider recorders (material + cupertino) to avoid duplicated helpers. +class ShapeWireframeBuilder { + const ShapeWireframeBuilder._(); + + static SRShapeWireframe shape({ required int id, required Rect rect, required Color color, double? cornerRadius, - Color borderColor = Colors.transparent, }) { return SRShapeWireframe( id: id, @@ -673,7 +679,6 @@ class SliderNode extends CaptureNode { backgroundColor: color.toHexString(), cornerRadius: cornerRadius ?? rect.shortestSide / 2, ), - border: SRShapeBorder(color: borderColor.toHexString(), width: 1), ); } } diff --git a/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart b/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart index e69de29b..4b6a5b64 100644 --- a/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart +++ b/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart @@ -0,0 +1,442 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2025-Present Datadog, Inc. + +import 'package:datadog_common_test/datadog_common_test.dart'; +import 'package:datadog_session_replay/datadog_session_replay.dart'; +import 'package:datadog_session_replay/src/capture/capture_node.dart'; +import 'package:datadog_session_replay/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart'; +import 'package:datadog_session_replay/src/capture/element_recorders/material_widgets/slider_recorder.dart'; +import 'package:datadog_session_replay/src/capture/recorder.dart'; +import 'package:datadog_session_replay/src/extensions.dart'; +import 'package:datadog_session_replay/src/rum_context.dart'; +import 'package:datadog_session_replay/src/sr_data_models.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../simple_test_capture.dart'; + +SimpleTestCapture captureSlider( + SessionReplayRecorder recorder, + Widget slider, { + ThemeData? theme, +}) { + return SimpleTestCapture( + key: Key('key'), + recorder: recorder, + child: MaterialApp( + theme: theme, + home: Scaffold( + body: Center(child: slider), + ), + ), + ); +} + +void main() { + late SessionReplayRecorder recorder; + late RUMContext context; + + setUp(() { + recorder = SessionReplayRecorder.withCustomRecorders( + [ + SliderRecorder(KeyGenerator()), + CupertinoSliderRecorder(KeyGenerator()), + ], + defaultCapturePrivacy: TreeCapturePrivacy( + textAndInputPrivacyLevel: TextAndInputPrivacyLevel.maskSensitiveInputs, + imagePrivacyLevel: ImagePrivacyLevel.maskNonAssetsOnly, + ), + touchPrivacyLevel: TouchPrivacyLevel.show, + ); + + registerFallbackValue( + CapturedViewAttributes(paintBounds: Rect.zero, scaleX: 1.0, scaleY: 1.0), + ); + + context = RUMContext( + applicationId: randomString(), + sessionId: randomString(), + ); + recorder.updateContext(context); + }); + + List wireframesOf(CaptureResult? capture) { + return capture!.viewTreeSnapshot.nodes.first.buildWireframes(); + } + + // Both recorders emit the inactive track first and the thumb last. Anything + // in between (secondary, ticks, gap, stop indicator) varies by configuration. + SRShapeWireframe inactiveTrackOf(CaptureResult? capture) => + wireframesOf(capture).first as SRShapeWireframe; + + SRShapeWireframe thumbOf(CaptureResult? capture) => + wireframesOf(capture).last as SRShapeWireframe; + + void metaTestWidgets( + String testDescription, + List setups, + void Function(CaptureResult?) checks, { + VoidCallback? beforeEach, + VoidCallback? afterEach, + }) { + for (final setup in setups) { + testWidgets(testDescription, (tester) async { + // Given + beforeEach?.call(); + final tree = setup(); + await tester.pumpWidget(tree); + + // When + final capture = await recorder.performCapture(); + + // Then + checks(capture); + afterEach?.call(); + }); + } + } + + group('wireframe count', () { + metaTestWidgets( + 'slider produces 3 wireframes (inactive + active + thumb)', + [ + () => captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + expect(wireframesOf(capture).length, 3); + }, + ); + + metaTestWidgets( + 'slider produces a single capture node', + [ + () => captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + expect(capture!.viewTreeSnapshot.nodes.length, 1); + }, + ); + + metaTestWidgets( + 'disabled slider (onChanged: null) still produces 3 wireframes', + [ + () => captureSlider(recorder, Slider(value: 0.5, onChanged: null)), + () => captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: null)), + ], + (capture) { + expect(capture, isNotNull); + expect(wireframesOf(capture).length, 3); + }, + ); + + testWidgets( + 'material slider with secondaryTrackValue adds a secondary track wireframe', + (tester) async { + final tree = captureSlider( + recorder, + Slider(value: 0.3, secondaryTrackValue: 0.7, onChanged: (_) {}), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + expect(wireframesOf(capture).length, 4); + }); + + testWidgets( + 'material slider with divisions=N adds N+1 tick mark wireframes', + (tester) async { + final tree = captureSlider( + recorder, + Slider(value: 0.5, divisions: 4, onChanged: (_) {}), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + // 3 (inactive + active + thumb) + 5 ticks = 8 + expect(wireframesOf(capture).length, 8); + }); + + testWidgets('CupertinoSlider with divisions still produces 3 wireframes', + (tester) async { + // CupertinoSlider snaps the value to divisions but doesn't render + // visual tick marks (unlike Material). + final tree = captureSlider( + recorder, + CupertinoSlider(value: 0.5, divisions: 4, onChanged: (_) {}), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + expect(wireframesOf(capture).length, 3); + }); + + testWidgets( + 'M3-2024 material slider (year2023: false) adds gap + stop indicator wireframes', + (tester) async { + final tree = captureSlider( + recorder, + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.5, + onChanged: (_) {}, + ), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + // 3 (inactive + active + thumb) + 2 (gap + stop indicator) = 5 + expect(wireframesOf(capture).length, 5); + }); + }); + + group('colors', () { + metaTestWidgets( + 'thumb uses widget.thumbColor when set', + [ + () => captureSlider( + recorder, + Slider(value: 0.5, onChanged: (_) {}, thumbColor: Colors.red)), + () => captureSlider( + recorder, + CupertinoSlider( + value: 0.5, onChanged: (_) {}, thumbColor: Colors.red)), + ], + (capture) { + expect(capture, isNotNull); + expect(thumbOf(capture).shapeStyle!.backgroundColor, + Colors.red.toHexString()); + }, + ); + + metaTestWidgets( + 'active track uses widget.activeColor when set', + [ + () => captureSlider( + recorder, + Slider(value: 0.5, onChanged: (_) {}, activeColor: Colors.green)), + () => captureSlider( + recorder, + CupertinoSlider( + value: 0.5, onChanged: (_) {}, activeColor: Colors.green)), + ], + (capture) { + expect(capture, isNotNull); + // For both recorders, with no secondary/ticks/gap, the active track + // lives at index 1 (between [0] inactive and [last] thumb). + final active = wireframesOf(capture)[1] as SRShapeWireframe; + expect(active.shapeStyle!.backgroundColor, Colors.green.toHexString()); + }, + ); + + testWidgets('material inactive track uses widget.inactiveColor when set', + (tester) async { + final tree = captureSlider( + recorder, + Slider(value: 0.5, onChanged: (_) {}, inactiveColor: Colors.yellow), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + expect(inactiveTrackOf(capture).shapeStyle!.backgroundColor, + Colors.yellow.toHexString()); + }); + + testWidgets( + 'material secondary track uses widget.secondaryActiveColor when set', + (tester) async { + final tree = captureSlider( + recorder, + Slider( + value: 0.3, + secondaryTrackValue: 0.7, + onChanged: (_) {}, + secondaryActiveColor: Colors.purple, + ), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + // Order: [0] inactive, [1] secondary, [2] active, [3] thumb. + final secondary = wireframesOf(capture)[1] as SRShapeWireframe; + expect(secondary.shapeStyle!.backgroundColor, + Colors.purple.toHexString()); + }); + }); + + group('thumb position', () { + metaTestWidgets( + 'thumb at value=min sits on the left side of the track', + [ + () => captureSlider(recorder, Slider(value: 0.0, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 0.0, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + expect(thumb.x + thumb.width / 2, lessThan(track.x + track.width / 2)); + }, + ); + + metaTestWidgets( + 'thumb at value=max sits on the right side of the track', + [ + () => captureSlider(recorder, Slider(value: 1.0, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 1.0, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + expect( + thumb.x + thumb.width / 2, greaterThan(track.x + track.width / 2)); + }, + ); + + metaTestWidgets( + 'thumb at value=0.5 sits near the middle of the track', + [ + () => captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + final trackMid = track.x + track.width / 2; + final thumbMid = thumb.x + thumb.width / 2; + expect((thumbMid - trackMid).abs(), lessThan(2)); + }, + ); + + testWidgets( + 'material thumb position scales linearly with custom min/max range', + (tester) async { + final tree = captureSlider( + recorder, + Slider(value: 50.0, min: 0.0, max: 100.0, onChanged: (_) {}), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + final trackMid = track.x + track.width / 2; + final thumbMid = thumb.x + thumb.width / 2; + expect((thumbMid - trackMid).abs(), lessThan(2)); + }); + }); + + group('material year2023', () { + testWidgets('year2023: false produces a handle-style thumb (taller than wide)', + (tester) async { + final tree = captureSlider( + recorder, + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.5, + onChanged: (_) {}, + ), + ); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + final thumb = thumbOf(capture); + expect(thumb.height, 44); + expect(thumb.width, greaterThanOrEqualTo(2.0)); + expect(thumb.width, lessThanOrEqualTo(4.0)); + }); + + testWidgets('year2023: true produces a round thumb (square)', + (tester) async { + final tree = + captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {})); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + final thumb = thumbOf(capture); + expect(thumb.width, thumb.height); + }); + }); + + group('cupertino specifics', () { + testWidgets('CupertinoSlider thumb is a circle (square wireframe)', + (tester) async { + final tree = captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: (_) {})); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + final thumb = thumbOf(capture); + expect(thumb.width, thumb.height); + }); + + testWidgets( + 'CupertinoSlider default thumb color is white when no thumbColor is set', + (tester) async { + final tree = captureSlider( + recorder, CupertinoSlider(value: 0.5, onChanged: (_) {})); + await tester.pumpWidget(tree); + final capture = await recorder.performCapture(); + expect(capture, isNotNull); + expect(thumbOf(capture).shapeStyle!.backgroundColor, + CupertinoColors.white.toHexString()); + }); + }); + + group('privacy', () { + metaTestWidgets( + 'maskAllInputs anchors the thumb at midpoint regardless of value', + [ + () => captureSlider(recorder, Slider(value: 0.95, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 0.95, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + final trackMid = track.x + track.width / 2; + final thumbMid = thumb.x + thumb.width / 2; + expect((thumbMid - trackMid).abs(), lessThan(2)); + }, + beforeEach: () { + recorder.defaultTreeCapturePrivacy = TreeCapturePrivacy( + textAndInputPrivacyLevel: TextAndInputPrivacyLevel.maskAllInputs, + imagePrivacyLevel: ImagePrivacyLevel.maskNonAssetsOnly, + ); + }, + ); + + metaTestWidgets( + 'maskSensitiveInputs does not anchor the thumb (default)', + [ + () => captureSlider(recorder, Slider(value: 1.0, onChanged: (_) {})), + () => captureSlider( + recorder, CupertinoSlider(value: 1.0, onChanged: (_) {})), + ], + (capture) { + expect(capture, isNotNull); + final track = inactiveTrackOf(capture); + final thumb = thumbOf(capture); + expect( + thumb.x + thumb.width / 2, greaterThan(track.x + track.width / 2)); + }, + ); + }); +} From d6efe9f32a6d392b31e3b69fcab048c3232631de Mon Sep 17 00:00:00 2001 From: Juan Naranjo Date: Tue, 19 May 2026 15:03:55 +0200 Subject: [PATCH 9/9] chore(sr): dart format applied and feedback comments addressed --- .../goldens/masked_cupertino_sliders.png | Bin 25724 -> 29020 bytes .../goldens/masked_material_sliders.png | Bin 30239 -> 37690 bytes .../goldens/unmasked_material_sliders.png | Bin 35124 -> 37469 bytes .../simple_widget_golden_test.dart | 44 +++++++++++++- .../cupertino_slider_recorder.dart | 23 ++++--- .../material_widgets/slider_recorder.dart | 57 ++++++++++-------- .../capture/widgets/slider_recorder_test.dart | 13 ++-- 7 files changed, 92 insertions(+), 45 deletions(-) diff --git a/packages/datadog_session_replay/example/golden_test/goldens/masked_cupertino_sliders.png b/packages/datadog_session_replay/example/golden_test/goldens/masked_cupertino_sliders.png index 67d8ab79028c53a14b737c9e93ba30dec81d6316..36dde8f6afafb7283f0be091c81f2a9e67e754fe 100644 GIT binary patch literal 29020 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 literal 25724 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 syisAXRn-W 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 literal 30239 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 diff --git a/packages/datadog_session_replay/example/golden_test/goldens/unmasked_material_sliders.png b/packages/datadog_session_replay/example/golden_test/goldens/unmasked_material_sliders.png index fcc86823a8551abfe114fe8c1edf02fd4f120ef8..d3fc889e9aaf7728b82bb12be5ff386250a05bc9 100644 GIT binary patch literal 37469 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 literal 35124 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 KCH1t~gZ~c>&*G~9 diff --git a/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart b/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart index 1efa7c52..69298deb 100644 --- a/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart +++ b/packages/datadog_session_replay/example/golden_test/simple_widget_golden_test.dart @@ -509,6 +509,13 @@ void main() { value: 0.4, onChanged: (_) {}, ), + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.4, + divisions: 5, + onChanged: (_) {}, + ), // Disabled. Slider(value: 0.5, onChanged: null), ], @@ -571,16 +578,42 @@ void main() { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + // year2023 = true (default M3 round thumb) — min, mid, max. Slider(value: 0.0, onChanged: (_) {}), Slider(value: 0.5, onChanged: (_) {}), Slider(value: 1.0, onChanged: (_) {}), + // Custom colors. + Slider( + value: 0.5, + onChanged: (_) {}, + activeColor: Colors.green, + inactiveColor: Colors.yellow, + thumbColor: Colors.red, + ), + // With secondary track value. + Slider( + value: 0.3, + secondaryTrackValue: 0.7, + onChanged: (_) {}, + ), + // Discrete slider with tick marks. + Slider(value: 0.4, divisions: 5, onChanged: (_) {}), + // M3-2024 (year2023 = false) — handle thumb + gap + stop indicator. + Slider( + // ignore: deprecated_member_use + year2023: false, + value: 0.4, + onChanged: (_) {}, + ), Slider( // ignore: deprecated_member_use year2023: false, - value: 0.9, + value: 0.4, + divisions: 5, onChanged: (_) {}, ), - Slider(value: 0.2, divisions: 5, onChanged: (_) {}), + // Disabled. + Slider(value: 0.5, onChanged: null), ], ), ), @@ -614,6 +647,13 @@ void main() { CupertinoSlider(value: 0.0, onChanged: (_) {}), CupertinoSlider(value: 0.5, onChanged: (_) {}), CupertinoSlider(value: 1.0, onChanged: (_) {}), + CupertinoSlider( + value: 0.5, + onChanged: (_) {}, + activeColor: CupertinoColors.systemPurple, + thumbColor: CupertinoColors.systemPurple, + ), + CupertinoSlider(value: 0.5, onChanged: null), CupertinoSlider(value: 0.5, divisions: 5, onChanged: (_) {}), ], ), diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart index 9e30c9c2..3c6b480b 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/cupertino_widgets/cupertino_slider_recorder.dart @@ -76,8 +76,7 @@ class CupertinoSliderRecorder implements ElementRecorder { ); return SpecificElement( - subtreeStrategy: CaptureNodeSubtreeStrategy - .ignore, // Ignore subtree to prevent CustomPaintRecorder from capturing the inner CustomPaint + subtreeStrategy: CaptureNodeSubtreeStrategy.ignore, nodes: [node], ); } @@ -123,18 +122,18 @@ class CupertinoSliderRecorder implements ElementRecorder { final double trackBottom = trackCenterY + trackHalfHeight; final double range = widget.max - widget.min; - final double valueRatio = isMasked - ? 0.5 - : (range == 0 - ? 0.0 - : ((widget.value - widget.min) / range) - .clamp(0.0, 1.0) - .toDouble()); - + final double valueRatio; + if (isMasked) { + valueRatio = 0.5; + } else { + valueRatio = range == 0 + ? 0.0 + : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble(); + } final double thumbTravel = (trackRight - trackLeft) - 2 * thumbRadius; final double thumbCenterX = trackLeft + thumbRadius + thumbTravel * valueRatio; - + final Rect inactiveTrack = Rect.fromLTRB( trackLeft, trackTop, @@ -208,4 +207,4 @@ class CupertinoSliderNode extends CaptureNode { ), ]; } -} \ No newline at end of file +} diff --git a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart index 78849ea7..3d0cd77e 100644 --- a/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart +++ b/packages/datadog_session_replay/lib/src/capture/element_recorders/material_widgets/slider_recorder.dart @@ -14,6 +14,9 @@ import '../../recorder.dart'; import '../../view_tree_snapshot.dart'; import '../recording_extensions.dart'; +const Size _handleThumbSize = Size(4.0, 44.0); +const double _roundedThumbDiameter = 20.0; + enum _SliderThumbStyle { round, handle } typedef _SliderThumbGeometry = ({ @@ -54,6 +57,9 @@ class SliderRecorder implements ElementRecorder { CapturedViewAttributes attributes, TreeCapturePrivacy capturePrivacy, ) { + final widget = element.widget; + if (widget is! Slider) return null; + // Check for cupertino slider style { bool isCupertinoAdaptive = false; @@ -63,9 +69,6 @@ class SliderRecorder implements ElementRecorder { if (isCupertinoAdaptive) return null; } - final widget = element.widget; - if (widget is! Slider) return null; - // Resolves for privacy settings final bool isMasked = capturePrivacy.shouldMaskInputs; @@ -134,8 +137,9 @@ class SliderRecorder implements ElementRecorder { scaleY: attributes.scaleY, ); - // Only walk the tree for a background color when we actually need to fake - // the gap (M3-2024 / year2023 == false) + // We only need the background color to create a fake gap, between the Track and the Thumb, + // so we only grab it when geometry.gap is set. This will be true for Material 3, or when + // year2023 is false. final Color? gapColor = geometry.gap != null ? _findBackgroundColor(element, theme) : null; @@ -151,10 +155,12 @@ class SliderRecorder implements ElementRecorder { for (int i = 0; i < tickCount; i++) keyGenerator.keyForElement(element, wireframeId: 3 + i), ]; - final gapKey = keyGenerator.keyForElement(element, wireframeId: 3 + tickCount); + final gapKey = + keyGenerator.keyForElement(element, wireframeId: 3 + tickCount); final stopIndicatorKey = keyGenerator.keyForElement(element, wireframeId: 4 + tickCount); - final thumbKey = keyGenerator.keyForElement(element, wireframeId: 5 + tickCount); + final thumbKey = + keyGenerator.keyForElement(element, wireframeId: 5 + tickCount); final node = SliderNode( attributes, @@ -183,12 +189,14 @@ class SliderRecorder implements ElementRecorder { ); return SpecificElement( - subtreeStrategy: CaptureNodeSubtreeStrategy - .ignore, // Ignore subtree to prevent CustomPaintRecorder from capturing the inner CustomPaint + subtreeStrategy: CaptureNodeSubtreeStrategy.ignore, nodes: [node], ); } + // All color values mirror Flutter’s own implementation, + // as declared in package:flutter/src/material/slider.dart. + Color _getActiveColor({ required Slider widget, required bool isEnabled, @@ -297,15 +305,15 @@ class SliderRecorder implements ElementRecorder { if (isGapped) { thumbStyle = _SliderThumbStyle.handle; final Size logicalThumbSize = - sliderTheme.thumbSize?.resolve({}) ?? - const Size(4.0, 44.0); + sliderTheme.thumbSize?.resolve({}) ?? _handleThumbSize; thumbSize = Size( logicalThumbSize.width * scale, logicalThumbSize.height * scale, ); } else { thumbStyle = _SliderThumbStyle.round; - thumbSize = Size(20.0 * scale, 20.0 * scale); + thumbSize = + Size(_roundedThumbDiameter * scale, _roundedThumbDiameter * scale); } final double overlayWidth = 48.0 * scale; @@ -327,11 +335,14 @@ class SliderRecorder implements ElementRecorder { final double range = widget.max - widget.min; // When inputs are masked, anchor the thumb at the center of the track so // the recorded replay doesn't leak the actual value. - final double valueRatio = isMasked - ? 0.5 - : (range == 0 - ? 0.0 - : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble()); + final double valueRatio; + if (isMasked) { + valueRatio = 0.5; + } else { + valueRatio = range == 0 + ? 0.0 + : ((widget.value - widget.min) / range).clamp(0.0, 1.0).toDouble(); + } final double thumbTravel = trackWidth - 2 * trackEndRadius.x; final double thumbCenterX = trackLeft + trackEndRadius.x + thumbTravel * valueRatio; @@ -354,14 +365,13 @@ class SliderRecorder implements ElementRecorder { _SliderTrackSegmentGeometry? secondaryActiveTrack; final double? secValue = widget.secondaryTrackValue; if (secValue != null) { - final double clampedSec = - secValue.clamp(widget.min, widget.max).toDouble(); - final double secRatio = - range == 0 ? 0.0 : (clampedSec - widget.min) / range; - final double secX = trackLeft + trackWidth * secRatio; + final clampedSec = secValue.clamp(widget.min, widget.max); + final secRatio = range == 0 ? 0.0 : (clampedSec - widget.min) / range; + final secX = trackLeft + trackWidth * secRatio; if (secX > trackLeft) { secondaryActiveTrack = ( - rect: Rect.fromLTRB(trackLeft, trackTop, secX, trackBottom), + rect: + Rect.fromLTRB(trackLeft, trackTop, secX.toDouble(), trackBottom), borderRadius: BorderRadius.all(trackEndRadius), ); } @@ -655,7 +665,6 @@ class SliderNode extends CaptureNode { return wireframes; } - } /// Builds [SRShapeWireframe] instances from a [Rect] + [Color]. Shared across diff --git a/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart b/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart index 4b6a5b64..05f28869 100644 --- a/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart +++ b/packages/datadog_session_replay/test/capture/widgets/slider_recorder_test.dart @@ -204,8 +204,7 @@ void main() { metaTestWidgets( 'thumb uses widget.thumbColor when set', [ - () => captureSlider( - recorder, + () => captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {}, thumbColor: Colors.red)), () => captureSlider( recorder, @@ -222,8 +221,7 @@ void main() { metaTestWidgets( 'active track uses widget.activeColor when set', [ - () => captureSlider( - recorder, + () => captureSlider(recorder, Slider(value: 0.5, onChanged: (_) {}, activeColor: Colors.green)), () => captureSlider( recorder, @@ -269,8 +267,8 @@ void main() { expect(capture, isNotNull); // Order: [0] inactive, [1] secondary, [2] active, [3] thumb. final secondary = wireframesOf(capture)[1] as SRShapeWireframe; - expect(secondary.shapeStyle!.backgroundColor, - Colors.purple.toHexString()); + expect( + secondary.shapeStyle!.backgroundColor, Colors.purple.toHexString()); }); }); @@ -342,7 +340,8 @@ void main() { }); group('material year2023', () { - testWidgets('year2023: false produces a handle-style thumb (taller than wide)', + testWidgets( + 'year2023: false produces a handle-style thumb (taller than wide)', (tester) async { final tree = captureSlider( recorder,