feat(macos): 实现 macOS 系统菜单栏多语言支持与闪退修复

本次提交完成 macOS 端多项关键优化与修复:
1. 修复启动期竞态闪退问题,通过前移窗口属性初始化、串行化特效调用、跳过首次同步实现稳定启动
2. 实现系统菜单栏多语言本地化,支持中英日韩繁五种语言,软件内切换语言可同步更新菜单栏
3. 移除视图菜单中重复的全屏按钮,统一窗口标题栏逻辑
4. 新增 macOS App Store 打包配置与本地化资源
This commit is contained in:
Developer
2026-06-24 08:35:08 +08:00
parent 6119918185
commit 93d580fa1e
19 changed files with 958 additions and 37 deletions

View File

@@ -1,3 +1,4 @@
/// ============================================================
/// 闲言APP — 桌面端系统托盘服务抽象
/// 创建时间: 2026-06-18

View File

@@ -1,11 +1,15 @@
/// ============================================================
/// 闲言APP — macOS 窗口特效服务实现
/// 创建时间: 2026-06-18
/// 更新时间: 2026-06-18
/// 更新时间: 2026-06-24
/// 作用: 基于 macos_window_utils 实现 macOS 窗口特效(标题栏融合/侧边栏毛玻璃)
/// 上次更新: 初始创建,实现 DesktopWindowEffectService 接口
/// 上次更新: 修复 Release 模式闪退 — 添加互斥锁串行化 applyEffect 调用,
/// 跳过已在 MainFlutterWindow.awakeFromNib 中预设的不变属性调用,
/// 避免 [NSWindow setBackgroundColor:] 与 Impeller SetupRenderPass 竞态
/// ============================================================
import 'dart:async';
import 'package:macos_window_utils/macos_window_utils.dart';
import '../desktop_window_effect_service.dart';
@@ -18,6 +22,10 @@ import 'package:xianyan/core/utils/logger.dart';
/// - 标题栏融合titlebarAppearsTransparent + fullSizeContentView
/// - 侧边栏毛玻璃NSVisualEffectViewMaterial.sidebar
/// - 主题跟随overrideMacOSBrightness
///
/// 不变属性isOpaque/backgroundColor/titlebarAppearsTransparent/fullSizeContentView/
/// titleVisibility已在 [MainFlutterWindow.awakeFromNib] 中预设,
/// 此处不再通过 method channel 重复设置,避免与 Impeller 渲染线程竞态。
class MacosWindowEffectService implements DesktopWindowEffectService {
MacosWindowEffectService._();
@@ -29,6 +37,9 @@ class MacosWindowEffectService implements DesktopWindowEffectService {
bool _initialized = false;
int? _sidebarVisualEffectSubviewId;
/// 互斥锁:串行化 applyEffect 调用,防止并发导致原生视图层级竞争
Completer<void>? _applyLock;
@override
Future<void> initialize() async {
if (!pu.isMacOS) return;
@@ -50,26 +61,41 @@ class MacosWindowEffectService implements DesktopWindowEffectService {
}) async {
if (!pu.isMacOS || !_initialized) return;
try {
// 1. 标题栏融合:透明标题栏 + 全尺寸内容视图
await WindowManipulator.makeTitlebarTransparent();
await WindowManipulator.enableFullSizeContentView();
await WindowManipulator.hideTitle();
// 串行化:若上一次 applyEffect 尚未完成,等待其结束后再执行
while (_applyLock != null) {
try {
await _applyLock!.future;
} catch (_) {
break;
}
}
_applyLock = Completer<void>();
// 2. 主题跟随:覆盖 macOS 亮度设置
try {
// 注意:以下属性已在 MainFlutterWindow.awakeFromNib 中预设,此处不再重复调用:
// - makeTitlebarTransparent() → titlebarAppearsTransparent = true
// - enableFullSizeContentView() → styleMask.insert(.fullSizeContentView)
// - hideTitle() → titleVisibility = .hidden
// - setWindowBackgroundColorToClear() → isOpaque=false + backgroundColor=clear
//
// 这些调用会触发 [NSWindow setBackgroundColor:] → NSThemeFrame._updateBackdropView
// → removeFromSuperview与 Impeller raster 线程的 SetupRenderPass 产生竞态,
// 导致 EXC_BAD_ACCESS 空指针崩溃Release 模式时序更紧更易触发)。
// 1. 主题跟随:覆盖 macOS 亮度设置(仅修改 appearance不触发 _updateBackdropView
await WindowManipulator.overrideMacOSBrightness(dark: isDark);
// 3. 侧边栏毛玻璃
// 2. 侧边栏毛玻璃NSVisualEffectView 操作,不修改 NSWindow 背景)
if (sidebarBlur) {
await _applySidebarBlur();
}
// 4. 窗口背景色设为透明(让 NSVisualEffectView 透出)
await WindowManipulator.setWindowBackgroundColorToClear();
Log.i('MacosWindowEffectService 特效已应用 (isDark=$isDark, sidebarBlur=$sidebarBlur)');
} catch (e) {
Log.e('MacosWindowEffectService.applyEffect 失败: $e');
} finally {
_applyLock?.complete();
_applyLock = null;
}
}

View File

@@ -2,8 +2,8 @@
/// 闲言APP — macOS平台统一服务
/// 创建时间: 2026-06-02
/// 更新时间: 2026-06-24
/// 作用: 集中管理所有macOS原生MethodChannel交互主题同步/窗口管理/Touch Bar/共享/Dock徽章/菜单栏金句/Spotlight
/// 上次更新: 新增原生事件监听onFullScreenExited支持全屏退出后重新应用窗口特效
/// 作用: 集中管理所有macOS原生MethodChannel交互主题同步/窗口管理/Touch Bar/共享/Dock徽章/菜单栏金句/Spotlight/菜单语言
/// 上次更新: 新增 setMenuLanguage 方法,支持软件内切换语言后同步更新 macOS 系统菜单栏
/// ============================================================
import 'package:flutter/services.dart';
@@ -238,6 +238,31 @@ class MacosPlatformService {
}
}
// ============================================================
// 菜单栏语言切换
// ============================================================
/// 通知原生侧切换 macOS 系统菜单栏语言
///
/// [languageId] 语言 ID对应 macOS 本地化区域:
/// - 'system' → 跟随系统语言
/// - 'zh-CN' / 'zh-Hans' → 简体中文
/// - 'zh-TW' / 'zh-Hant' → 繁体中文
/// - 'en' → 英语
/// - 'ja' → 日语
/// - 'ko' → 韩语
///
/// 软件内切换语言后调用此方法,原生侧会重新加载对应语言的 MainMenu.strings
/// 并更新 NSApp.mainMenu 的所有菜单项标题。
static Future<void> setMenuLanguage(String languageId) async {
if (!pu.isMacOS) return;
try {
await _channel.invokeMethod('setMenuLanguage', {'languageId': languageId});
} catch (e) {
Log.w('MacosPlatformService.setMenuLanguage失败: $e');
}
}
// ============================================================
// 内部工具
// ============================================================