chore: 发布v6.36.1版本,鸿蒙适配+隐私合规收尾

### 变更详情
1.  **多语言国际化**:新增`rejectAndExit`翻译键,覆盖14种语言的拒绝退出文案
2.  **鸿蒙端适配**:
    - 修复USB事件监听报错,仅Android端启用原生USB监听
    - 调整小部件管理页交互,鸿蒙端跳过Beta弹窗并显示"敬请期待"提示
    - 适配通用设置页和功能检查的鸿蒙端toast替代弹窗逻辑
    - 调整句子详情页"编辑"按钮的鸿蒙端交互
3.  **隐私合规优化**:
    - 移除剪贴板定时轮询,改为被动触发模式,仅在用户主动进入稍后读页面时检查剪贴板
    - 移除Android端非核心权限,精简权限声明
    - 新增启动页SplashActivity作为协议守门人,从根本上阻止敏感插件在协议前初始化
    - 为Android端引导页新增双按钮布局,优化未勾选协议时的用户操作引导
4.  **依赖与代码清理**:
    - 修复macOS端flutter_secure_storage插件导入路径
    - 简化字体导入逻辑,移除冗余的readAsBytes降级逻辑
    - 更新CHANGELOG文档,记录版本变更细节
This commit is contained in:
Developer
2026-06-11 01:23:32 +08:00
parent 3a38c69521
commit b36b9bcf9e
38 changed files with 1422 additions and 542 deletions

View File

@@ -1,9 +1,9 @@
/// ============================================================
/// 闲言APP — 剪贴板链接监控服务
/// 创建时间: 2026-05-15
/// 更新时间: 2026-05-15
/// 作用: 动检测剪贴板中的URL链接并提示保存到稍后读
/// 上次更新: E10 初始创建支持3秒轮询剪贴板+URL检测+隐私保护
/// 更新时间: 2026-06-10
/// 作用: 动检测剪贴板中的URL链接并提示保存到稍后读
/// 上次更新: 移除定时轮询,改为被动触发(隐私合规),仅用户主动操作时检查
/// ============================================================
import 'package:xianyan/core/utils/data/pattern_utils.dart';
@@ -23,50 +23,44 @@ class ClipboardMonitorService {
static const _keyEnabled = 'clipboard_monitor_enabled';
static const _readlaterConvId = 'readlater';
static const _monitorInterval = Duration(seconds: 3);
String? _lastClipboardText;
Timer? _monitorTimer;
bool _enabled = false;
DateTime? _lastCheckTime;
bool get isEnabled => _enabled;
// ============================================================
// 启动监控
// 启用/禁用监控(仅设置标志,不启动定时器)
// ============================================================
void startMonitor() {
if (_enabled) return;
_enabled = true;
_monitorTimer?.cancel();
_monitorTimer = Timer.periodic(_monitorInterval, (_) {
_checkClipboard();
});
Log.i('ClipboardMonitor: 剪贴板监控已启动 (每3秒检查)');
Log.i('ClipboardMonitor: 剪贴板监控已启用(被动模式,不主动轮询)');
}
// ============================================================
// 停止监控
// ============================================================
void stopMonitor() {
_monitorTimer?.cancel();
_monitorTimer = null;
_enabled = false;
_lastClipboardText = null;
Log.i('ClipboardMonitor: 剪贴板监控已停止');
}
// ============================================================
// 检查剪贴板
// 被动检查剪贴板 — 仅在用户主动操作时调用
// 如打开稍后读页面、从其他App返回时用户在首页
// ============================================================
Future<void> _checkClipboard() async {
Future<void> checkClipboardOnce() async {
if (!_enabled) return;
// 防抖5秒内不重复检查
final now = DateTime.now();
if (_lastCheckTime != null &&
now.difference(_lastCheckTime!).inSeconds < 5) {
return;
}
_lastCheckTime = now;
try {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
final text = clipboardData?.text;
@@ -141,13 +135,14 @@ class ClipboardMonitorService {
}
// ============================================================
// 初始化 — 根据存储的开关状态决定是否启动
// 初始化 — 根据存储的开关状态设置标志(不启动定时器)
// ============================================================
Future<void> initFromStore() async {
final stored = KvStorage.getBool(_keyEnabled) ?? false;
if (stored) {
startMonitor();
_enabled = true;
Log.i('ClipboardMonitor: 已从存储恢复启用状态(被动模式)');
}
}

View File

@@ -16,6 +16,8 @@ import 'package:hive_flutter/hive_flutter.dart';
import '../../network/api_client.dart';
import '../../storage/kv_storage.dart';
import '../../utils/logger.dart';
import '../../utils/platform/platform_utils.dart' as pu;
import '../../../shared/widgets/feedback/app_toast.dart';
// ============================================================
// 本地功能标志枚举(原有,保持向后兼容)
@@ -159,8 +161,8 @@ class FeatureFlagNotifier extends Notifier<FeatureFlagState> {
final featureFlagProvider =
NotifierProvider<FeatureFlagNotifier, FeatureFlagState>(
FeatureFlagNotifier.new,
);
FeatureFlagNotifier.new,
);
// ============================================================
// 远程功能标志状态枚举
@@ -226,9 +228,12 @@ class FeatureFlagItem {
name: json['name'] as String? ?? '',
description: json['description'] as String? ?? '',
enabled: json['enabled'] as bool? ?? false,
status: FeatureFlagStatus.fromId(json['status'] as String? ?? 'developing'),
status: FeatureFlagStatus.fromId(
json['status'] as String? ?? 'developing',
),
progress: (json['progress'] as num?)?.toDouble() ?? 0.0,
rolloutPercentage: (json['rollout_percentage'] as num?)?.toDouble() ?? 1.0,
rolloutPercentage:
(json['rollout_percentage'] as num?)?.toDouble() ?? 1.0,
targetGroup: json['target_group'] as String?,
expiresAt: json['expires_at'] != null
? DateTime.tryParse(json['expires_at'] as String)
@@ -281,7 +286,8 @@ class FeatureFlagItem {
class RemoteFeatureFlagService {
RemoteFeatureFlagService._();
static final RemoteFeatureFlagService _instance = RemoteFeatureFlagService._();
static final RemoteFeatureFlagService _instance =
RemoteFeatureFlagService._();
static RemoteFeatureFlagService get instance => _instance;
static const String _cacheKey = 'remote_flags';
@@ -441,6 +447,12 @@ class FeatureFlagService {
static bool check(BuildContext context, FeatureFlag flag) {
if (flag.isEnabled) return true;
// 鸿蒙端不显示弹窗改为toast提示
if (pu.isOhos) {
AppToast.showInfo('敬请期待');
return false;
}
showCupertinoDialog<void>(
context: context,
builder: (_) => CupertinoAlertDialog(

View File

@@ -3,12 +3,11 @@
// 创建时间: 2026-05-22
// 更新时间: 2026-06-10
// 作用: 统一管理前后台切换时暂停/恢复后台服务和动画
// 上次更新: 移除ShakeDetector相关代码
// 上次更新: 移除剪贴板监控自动恢复(改为被动触发)
// ============================================================
import 'package:flutter/widgets.dart';
import 'package:xianyan/core/services/performance/performance_orchestrator.dart';
import 'package:xianyan/core/services/clipboard_monitor_service.dart';
import 'package:xianyan/core/services/device/battery_info_service.dart';
import 'package:xianyan/core/utils/logger.dart';
@@ -43,7 +42,6 @@ mixin AppLifecycleGate on WidgetsBindingObserver {
Log.i('AppLifecycleGate: 进入后台 — 暂停服务和动画');
PerformanceOrchestrator.instance.onAppPaused();
ClipboardMonitorService.instance.stopMonitor();
BatteryInfoService.instance.pausePolling();
}
@@ -51,13 +49,7 @@ mixin AppLifecycleGate on WidgetsBindingObserver {
Log.i('AppLifecycleGate: 回到前台 — 恢复服务和动画');
PerformanceOrchestrator.instance.onAppResumed();
try {
if (ClipboardMonitorService.instance.isEnabled) {
ClipboardMonitorService.instance.startMonitor();
}
} catch (e) {
Log.e('AppLifecycleGate: 恢复剪贴板监控失败', e);
}
// 剪贴板监控已改为被动触发,不再自动恢复轮询
try {
BatteryInfoService.instance.resumePolling();
} catch (e) {

View File

@@ -1,9 +1,9 @@
// ============================================================
// 闲言APP — 协议同意后初始化器
// 创建时间: 2026-05-30
// 更新时间: 2026-06-06
// 更新时间: 2026-06-10
// 作用: 将权限敏感的服务初始化延迟到用户同意协议后执行
// 上次更新: 新增iOS ATT(App Tracking Transparency)授权请求
// 上次更新: 移除ClipboardMonitorService改为被动触发不在此初始化
// ============================================================
import 'dart:io';
@@ -16,7 +16,6 @@ import '../utils/platform/platform_utils.dart' as pu;
import '../router/app_router.dart' show rootNavigatorKey;
import 'auth/permission_service.dart';
import 'network/connectivity_service.dart';
import 'clipboard_monitor_service.dart';
import 'background/background_task_service.dart';
import 'notification/local_notification_service.dart';
import 'device/screen_wake_service.dart';
@@ -43,9 +42,11 @@ class PostAgreementInitializer {
// 先检查当前授权状态仅在未决定时请求避免Info.plist缺少描述时原生崩溃
if (Platform.isIOS) {
try {
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
final status =
await AppTrackingTransparency.trackingAuthorizationStatus;
if (status == TrackingStatus.notDetermined) {
final authorized = await PermissionService.requestTrackingPermission();
final authorized =
await PermissionService.requestTrackingPermission();
Log.i('iOS ATT授权结果: ${authorized ? "已授权" : "未授权"}');
} else {
Log.i('iOS ATT已授权状态: $status,跳过请求');
@@ -117,14 +118,8 @@ class PostAgreementInitializer {
}
}
if (!pu.isWeb) {
try {
await ClipboardMonitorService.instance.initFromStore();
Log.i('剪贴板监控服务初始化完成');
} catch (e, st) {
Log.e('剪贴板监控服务初始化失败', e, st);
}
}
// 剪贴板监控服务已改为被动触发模式,不在此初始化
// 仅在用户主动操作时(如打开稍后读页面)才检查剪贴板
if (!pu.isWeb) {
try {