import 'dart:convert'; import 'package:analytics_sdk/const/event_type.dart'; import 'package:analytics_sdk/manager/sdk_context.dart'; /// 应用生命周期事件。 /// /// `app_lifecycle` 的子事件字段写入 `payload`。每个工厂方法只写入该 /// 子事件文档定义的字段。 class AppLifecycleEvent { final String event = EventType.appLifecycle.event; final String lifecycleEventKey; final Map _fields; late final String eventId; late final Map _commonFields; AppLifecycleEvent._( this.lifecycleEventKey, Map fields, ) : _fields = Map.from(fields) ..removeWhere((_, value) => value == null) { _commonFields = SdkContext.generateCommonFields(event); eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [ lifecycleEventKey, jsonEncode(_stableFields), ]); } factory AppLifecycleEvent.coldStart({ required int coldStartDurationMs, String lastExitType = 'unknown', int bootTime = 0, }) => AppLifecycleEvent._('cold_start', { 'cold_start_duration_ms': _nonNegative(coldStartDurationMs), 'last_exit_type': _allowedString( lastExitType, const {'normal', 'killed', 'crash', 'unknown'}, 'unknown', ), 'boot_time': bootTime > 0 ? bootTime : 0, }); factory AppLifecycleEvent.webLoad({ required String pageUrl, required int pageLoadTimeMs, required int viewportWidth, required int viewportHeight, }) => AppLifecycleEvent._('web_load', { 'page_url': pageUrl, 'page_load_time_ms': _nonNegative(pageLoadTimeMs), 'viewport_width': viewportWidth, 'viewport_height': viewportHeight, }); factory AppLifecycleEvent.warmStart({ required int backgroundDurationMs, String resumeReason = 'unknown', }) => AppLifecycleEvent._('warm_start', { 'background_duration_ms': _nonNegative(backgroundDurationMs), 'resume_reason': _allowedString( resumeReason, const {'user', 'push', 'scheme', 'unknown'}, 'unknown', ), }); factory AppLifecycleEvent.foreground({ required int foregroundCountInSid, }) => AppLifecycleEvent._('foreground', { 'foreground_count_in_sid': foregroundCountInSid < 1 ? 1 : foregroundCountInSid, 'is_foreground': 1, }); factory AppLifecycleEvent.background({ required int foregroundDurationMs, required String lastActivePageKey, }) => AppLifecycleEvent._('background', { 'is_foreground': 0, 'foreground_duration_ms': _nonNegative(foregroundDurationMs), 'last_active_page_key': lastActivePageKey, }); factory AppLifecycleEvent.webUnload({ required String pageUrl, required String lastActivePageKey, required int pageStayDurationMs, String unloadReason = 'unknown', required int scrollDepthPercentAtUnload, required String webVisibilityState, }) => AppLifecycleEvent._('web_unload', { 'page_url': pageUrl, 'last_active_page_key': lastActivePageKey, 'page_stay_duration_ms': _nonNegative(pageStayDurationMs), 'unload_reason': _allowedString( unloadReason, const { 'normal', 'visibility_hidden', 'navigation', 'tab_close', 'unknown', }, 'unknown', ), 'scroll_depth_percent_at_unload': scrollDepthPercentAtUnload.clamp(0, 100), 'web_visibility_state': _allowedString( webVisibilityState, const {'visible', 'hidden', 'unknown'}, 'unknown', ), }); factory AppLifecycleEvent.terminate({ required String lastActivePageKey, String terminateType = 'unknown', required int sessionDurationMs, required int terminateTs, }) => AppLifecycleEvent._('terminate', { 'last_active_page_key': lastActivePageKey, 'terminate_type': _allowedString( terminateType, const {'user_quit', 'system_kill', 'app_finish', 'unknown'}, 'unknown', ), 'session_duration_ms': _nonNegative(sessionDurationMs), 'terminate_ts': terminateTs > 0 ? terminateTs : DateTime.now().millisecondsSinceEpoch ~/ 1000, }); factory AppLifecycleEvent.unknown({ String unknownReason = 'unknown', }) => AppLifecycleEvent._('unknown', { 'unknown_reason': _allowedString( unknownReason, const { 'callback_missing', 'state_conflict', 'unsupported_platform', 'sdk_not_ready', 'system_restricted', 'route_unrecognized', 'collect_error', 'unknown', }, 'unknown', ), }); Map toJson() { return { ..._commonFields, 'event_id': eventId, 'payload': { 'lifecycle_event_key': lifecycleEventKey, ..._fields, }, }; } Map get _stableFields { final sortedKeys = _fields.keys.toList()..sort(); return { for (final key in sortedKeys) key: _fields[key], }; } static int _nonNegative(int value) => value < 0 ? 0 : value; static String _allowedString( String value, Set allowed, String fallback, ) { final normalized = value.trim(); return allowed.contains(normalized) ? normalized : fallback; } }