You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_Patient_App_New/lib/widgets/clarity_interaction_tracker...

201 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:clarity_flutter/clarity_flutter.dart';
/// A wrapper widget that tracks user interactions with Clarity for UX frustration analysis.
/// Helps identify rage clicks, dead clicks, and rapid scrolls.
///
/// Usage:
/// ```dart
/// ClarityInteractionTracker(
/// semanticLabel: 'Login Button',
/// interactionType: ClarityInteractionType.button,
/// onTap: () { /* your action */ },
/// child: YourWidget(),
/// )
/// ```
class ClarityInteractionTracker extends StatelessWidget {
final Widget child;
final String semanticLabel;
final VoidCallback? onTap;
final ClarityInteractionType interactionType;
final Map<String, String>? customTags;
final bool enabled;
const ClarityInteractionTracker({
super.key,
required this.child,
required this.semanticLabel,
this.onTap,
this.interactionType = ClarityInteractionType.button,
this.customTags,
this.enabled = true,
});
void _logInteraction(BuildContext context) {
if (!enabled) return;
try {
// Set common tags
Clarity.setCustomTag('interaction_type', interactionType.name);
Clarity.setCustomTag('interaction_label',
semanticLabel.length > 100 ? semanticLabel.substring(0, 100) : semanticLabel
);
Clarity.setCustomTag('interaction_enabled', enabled.toString());
// Set route/screen name if available
final route = ModalRoute.of(context);
if (route != null && route.settings.name != null) {
Clarity.setCustomTag('screen_route', route.settings.name!);
}
// Add custom tags if provided
if (customTags != null) {
customTags!.forEach((key, value) {
// Ensure key and value fit Clarity's 255 char limit
final truncatedValue = value.length > 250 ? value.substring(0, 250) : value;
Clarity.setCustomTag(key, truncatedValue);
});
}
// Send event to Clarity
Clarity.sendCustomEvent('user_interaction_${interactionType.name}');
} catch (e) {
// Silence Clarity errors to not interrupt user flow
debugPrint('Clarity interaction tracking error: $e');
}
}
@override
Widget build(BuildContext context) {
return Semantics(
label: semanticLabel,
button: interactionType == ClarityInteractionType.button,
enabled: enabled,
excludeSemantics: false,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: enabled && onTap != null
? () {
_logInteraction(context);
onTap?.call();
}
: null,
child: child,
),
);
}
}
/// Types of interactions that can be tracked
enum ClarityInteractionType {
button,
link,
card,
listItem,
icon,
image,
input,
custom,
}
/// Extension to easily wrap any widget with Clarity tracking
extension ClarityTrackingExtension on Widget {
/// Wraps this widget with Clarity interaction tracking
Widget trackWithClarity({
required String label,
VoidCallback? onTap,
ClarityInteractionType type = ClarityInteractionType.custom,
Map<String, String>? customTags,
bool enabled = true,
}) {
return ClarityInteractionTracker(
semanticLabel: label,
onTap: onTap,
interactionType: type,
customTags: customTags,
enabled: enabled,
child: this,
);
}
}
/// Wrapper for ScrollView to track rapid scrolling behavior
class ClarityScrollTracker extends StatefulWidget {
final Widget child;
final String scrollViewLabel;
final ScrollController? controller;
const ClarityScrollTracker({
super.key,
required this.child,
required this.scrollViewLabel,
this.controller,
});
@override
State<ClarityScrollTracker> createState() => _ClarityScrollTrackerState();
}
class _ClarityScrollTrackerState extends State<ClarityScrollTracker> {
late ScrollController _controller;
bool _isRapidScrolling = false;
DateTime? _lastScrollTime;
int _rapidScrollCount = 0;
@override
void initState() {
super.initState();
_controller = widget.controller ?? ScrollController();
_controller.addListener(_onScroll);
}
void _onScroll() {
final now = DateTime.now();
// Detect rapid scrolling (scroll events within 100ms)
if (_lastScrollTime != null) {
final timeDiff = now.difference(_lastScrollTime!).inMilliseconds;
if (timeDiff < 100) {
_rapidScrollCount++;
if (!_isRapidScrolling && _rapidScrollCount > 5) {
_isRapidScrolling = true;
_logRapidScroll();
}
} else if (timeDiff > 500) {
// Reset if scrolling slows down
_isRapidScrolling = false;
_rapidScrollCount = 0;
}
}
_lastScrollTime = now;
}
void _logRapidScroll() {
try {
Clarity.setCustomTag('scroll_type', 'rapid');
Clarity.setCustomTag('scroll_view', widget.scrollViewLabel);
Clarity.setCustomTag('scroll_position', _controller.position.pixels.toString());
Clarity.sendCustomEvent('rapid_scroll_detected');
} catch (e) {
debugPrint('Clarity scroll tracking error: $e');
}
}
@override
void dispose() {
if (widget.controller == null) {
_controller.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}