本次提交完成 macOS 端多项关键优化与修复: 1. 修复启动期竞态闪退问题,通过前移窗口属性初始化、串行化特效调用、跳过首次同步实现稳定启动 2. 实现系统菜单栏多语言本地化,支持中英日韩繁五种语言,软件内切换语言可同步更新菜单栏 3. 移除视图菜单中重复的全屏按钮,统一窗口标题栏逻辑 4. 新增 macOS App Store 打包配置与本地化资源
134 lines
4.7 KiB
Dart
134 lines
4.7 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 主题同步服务
|
||
/// 创建时间: 2026-06-22
|
||
/// 更新时间: 2026-06-22
|
||
/// 作用: 将主题状态同步到桌面端平台(macOS/Windows),管理窗口特效和托盘图标主题
|
||
/// 上次更新: 从 app.dart 中提取主题同步职责,实现单一职责分离
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
import '../../core/services/device/macos_platform_service.dart';
|
||
import '../../core/services/device/windows_platform_service.dart';
|
||
import '../../core/services/desktop/desktop_window_effect_service.dart';
|
||
import '../../core/utils/logger.dart';
|
||
import '../../core/utils/platform/platform_utils.dart' as pu;
|
||
import '../../features/desktop/desktop_tray_controller.dart';
|
||
|
||
/// 主题同步服务
|
||
///
|
||
/// 负责将应用主题状态同步到桌面端原生平台:
|
||
/// - macOS/Windows 原生主题同步
|
||
/// - 窗口特效(毛玻璃/Acrylic/Mica)主题同步
|
||
/// - 托盘图标主题同步
|
||
///
|
||
/// 使用去重机制避免每次 build 重复调用平台 API。
|
||
class ThemeSyncService {
|
||
/// 上次同步到桌面端的暗色状态(去重用)
|
||
bool? _lastDesktopDarkState;
|
||
|
||
/// 上次同步到桌面端的纯黑模式状态(去重用)
|
||
///
|
||
/// 单独跟踪 amoled 状态,因为 dark 和 amoled 模式的 [effectiveIsDark] 均为 true,
|
||
/// 仅靠 [_lastDesktopDarkState] 无法检测 dark↔amoled 切换。
|
||
bool? _lastDesktopAmoledState;
|
||
|
||
/// 是否已跳过首次窗口特效同步
|
||
///
|
||
/// 首次窗口特效由 [DesktopServicesManager._initWindowEffect] 统一处理
|
||
/// (含 500ms 延迟等待 Impeller 完成初始渲染)。
|
||
/// 此处跳过首次同步,避免 build() 与 PostFrame 回调并发触发 applyEffect,
|
||
/// 导致 NSWindow 属性修改与 Impeller raster 线程竞态崩溃。
|
||
bool _windowEffectFirstSyncSkipped = false;
|
||
|
||
/// 同步主题到桌面端原生平台
|
||
///
|
||
/// 仅在状态变化时同步,避免重复调用。
|
||
/// [isDark] 当前是否暗色模式
|
||
/// [isAmoled] 当前是否纯黑模式
|
||
/// [trayController] 托盘控制器,用于同步托盘图标主题
|
||
void syncThemeToDesktop({
|
||
required bool isDark,
|
||
required bool isAmoled,
|
||
DesktopTrayController? trayController,
|
||
}) {
|
||
// 同步原生平台主题(macOS/Windows)
|
||
if (_lastDesktopDarkState != isDark) {
|
||
_lastDesktopDarkState = isDark;
|
||
if (pu.isMacOS) {
|
||
MacosPlatformService.syncTheme(isDark);
|
||
}
|
||
if (pu.isWindows) {
|
||
WindowsPlatformService.syncTheme(isDark);
|
||
}
|
||
}
|
||
|
||
// 同步窗口特效和托盘图标(需要同时检测 dark 和 amoled 变化)
|
||
if (_lastDesktopDarkState != isDark ||
|
||
_lastDesktopAmoledState != isAmoled) {
|
||
_lastDesktopDarkState = isDark;
|
||
_lastDesktopAmoledState = isAmoled;
|
||
|
||
// 跳过首次窗口特效同步:由 DesktopServicesManager._initWindowEffect 统一处理
|
||
// (含 500ms 延迟等待 Impeller 完成初始渲染),避免启动期并发调用导致竞态崩溃
|
||
if (!_windowEffectFirstSyncSkipped) {
|
||
_windowEffectFirstSyncSkipped = true;
|
||
Log.i('跳过首次窗口特效同步,由 _initWindowEffect 统一处理');
|
||
} else {
|
||
// 同步窗口特效
|
||
DesktopWindowEffectService.instance
|
||
.applyEffect(isDark: isDark)
|
||
.catchError((Object e) {
|
||
Log.e('窗口特效主题同步失败', e);
|
||
});
|
||
}
|
||
|
||
// 同步托盘图标主题
|
||
if (trayController != null) {
|
||
// 使用 addPostFrameCallback 确保 Widget 树已构建
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
trayController.onThemeChanged(isDark, isAmoled: isAmoled);
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 系统亮度变化时同步主题
|
||
///
|
||
/// 仅在跟随系统模式下调用。
|
||
/// [isDark] 系统当前是否暗色模式
|
||
/// [trayController] 托盘控制器
|
||
void syncFromSystemBrightness({
|
||
required bool isDark,
|
||
DesktopTrayController? trayController,
|
||
}) {
|
||
// 跟随系统模式下 isAmoled 始终为 false
|
||
_lastDesktopDarkState = isDark;
|
||
_lastDesktopAmoledState = false;
|
||
|
||
if (pu.isMacOS) {
|
||
MacosPlatformService.syncTheme(isDark);
|
||
}
|
||
if (pu.isWindows) {
|
||
WindowsPlatformService.syncTheme(isDark);
|
||
}
|
||
|
||
// 同步窗口特效
|
||
DesktopWindowEffectService.instance
|
||
.applyEffect(isDark: isDark)
|
||
.catchError((Object e) {
|
||
Log.e('窗口特效主题同步失败(系统亮度变化)', e);
|
||
});
|
||
|
||
// 同步托盘图标主题
|
||
trayController?.onThemeChanged(isDark);
|
||
}
|
||
|
||
/// 重置状态(用于测试)
|
||
void reset() {
|
||
_lastDesktopDarkState = null;
|
||
_lastDesktopAmoledState = null;
|
||
_windowEffectFirstSyncSkipped = false;
|
||
}
|
||
}
|