Initial commit: 91dj android client dev_1.0.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user