优化:禁止截屏字段语义优化

This commit is contained in:
ivan 2026-04-14 15:29:21 +08:00
parent f14474a39f
commit 7192c7275e
8 changed files with 210 additions and 22 deletions

View File

@ -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';

View File

@ -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(

View File

@ -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,

View File

@ -61,7 +61,7 @@
"extConfig": {
"keys": {
"showVideoMenu": ["go_run", "need_wait"],
"allowScreenshot": ["screen"],
"forbidScreenshot": ["screen"],
"blockScreenshot": ["safe_area"],
"allowThirdPartyPayment": ["san_fang", "lucky"],
"privacyUrl": ["privacy"],

View 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);
}
}

View File

@ -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');
}
}
}

View 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);
}
}

View File

@ -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';