Skip to content

feat: 添加最小化到状态栏功能#244

Open
WhiteElephantIsNotARobot wants to merge 2 commits intoFerry-200:mainfrom
WhiteElephantIsNotARobot:main
Open

feat: 添加最小化到状态栏功能#244
WhiteElephantIsNotARobot wants to merge 2 commits intoFerry-200:mainfrom
WhiteElephantIsNotARobot:main

Conversation

@WhiteElephantIsNotARobot
Copy link
Copy Markdown

@WhiteElephantIsNotARobot WhiteElephantIsNotARobot commented Jan 28, 2026

功能描述

实现最小化到系统托盘的功能(Fix #217),防止用户误关软件。该功能默认开启,可在设置中关闭。

实现内容

  1. 新增依赖

    • 添加 system_tray: ^2.0.3 用于创建系统托盘图标
  2. 新增文件

    • lib/play_service/system_tray_service.dart - 系统托盘服务,管理托盘图标和菜单
    • lib/window_handler.dart - 窗口事件处理器,处理窗口关闭事件
  3. 修改文件

    • lib/main.dart - 初始化系统托盘和窗口处理器
    • lib/app_settings.dart - 添加 minimizeToTray 设置项
    • lib/component/title_bar.dart - 关闭按钮改为触发窗口关闭事件
    • lib/page/settings_page/other_settings.dart - 添加设置开关组件
    • lib/page/settings_page/page.dart - 在设置页面添加开关选项
    • pubspec.yaml - 添加 system_tray 依赖

功能特性

  • 最小化到托盘:点击关闭按钮或按 Alt+F4 时,窗口隐藏到系统托盘而非退出
  • 托盘菜单:支持显示窗口、播放/暂停、下一首、上一首、退出
  • 双击恢复:双击托盘图标可快速显示窗口
  • 可配置:可在设置页面开启/关闭此功能(默认开启)

测试建议

  1. 启用"最小化到状态栏"设置
  2. 点击关闭按钮,验证窗口隐藏到托盘
  3. 双击托盘图标,验证窗口恢复
  4. 右键托盘菜单,测试各功能项
  5. 关闭此设置,验证关闭按钮正常退出应用

🤖 Generated with Claude Code

实现最小化到系统托盘的功能,防止误关软件:

- 添加 system_tray 依赖 (v2.0.3)
- 创建 SystemTrayService 管理托盘图标和菜单
- 创建 WindowHandler 处理窗口关闭事件
- 添加 minimizeToTray 设置选项,默认开启
- 在设置页面添加开关控制此行为
- 托盘菜单支持:显示窗口、播放/暂停、下一首、上一首、退出

当启用此功能时:
- 点击关闭按钮或 Alt+F4 会最小化到托盘而不是退出
- 可通过托盘图标右键菜单恢复窗口或退出应用
- 双击托盘图标可快速显示窗口

Co-Authored-By: Claude (mimo-v2-flash) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @WhiteElephantIsNotARobot, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在通过引入“最小化到系统托盘”功能来增强用户体验,该功能允许用户在关闭窗口时将应用程序隐藏到系统托盘,而不是完全退出。这有助于防止意外关闭,并提供了一种便捷的方式来管理应用程序的运行状态,同时通过托盘菜单提供了快速控制播放和显示窗口的选项。

Highlights

  • 新增最小化到托盘功能: 引入了将应用窗口最小化到系统托盘而非直接退出的功能,以防止用户误操作关闭。
  • 可配置的托盘行为: 用户可以在设置中开启或关闭此功能,默认状态为开启。
  • 系统托盘交互: 提供了系统托盘图标,支持双击恢复窗口,右键菜单包含显示窗口、播放/暂停、下一首、上一首和退出应用等操作。
  • 统一的窗口关闭处理: 引入 WindowHandler 统一处理窗口关闭事件,根据设置决定是最小化到托盘还是完全退出。
  • 新增 system_tray 依赖: 集成了 system_tray 插件以实现系统托盘功能。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

你好,感谢你的贡献!这个 PR 实现了最小化到系统托盘的核心功能,这是一个很棒的增强。整体结构清晰,通过 SystemTrayServiceWindowHandler 将相关逻辑分离,是很好的实践。

我在代码中发现了一些可以改进的地方,主要集中在新引入的 SystemTrayService 中:

  • 退出逻辑: _quitApp 方法存在代码重复和逻辑错误,建议将其统一管理。
  • 路径处理: _getIconPath 方法的路径拼接方式不够健壮,并且 Linux 平台的图标配置可能存在问题。
  • 事件处理: 托盘图标的点击事件处理与功能描述(双击恢复)不符,且未充分考虑跨平台差异。
  • 设置动态更新: 在设置页面切换“最小化到托盘”开关时,应用状态没有实时更新。

我已经就这些问题在代码中留下了具体的建议,希望能帮助你进一步完善这个功能。再次感谢你的工作!

Comment on lines +121 to +131
Future<void> _quitApp() async {
PlayService.instance.close();

await savePlaylists();
await saveLyricSources();
await AppSettings.instance.saveSettings();
await AppPreference.instance.save();

await HotkeysHelper.unregisterAll();
await windowManager.close();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这里的 _quitApp 方法与 window_handler.dart 中的 _quitApp 逻辑重复,并且存在一些问题:

  1. 缺少必要的 import,会导致编译失败。
  2. 调用 windowManager.close() 行为不正确,当“最小化到托盘”开启时,点击退出只会隐藏窗口。
  3. 代码重复增加了维护成本。

建议将退出逻辑统一管理。例如,可以在 WindowHandler 中暴露一个公共的 quitApp 方法,其中包含完整的退出流程(保存数据、释放资源、销毁窗口等),然后在托盘菜单中调用此方法。这样可以确保退出行为的一致性,并消除重复代码。

Comment on lines +71 to +76
onChanged: (value) async {
setState(() {
settings.minimizeToTray = value;
});
await settings.saveSettings();
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

设置项的 onChanged 回调中,只更新了设置值并保存,但没有动态地创建或销毁系统托盘图标。当用户在设置中打开此功能时,托盘图标不会立即出现;关闭时,托盘图标也不会消失。这需要重启应用才能生效,影响了用户体验。

建议在 onChanged 中直接调用 SystemTrayServiceinit()dispose() 方法来即时应用更改(需要为此文件添加 system_tray_service.dart 的导入)。

        onChanged: (value) async {
          setState(() {
            settings.minimizeToTray = value;
          });
          if (value) {
            await SystemTrayService.instance.init();
          } else {
            await SystemTrayService.instance.dispose();
          }
          await settings.saveSettings();
        },

Comment on lines +77 to +82
_systemTray.registerSystemTrayEventHandler((eventName) {
if (eventName == 'click') {
windowManager.show();
windowManager.focus();
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

PR 描述中提到“双击恢复”,但这里的实现是监听 'click' 事件。这个事件在 macOS 上是单击,在 Windows 上甚至可能不会触发。这导致了行为与描述不符,并且缺乏跨平台兼容性。
为了实现双击恢复窗口的功能,应该监听双击事件。不同平台的双击事件名不同(例如 Windows 是 'leftMouseDoubleClick',macOS 是 'double-click')。请根据 system_tray 包的文档,正确处理跨平台的双击事件。

Comment on lines +91 to +108
Future<String> _getIconPath() async {
if (Platform.isWindows) {
// Windows: 使用 .ico 文件
final exePath = Platform.resolvedExecutable;
final exeDir = exePath.substring(0, exePath.lastIndexOf('\\'));
return '$exeDir\\data\\flutter_assets\\app_icon.ico';
} else if (Platform.isMacOS) {
// macOS: 使用 .icns 文件
final exePath = Platform.resolvedExecutable;
final exeDir = exePath.substring(0, exePath.lastIndexOf('/'));
return '$exeDir/../Resources/AppIcon.icns';
} else {
// Linux: 使用 .png 文件
final exePath = Platform.resolvedExecutable;
final exeDir = exePath.substring(0, exePath.lastIndexOf('/'));
return '$exeDir/data/flutter_assets/app_icon.ico';
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这个方法中构造路径的方式不够健壮,并且存在平台兼容性问题:

  1. 使用字符串拼接和 substring 来处理路径在不同平台或特殊路径下可能出错。建议使用 path 包的 path.dirname()path.join() 来处理路径,这样更安全和跨平台(需要添加 import 'package:path/path.dart' as p;)。
  2. 对于 Linux 平台,注释中说使用 .png 文件,但代码中实际使用了 .ico 文件。这可能导致在 Linux 上图标无法正确显示。
  Future<String> _getIconPath() async {
    final exeDir = p.dirname(Platform.resolvedExecutable);
    if (Platform.isWindows) {
      // Windows: 使用 .ico 文件
      return p.join(exeDir, 'data', 'flutter_assets', 'app_icon.ico');
    } else if (Platform.isMacOS) {
      // macOS: 使用 .icns 文件
      return p.join(exeDir, '..', 'Resources', 'AppIcon.icns');
    } else {
      // Linux: 使用 .png 文件
      // 注意:注释指明使用 .png,但原代码使用 .ico。此处假设有 app_icon.png。
      return p.join(exeDir, 'data', 'flutter_assets', 'app_icon.png');
    }
  }

根据 review 意见修复以下问题:

1. 修复 SystemTrayService._quitApp() 中的逻辑错误
   - 将 windowManager.close() 改为 windowManager.destroy()
   - 确保完全退出应用时正确销毁窗口

2. 修复 _getIconPath() 方法的路径处理
   - 使用 path.join() 替代字符串拼接,提高跨平台兼容性
   - 使用 path.dirname() 替代 substring 和 lastIndexOf

3. 修复 Linux 平台的图标配置
   - Linux 使用 .png 文件而不是 .ico 文件
   - 添加 app_icon.png 资源文件

4. 修复托盘点击事件处理
   - 支持 'click' 和 'double-click' 事件
   - 添加注释说明不同平台的行为差异

5. 在设置页面切换开关时动态更新系统托盘状态
   - 启用时初始化系统托盘
   - 禁用时销毁系统托盘

6. 添加缺失的导入
   - 添加 playlist.dart 和 lyric_source.dart 导入
   - 添加 path 包导入

Co-Authored-By: Claude (mimo-v2-flash) <noreply@anthropic.com>
@WhiteElephantIsNotARobot
Copy link
Copy Markdown
Author

已修复 review 问题

感谢 review!已根据意见完成以下修复:

1. 修复 SystemTrayService._quitApp() 中的逻辑错误 ✅

  • windowManager.close() 改为 windowManager.destroy()
  • 确保完全退出应用时正确销毁窗口

2. 修复 _getIconPath() 方法的路径处理 ✅

  • 使用 path.join() 替代字符串拼接,提高跨平台兼容性
  • 使用 path.dirname() 替代 substringlastIndexOf

3. 修复 Linux 平台的图标配置 ✅

  • Linux 使用 .png 文件而不是 .ico 文件
  • 添加了 app_icon.png 资源文件

4. 修复托盘点击事件处理 ✅

  • 支持 'click''double-click' 事件
  • 添加注释说明不同平台的行为差异

5. 在设置页面切换开关时动态更新系统托盘状态 ✅

  • 启用时初始化系统托盘
  • 禁用时销毁系统托盘

6. 添加缺失的导入 ✅

  • 添加 playlist.dartlyric_source.dart 导入
  • 添加 path 包导入

所有修改已完成并推送到分支。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[改进] 最小化到状态栏

1 participant