import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:flutter/foundation.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; /// Centralized service for Clarity tracking and analytics /// Provides methods for tracking user actions, errors, and app state class ClarityService { final AppState _appState; ClarityService({required AppState appState}) : _appState = appState; /// Initialize Clarity with user context void setUserContext() { if (kDebugMode) return; // Skip in debug mode try { if (_appState.isAuthenticated) { final user = _appState.getAuthenticatedUser(); if (user != null) { // Set user ID Clarity.setCustomUserId(user.patientId.toString()); // Set user segments for filtering Clarity.setCustomTag('user_type', user.patientType?.toString() ?? 'unknown'); Clarity.setCustomTag('user_segment', user.outSa == 0 ? 'in_sa' : 'out_sa'); Clarity.setCustomTag('auth_status', 'authenticated'); } } else { Clarity.setCustomTag('auth_status', 'guest'); } } catch (e) { debugPrint('Failed to set Clarity user context: $e'); } } /// Track screen views / route changes void trackScreenView(String screenName, {Map? properties}) { if (kDebugMode) return; try { Clarity.setCustomTag('current_screen', _truncate(screenName, 100)); if (properties != null) { properties.forEach((key, value) { Clarity.setCustomTag(key, _truncate(value, 250)); }); } Clarity.sendCustomEvent('screen_view_$screenName'); } catch (e) { debugPrint('Failed to track screen view: $e'); } } /// Track user actions (button clicks, form submissions, etc.) void trackAction(String actionName, {Map? properties}) { if (kDebugMode) return; try { Clarity.setCustomTag('action_name', _truncate(actionName, 100)); if (properties != null) { properties.forEach((key, value) { Clarity.setCustomTag('action_$key', _truncate(value, 250)); }); } Clarity.sendCustomEvent('user_action_$actionName'); } catch (e) { debugPrint('Failed to track action: $e'); } } /// Track API calls with success/failure void trackApiCall({ required String endpoint, required String method, required bool isSuccess, int? statusCode, String? errorType, String? errorMessage, }) { if (kDebugMode) return; try { final eventType = isSuccess ? 'api_success' : 'api_failure'; Clarity.setCustomTag('api_endpoint', _getEndpointName(endpoint)); Clarity.setCustomTag('api_method', method); Clarity.setCustomTag('api_status', isSuccess ? 'success' : 'failure'); if (statusCode != null) { Clarity.setCustomTag('api_status_code', statusCode.toString()); } if (errorType != null) { Clarity.setCustomTag('api_error_type', _truncate(errorType, 100)); } if (errorMessage != null) { Clarity.setCustomTag('api_error_msg', _truncate(errorMessage, 250)); } if (_appState.isAuthenticated) { Clarity.setCustomTag('api_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } Clarity.sendCustomEvent(eventType); } catch (e) { debugPrint('Failed to track API call: $e'); } } /// Track app errors and exceptions void trackError({ required String errorType, required String errorMessage, String? stackTrace, bool isFatal = false, Map? additionalContext, }) { if (kDebugMode) return; try { Clarity.setCustomTag('error_type', _truncate(errorType, 100)); Clarity.setCustomTag('error_fatal', isFatal.toString()); Clarity.setCustomTag('error_message', _truncate(errorMessage, 250)); if (stackTrace != null) { // Only send first 250 chars of stack trace Clarity.setCustomTag('error_stack', _truncate(stackTrace, 250)); } if (additionalContext != null) { additionalContext.forEach((key, value) { Clarity.setCustomTag('error_$key', _truncate(value, 250)); }); } if (_appState.isAuthenticated) { Clarity.setCustomTag('error_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } Clarity.sendCustomEvent(isFatal ? 'app_fatal_error' : 'app_error'); } catch (e) { debugPrint('Failed to track error: $e'); } } /// Track user frustration events (rage clicks, dead clicks) void trackFrustration({ required String frustrationType, required String elementLabel, Map? context, }) { if (kDebugMode) return; try { Clarity.setCustomTag('frustration_type', frustrationType); Clarity.setCustomTag('frustration_element', _truncate(elementLabel, 100)); if (context != null) { context.forEach((key, value) { Clarity.setCustomTag('frustration_$key', _truncate(value, 250)); }); } Clarity.sendCustomEvent('user_frustration_$frustrationType'); } catch (e) { debugPrint('Failed to track frustration: $e'); } } /// Track feature usage void trackFeatureUsage(String featureName, {Map? metadata}) { if (kDebugMode) return; try { Clarity.setCustomTag('feature_name', _truncate(featureName, 100)); if (metadata != null) { metadata.forEach((key, value) { Clarity.setCustomTag('feature_$key', _truncate(value, 250)); }); } if (_appState.isAuthenticated) { Clarity.setCustomTag('feature_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } Clarity.sendCustomEvent('feature_used_$featureName'); } catch (e) { debugPrint('Failed to track feature usage: $e'); } } /// Track payment events void trackPayment({ required String paymentStatus, String? paymentMethod, String? amount, String? transactionId, }) { if (kDebugMode) return; try { Clarity.setCustomTag('payment_status', paymentStatus); if (paymentMethod != null) { Clarity.setCustomTag('payment_method', paymentMethod); } if (amount != null) { Clarity.setCustomTag('payment_amount', amount); } if (transactionId != null) { Clarity.setCustomTag('payment_tx_id', _truncate(transactionId, 100)); } if (_appState.isAuthenticated) { Clarity.setCustomTag('payment_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } Clarity.sendCustomEvent('payment_$paymentStatus'); } catch (e) { debugPrint('Failed to track payment: $e'); } } /// Track appointment booking flow void trackAppointmentEvent({ required String eventType, String? doctorId, String? clinicId, String? appointmentDate, }) { if (kDebugMode) return; try { Clarity.setCustomTag('appointment_event', eventType); if (doctorId != null) { Clarity.setCustomTag('appointment_doctor', doctorId); } if (clinicId != null) { Clarity.setCustomTag('appointment_clinic', clinicId); } if (appointmentDate != null) { Clarity.setCustomTag('appointment_date', appointmentDate); } if (_appState.isAuthenticated) { Clarity.setCustomTag('appointment_user_id', _appState.getAuthenticatedUser()!.patientId.toString()); } Clarity.sendCustomEvent('appointment_$eventType'); } catch (e) { debugPrint('Failed to track appointment event: $e'); } } /// Clear user context on logout void clearUserContext() { if (kDebugMode) return; try { Clarity.setCustomTag('auth_status', 'guest'); Clarity.setCustomTag('user_action', 'logout'); Clarity.sendCustomEvent('user_logged_out'); } catch (e) { debugPrint('Failed to clear Clarity user context: $e'); } } /// Helper: Truncate string to fit Clarity's limits String _truncate(String text, int maxLength) { if (text.length <= maxLength) return text; return '...${text.substring(text.length - (maxLength - 3))}'; } /// Helper: Extract endpoint name from full URL String _getEndpointName(String endpoint) { final cleanEndpoint = endpoint.split('/').last; return _truncate(cleanEndpoint, 100); } }