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/services/clarity_service.dart

292 lines
8.5 KiB
Dart

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<String, String>? 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<String, String>? 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<String, String>? 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<String, String>? 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<String, String>? 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);
}
}