此版本包含以下核心更新: 1. 版本号升级至6.6.18,更新全平台配置文件 2. 实现隐私合规改造: - 新增剪贴板隐私守卫,未同意协议前禁止读取剪贴板 - 所有桌面小部件继承隐私感知基类,未同意协议时显示占位提示 - 移除自动剪贴板监控,改为用户主动触发 3. 新增Windows平台深色主题同步功能 4. 补全多语言默认句子翻译 5. 优化安卓快捷方式配置与小部件合规性 6. 修复macOS插件注册问题 7. 新增Windows安装脚本 8. 优化触觉反馈服务初始化时机
113 lines
3.2 KiB
Dart
113 lines
3.2 KiB
Dart
// ============================================================
|
||
// 闲言APP — 剪贴板同步服务
|
||
// 创建时间: 2026-05-10
|
||
// 更新时间: 2026-06-16
|
||
// 作用: 跨设备剪贴板文本同步 — 信令通道推送
|
||
// 上次更新: 使用ClipboardBridge统一入口,含隐私协议守卫,
|
||
// 未同意协议时禁止读取剪贴板
|
||
// ============================================================
|
||
|
||
import 'dart:async';
|
||
|
||
import 'package:flutter/services.dart';
|
||
import 'package:xianyan/core/utils/logger.dart';
|
||
import 'package:xianyan/core/utils/platform/clipboard_bridge.dart';
|
||
|
||
import 'signaling_service.dart';
|
||
|
||
class ClipboardSyncService {
|
||
ClipboardSyncService({required SignalingService signalingService})
|
||
: _signalingService = signalingService;
|
||
|
||
final SignalingService _signalingService;
|
||
Timer? _pollTimer;
|
||
String? _lastClipboardText;
|
||
bool _isSyncing = false;
|
||
bool get isSyncing => _isSyncing;
|
||
|
||
final StreamController<String> _onRemoteClipboardController =
|
||
StreamController<String>.broadcast();
|
||
Stream<String> get onRemoteClipboard => _onRemoteClipboardController.stream;
|
||
|
||
void startSync({Duration interval = const Duration(seconds: 2)}) {
|
||
if (_isSyncing) return;
|
||
_isSyncing = true;
|
||
Log.i('ClipboardSync: Starting sync (interval: ${interval.inSeconds}s)');
|
||
|
||
_signalingService.onMessage.listen(_handleSignalingMessage);
|
||
|
||
_pollTimer = Timer.periodic(interval, (_) async {
|
||
await _checkLocalClipboard();
|
||
});
|
||
}
|
||
|
||
void stopSync() {
|
||
_isSyncing = false;
|
||
_pollTimer?.cancel();
|
||
_pollTimer = null;
|
||
Log.i('ClipboardSync: Stopped');
|
||
}
|
||
|
||
Future<void> _checkLocalClipboard() async {
|
||
try {
|
||
final text = await ClipboardBridge.getData();
|
||
if (text == null || text.isEmpty) return;
|
||
|
||
if (text == _lastClipboardText) return;
|
||
if (text.length > 10000) return;
|
||
|
||
_lastClipboardText = text;
|
||
|
||
_signalingService.sendCustomMessage(
|
||
SignalingMessage(
|
||
type: SignalingMessageType.clipboardSync,
|
||
from: _signalingService.deviceId ?? '',
|
||
payload: {'text': text},
|
||
),
|
||
);
|
||
|
||
Log.i('ClipboardSync: Sent clipboard (${text.length} chars)');
|
||
} catch (e) {
|
||
Log.w('ClipboardSync: Failed to read clipboard: $e');
|
||
}
|
||
}
|
||
|
||
void _handleSignalingMessage(SignalingMessage message) {
|
||
if (message.type != SignalingMessageType.clipboardSync) return;
|
||
|
||
final text = message.payload?['text'] as String?;
|
||
if (text == null) return;
|
||
|
||
_lastClipboardText = text;
|
||
|
||
Clipboard.setData(ClipboardData(text: text));
|
||
|
||
if (!_onRemoteClipboardController.isClosed) {
|
||
_onRemoteClipboardController.add(text);
|
||
}
|
||
|
||
Log.i(
|
||
'ClipboardSync: Received clipboard from ${message.from} (${text.length} chars)',
|
||
);
|
||
}
|
||
|
||
Future<void> sendText(String text) async {
|
||
_lastClipboardText = text;
|
||
|
||
_signalingService.sendCustomMessage(
|
||
SignalingMessage(
|
||
type: SignalingMessageType.clipboardSync,
|
||
from: _signalingService.deviceId ?? '',
|
||
payload: {'text': text},
|
||
),
|
||
);
|
||
|
||
Log.i('ClipboardSync: Manually sent text (${text.length} chars)');
|
||
}
|
||
|
||
Future<void> dispose() async {
|
||
stopSync();
|
||
await _onRemoteClipboardController.close();
|
||
}
|
||
}
|