Initial commit: 91dj android client dev_1.0.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-08 11:37:25 +07:00
co-authored by Claude Opus 5.5
commit 97dd6a739a
1262 changed files with 119813 additions and 0 deletions
@@ -0,0 +1,79 @@
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 广告点击日志 (手动上报)
class AdClickEvent {
/// 事件类型(固定)
final String event = EventType.adClick.event;
/// 去重 ID(动态计算)
late final String eventId;
/// 页面唯一标识,建议使用 [AppPageEnum](如 `AppPageEnum.videoPlay.key`),
/// 也可传入自定义字符串。
final String pageKey;
/// 页面展示名称,建议与 [pageKey] 保持对应(如 `AppPageEnum.videoPlay.name`)。
final String pageName;
/// 广告位标识:与展示事件一致,如 home_banner_1
final String adSlotKey;
/// 被点击的广告ID
final String adId;
/// 素材ID(可选)
final String creativeId;
/// 广告类型字符串,由广告中心定义,如 banner, feed 等
final String adType;
/// 公共字段(通过工具类生成)
late final Map<String, dynamic> _commonFields;
AdClickEvent({
required String pageKey,
required this.pageName,
required this.adSlotKey,
required this.adId,
this.creativeId = '',
this.adType = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
pageKey,
pageName,
adSlotKey,
adId,
adType,
creativeId,
]);
}
/// 转换为 JSON Map,用于上报和缓存
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'page_key': pageKey,
'page_name': pageName,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'creative_id': creativeId,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
/// 从 JSON 恢复实例(用于缓存恢复)
factory AdClickEvent.fromJson(Map<String, dynamic> json) {
return AdClickEvent(
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
adSlotKey: json['ad_slot_key'] as String,
adId: json['ad_id'] as String,
creativeId: json['creative_id'] as String? ?? '',
adType: json['ad_type'] as String? ?? '',
);
}
}
@@ -0,0 +1,97 @@
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 广告展示日志 (手动)
/// 展示上报逻辑:
/// 1. 进入 APP → TabA
/// - 20 条曝光 → 上报 20 条(批量)
/// 2. 切换到TabB
/// - 20 条曝光 → 上报 20 条
/// 3. 返回TabA → 上滑加载 10 条
/// - 新 10 条渲染出来 → 上报 10 条
/// 4. 用户再次上下滑动TabA
/// - 所有广告已曝光,无需再上报
class AdImpressionEvent {
final String event = EventType.adImpression.event;
late final String eventId;
/// 页面唯一标识,建议使用 [AppPageEnum](如 `AppPageEnum.home.key`),
/// 也可传入自定义字符串。
final String pageKey;
/// 页面展示名称,建议与 [pageKey] 保持对应(如 `AppPageEnum.home.name`)。
final String pageName;
/// 广告位标识:如 home_banner_1等 对应广告导入数据的advertiseLocationCode
final String adSlotKey;
/// 本次广告请求唯一 ID。
final String requestId;
/// 广告ID, 多个广告ID英文逗号分隔 对应广告导入数据的advertiseCode
final String adId;
/// 实际素材尺寸,格式如 "1080x1920"。
final String materialSize;
/// 素材ID(可选)多个素材ID英文逗号分隔,和广告ID一一对应,没有的填充空字符串
final String creativeId;
/// 广告类型字符串,如 banner, feed, interstitial, reward_video 等,由广告中心定义
final String adType;
late final Map<String, dynamic> _commonFields;
AdImpressionEvent({
required String pageKey,
required this.pageName,
required this.adSlotKey,
this.requestId = '',
required this.adId,
this.materialSize = '',
this.creativeId = '',
this.adType = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
pageKey,
pageName,
adSlotKey,
requestId,
adId,
adType,
materialSize,
creativeId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'page_key': pageKey,
'page_name': pageName,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'material_size': materialSize,
'creative_id': creativeId,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory AdImpressionEvent.fromJson(Map<String, dynamic> json) {
return AdImpressionEvent(
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
adSlotKey: json['ad_slot_key'] as String,
requestId: json['request_id'] as String? ?? '',
adId: json['ad_id'] as String,
materialSize: json['material_size'] as String? ?? '',
creativeId: json['creative_id'] as String? ?? '',
adType: json['ad_type'] as String? ?? '',
);
}
}
+459
View File
@@ -0,0 +1,459 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
abstract class _AdSdkEvent {
final String event;
late final String eventId;
late final Map<String, dynamic> _commonFields;
_AdSdkEvent(this.event, List<String> eventIdFields) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(
_commonFields,
eventIdFields,
);
}
Map<String, dynamic> buildPayload();
Map<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': buildPayload(),
};
}
}
/// 广告 SDK 初始化事件。
class AdSdkInitEvent extends _AdSdkEvent {
final int screenWidth;
final int screenHeight;
final int screenDpi;
final String orientation;
final String networkType;
final String carrier;
final String language;
final String timezone;
final String oaid;
final String androidId;
final String idfv;
final String idfa;
final bool lat;
final String adSdkVersion;
final int availableStorage;
final int batteryLevel;
final String cpu;
final int storage;
final String activationTime;
AdSdkInitEvent({
this.screenWidth = 0,
this.screenHeight = 0,
this.screenDpi = 0,
this.orientation = '',
this.networkType = '',
this.carrier = '',
this.language = '',
this.timezone = '',
this.oaid = '',
this.androidId = '',
this.idfv = '',
this.idfa = '',
this.lat = false,
required this.adSdkVersion,
this.availableStorage = 0,
this.batteryLevel = 0,
this.cpu = '',
this.storage = 0,
this.activationTime = '',
}) : super(EventType.adSdkInit.event, [
screenWidth.toString(),
screenHeight.toString(),
screenDpi.toString(),
orientation,
networkType,
carrier,
language,
timezone,
oaid,
androidId,
idfv,
idfa,
lat.toString(),
adSdkVersion,
availableStorage.toString(),
batteryLevel.toString(),
cpu,
storage.toString(),
activationTime,
]);
@override
Map<String, dynamic> buildPayload() => {
'screen_width': screenWidth,
'screen_height': screenHeight,
'screen_dpi': screenDpi,
'orientation': orientation,
'network_type': networkType,
'carrier': carrier,
'language': language,
'timezone': timezone,
'oaid': oaid,
'android_id': androidId,
'idfv': idfv,
'idfa': idfa,
'lat': lat,
'ad_sdk_version': adSdkVersion,
'available_storage': availableStorage,
'battery_level': batteryLevel,
'cpu': cpu,
'storage': storage,
'activation_time': activationTime,
};
}
/// 广告请求事件。
class AdRequestEvent extends _AdSdkEvent {
final String requestId;
final String adSlotKey;
final String adType;
final String? adId;
AdRequestEvent({
required this.requestId,
required this.adSlotKey,
this.adType = '',
this.adId,
}) : super(EventType.adRequest.event, [
requestId,
adSlotKey,
adType,
adId ?? '',
]);
@override
Map<String, dynamic> buildPayload() => {
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_type': adType,
if (adId?.trim().isNotEmpty ?? false) 'ad_id': adId!.trim(),
};
}
/// 广告填充成功事件。
class AdFillEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final int fillCount;
AdFillEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
required this.fillCount,
}) : super(EventType.adFill.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adId,
adType,
fillCount.toString(),
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'fill_count': fillCount,
};
}
/// 广告无填充事件。
class AdNoFillEvent extends _AdSdkEvent {
final String requestId;
final String adSlotKey;
final String adType;
final String reason;
AdNoFillEvent({
required this.requestId,
required this.adSlotKey,
required this.adType,
required this.reason,
}) : super(EventType.adNoFill.event, [requestId, adSlotKey, adType, reason]);
@override
Map<String, dynamic> buildPayload() => {
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_type': adType,
'reason': reason,
};
}
/// 广告素材加载成功事件。
class AdLoadSuccessEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final int loadDuration;
AdLoadSuccessEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
required this.loadDuration,
}) : super(EventType.adLoadSuccess.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adId,
adType,
loadDuration.toString(),
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'load_duration': loadDuration,
};
}
/// 广告素材加载失败事件。
@Deprecated('ad_load_fail 已并入 ad_error,使用 MATERIAL_LOAD_FAIL 错误码。')
class AdLoadFailEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final String errorCode;
AdLoadFailEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
required this.errorCode,
}) : super(EventType.adLoadFail.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adId,
adType,
errorCode,
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'error_code': errorCode,
};
}
/// 广告 SDK 曝光事件。
class AdImpressionSdkEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final String materialSize;
AdImpressionSdkEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
required this.materialSize,
}) : super(EventType.adImpressionSdk.event, [
traceId,
requestId,
adSlotKey,
adId,
adType,
materialSize,
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'material_size': materialSize,
};
}
/// 广告 SDK 点击事件。
class AdClickSdkEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
AdClickSdkEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
}) : super(EventType.adClickSdk.event, [
traceId,
requestId,
adSlotKey,
adId,
adType,
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
};
}
/// 广告关闭事件。
class AdCloseEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final String closeType;
final int shownDuration;
AdCloseEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
required this.closeType,
this.shownDuration = 0,
}) : super(EventType.adClose.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adId,
adType,
closeType,
shownDuration.toString(),
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'close_type': closeType,
'shown_duration': shownDuration,
};
}
/// 广告跳过事件。
class AdSkipEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adId;
final String adType;
final int shownDuration;
AdSkipEvent({
this.traceId = '',
required this.requestId,
required this.adSlotKey,
required this.adId,
required this.adType,
this.shownDuration = 0,
}) : super(EventType.adSkip.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adId,
adType,
shownDuration.toString(),
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_id': adId,
'ad_type': adType,
'shown_duration': shownDuration,
};
}
/// 广告错误事件。
class AdErrorEvent extends _AdSdkEvent {
final String traceId;
final String requestId;
final String adSlotKey;
final String adType;
final String errorCode;
final String errorMsg;
final String errorStage;
AdErrorEvent({
this.traceId = '',
this.requestId = '',
required this.adSlotKey,
required this.adType,
required this.errorCode,
required this.errorMsg,
required this.errorStage,
}) : super(EventType.adError.event, [
if (traceId.isNotEmpty) traceId,
requestId,
adSlotKey,
adType,
errorCode,
errorMsg,
errorStage,
]);
@override
Map<String, dynamic> buildPayload() => {
'trace_id': traceId,
'request_id': requestId,
'ad_slot_key': adSlotKey,
'ad_type': adType,
'error_code': errorCode,
'error_msg': errorMsg,
'error_stage': errorStage,
};
}
@@ -0,0 +1,57 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// APP广告行为
class AdvertisingEvent {
final String event = EventType.advertising.event;
late final String eventId;
///事件类型:click(点击), close(关闭), show(展示)
final String eventType;
/// 广告标识:home_popup(首页弹窗), home_banner(首页Banner), video_reward(激励视频)等
final String advertisingKey;
/// 广告标识名称:首页弹窗, 首页Banner, 激励视频
final String advertisingName;
/// 广告ID
final String advertisingId;
late final Map<String, dynamic> _commonFields;
AdvertisingEvent({
required this.eventType,
required this.advertisingKey,
required this.advertisingName,
required this.advertisingId,
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
eventType,
advertisingKey,
advertisingName,
advertisingId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'event_type': eventType,
'advertising_key': advertisingKey,
'advertising_name': advertisingName,
'advertising_id': advertisingId,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory AdvertisingEvent.fromJson(Map<String, dynamic> json) {
return AdvertisingEvent(
eventType: json['event_type'] as String,
advertisingKey: json['advertising_key'] as String,
advertisingName: json['advertising_name'] as String,
advertisingId: json['advertising_id'] as String,
);
}
}
@@ -0,0 +1,24 @@
/// Tab 页面的 analytics 配置。
///
/// - [key]:pageKey(必填),建议使用 [AppPageEnum](如 `AppPageEnum.home.key`),
/// 也可传入自定义字符串。
/// - [name]:pageName(可选),省略时由 [PageNameMapper] 自动解析;
/// 传入时建议与 [AppPageEnum] 保持一致(如 `AppPageEnum.home.name`)。
///
/// ```dart
/// // 使用枚举
/// AnalyticsTab(AppPageEnum.home.key, AppPageEnum.home.name)
/// AnalyticsTab(AppPageEnum.video.key, AppPageEnum.video.name)
///
/// // 使用自定义值
/// AnalyticsTab('live_room', '直播间')
///
/// // 省略 name(由 PageNameMapper 解析)
/// AnalyticsTab('home')
/// ```
class AnalyticsTab {
final String key;
final String? name;
const AnalyticsTab(this.key, [this.name]);
}
@@ -0,0 +1,5 @@
import 'package:analytics_sdk/manager/sdk_context.dart';
export 'package:analytics_sdk/manager/sdk_context.dart';
// SdkContext 的向后兼容别名,内部使用。
typedef AnalyticsUtils = SdkContext;
@@ -0,0 +1,46 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// App 安装事件(客户端)
class AppInstallEvent {
/// 事件类型(固定)
final String event = EventType.appInstall.event;
/// 去重 ID(动态计算)
late final String eventId;
/// 落地页点击生成唯一ID, 复制到剪切板, 打开APP后读取剪切板
final String traceId;
/// 原生包是否开启归因:1 开启,0 关闭(默认)
final int attributionFlag;
late final Map<String, dynamic> _commonFields;
AppInstallEvent({
required this.traceId,
this.attributionFlag = 0,
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
traceId,
attributionFlag.toString(),
]);
}
Map<String, dynamic> toJson() {
final payload = <String, dynamic>{
'trace_id': traceId,
'attribution_flag': attributionFlag,
};
return {..._commonFields, 'event_id': eventId, 'payload': payload};
}
factory AppInstallEvent.fromJson(Map<String, dynamic> json) {
return AppInstallEvent(
traceId: json['trace_id'] as String,
attributionFlag: (json['attribution_flag'] as int?) ?? 0,
);
}
}
@@ -0,0 +1,189 @@
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<String, dynamic> _fields;
late final String eventId;
late final Map<String, dynamic> _commonFields;
AppLifecycleEvent._(
this.lifecycleEventKey,
Map<String, dynamic> fields,
) : _fields = Map<String, dynamic>.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<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': {
'lifecycle_event_key': lifecycleEventKey,
..._fields,
},
};
}
Map<String, dynamic> 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<String> allowed,
String fallback,
) {
final normalized = value.trim();
return allowed.contains(normalized) ? normalized : fallback;
}
}
@@ -0,0 +1,217 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 页面内元素曝光事件(客户端手动上报)。
///
/// 仅限需要采集页面内元素曝光的项目接入。未对接推荐引擎时,推荐相关字段传空字符串。
class AppPageItemExposureEvent {
final String event = EventType.appPageItemExposure.event;
late final String eventId;
/// 元素唯一 ID。
final String itemId;
/// 元素类型,如推荐、关注、评论、小说、视频、漫画等业务字典值。
final String itemType;
/// 元素名称,如推荐、关注、评论、小说、视频、漫画等业务字典值。
final String itemName;
/// 父元素 ID。
final String parentItemId;
/// 当前页面标识,建议使用 [AppPageEnum] 或业务字典中的稳定 key。
final String pageKey;
/// 当前页面名称。
final String pageName;
/// 来路页面标识。
final String referrerPageKey;
/// 来路页面名称。
final String referrerPageName;
/// 推荐引擎 trace_id;多个推荐列表用英文逗号分隔。
final String recommendTraceId;
/// 推荐内容 ID;未对接推荐引擎传空字符串。
final String recommendId;
/// 推荐引擎 trace_info;未对接推荐引擎传空字符串。
final String recommendTraceInfo;
late final Map<String, dynamic> _commonFields;
AppPageItemExposureEvent({
required this.itemId,
required this.itemType,
required this.itemName,
required this.parentItemId,
required String pageKey,
required this.pageName,
required String referrerPageKey,
required this.referrerPageName,
this.recommendTraceId = '',
this.recommendId = '',
this.recommendTraceInfo = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey),
referrerPageKey = PageNameMapper.normalizeKey(referrerPageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
itemId,
itemType,
itemName,
parentItemId,
this.pageKey,
pageName,
this.referrerPageKey,
referrerPageName,
recommendTraceId,
recommendId,
recommendTraceInfo,
]);
}
Map<String, dynamic> toJson() {
final payload = <String, dynamic>{
'item_id': itemId,
'item_type': itemType,
'item_name': itemName,
'parent_item_id': parentItemId,
'page_key': pageKey,
'page_name': pageName,
'referrer_page_key': referrerPageKey,
'referrer_page_name': referrerPageName,
'recommend_trace_id': recommendTraceId,
'recommend_id': recommendId,
'recommend_trace_info': recommendTraceInfo,
};
return {..._commonFields, 'event_id': eventId, 'payload': payload};
}
factory AppPageItemExposureEvent.fromJson(Map<String, dynamic> json) {
final source = _payloadOrSelf(json);
return AppPageItemExposureEvent(
itemId: source['item_id'] as String,
itemType: source['item_type'] as String,
itemName: source['item_name'] as String,
parentItemId: source['parent_item_id'] as String,
pageKey: source['page_key'] as String,
pageName: source['page_name'] as String,
referrerPageKey: source['referrer_page_key'] as String,
referrerPageName: source['referrer_page_name'] as String,
recommendTraceId: source['recommend_trace_id'] as String? ?? '',
recommendId: source['recommend_id'] as String? ?? '',
recommendTraceInfo: source['recommend_trace_info'] as String? ?? '',
);
}
}
/// 页面内元素点击事件(客户端手动上报)。
///
/// 仅限需要采集页面内元素点击的项目接入。未对接推荐引擎时,推荐相关字段传空字符串。
class AppPageItemClickEvent {
final String event = EventType.appPageItemClick.event;
late final String eventId;
/// 元素唯一 ID。
final String itemId;
/// 点击元素类型,如推荐、关注、评论、小说、视频、漫画等业务字典值。
final String itemType;
/// 点击元素名称,如推荐、关注、评论、小说、视频、漫画等业务字典值。
final String itemName;
/// 父元素 ID。
final String parentItemId;
/// 在父元素中的顺序索引(从 0 或 1 开始,按业务约定传入)。
final int position;
/// 当前页面标识,建议使用 [AppPageEnum] 或业务字典中的稳定 key。
final String pageKey;
/// 当前页面名称。
final String pageName;
/// 推荐引擎 trace_id;多个推荐列表用英文逗号分隔。
final String recommendTraceId;
/// 推荐内容 ID;未对接推荐引擎传空字符串。
final String recommendId;
/// 推荐引擎 trace_info;未对接推荐引擎传空字符串。
final String recommendTraceInfo;
late final Map<String, dynamic> _commonFields;
AppPageItemClickEvent({
required this.itemId,
required this.itemType,
required this.itemName,
required this.parentItemId,
required this.position,
required String pageKey,
required this.pageName,
this.recommendTraceId = '',
this.recommendId = '',
this.recommendTraceInfo = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
itemId,
itemType,
itemName,
parentItemId,
position.toString(),
this.pageKey,
pageName,
recommendTraceId,
recommendId,
recommendTraceInfo,
]);
}
Map<String, dynamic> toJson() {
final payload = <String, dynamic>{
'item_id': itemId,
'item_type': itemType,
'item_name': itemName,
'parent_item_id': parentItemId,
'position': position,
'page_key': pageKey,
'page_name': pageName,
'recommend_trace_id': recommendTraceId,
'recommend_id': recommendId,
'recommend_trace_info': recommendTraceInfo,
};
return {..._commonFields, 'event_id': eventId, 'payload': payload};
}
factory AppPageItemClickEvent.fromJson(Map<String, dynamic> json) {
final source = _payloadOrSelf(json);
return AppPageItemClickEvent(
itemId: source['item_id'] as String,
itemType: source['item_type'] as String,
itemName: source['item_name'] as String,
parentItemId: source['parent_item_id'] as String,
position: source['position'] as int,
pageKey: source['page_key'] as String,
pageName: source['page_name'] as String,
recommendTraceId: source['recommend_trace_id'] as String? ?? '',
recommendId: source['recommend_id'] as String? ?? '',
recommendTraceInfo: source['recommend_trace_info'] as String? ?? '',
);
}
}
Map<String, dynamic> _payloadOrSelf(Map<String, dynamic> json) {
final payload = json['payload'];
if (payload is Map) return Map<String, dynamic>.from(payload);
return json;
}
@@ -0,0 +1,181 @@
import 'dart:ui' show window;
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/page_view_auto_fields.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/utils/uuid_util.dart';
/// 应用页面展示日志(由 [PageLifecycleObserver] 和 Tab 包装器自动上报)。
///
/// **page_key / page_name 填写规范**
///
/// 建议使用 [AppPageEnum] 预定义值,也可直接传入自定义字符串:
/// ```dart
/// AppPageViewEvent(
/// pageKey: AppPageEnum.videoDetail.key, // 'video_detail'
/// pageName: AppPageEnum.videoDetail.name, // '视频详情页'
/// ...
/// )
/// ```
class AppPageViewEvent {
final String event = EventType.appPageView.event;
late final String eventId;
final String userType;
/// 当前页面标识,建议使用 [AppPageEnum](如 `AppPageEnum.videoDetail.key`),
/// 也可传入自定义字符串。
final String pageKey;
/// 当前页面展示名称,建议与 [pageKey] 保持对应(如 `AppPageEnum.videoDetail.name`)。
final String pageName;
/// 来路页面标识,填写跳转前所在页面的 pageKey,规范同 [pageKey]。
final String referrerPageKey;
/// 来路页面展示名称,规范同 [pageName]。
final String referrerPageName;
/// 当前页面标识(与 [pageKey] 通常保持一致,保留用于扩展场景)。
final String currentPageKey;
/// 当前页面展示名称(与 [pageName] 通常保持一致)。
final String currentPageName;
/// 页面加载耗时(毫秒)
final int pageLoadTime;
/// 当前页面实例唯一 ID,由 SDK 自动生成。
final String pageId;
/// SDK 首次识别当前页面实例的时间,10 位时间戳。
final int pageEnterTs;
/// Web/H5 当前页面物理地址,仅包含 scheme + host + path。
final String pageUrl;
/// 当前页面根容器可交互可视区域宽度。
final int viewportWidth;
/// 当前页面根容器可交互可视区域高度。
final int viewportHeight;
/// 推荐引擎的trace_id,有多个推荐列表时用英文逗号分隔,未接推荐引擎传空字符串
final String recommendTraceId;
late final Map<String, dynamic> _commonFields;
AppPageViewEvent({
required this.userType,
required String pageKey,
required this.pageName,
required String referrerPageKey,
required this.referrerPageName,
required String currentPageKey,
required this.currentPageName,
required this.pageLoadTime,
String? pageId,
int? pageEnterTs,
String? pageUrl,
int? viewportWidth,
int? viewportHeight,
this.recommendTraceId = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey),
referrerPageKey = PageNameMapper.normalizeKey(referrerPageKey),
currentPageKey = PageNameMapper.normalizeKey(currentPageKey),
pageId = _pageId(pageId),
pageEnterTs = _pageEnterTs(pageEnterTs),
pageUrl = pageUrl ?? currentPageUrl(),
viewportWidth = _viewportDimension(viewportWidth, isWidth: true),
viewportHeight = _viewportDimension(viewportHeight, isWidth: false) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
this.pageId,
this.pageEnterTs.toString(),
this.pageUrl,
this.viewportWidth.toString(),
this.viewportHeight.toString(),
userType,
this.pageKey,
pageName,
this.referrerPageKey,
referrerPageName,
this.currentPageKey,
currentPageName,
pageLoadTime.toString(),
recommendTraceId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'user_type': userType,
'page_key': pageKey,
'page_name': pageName,
'referrer_page_key': referrerPageKey,
'referrer_page_name': referrerPageName,
'current_page_key': currentPageKey,
'current_page_name': currentPageName,
'page_load_time': pageLoadTime,
'page_id': pageId,
'page_enter_ts': pageEnterTs,
'page_url': pageUrl,
'viewport_width': viewportWidth,
'viewport_height': viewportHeight,
'recommend_trace_id': recommendTraceId,
};
return {
..._commonFields,
'event_id': eventId,
"payload": payload,
};
}
factory AppPageViewEvent.fromJson(Map<String, dynamic> json) {
return AppPageViewEvent(
userType: json['user_type'] as String,
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
referrerPageKey: json['referrer_page_key'] as String,
referrerPageName: json['referrer_page_name'] as String,
currentPageKey: json['current_page_key'] as String,
currentPageName: json['current_page_name'] as String,
pageLoadTime: json['page_load_time'] as int,
pageId: json['page_id'] as String?,
pageEnterTs: json['page_enter_ts'] as int?,
pageUrl: json['page_url'] as String?,
viewportWidth: json['viewport_width'] as int?,
viewportHeight: json['viewport_height'] as int?,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
);
}
static String _pageId(String? value) {
final normalized = value?.trim() ?? '';
if (normalized.isNotEmpty) return normalized;
return generateUuidV4().replaceAll('-', '');
}
static int _pageEnterTs(int? value) {
if (value != null && value > 0) return value;
return DateTime.now().millisecondsSinceEpoch ~/ 1000;
}
static int _viewportDimension(int? value, {required bool isWidth}) {
if (value != null) return value >= 0 ? value : -1;
try {
final pixelRatio = window.devicePixelRatio;
if (pixelRatio <= 0) return -1;
final physicalSize = window.physicalSize;
final logical = isWidth
? physicalSize.width / pixelRatio
: physicalSize.height / pixelRatio;
if (!logical.isFinite || logical <= 0) return -1;
return logical.round();
} catch (_) {
return -1;
}
}
}
@@ -0,0 +1,128 @@
import 'dart:convert';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/manager/session_manager.dart';
import 'package:analytics_sdk/manager/session_behavior_manager.dart';
import 'package:analytics_sdk/manager/session_metrics_manager.dart';
import 'package:analytics_sdk/manager/session_sensor_sampler.dart';
/// 会话汇总事件。
///
/// 汇总字段写入 `payload`;签名类 payload 字段由
/// [SdkContext.attachDeviceEnvIfNeeded] 在 track 管道中统一补充。
class AppSessionSummaryEvent {
final String event = EventType.appSessionSummary.event;
final Map<String, dynamic> _payloadFields;
late final String eventId;
late final Map<String, dynamic> _commonFields;
AppSessionSummaryEvent({
required String sessionEndReason,
DateTime? endedAt,
}) : _payloadFields = _buildPayloadFields(
sessionEndReason: sessionEndReason,
endedAt: endedAt ?? DateTime.now(),
) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
jsonEncode(_stablePayloadFields),
]);
}
Map<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': Map<String, dynamic>.from(_payloadFields),
};
}
Map<String, dynamic> get _stablePayloadFields {
final sortedKeys = _payloadFields.keys.toList()..sort();
return {
for (final key in sortedKeys) key: _payloadFields[key],
};
}
static Map<String, dynamic> _buildPayloadFields({
required String sessionEndReason,
required DateTime endedAt,
}) {
final metrics = SessionMetricsManager.instance.snapshot(endedAt: endedAt);
final behavior = SessionBehaviorManager.instance.snapshot();
final environment = SdkContext.getEnvironmentSnapshotFields();
final screenBrightness = _intField(environment['screen_brightness'], -1);
final sensor = SessionSensorSampler.instance.snapshot(
screenBrightness: screenBrightness,
);
final sessionStart = SessionManager.instance.sessionStartTime ?? endedAt;
final sessionStartTs = sessionStart.millisecondsSinceEpoch ~/ 1000;
final sessionEndTs = endedAt.millisecondsSinceEpoch ~/ 1000;
final durationMs = endedAt.difference(sessionStart).inMilliseconds;
return {
'session_start_ts': sessionStartTs,
'session_end_ts': sessionEndTs,
'session_duration_ms': durationMs < 0 ? 0 : durationMs,
'event_count_total': metrics.eventCountTotal,
'business_event_count': metrics.businessEventCount,
'foreground_count': metrics.foregroundCount,
'background_count_in_sid': metrics.backgroundCountInSid,
'page_count': metrics.pageCount,
'page_view_count': metrics.pageViewCount,
'page_click_count': metrics.pageClickCount,
'network_change_count': metrics.networkChangeCount,
'session_end_reason': _sessionEndReason(sessionEndReason),
'last_event_name': metrics.lastEventName,
'orientation': environment['orientation']?.toString() ?? '',
'screen_brightness': screenBrightness,
'volume': _intField(environment['volume'], -1),
'is_screen_on': _intField(environment['is_screen_on'], -1),
'battery_level': _intField(environment['battery_level'], -1),
'is_charging': _intField(environment['is_charging'], -1),
'battery_state': _stringField(environment['battery_state'], 'unknown'),
'sensor_signature': sensor.sensorSignature,
'sensor_count_active': sensor.sensorCountActive,
'scroll_total_count': behavior.scrollTotalCount,
'scroll_total_distance_px': behavior.scrollTotalDistancePx,
'scroll_up_count': behavior.scrollUpCount,
'scroll_down_count': behavior.scrollDownCount,
'scroll_direction_change_count': behavior.scrollDirectionChangeCount,
'first_scroll_delay_ms': behavior.firstScrollDelayMs,
'max_scroll_depth_percent': behavior.maxScrollDepthPercent,
'is_reach_bottom': behavior.isReachBottom,
'content_height': behavior.contentHeight,
'touch_total_count': behavior.touchTotalCount,
'swipe_total_count': behavior.swipeTotalCount,
'long_press_total_count': behavior.longPressTotalCount,
'pinch_total_count': behavior.pinchTotalCount,
'first_interaction_delay_ms': behavior.firstInteractionDelayMs,
'page_stay_list': metrics.pageStayList,
'behavior_sequence_hash': metrics.behaviorSequenceHash,
};
}
static int _intField(dynamic value, int fallback) {
if (value is int) return value;
if (value is num) return value.round();
return int.tryParse(value?.toString() ?? '') ?? fallback;
}
static String _stringField(dynamic value, String fallback) {
final text = value?.toString().trim() ?? '';
return text.isEmpty ? fallback : text;
}
static String _sessionEndReason(String value) {
final normalized = value.trim();
const allowed = {
'idle',
'app_exit',
'web_visibility_hidden',
'forced',
};
return allowed.contains(normalized) ? normalized : 'app_exit';
}
}
+130
View File
@@ -0,0 +1,130 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/read_behavior_enum.dart';
/// 漫画事件
class ComicEvent {
final String event = EventType.comicEvent.event;
late final String eventId;
/// 漫画 id
final String comicId;
/// 漫画标题
final String comicTitle;
/// 分类ID
final String comicTypeId;
/// 分类名称
final String comicTypeName;
/// 标签KEY, 多个标签使用英文逗号分隔
final String comicTagKey;
/// 标签名称,多个标签使用英文逗号分隔
final String comicTagName;
/// 推荐引擎的trace_id,未接推荐引擎传空字符串
final String recommendTraceId;
/// 阅读进度百分比(0-100)
final int readProgress;
/// 当前阅读第几页(从1开始)
final int pageNo;
/// 行为标识:view(展示), page_next(下一页), page_prev(上一页), complete(读完) 等
final ReadBehaviorEnum comicBehavior;
/// 章节ID,没有章节上报空字符串,没有对接搜索推荐引擎的上报空字符串
final String chapterId;
/// 章节名称,没有章节上报空字符串,没有对接搜索推荐引擎的上报空字符串
final String chapterName;
/// 老司机媒体资源ID,如未使用老司机库资源可传空字符串
final String mediaId;
late final Map<String, dynamic> _commonFields;
ComicEvent({
required this.comicId,
required this.comicTitle,
required this.comicTypeId,
required this.comicTypeName,
required this.comicTagKey,
required this.comicTagName,
required this.readProgress,
required this.pageNo,
required this.comicBehavior,
this.recommendTraceId = '',
this.mediaId = '',
this.chapterId = '',
this.chapterName = '',
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
comicId,
comicTitle,
comicTypeId,
comicTypeName,
comicTagKey,
comicTagName,
readProgress.toString(),
pageNo.toString(),
comicBehavior.key,
comicBehavior.name,
recommendTraceId,
mediaId,
chapterId,
chapterName,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'comic_id': comicId,
'comic_title': comicTitle,
'comic_type_id': comicTypeId,
'comic_type_name': comicTypeName,
'comic_tag_key': comicTagKey,
'comic_tag_name': comicTagName,
'recommend_trace_id': recommendTraceId,
'read_progress': readProgress,
'page_no': pageNo,
'comic_behavior_key': comicBehavior.key,
'comic_behavior_name': comicBehavior.name,
'media_id': mediaId,
'chapter_id': chapterId,
'chapter_name': chapterName,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory ComicEvent.fromJson(Map<String, dynamic> json) {
final behaviorKey = json['comic_behavior_key'] as String? ?? '';
final behaviorName = json['comic_behavior_name'] as String? ?? '';
final behavior = ReadBehaviorEnum(
behaviorKey.isNotEmpty ? behaviorKey : ReadBehaviorEnum.VIEW.key,
behaviorName.isNotEmpty ? behaviorName : ReadBehaviorEnum.VIEW.name,
);
return ComicEvent(
comicId: json['comic_id'] as String,
comicTitle: json['comic_title'] as String,
comicTypeId: json['comic_type_id'] as String,
comicTypeName: json['comic_type_name'] as String,
comicTagKey: json['comic_tag_key'] as String,
comicTagName: json['comic_tag_name'] as String,
readProgress: json['read_progress'] as int,
pageNo: json['page_no'] as int,
comicBehavior: behavior,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
mediaId: json['media_id'] as String? ?? '',
chapterId: json['chapter_id'] as String? ?? '',
chapterName: json['chapter_name'] as String? ?? '',
);
}
}
@@ -0,0 +1,71 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/click_item_type_enum.dart';
/// 关键词搜索结果点击事件
class KeywordClickEvent {
final String event = EventType.keywordClick.event;
late final String eventId;
/// 关联关键词
final String keyword;
/// 点击项目ID(视频ID/小说ID等)
final String clickItemId;
/// 点击项目类型,可用预定义常量或自定义:
/// `ClickItemTypeEnum.video` / `ClickItemTypeEnum('manga', '漫画类')`
final ClickItemTypeEnum clickItemType;
/// 点击位置(搜索结果中的排序位置)
final int clickPosition;
/// 搜索引擎 trace ID,未接搜索引擎时传空字符串
final String searchTraceId;
late final Map<String, dynamic> _commonFields;
KeywordClickEvent({
required this.keyword,
required this.clickItemId,
required this.clickItemType,
required this.clickPosition,
this.searchTraceId = '',
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
keyword,
clickItemId,
clickItemType.key,
clickItemType.name,
clickPosition.toString(),
searchTraceId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'keyword': keyword,
'click_item_id': clickItemId,
'click_item_type_key': clickItemType.key,
'click_item_type_name': clickItemType.name,
'click_position': clickPosition,
'search_trace_id': searchTraceId,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory KeywordClickEvent.fromJson(Map<String, dynamic> json) {
return KeywordClickEvent(
keyword: json['keyword'] as String,
clickItemId: json['click_item_id'] as String,
clickItemType: ClickItemTypeEnum(
json['click_item_type_key'] as String,
json['click_item_type_name'] as String,
),
clickPosition: json['click_position'] as int,
searchTraceId: json['search_trace_id'] as String? ?? '',
);
}
}
@@ -0,0 +1,65 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 关键词搜索事件
class KeywordSearchEvent {
final String event = EventType.keywordSearch.event;
late final String eventId;
/// 搜索关键词
final String keyword;
/// 搜索结果数量
final int searchResultCount;
/// 搜索引擎 trace ID,未接搜索引擎时传空字符串
final String searchTraceId;
/// 搜索引擎 search ID,未接搜索引擎时传空字符串
final String searchId;
/// 搜索列表的内容类型:video / novel / comic
/// 未接搜索推荐引擎时传空字符串
final String searchContentType;
late final Map<String, dynamic> _commonFields;
KeywordSearchEvent({
required this.keyword,
required this.searchResultCount,
this.searchTraceId = '',
this.searchId = '',
this.searchContentType = '',
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
keyword,
searchResultCount.toString(),
searchTraceId,
searchId,
searchContentType,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'keyword': keyword,
'search_result_count': searchResultCount,
'search_trace_id': searchTraceId,
'search_id': searchId,
'search_content_type': searchContentType,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory KeywordSearchEvent.fromJson(Map<String, dynamic> json) {
return KeywordSearchEvent(
keyword: json['keyword'] as String,
searchResultCount: json['search_result_count'] as int,
searchTraceId: json['search_trace_id'] as String? ?? '',
searchId: json['search_id'] as String? ?? '',
searchContentType: json['search_content_type'] as String? ?? '',
);
}
}
@@ -0,0 +1,67 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 导航菜单点击事件(纯手动上报)。
///
/// 仅在用户主动点击导航菜单时触发:底部 Tab、顶部 Tab、侧边抽屉等。
/// SDK 不会在路由跳转时自动发送此事件;触发方式:
/// - [AnalyticsSdk.trackNavigation] 手动调用
/// - [BottomNavigationBar.withAnalytics] / [TabBar.withAnalytics] 包装器内部自动处理
///
/// **page_key / page_name 填写规范**
///
/// 建议使用 [AppPageEnum] 预定义值,也可直接传入自定义字符串:
/// ```dart
/// NavigationEvent(
/// navigationKey: AppPageEnum.video.key, // 'video'
/// navigationName: AppPageEnum.video.name, // '影视'
/// )
/// ```
///
/// **上报时序**:应在同次导航的 [AppPageViewEvent] 之前发出。
class NavigationEvent {
final String event = EventType.navigation.event;
late final String eventId;
/// 导航目标的唯一标识,与对应页面的 [AppPageViewEvent.pageKey] 保持一致。
///
/// 示例:`home`(首页)、`video`(影视)、`video_hot`(影视·热门子 Tab)
final String navigationKey;
/// 导航目标的展示名称。
///
/// 示例:`首页`、`影视`、`影视·热门`
final String navigationName;
late final Map<String, dynamic> _commonFields;
NavigationEvent({
required String navigationKey,
required this.navigationName,
}) : navigationKey = PageNameMapper.normalizeKey(navigationKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
this.navigationKey,
navigationName,
]);
}
/// 序列化为上报 JSON;payload 包含 `navigation_key` 和 `navigation_name`。
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'navigation_key': navigationKey,
'navigation_name': navigationName,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
/// 从 JSON 反序列化(通常用于缓存恢复场景)。
factory NavigationEvent.fromJson(Map<String, dynamic> json) {
return NavigationEvent(
navigationKey: json['navigation_key'] as String,
navigationName: json['navigation_name'] as String,
);
}
}
@@ -0,0 +1,89 @@
import 'dart:convert';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/utils/network_type_resolver.dart';
/// 网络状态变化事件。
///
/// 文档要求除公共字段外,下表字段写入 `payload`。
/// 完整设备环境、SIM 详情和当前页面信息不在本事件重传。
class NetworkStateChangeEvent {
final String event = EventType.networkStateChange.event;
final Map<String, dynamic> _payloadFields;
late final String eventId;
late final Map<String, dynamic> _commonFields;
NetworkStateChangeEvent({
required String networkType,
required String fromNetworkType,
required int isOnline,
required int changeCountInSid,
String carrier = '',
Map<String, dynamic> webConnectionFields = const {},
}) : _payloadFields = _buildPayloadFields(
networkType: networkType,
fromNetworkType: fromNetworkType,
isOnline: isOnline,
changeCountInSid: changeCountInSid,
carrier: carrier,
webConnectionFields: webConnectionFields,
) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
jsonEncode(_stablePayloadFields),
]);
}
Map<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': Map<String, dynamic>.from(_payloadFields),
};
}
Map<String, dynamic> get _stablePayloadFields {
final sortedKeys = _payloadFields.keys.toList()..sort();
return {
for (final key in sortedKeys) key: _payloadFields[key],
};
}
static Map<String, dynamic> _buildPayloadFields({
required String networkType,
required String fromNetworkType,
required int isOnline,
required int changeCountInSid,
required String carrier,
required Map<String, dynamic> webConnectionFields,
}) {
final normalizedNetworkType = _networkType(networkType);
final fields = <String, dynamic>{
'network_type': normalizedNetworkType,
'carrier': carrier.trim(),
'from_network_type': _networkType(fromNetworkType),
'to_network_type': normalizedNetworkType,
'is_online': isOnlineForNetworkType(normalizedNetworkType),
'change_count_in_sid': changeCountInSid < 1 ? 1 : changeCountInSid,
};
for (final key in const [
'connection_type',
'effective_type',
'downlink',
'rtt',
]) {
final value = webConnectionFields[key];
if (value == null) continue;
if (value is String && value.trim().isEmpty) continue;
fields[key] = value;
}
return fields;
}
static String _networkType(String value) {
return normalizeNetworkType(value);
}
}
+130
View File
@@ -0,0 +1,130 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/read_behavior_enum.dart';
/// 小说事件
class NovelEvent {
final String event = EventType.novelEvent.event;
late final String eventId;
/// 小说 id
final String novelId;
/// 小说标题
final String novelTitle;
/// 分类ID
final String novelTypeId;
/// 分类名称
final String novelTypeName;
/// 标签KEY, 多个标签使用英文逗号分隔
final String novelTagKey;
/// 标签名称,多个标签使用英文逗号分隔
final String novelTagName;
/// 推荐引擎的trace_id,未接推荐引擎传空字符串
final String recommendTraceId;
/// 阅读进度百分比(0-100)
final int readProgress;
/// 当前阅读第几页(从1开始)
final int pageNo;
/// 行为标识:view(展示), page_next(下一页), page_prev(上一页), complete(读完) 等
final ReadBehaviorEnum novelBehavior;
/// 章节ID,没有章节上报空字符串,没有对接搜索推荐引擎的上报空字符串
final String chapterId;
/// 章节名称,没有章节上报空字符串,没有对接搜索推荐引擎的上报空字符串
final String chapterName;
/// 老司机媒体资源ID,如未使用老司机库资源可传空字符串
final String mediaId;
late final Map<String, dynamic> _commonFields;
NovelEvent({
required this.novelId,
required this.novelTitle,
required this.novelTypeId,
required this.novelTypeName,
required this.novelTagKey,
required this.novelTagName,
required this.readProgress,
required this.pageNo,
required this.novelBehavior,
this.recommendTraceId = '',
this.mediaId = '',
this.chapterId = '',
this.chapterName = '',
}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
novelId,
novelTitle,
novelTypeId,
novelTypeName,
novelTagKey,
novelTagName,
readProgress.toString(),
pageNo.toString(),
novelBehavior.key,
novelBehavior.name,
recommendTraceId,
mediaId,
chapterId,
chapterName,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'novel_id': novelId,
'novel_title': novelTitle,
'novel_type_id': novelTypeId,
'novel_type_name': novelTypeName,
'novel_tag_key': novelTagKey,
'novel_tag_name': novelTagName,
'recommend_trace_id': recommendTraceId,
'read_progress': readProgress,
'page_no': pageNo,
'novel_behavior_key': novelBehavior.key,
'novel_behavior_name': novelBehavior.name,
'media_id': mediaId,
'chapter_id': chapterId,
'chapter_name': chapterName,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory NovelEvent.fromJson(Map<String, dynamic> json) {
final behaviorKey = json['novel_behavior_key'] as String? ?? '';
final behaviorName = json['novel_behavior_name'] as String? ?? '';
final behavior = ReadBehaviorEnum(
behaviorKey.isNotEmpty ? behaviorKey : ReadBehaviorEnum.VIEW.key,
behaviorName.isNotEmpty ? behaviorName : ReadBehaviorEnum.VIEW.name,
);
return NovelEvent(
novelId: json['novel_id'] as String,
novelTitle: json['novel_title'] as String,
novelTypeId: json['novel_type_id'] as String,
novelTypeName: json['novel_type_name'] as String,
novelTagKey: json['novel_tag_key'] as String,
novelTagName: json['novel_tag_name'] as String,
readProgress: json['read_progress'] as int,
pageNo: json['page_no'] as int,
novelBehavior: behavior,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
mediaId: json['media_id'] as String? ?? '',
chapterId: json['chapter_id'] as String? ?? '',
chapterName: json['chapter_name'] as String? ?? '',
);
}
}
@@ -0,0 +1,284 @@
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/page_click_auto_fields.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 应用页面点击日志 (自动)
class PageClickEvent {
final String event = EventType.pageClick.event;
late final String eventId;
final String pageKey;
/// 页面名称:首页, 我的, 视频页
final String pageName;
/// 点击位置坐标x轴
final int clickPageX;
/// 点击位置坐标y轴
final int clickPageY;
/// SDK 标准化点击相对当前页面根容器可视区域左上角的 X 坐标。
final int clickViewportX;
/// SDK 标准化点击相对当前页面根容器可视区域左上角的 Y 坐标。
final int clickViewportY;
/// 点击相对当前页面主内容完整区域左上角的 X 坐标。
final int clickDocumentX;
/// 点击相对当前页面主内容完整区域左上角的 Y 坐标。
final int clickDocumentY;
/// 点击位置横坐标百分比(0-100)
final int clickXPercent;
/// 点击位置纵坐标百分比(0-100)
final int clickYPercent;
/// 屏幕宽度(像素)
final int screenWidth;
/// 屏幕高度(像素)
final int screenHeight;
/// 点击时当前页面根容器可交互可视区域宽度。
final int viewportWidth;
/// 点击时当前页面根容器可交互可视区域高度。
final int viewportHeight;
/// 点击时当前页面主内容容器完整宽度。
final int pageWidth;
/// 点击时当前页面主内容容器完整高度。
final int pageHeight;
/// 点击时当前页面主内容容器纵向滚动偏移。
final int scrollTop;
/// 点击时当前页面主内容容器横向滚动偏移。
final int scrollLeft;
/// 推荐引擎的trace_id,有多个推荐列表时用英文逗号分隔,未接推荐引擎传空字符串
final String recommendTraceId;
late final Map<String, dynamic> _commonFields;
PageClickEvent({
required String pageKey,
required this.pageName,
required this.clickPageX,
required this.clickPageY,
required this.clickXPercent,
required this.clickYPercent,
required this.screenWidth,
required this.screenHeight,
int? clickViewportX,
int? clickViewportY,
int? clickDocumentX,
int? clickDocumentY,
int? viewportWidth,
int? viewportHeight,
int? pageWidth,
int? pageHeight,
int? scrollTop,
int? scrollLeft,
this.recommendTraceId = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey),
clickViewportX = _coordinateOrFallback(clickViewportX, clickPageX),
clickViewportY = _coordinateOrFallback(clickViewportY, clickPageY),
viewportWidth = _dimensionOrFallback(viewportWidth, screenWidth),
viewportHeight = _dimensionOrFallback(viewportHeight, screenHeight),
clickDocumentX = _hasInvalidHorizontalDocumentMetrics(
pageWidth: pageWidth,
viewportWidth: viewportWidth,
screenWidth: screenWidth,
scrollLeft: scrollLeft,
)
? -1
: _unknownable(clickDocumentX),
clickDocumentY = _unknownable(clickDocumentY),
pageWidth = _hasInvalidHorizontalDocumentMetrics(
pageWidth: pageWidth,
viewportWidth: viewportWidth,
screenWidth: screenWidth,
scrollLeft: scrollLeft,
)
? -1
: _unknownable(pageWidth),
pageHeight = _unknownable(pageHeight),
scrollTop = _unknownable(scrollTop),
scrollLeft = _hasInvalidHorizontalDocumentMetrics(
pageWidth: pageWidth,
viewportWidth: viewportWidth,
screenWidth: screenWidth,
scrollLeft: scrollLeft,
)
? -1
: _unknownable(scrollLeft) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
this.pageKey,
pageName,
clickPageX.toString(),
clickPageY.toString(),
this.clickViewportX.toString(),
this.clickViewportY.toString(),
this.clickDocumentX.toString(),
this.clickDocumentY.toString(),
clickXPercent.toString(),
clickYPercent.toString(),
screenWidth.toString(),
screenHeight.toString(),
this.viewportWidth.toString(),
this.viewportHeight.toString(),
this.pageWidth.toString(),
this.pageHeight.toString(),
this.scrollTop.toString(),
this.scrollLeft.toString(),
recommendTraceId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'page_key': pageKey,
'page_name': pageName,
'click_page_x': clickPageX,
'click_page_y': clickPageY,
'click_viewport_x': clickViewportX,
'click_viewport_y': clickViewportY,
'click_document_x': clickDocumentX,
'click_document_y': clickDocumentY,
'click_x_percent': clickXPercent,
'click_y_percent': clickYPercent,
'screen_width': screenWidth,
'screen_height': screenHeight,
'viewport_width': viewportWidth,
'viewport_height': viewportHeight,
'page_width': pageWidth,
'page_height': pageHeight,
'scroll_top': scrollTop,
'scroll_left': scrollLeft,
'recommend_trace_id': recommendTraceId,
};
return {
..._commonFields,
'event_id': eventId,
"payload": payload,
};
}
factory PageClickEvent.fromJson(Map<String, dynamic> json) {
return PageClickEvent(
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
clickPageX: json['click_page_x'] as int,
clickPageY: json['click_page_y'] as int,
clickXPercent: json['click_x_percent'] as int,
clickYPercent: json['click_y_percent'] as int,
screenWidth: json['screen_width'] as int,
screenHeight: json['screen_height'] as int,
clickViewportX: json['click_viewport_x'] as int?,
clickViewportY: json['click_viewport_y'] as int?,
clickDocumentX: json['click_document_x'] as int?,
clickDocumentY: json['click_document_y'] as int?,
viewportWidth: json['viewport_width'] as int?,
viewportHeight: json['viewport_height'] as int?,
pageWidth: json['page_width'] as int?,
pageHeight: json['page_height'] as int?,
scrollTop: json['scroll_top'] as int?,
scrollLeft: json['scroll_left'] as int?,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
);
}
static PageClickEvent fromViewport({
required String pageKey,
required String pageName,
required int clickPageX,
required int clickPageY,
required int clickViewportX,
required int clickViewportY,
required int viewportWidth,
required int viewportHeight,
int? clickDocumentX,
int? clickDocumentY,
int? pageWidth,
int? pageHeight,
int? scrollTop,
int? scrollLeft,
String recommendTraceId = '',
}) {
final documentMetrics = currentClickDocumentMetrics(
clickViewportX: clickViewportX,
clickViewportY: clickViewportY,
);
final clickXPercent = viewportWidth > 0
? (clickViewportX / viewportWidth * 100).round().clamp(0, 100)
: 0;
final clickYPercent = viewportHeight > 0
? (clickViewportY / viewportHeight * 100).round().clamp(0, 100)
: 0;
return PageClickEvent(
pageKey: pageKey,
pageName: pageName,
clickPageX: clickPageX,
clickPageY: clickPageY,
clickXPercent: clickXPercent,
clickYPercent: clickYPercent,
screenWidth: viewportWidth,
screenHeight: viewportHeight,
clickViewportX: clickViewportX,
clickViewportY: clickViewportY,
clickDocumentX: clickDocumentX ?? documentMetrics.clickDocumentX,
clickDocumentY: clickDocumentY ?? documentMetrics.clickDocumentY,
viewportWidth: viewportWidth,
viewportHeight: viewportHeight,
pageWidth: pageWidth ?? documentMetrics.pageWidth,
pageHeight: pageHeight ?? documentMetrics.pageHeight,
scrollTop: scrollTop ?? documentMetrics.scrollTop,
scrollLeft: scrollLeft ?? documentMetrics.scrollLeft,
recommendTraceId: recommendTraceId,
);
}
static int _coordinateOrFallback(int? value, int fallback) {
final resolved = value ?? fallback;
return resolved < 0 ? -1 : resolved;
}
static int _dimensionOrFallback(int? value, int fallback) {
final resolved = value ?? fallback;
return resolved < 0 ? -1 : resolved;
}
static int _unknownable(int? value) {
if (value == null || value < 0) return -1;
return value;
}
static bool _hasInvalidHorizontalDocumentMetrics({
required int? pageWidth,
required int? viewportWidth,
required int screenWidth,
required int? scrollLeft,
}) {
final resolvedPageWidth = _unknownable(pageWidth);
final resolvedViewportWidth =
_dimensionOrFallback(viewportWidth, screenWidth);
final resolvedScrollLeft = _unknownable(scrollLeft);
if (resolvedPageWidth < 0 ||
resolvedViewportWidth < 0 ||
resolvedScrollLeft <= 0) {
return false;
}
final maxScrollLeft = resolvedPageWidth - resolvedViewportWidth;
return maxScrollLeft < 0 || resolvedScrollLeft > maxScrollLeft;
}
}
@@ -0,0 +1,87 @@
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/recommend_content_type_enum.dart';
/// 推荐引擎列表点击事件(服务端/客户端)
class RecommendListClickEvent {
/// 事件类型(固定)
final String event = EventType.recommendListClick.event;
/// 去重 ID(动态计算)
late final String eventId;
/// 页面标识:page_home(首页), page_user(我的), page_video(视频页)等
final String pageKey;
/// 页面名称:首页, 我的, 视频页
final String pageName;
/// 推荐列表的类型: video(视频),novel(小说),comic(漫画)
final RecommendContentTypeEnum recommendContentType;
/// 推荐引擎的trace_id,未接推荐引擎传空字符串
final String recommendTraceId;
/// 点击内容的ID
final String recommendId;
/// 推荐引擎的trace_info,未接推荐引擎传空字符串
final String recommendTraceInfo;
/// 客户端版本,三位数字点分隔,例如:1.0.1
final String clientVersion;
late final Map<String, dynamic> _commonFields;
RecommendListClickEvent({
required String pageKey,
required this.pageName,
required this.recommendContentType,
required this.recommendId,
this.recommendTraceId = '',
this.recommendTraceInfo = '',
this.clientVersion = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
pageKey,
pageName,
recommendContentType.label,
recommendTraceId,
recommendId,
recommendTraceInfo,
clientVersion,
]);
}
Map<String, dynamic> toJson() {
final payload = <String, dynamic>{
'page_key': pageKey,
'page_name': pageName,
'recommend_content_type': recommendContentType.label,
'recommend_trace_id': recommendTraceId,
'recommend_id': recommendId,
'recommend_trace_info': recommendTraceInfo,
'client_version': clientVersion,
};
return {..._commonFields, 'event_id': eventId, 'payload': payload};
}
factory RecommendListClickEvent.fromJson(Map<String, dynamic> json) {
final contentTypeStr = json['recommend_content_type'] as String? ?? '';
final contentType = RecommendContentTypeEnum(
contentTypeStr.isNotEmpty ? contentTypeStr : RecommendContentTypeEnum.VIDEO.label,
);
return RecommendListClickEvent(
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
recommendContentType: contentType,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
recommendId: json['recommend_id'] as String? ?? '',
recommendTraceInfo: json['recommend_trace_info'] as String? ?? '',
clientVersion: json['client_version'] as String? ?? '',
);
}
}
@@ -0,0 +1,88 @@
import 'package:analytics_sdk/manager/page_name_manager.dart';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/recommend_content_type_enum.dart';
/// 推荐列表内容展示事件(服务端/客户端)
class RecommendListViewEvent {
/// 事件类型(固定)
final String event = EventType.recommendListView.event;
/// 去重 ID(动态计算)
late final String eventId;
/// 页面标识:home(首页), video_detail(视频详情), user_center(个人中心), discover(发现页)等
final String pageKey;
/// 页面名称:首页, 视频详情页, 个人中心, 发现页
final String pageName;
/// 推荐列表的类型: video(视频),novel(小说),comic(漫画)
final RecommendContentTypeEnum recommendContentType;
/// 推荐引擎的trace_id
final String recommendTraceId;
/// 推荐引擎列表的video_id(视频),novel_id(小说),comic_id(漫画), 可视窗口有多个,使用英文逗号分隔
final String recommendId;
/// 推荐引擎的trace_info,未接推荐引擎传空字符串
final String recommendTraceInfo;
/// 客户端版本,三位数字点分隔,例如:1.0.1
final String clientVersion;
late final Map<String, dynamic> _commonFields;
RecommendListViewEvent({
required String pageKey,
required this.pageName,
required this.recommendContentType,
required this.recommendId,
this.recommendTraceId = '',
this.recommendTraceInfo = '',
this.clientVersion = '',
}) : pageKey = PageNameMapper.normalizeKey(pageKey) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
pageKey,
pageName,
recommendContentType.label,
recommendTraceId,
recommendId,
recommendTraceInfo,
clientVersion,
]);
}
Map<String, dynamic> toJson() {
final payload = <String, dynamic>{
'event': event,
'page_key': pageKey,
'page_name': pageName,
'recommend_content_type': recommendContentType.label,
'recommend_trace_id': recommendTraceId,
'recommend_id': recommendId,
'recommend_trace_info': recommendTraceInfo,
'client_version': clientVersion,
};
return {..._commonFields, 'event_id': eventId, 'payload': payload};
}
factory RecommendListViewEvent.fromJson(Map<String, dynamic> json) {
final contentTypeStr = json['recommend_content_type'] as String? ?? '';
final contentType = RecommendContentTypeEnum(
contentTypeStr.isNotEmpty ? contentTypeStr : RecommendContentTypeEnum.VIDEO.label,
);
return RecommendListViewEvent(
pageKey: json['page_key'] as String,
pageName: json['page_name'] as String,
recommendContentType: contentType,
recommendTraceId: json['recommend_trace_id'] as String,
recommendId: json['recommend_id'] as String,
recommendTraceInfo: json['recommend_trace_info'] as String? ?? '',
clientVersion: json['client_version'] as String? ?? '',
);
}
}
@@ -0,0 +1,32 @@
import 'dart:convert';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// SDK 初始化快照事件。
///
/// `sdk_init` 不携带 `device_env`,硬件与环境快照字段直接写入 payload。
class SdkInitEvent {
final String event = EventType.sdkInit.event;
late final String eventId;
final Map<String, dynamic> payload;
late final Map<String, dynamic> _commonFields;
SdkInitEvent({required Map<String, dynamic> payload})
: payload = Map<String, dynamic>.from(payload) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
jsonEncode(this.payload),
]);
}
Map<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': payload,
};
}
}
+132
View File
@@ -0,0 +1,132 @@
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
import 'package:analytics_sdk/enum/video_content_type_enum.dart';
import 'package:analytics_sdk/enum/video_event_enum.dart';
/// 视频事件 (手动)
class VideoEvent {
final String event = EventType.videoEvent.event;
late final String eventId;
final String videoId;
/// 视频标题
final String videoTitle;
/// 视频分类ID
final String videoTypeId;
/// 视频分类名称
final String videoTypeName;
/// 标签名称,多个标签使用英文逗号分隔
final String videoTagName;
/// 标签KEY, 多个标签使用英文逗号分隔
final String videoTagKey;
/// 视频总时长(秒)
final int videoDuration;
/// 本次播放时长(秒)
final int playDuration;
/// 播放进度百分比(0-100)
final int playProgress;
/// 视频行为标识:video_play(播放), video_pause(暂停), video_share(分享), video_complete(播放完成), video_forward(快进), video_rewind(快退)等
final VideoEventEnum videoBehavior;
/// 视频内容类型:video(长视频), short_video(短视频)
final VideoContentTypeEnum? videoContentType;
/// 推荐引擎的trace_id,未接推荐引擎传空字符串
final String recommendTraceId;
/// 老司机媒体资源ID,视频分析日志新加字段,可为空字符串
final String mediaId;
late final Map<String, dynamic> _commonFields;
VideoEvent(
{required this.videoId,
required this.videoTitle,
required this.videoTypeId,
required this.videoTypeName,
required this.videoTagKey,
required this.videoTagName,
required this.videoDuration,
required this.playDuration,
required this.playProgress,
required this.videoBehavior,
required this.videoContentType,
this.recommendTraceId = '',
this.mediaId = ''}) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
videoId,
videoTitle,
videoTypeId,
videoTypeName,
videoTagKey,
videoTagName,
videoDuration.toString(),
playDuration.toString(),
playProgress.toString(),
videoBehavior.key,
videoBehavior.name,
videoContentType?.label ?? '',
recommendTraceId,
]);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> payload = {
'video_id': videoId,
'video_title': videoTitle,
'video_type_id': videoTypeId,
'video_type_name': videoTypeName,
'video_tag_key': videoTagKey,
'video_tag_name': videoTagName,
'video_duration': videoDuration,
'play_duration': playDuration,
'play_progress': playProgress,
'video_behavior_key': videoBehavior.key,
'video_behavior_name': videoBehavior.name,
'video_content_type': videoContentType?.label ?? '',
'recommend_trace_id': recommendTraceId,
'media_id': mediaId,
};
return {..._commonFields, 'event_id': eventId, "payload": payload};
}
factory VideoEvent.fromJson(Map<String, dynamic> json) {
final behaviorKey = json['video_behavior_key'] as String? ?? '';
final behaviorName = json['video_behavior_name'] as String? ?? '';
final behavior = VideoEventEnum(
behaviorKey.isNotEmpty ? behaviorKey : VideoEventEnum.VIDEO_PLAY.key,
behaviorName.isNotEmpty ? behaviorName : VideoEventEnum.VIDEO_PLAY.name,
);
final contentTypeStr = json['video_content_type'] as String? ?? '';
final VideoContentTypeEnum? contentType =
contentTypeStr.isNotEmpty ? VideoContentTypeEnum(contentTypeStr) : null;
return VideoEvent(
videoId: json['video_id'] as String,
videoTitle: json['video_title'] as String,
videoTypeId: json['video_type_id'] as String,
videoTypeName: json['video_type_name'] as String,
videoTagKey: json['video_tag_key'] as String,
videoTagName: json['video_tag_name'] as String,
videoDuration: json['video_duration'] as int,
playDuration: json['play_duration'] as int,
playProgress: json['play_progress'] as int,
videoBehavior: behavior,
videoContentType: contentType,
recommendTraceId: json['recommend_trace_id'] as String? ?? '',
mediaId: json['media_id'] as String? ?? '',
);
}
}