feat(ui): support running as menubar app (#1216)

This commit is contained in:
awe123343 authored and GitHub committed 2025-12-31 09:57:16 +08:00
1 parent b0e133b554
commit cb6331c20d
28 files changed
+214 -40

No files matched your search

@@ -115,6 +115,9 @@ class AppController extends GetxController with WindowListener, TrayListener {
@override
void onWindowFocus() {
refresh();
if (Util.isMacos() && Database.instance.getRunAsMenubarApp()) {
windowManager.setSkipTaskbar(true);
}
}
@override
@@ -9,9 +9,11 @@ import 'package:gopeed/app/views/copy_button.dart';
import 'package:intl/intl.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:window_manager/window_manager.dart';
import '../../../../api/api.dart' as api;
import '../../../../api/model/downloader_config.dart';
import '../../../../database/database.dart';
import '../../../../i18n/message.dart';
import '../../../../util/input_formatter.dart';
import '../../../../util/locale_manager.dart';
@@ -369,6 +371,46 @@ class SettingView extends GetView<SettingController> {
);
});
// Menubar mode only for macOS
final buildMenubarMode = !Util.isMacos()
? () => null
: _buildConfigItem('runAsMenubarApp', () {
return Database.instance.getRunAsMenubarApp() ? 'on'.tr : 'off'.tr;
}, (Key key) {
return Container(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Switch(
value: Database.instance.getRunAsMenubarApp(),
onChanged: (bool value) async {
// Save to database (single source of truth)
Database.instance.saveRunAsMenubarApp(value);
// Apply dock icon visibility
await windowManager.setSkipTaskbar(value);
// Small delay to let macOS process the activation policy change
await Future.delayed(const Duration(milliseconds: 100));
// Ensure window stays visible after toggle
await windowManager.show();
await windowManager.focus();
// Force UI refresh
controller.clearTap();
await debounceSave();
},
),
Text(
'runAsMenubarAppDesc'.tr,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).hintColor,
),
),
],
),
);
});
// http config items start
final httpConfig = downloaderCfg.value.protocolConfig.http;
final buildHttpUa =
@@ -1353,6 +1395,7 @@ class SettingView extends GetView<SettingController> {
buildDefaultDirectDownload(),
buildBrowserExtension(),
buildAutoStartup(),
buildMenubarMode(),
]),
)),
Text('archives'.tr),
+9
View File
@@ -10,6 +10,7 @@ const String _windowState = 'windowState';
const String _bookmark = 'bookmark';
const String _createHistory = 'createHistory';
const String _webToken = 'webToken';
const String _runAsMenubarApp = 'runAsMenubarApp';
class Database {
static final Database _instance = Database._internal();
@@ -109,4 +110,12 @@ class Database {
void clearCreateHistory() {
clear(_createHistory);
}
void saveRunAsMenubarApp(bool value) {
box.put(_runAsMenubarApp, value);
}
bool getRunAsMenubarApp() {
return box.get(_runAsMenubarApp, defaultValue: false) as bool;
}
}
+2
View File
@@ -106,6 +106,8 @@ const deDE = {
'Vielen Dank an alle Mitwirkenden, die beim Aufbau und der Entwicklung der Gopeed-Community geholfen haben!',
'browserExtension': 'Browsererweiterung',
'launchAtStartup': 'Beim Start starten',
'runAsMenubarApp': 'Als Menüleisten-App ausführen',
'runAsMenubarAppDesc': 'Dock-Symbol ausblenden und nur in der Menüleiste ausführen',
'seedConfig': 'Seed-Konfiguration',
'seedKeep': 'Weiter-seeden bis manuell gestoppt',
'seedRatio': 'Seed Verhältniss',
+2
View File
@@ -114,6 +114,8 @@ const enUS = {
'Thanks to all the contributors who have helped build and develop the Gopeed community!',
'browserExtension': 'Browser Extension',
'launchAtStartup': 'Launch at Startup',
'runAsMenubarApp': 'Run as menubar app',
'runAsMenubarAppDesc': 'Hide dock icon and run in menubar only',
'seedConfig': 'Seed Config',
'seedKeep': 'Keep seeding until manually stopped',
'seedRatio': 'Seed ratio',
+2
View File
@@ -99,6 +99,8 @@ const esES = {
'¡Gracias a todos los contribuyentes que han ayudado a construir y desarrollar la comunidad Gopeed!',
'browserExtension': 'Extensión del Navegador',
'launchAtStartup': 'Iniciar al Arranque',
'runAsMenubarApp': 'Ejecutar como aplicación de barra de menú',
'runAsMenubarAppDesc': 'Ocultar icono del Dock y ejecutar solo en la barra de menú',
'seedConfig': 'Configuración de Semilla',
'seedKeep': 'Mantener sembrado',
'seedRatio': 'Ratio de semilla',
+3
View File
@@ -86,5 +86,8 @@ const faIR = {
'categoryPath': 'مسیر دسته‌بندی',
'builtInCategory': 'دسته‌بندی داخلی قابل حذف نیست',
'selectCategory': 'انتخاب دسته‌بندی',
'launchAtStartup': 'راه‌اندازی در استارت‌آپ',
'runAsMenubarApp': 'اجرا به عنوان برنامه نوار منو',
'runAsMenubarAppDesc': 'پنهان کردن آیکون Dock و اجرا فقط در نوار منو',
}
};
+2
View File
@@ -99,6 +99,8 @@ const frFR = {
'Merci à tous les contributeurs qui ont aidé à construire et développer la communauté Gopeed !',
'browserExtension': 'Extension du navigateur',
'launchAtStartup': 'Lancer au démarrage',
'runAsMenubarApp': 'Exécuter en tant qu\'application de barre de menus',
'runAsMenubarAppDesc': 'Masquer l\'icône du Dock et exécuter uniquement dans la barre de menus',
'skipVerifyCert': 'Ignorer la vérification du certificat',
'archives': 'Archives',
'autoExtract': 'Extraire automatiquement les archives',
+2
View File
@@ -106,6 +106,8 @@ const huHU = {
'Köszönet minden hozzájárulónak, aki segített a Gopeed közösség építésében és fejlesztésében!',
'browserExtension': 'Böngésző bővítmény',
'launchAtStartup': 'Indítás indításkor',
'runAsMenubarApp': 'Futtatás menüsáv alkalmazásként',
'runAsMenubarAppDesc': 'Dock ikon elrejtése és csak a menüsávban való futtatás',
'seedConfig': 'Seed beállítás',
'seedKeep': 'Tartsa a seedet amíg manuálisan le nem állítják',
'seedRatio': 'Seed arány',
+2
View File
@@ -101,6 +101,8 @@ const idID = {
'Terima kasih kepada semua kontributor yang telah membantu membangun dan mengembangkan komunitas Gopeed!',
'browserExtension': 'Ekstensi Perambanan',
'launchAtStartup': 'Luncurkan saat Startup',
'runAsMenubarApp': 'Jalankan sebagai aplikasi bilah menu',
'runAsMenubarAppDesc': 'Sembunyikan ikon Dock dan jalankan hanya di bilah menu',
'seedConfig': 'Konfigurasi Seed',
'seedKeep': 'Terus seeding hingga dihentikan secara manual',
'seedRatio': 'Rasio Seed',
+2
View File
@@ -97,6 +97,8 @@ const itIT = {
'Grazie a tutti i contributori che hanno contribuito a costruire e sviluppare la comunità Gopeed!',
'browserExtension': 'Estensione del browser',
'launchAtStartup': "Lancia all'avvio",
'runAsMenubarApp': 'Esegui come app della barra dei menu',
'runAsMenubarAppDesc': 'Nascondi icona Dock ed esegui solo nella barra dei menu',
'skipVerifyCert': 'Salta la verifica del certificato',
'archives': 'Archivi',
'autoExtract': 'Estrai automaticamente gli archivi',
+3
View File
@@ -87,5 +87,8 @@ const jaJP = {
'categoryPath': 'カテゴリパス',
'builtInCategory': '内蔵カテゴリは削除できません',
'selectCategory': 'カテゴリを選択',
'launchAtStartup': '起動時に起動',
'runAsMenubarApp': 'メニューバーアプリとして実行',
'runAsMenubarAppDesc': 'Dockアイコンを非表示にして、メニューバーのみで実行',
}
};
+2
View File
@@ -95,6 +95,8 @@ const plPL = {
'Dziękuję wszystkim za pomoc przy powstawaniu aplikacji Gopeed!',
'browserExtension': 'Wtyczka dla przeglądarki',
'launchAtStartup': 'Uruchom na starcie',
'runAsMenubarApp': 'Uruchom jako aplikacja paska menu',
'runAsMenubarAppDesc': 'Ukryj ikonę Docka i uruchom tylko w pasku menu',
'skipVerifyCert': 'Pomiń weryfikację certyfikatu',
'archives': 'Archiwa',
'autoExtract': 'Automatycznie wyodrębnij archiwa',
+2
View File
@@ -103,6 +103,8 @@ const ptBR = {
'Obrigado a todos os colaboradores que ajudaram a construir e desenvolver a comunidade Gopeed!',
'browserExtension': 'Extensões para Navegadores',
'launchAtStartup': 'Launch at Startup',
'runAsMenubarApp': 'Executar como aplicativo da barra de menus',
'runAsMenubarAppDesc': 'Ocultar ícone do Dock e executar apenas na barra de menus',
'seedConfig': 'Seed Config',
'seedKeep': 'Keep seeding until manually stopped',
'seedRatio': 'Seed ratio',
+2
View File
@@ -103,6 +103,8 @@ const ruRU = {
'Благодарим всех участников за их вклад в строительство и развитие сообщества Gopeed!',
'browserExtension': 'Расширение браузера',
'launchAtStartup': 'Запускать при загрузке системы',
'runAsMenubarApp': 'Запускать как приложение в строке меню',
'runAsMenubarAppDesc': 'Скрыть значок в Dock и запускать только в строке меню',
'seedConfig': 'Настройка раздачи',
'seedKeep': 'Раздавать пока не остановлю вручную',
'seedRatio': 'Коэффициент раздачи',
+2
View File
@@ -97,6 +97,8 @@ const taTA = {
'Gopeed சமூகத்தை உருவாக்கவும் மற்றும் மேம்படுத்த உதவிய அனைத்து பங்களிப்பாளர்களுக்கும் நன்றி!',
'browserExtension': 'உலாவி நீட்டிப்பு',
'launchAtStartup': 'தொடக்கத்தின் போது இயங்கு',
'runAsMenubarApp': 'மெனுபார் பயன்பாடாக இயக்கு',
'runAsMenubarAppDesc': 'டாக் ஐகானை மறைத்து மெனுபாரில் மட்டும் இயக்கு',
'skipVerifyCert': 'திருத்தி சான்று சரிபார்க்கவும்',
'archives': 'காப்பகங்கள்',
'autoExtract': 'காப்பகங்களை தானாக பிரித்தெடு',
+2
View File
@@ -104,6 +104,8 @@ const trTR = {
'Gopeed topluluğunun inşasında ve geliştirilmesinde katkıda bulunan herkese teşekkürler!',
'browserExtension': 'Tarayıcı Eklentisi',
'launchAtStartup': 'Başlangıçta Çalıştır',
'runAsMenubarApp': 'Menü çubuğu uygulaması olarak çalıştır',
'runAsMenubarAppDesc': 'Dock simgesini gizle ve yalnızca menü çubuğunda çalıştır',
'seedConfig': 'Seed Ayarları',
'seedKeep': 'Manuel olarak durdurulana kadar seed etmeye devam et',
'seedRatio': 'Seed oranı',
+2
View File
@@ -103,6 +103,8 @@ const ukUA = {
'Дякуємо всім учасникам, які допомогли створити та розвивати спільноту Gopeed!',
'browserExtension': 'Розширення для браузера',
'launchAtStartup': 'Запускати під час завантаження системи',
'runAsMenubarApp': 'Запускати як додаток у рядку меню',
'runAsMenubarAppDesc': 'Приховати значок у Dock і запускати лише в рядку меню',
'seedConfig': 'Налаштування роздачі',
'seedKeep': 'Роздавати доки не зупиню вручну',
'seedRatio': 'Коефіцієнт роздачі',
+3
View File
@@ -114,5 +114,8 @@ const viVN = {
'categoryPath': 'Đường dẫn danh mục',
'builtInCategory': 'Không thể xóa danh mục tích hợp',
'selectCategory': 'Chọn danh mục',
'launchAtStartup': 'Khởi động cùng hệ thống',
'runAsMenubarApp': 'Chạy như ứng dụng thanh menu',
'runAsMenubarAppDesc': 'Ẩn biểu tượng Dock và chỉ chạy trong thanh menu',
},
};
+2
View File
@@ -112,6 +112,8 @@ const zhCN = {
'thanksDesc': '感谢所有为 Gopeed 社区建设添砖加瓦的贡献者们!',
'browserExtension': '浏览器扩展',
'launchAtStartup': '开机自动运行',
'runAsMenubarApp': '以菜单栏应用运行',
'runAsMenubarAppDesc': '隐藏程序坞图标,仅在菜单栏运行',
'seedConfig': '做种设置',
'seedKeep': '持续做种直到手动停止',
'seedRatio': '做种分享率',
+2
View File
@@ -100,6 +100,8 @@ const zhTW = {
'thanksDesc': '感謝所有幫助建立和發展 Gopeed 社群的貢獻者!',
'browserExtension': '瀏覽器擴充功能',
'launchAtStartup': '開機自動執行',
'runAsMenubarApp': '以選單列應用程式執行',
'runAsMenubarAppDesc': '隱藏 Dock 圖示,僅在選單列執行',
'seedConfig': '做種設定',
'seedKeep': '持續做種直到手動停止',
'seedRatio': '做種分享率',
+23 -1
View File
@@ -1,8 +1,10 @@
import 'package:args/args.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:window_manager/window_manager.dart';
import 'api/api.dart' as api;
@@ -67,10 +69,14 @@ Future<void> init(Args args) async {
if (Util.isDesktop()) {
await windowManager.ensureInitialized();
final windowState = Database.instance.getWindowState();
// Check if menubar mode is enabled (only for macOS)
final runAsMenubarApp = Util.isMacos() && Database.instance.getRunAsMenubarApp();
final windowOptions = WindowOptions(
size: Size(windowState?.width ?? 800, windowState?.height ?? 600),
center: true,
skipTaskbar: false,
skipTaskbar: runAsMenubarApp,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
if (!args.hidden) {
@@ -84,6 +90,22 @@ Future<void> init(Args args) async {
// await windowManager.maximize();
// }
});
// Register Cmd+W hotkey on macOS to close window
if (Util.isMacos()) {
await hotKeyManager.unregisterAll();
HotKey hotKey = HotKey(
key: PhysicalKeyboardKey.keyW,
modifiers: [HotKeyModifier.meta],
scope: HotKeyScope.inapp,
);
await hotKeyManager.register(
hotKey,
keyDownHandler: (hotKey) {
windowManager.hide();
},
);
}
}
initLogger();