diff --git a/README.md b/README.md index d5036cd9..e38a9cbb 100644 --- a/README.md +++ b/README.md @@ -62,4 +62,3 @@ If you want to build the release version, use the flutter build command. Learn m ```bash flutter build ``` - diff --git a/assets/icons/icon-outline.svg b/assets/icons/icon-outline.svg new file mode 100644 index 00000000..2fa8b527 --- /dev/null +++ b/assets/icons/icon-outline.svg @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/lib/models/post_category.dart b/lib/models/post_category.dart index 767f2883..400d2958 100644 --- a/lib/models/post_category.dart +++ b/lib/models/post_category.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:island/models/post.dart'; -import 'package:island/services/text.dart'; +import 'package:island/utils/text.dart'; part 'post_category.freezed.dart'; part 'post_category.g.dart'; diff --git a/lib/screens/account/me/settings_connections.dart b/lib/screens/account/me/settings_connections.dart index 383ac7e3..f5cc434a 100644 --- a/lib/screens/account/me/settings_connections.dart +++ b/lib/screens/account/me/settings_connections.dart @@ -10,7 +10,7 @@ import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/account/me/account_settings.dart'; import 'package:island/screens/auth/oidc.native.dart'; -import 'package:island/services/text.dart'; +import 'package:island/utils/text.dart'; import 'package:island/services/time.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/sheet.dart'; diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart index a2ca9705..081ae6cc 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -17,7 +17,7 @@ import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/services/color.dart'; import 'package:island/services/responsive.dart'; -import 'package:island/services/text.dart'; +import 'package:island/utils/text.dart'; import 'package:island/services/time.dart'; import 'package:island/services/timezone/native.dart'; import 'package:island/widgets/account/account_name.dart'; diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index e77e7054..40b230f3 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -11,7 +11,7 @@ import 'package:island/models/publisher.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/creators/publishers.dart'; import 'package:island/services/responsive.dart'; -import 'package:island/services/text.dart'; +import 'package:island/utils/text.dart'; import 'package:island/widgets/account/account_picker.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; diff --git a/lib/screens/tray_manager.dart b/lib/screens/tray_manager.dart new file mode 100644 index 00000000..a9191b1e --- /dev/null +++ b/lib/screens/tray_manager.dart @@ -0,0 +1,58 @@ +import 'dart:io'; + +import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:flutter/foundation.dart'; +import 'package:tray_manager/tray_manager.dart'; + +class TrayService { + TrayService._(); + + static final TrayService _instance = TrayService._(); + + static TrayService get instance => _instance; + + bool _checkPlatformAvalability() { + if (kIsWeb) return false; + if (Platform.isAndroid || Platform.isIOS) return false; + return true; + } + + Future initialize(TrayListener listener) async { + if (!_checkPlatformAvalability()) return; + + await trayManager.setIcon( + Platform.isWindows + ? 'assets/icons/icon.ico' + : 'assets/icons/icon-outline.svg', + ); + + final menu = Menu( + items: [ + MenuItem(key: 'show_window', label: 'Show Window'), + MenuItem.separator(), + MenuItem(key: 'exit_app', label: 'Exit App'), + ], + ); + await trayManager.setContextMenu(menu); + + trayManager.addListener(listener); + } + + Future dispose(TrayListener listener) async { + if (!_checkPlatformAvalability()) return; + + trayManager.removeListener(listener); + await trayManager.destroy(); + } + + void handleAction(MenuItem item) { + switch (item.key) { + case 'show_window': + appWindow.show(); + break; + case 'exit_app': + appWindow.close(); + break; + } + } +} diff --git a/lib/services/text.dart b/lib/utils/text.dart similarity index 100% rename from lib/services/text.dart rename to lib/utils/text.dart diff --git a/lib/widgets/app_wrapper.dart b/lib/widgets/app_wrapper.dart index 989a63b8..5a8b84e4 100644 --- a/lib/widgets/app_wrapper.dart +++ b/lib/widgets/app_wrapper.dart @@ -1,15 +1,18 @@ import 'dart:async'; +import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/websocket.dart'; +import 'package:island/screens/tray_manager.dart'; import 'package:island/services/notify.dart'; import 'package:island/services/sharing_intent.dart'; import 'package:island/services/update_service.dart'; import 'package:island/widgets/content/network_status_sheet.dart'; import 'package:island/widgets/tour/tour.dart'; +import 'package:tray_manager/tray_manager.dart'; -class AppWrapper extends HookConsumerWidget { +class AppWrapper extends HookConsumerWidget with TrayListener { final Widget child; const AppWrapper({super.key, required this.child}); @@ -20,10 +23,16 @@ class AppWrapper extends HookConsumerWidget { Future(() { if (context.mounted) ntySubs = setupNotificationListener(context, ref); }); + final sharingService = SharingIntentService(); sharingService.initialize(context); + UpdateService().checkForUpdates(context); + + TrayService.instance.initialize(this); + return () { + TrayService.instance.dispose(this); sharingService.dispose(); ntySubs?.cancel(); }; @@ -52,4 +61,27 @@ class AppWrapper extends HookConsumerWidget { return TourTriggerWidget(key: UniqueKey(), child: child); } + + void _trayIconPrimaryAction() { + appWindow.show(); + } + + void _trayIconSecondaryAction() { + trayManager.popUpContextMenu(); + } + + @override + void onTrayIconMouseUp() { + _trayIconPrimaryAction(); + } + + @override + void onTrayIconRightMouseDown() { + _trayIconSecondaryAction(); + } + + @override + void onTrayMenuItemClick(MenuItem menuItem) { + TrayService.instance.handleAction(menuItem); + } } diff --git a/lib/widgets/post/post_shuffle.dart b/lib/widgets/post/post_shuffle.dart index 0d3d957f..20923821 100644 --- a/lib/widgets/post/post_shuffle.dart +++ b/lib/widgets/post/post_shuffle.dart @@ -41,6 +41,7 @@ class PostShuffleScreen extends HookConsumerWidget { ? CardSwiper( controller: cardSwiperController, cardsCount: postListState.value!.items.length, + isLoop: false, cardBuilder: ( context, index, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index af4a44d3..e2cb05c4 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -74,6 +75,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); + g_autoptr(FlPluginRegistrar) tray_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); + tray_manager_plugin_register_with_registrar(tray_manager_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7e6ab7fe..400c4236 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -19,6 +19,7 @@ list(APPEND FLUTTER_PLUGIN_LIST record_linux sqlite3_flutter_libs super_native_extensions + tray_manager url_launcher_linux volume_controller ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 17e367f9..06e5776c 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -37,6 +37,7 @@ import sign_in_with_apple import sqflite_darwin import sqlite3_flutter_libs import super_native_extensions +import tray_manager import url_launcher_macos import volume_controller import wakelock_plus @@ -74,6 +75,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) + TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b2dd316d..c90f7b89 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -237,6 +237,8 @@ PODS: - sqlite3/session - super_native_extensions (0.0.1): - FlutterMacOS + - tray_manager (0.0.1): + - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS - volume_controller (0.0.1): @@ -280,6 +282,7 @@ DEPENDENCIES: - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) + - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`) - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) @@ -376,6 +379,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin super_native_extensions: :path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos + tray_manager: + :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos volume_controller: @@ -437,6 +442,7 @@ SPEC CHECKSUMS: sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 + tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497 diff --git a/pubspec.lock b/pubspec.lock index 4b0f604f..5a236c53 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1573,6 +1573,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + menu_base: + dependency: transitive + description: + name: menu_base + sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405" + url: "https://pub.dev" + source: hosted + version: "0.1.1" meta: dependency: transitive description: @@ -2165,6 +2173,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + shortid: + dependency: transitive + description: + name: shortid + sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb + url: "https://pub.dev" + source: hosted + version: "0.1.2" sign_in_with_apple: dependency: "direct main" description: @@ -2435,6 +2451,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.0" + tray_manager: + dependency: "direct main" + description: + name: tray_manager + sha256: "537e539f48cd82d8ee2240d4330158c7b44c7e043e8e18b5811f2f8f6b7df25a" + url: "https://pub.dev" + source: hosted + version: "0.5.1" tuple: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 46fb2d2b..667eb176 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -138,6 +138,7 @@ dependencies: screenshot: ^3.0.0 flutter_card_swiper: ^7.0.2 file_saver: ^0.3.1 + tray_manager: ^0.5.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 69437071..5f486e7e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); SuperNativeExtensionsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); + TrayManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("TrayManagerPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VolumeControllerPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f18218b0..f0c7452a 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -25,6 +25,7 @@ list(APPEND FLUTTER_PLUGIN_LIST share_plus sqlite3_flutter_libs super_native_extensions + tray_manager url_launcher_windows volume_controller )