优化:禁止截屏字段语义优化
This commit is contained in:
parent
f14474a39f
commit
7192c7275e
@ -29,6 +29,7 @@ export 'src/log/sdk_reminder_log.dart';
|
||||
export 'src/media/video_thumbnail_cache.dart';
|
||||
export 'src/services/adjust_service.dart';
|
||||
export 'src/services/analytics_attribution_callbacks.dart';
|
||||
export 'src/services/analytics_events.dart';
|
||||
export 'src/services/analytics_service.dart';
|
||||
export 'src/services/auth_service.dart';
|
||||
export 'src/services/facebook_service.dart';
|
||||
|
||||
@ -16,7 +16,7 @@ import 'dart:convert';
|
||||
class ExtConfigKeySchema {
|
||||
const ExtConfigKeySchema({
|
||||
required this.showVideoMenuKeys,
|
||||
required this.allowScreenshotKeys,
|
||||
required this.forbidScreenshotKeys,
|
||||
required this.blockScreenshotKeys,
|
||||
required this.allowThirdPartyPaymentKeys,
|
||||
required this.privacyUrlKeys,
|
||||
@ -44,10 +44,11 @@ class ExtConfigKeySchema {
|
||||
/// 是否展示顶部 Video/分类 Tab,并把 items 固定为最后一格 Tab。
|
||||
final List<String> showVideoMenuKeys;
|
||||
|
||||
/// 为 `true` 时表示**允许**截屏(直接读取这些键的布尔值)。
|
||||
final List<String> allowScreenshotKeys;
|
||||
/// 线网布尔为 `true` 时表示**禁止**截屏的键(默认 `screen`)。
|
||||
/// 与 [blockScreenshotKeys] 二选一:[forbidScreenshotKeys] 优先(首个存在的键即生效)。
|
||||
final List<String> forbidScreenshotKeys;
|
||||
|
||||
/// 为 `true` 时表示**禁止**截屏(与 [allowScreenshotKeys] 互斥推导:本键为 true ⇒ 不允许截屏)。
|
||||
/// 备选用键:`true` 同样表示禁止截屏(默认 `safe_area`),仅当 [forbidScreenshotKeys] 未命中任何键时使用。
|
||||
final List<String> blockScreenshotKeys;
|
||||
|
||||
/// 是否允许第三方支付。
|
||||
@ -125,7 +126,7 @@ class ExtConfigKeySchema {
|
||||
factory ExtConfigKeySchema.defaults() {
|
||||
return const ExtConfigKeySchema(
|
||||
showVideoMenuKeys: ['go_run', 'need_wait'],
|
||||
allowScreenshotKeys: ['screen'],
|
||||
forbidScreenshotKeys: ['screen'],
|
||||
blockScreenshotKeys: ['safe_area'],
|
||||
allowThirdPartyPaymentKeys: ['san_fang', 'lucky'],
|
||||
privacyUrlKeys: ['privacy'],
|
||||
@ -178,10 +179,26 @@ class ExtConfigKeySchema {
|
||||
return rootList(itemKeys, k, fallback);
|
||||
}
|
||||
|
||||
/// `skin_config.extConfig.keys`:优先 [primary],为空则回退 [legacy](兼容旧键名),再不行用 [fallback]。
|
||||
List<String> mergedRootList(
|
||||
String primary,
|
||||
String legacy,
|
||||
List<String> fallback,
|
||||
) {
|
||||
final p = rootList(keyMap, primary, const <String>[]);
|
||||
if (p.isNotEmpty) return p;
|
||||
final l = rootList(keyMap, legacy, const <String>[]);
|
||||
if (l.isNotEmpty) return l;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return ExtConfigKeySchema(
|
||||
showVideoMenuKeys: rootList(keyMap, 'showVideoMenu', d.showVideoMenuKeys),
|
||||
allowScreenshotKeys:
|
||||
rootList(keyMap, 'allowScreenshot', d.allowScreenshotKeys),
|
||||
forbidScreenshotKeys: mergedRootList(
|
||||
'forbidScreenshot',
|
||||
'allowScreenshot',
|
||||
d.forbidScreenshotKeys,
|
||||
),
|
||||
blockScreenshotKeys:
|
||||
rootList(keyMap, 'blockScreenshot', d.blockScreenshotKeys),
|
||||
allowThirdPartyPaymentKeys: rootList(
|
||||
|
||||
@ -14,7 +14,7 @@ import 'field_mapping.dart';
|
||||
class ExtConfigData {
|
||||
const ExtConfigData({
|
||||
this.showVideoMenu,
|
||||
this.allowScreenshot,
|
||||
this.forbidScreenshot,
|
||||
this.allowThirdPartyPayment,
|
||||
this.privacyUrl,
|
||||
this.agreementUrl,
|
||||
@ -22,18 +22,16 @@ class ExtConfigData {
|
||||
});
|
||||
|
||||
final bool? showVideoMenu;
|
||||
final bool? allowScreenshot;
|
||||
|
||||
/// 是否**禁止**截屏(逻辑值)。
|
||||
///
|
||||
/// 线网键(如 `screen`、`safe_area`)为 `true` 时本字段为 `true`;未命中相关键时为 `null`。
|
||||
final bool? forbidScreenshot;
|
||||
final bool? allowThirdPartyPayment;
|
||||
final String? privacyUrl;
|
||||
final String? agreementUrl;
|
||||
final List<ExtConfigItem> items;
|
||||
|
||||
bool? get shouldPreventCapture {
|
||||
final a = allowScreenshot;
|
||||
if (a == null) return null;
|
||||
return !a;
|
||||
}
|
||||
|
||||
static ExtConfigData empty() => const ExtConfigData();
|
||||
|
||||
static ExtConfigData parse(
|
||||
@ -68,13 +66,14 @@ class ExtConfigData {
|
||||
}) {
|
||||
final showVideo = _readBoolFromKeys(map, schema.showVideoMenuKeys);
|
||||
|
||||
bool? allowShot;
|
||||
allowShot = _readBoolFromKeys(map, schema.allowScreenshotKeys);
|
||||
if (allowShot == null && schema.blockScreenshotKeys.isNotEmpty) {
|
||||
bool? forbidScreenshot;
|
||||
final fromPrimary = _readBoolFromKeys(map, schema.forbidScreenshotKeys);
|
||||
if (fromPrimary != null) {
|
||||
forbidScreenshot = fromPrimary;
|
||||
} else if (schema.blockScreenshotKeys.isNotEmpty) {
|
||||
for (final k in schema.blockScreenshotKeys) {
|
||||
if (!map.containsKey(k)) continue;
|
||||
final block = _readBool(map, k) == true;
|
||||
allowShot = !block;
|
||||
forbidScreenshot = _readBool(map, k) == true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -131,7 +130,7 @@ class ExtConfigData {
|
||||
|
||||
return ExtConfigData(
|
||||
showVideoMenu: showVideo,
|
||||
allowScreenshot: allowShot,
|
||||
forbidScreenshot: forbidScreenshot,
|
||||
allowThirdPartyPayment: third,
|
||||
privacyUrl: privacy,
|
||||
agreementUrl: agreement,
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
"extConfig": {
|
||||
"keys": {
|
||||
"showVideoMenu": ["go_run", "need_wait"],
|
||||
"allowScreenshot": ["screen"],
|
||||
"forbidScreenshot": ["screen"],
|
||||
"blockScreenshot": ["safe_area"],
|
||||
"allowThirdPartyPayment": ["san_fang", "lucky"],
|
||||
"privacyUrl": ["privacy"],
|
||||
|
||||
135
lib/src/services/analytics_events.dart
Normal file
135
lib/src/services/analytics_events.dart
Normal file
@ -0,0 +1,135 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../bootstrap/client_bootstrap.dart';
|
||||
import '../config/skin_config.dart';
|
||||
import '../entities/payment_entities.dart';
|
||||
import 'analytics_service.dart';
|
||||
import 'facebook_service.dart';
|
||||
import 'payment_flow/payment_flow_models.dart';
|
||||
|
||||
/// 宿主侧业务埋点(Adjust token 来自 `skin_config.json` 的 `adjustEvents` + Facebook App Events)。
|
||||
///
|
||||
/// 须在 [ClientBootstrap.initFromAsset] / [ClientBootstrap.initFromJson] 与 [ClientBootstrap.initAnalytics] 之后调用。
|
||||
/// 行为对齐常见 app 线(如 app_client [AdjustEvents]):档位价、首日充值标记、支付成功/失败。
|
||||
abstract final class AnalyticsEvents {
|
||||
AnalyticsEvents._();
|
||||
|
||||
static const _prefsRegisterDayKey =
|
||||
'client_proxy_framework_skin_analytics_register_day';
|
||||
|
||||
/// 历史宿主键(FunyMee 曾用),仅用于读取兼容。
|
||||
static const _prefsLegacyRegisterDayKey = 'funymee_analytics_register_date';
|
||||
|
||||
static SkinConfig get _skin => ClientBootstrap.skin;
|
||||
|
||||
/// 从金额文案解析数字(如 `"¥19.99"` / `"\$9.99"`)。
|
||||
static num? parsePrice(String? amount) {
|
||||
if (amount == null || amount.trim().isEmpty) return null;
|
||||
final m = RegExp(r'[\d.]+').firstMatch(amount);
|
||||
if (m == null) return null;
|
||||
return num.tryParse(m.group(0)!);
|
||||
}
|
||||
|
||||
static String? _tierLogicalNameForPrice(num price) {
|
||||
final s = price.toStringAsFixed(2);
|
||||
switch (s) {
|
||||
case '5.99':
|
||||
return 'price_599';
|
||||
case '9.99':
|
||||
return 'price_999';
|
||||
case '19.99':
|
||||
return 'price_1999';
|
||||
case '49.99':
|
||||
return 'price_4999';
|
||||
case '99.99':
|
||||
return 'price_9999';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户点击某档位发起支付前(按价格匹配 `adjustEvents.price_*`,否则 `purchase`)。
|
||||
static void trackTierSelection(PaymentProductItem item) {
|
||||
final p = parsePrice(item.actualAmount);
|
||||
if (p == null) return;
|
||||
final name = _tierLogicalNameForPrice(p);
|
||||
if (name != null) {
|
||||
_skin.trackAdjustEvent(name);
|
||||
} else {
|
||||
_skin.trackAdjustEvent('purchase');
|
||||
}
|
||||
}
|
||||
|
||||
/// [FastLoginResponse.firstRegister] 为 true 时调用。
|
||||
static Future<void> trackRegisterIfNeeded({required bool firstRegister}) async {
|
||||
if (!firstRegister) return;
|
||||
_skin.trackAdjustEvent('register');
|
||||
AnalyticsService.trackRegister();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
_prefsRegisterDayKey,
|
||||
DateTime.now().toIso8601String().substring(0, 10),
|
||||
);
|
||||
if (kDebugMode) {
|
||||
debugPrint('[AnalyticsEvents] register + first-day marker set');
|
||||
}
|
||||
}
|
||||
|
||||
/// 支付成功:Adjust `purchase`;若注册当日则 `firstPurchase`;Facebook [AnalyticsService.trackPurchase]。
|
||||
static Future<void> trackPurchaseSuccess(double amount) async {
|
||||
_skin.trackAdjustEvent('purchase');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final regDate = prefs.getString(_prefsRegisterDayKey) ??
|
||||
prefs.getString(_prefsLegacyRegisterDayKey);
|
||||
final today = DateTime.now().toIso8601String().substring(0, 10);
|
||||
if (regDate != null && regDate == today) {
|
||||
_skin.trackAdjustEvent('firstPurchase');
|
||||
}
|
||||
if (amount > 0) {
|
||||
AnalyticsService.trackPurchase(amount: amount, currency: 'USD');
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用 [PaymentProductItem.actualAmount] 解析金额后 [trackPurchaseSuccess]。
|
||||
static Future<void> trackPurchaseSuccessForProduct(
|
||||
PaymentProductItem? item) async {
|
||||
final raw = parsePrice(item?.actualAmount);
|
||||
final amount = raw?.toDouble() ?? 0.0;
|
||||
await trackPurchaseSuccess(amount);
|
||||
}
|
||||
|
||||
/// 支付失败 / 下单失败(Adjust `paymentFailed` + FB `payment_failed`)。
|
||||
static void trackPaymentFailed() {
|
||||
_skin.trackAdjustEvent('paymentFailed');
|
||||
FacebookService.logEvent('payment_failed');
|
||||
}
|
||||
|
||||
/// 根据支付编排结果 [PaymentSettlement] 自动埋点(成功 → [trackPurchaseSuccessForProduct];失败 → [trackPaymentFailed])。
|
||||
///
|
||||
/// 取消、超时、[PaymentFlowOutcomeType.nativePendingHostVerification] 不打点。
|
||||
static void trackPaymentSettlement(
|
||||
PaymentSettlement settlement, {
|
||||
PaymentProductItem? product,
|
||||
}) {
|
||||
switch (settlement.type) {
|
||||
case PaymentFlowOutcomeType.success:
|
||||
unawaited(trackPurchaseSuccessForProduct(product));
|
||||
break;
|
||||
case PaymentFlowOutcomeType.failure:
|
||||
trackPaymentFailed();
|
||||
break;
|
||||
case PaymentFlowOutcomeType.cancelled:
|
||||
case PaymentFlowOutcomeType.timeout:
|
||||
case PaymentFlowOutcomeType.nativePendingHostVerification:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Facebook 自定义事件。
|
||||
static void trackCustomEvent(String name, {Map<String, dynamic>? parameters}) {
|
||||
FacebookService.logEvent(name, parameters: parameters);
|
||||
}
|
||||
}
|
||||
@ -94,4 +94,13 @@ class FacebookService {
|
||||
SdkReminderLog.facebook('logEvent 失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 手动上报应用激活(当 `AndroidManifest` / `Info.plist` 关闭自动 App Events 时常用)。
|
||||
static void activateApp() {
|
||||
try {
|
||||
_fb.activateApp();
|
||||
} catch (e) {
|
||||
SdkReminderLog.facebook('activateApp 失败: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
lib/src/services/payment_flow/analytics_payment_sink.dart
Normal file
26
lib/src/services/payment_flow/analytics_payment_sink.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import '../../entities/payment_entities.dart';
|
||||
import '../analytics_events.dart';
|
||||
import 'payment_flow_models.dart';
|
||||
import 'payment_settlement_sink.dart';
|
||||
|
||||
/// 在 [inner] 收到结果前写入 Adjust / Facebook 支付相关埋点(见 [AnalyticsEvents.trackPaymentSettlement])。
|
||||
///
|
||||
/// 宿主只实现 UI / 刷新逻辑的 [PaymentSettlementSink],用本类包一层即可。
|
||||
final class PaymentSettlementSinkWithAnalytics implements PaymentSettlementSink {
|
||||
PaymentSettlementSinkWithAnalytics({
|
||||
required this.inner,
|
||||
this.analyticsProduct,
|
||||
});
|
||||
|
||||
final PaymentSettlementSink inner;
|
||||
final PaymentProductItem? analyticsProduct;
|
||||
|
||||
@override
|
||||
void onPaymentSettled(PaymentSettlement settlement) {
|
||||
AnalyticsEvents.trackPaymentSettlement(
|
||||
settlement,
|
||||
product: analyticsProduct,
|
||||
);
|
||||
inner.onPaymentSettled(settlement);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
/// 支付编排(宿主策略 + 框架编排):档位列表、三方建单/轮询、Google Play 内购收口。
|
||||
library payment_flow;
|
||||
|
||||
export 'analytics_payment_sink.dart';
|
||||
export 'payment_checkout_launcher.dart';
|
||||
export 'payment_flow_catalog.dart';
|
||||
export 'payment_flow_models.dart';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user