diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index e8c8679a..e77ef5c9 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -1,4 +1,3 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,6 +8,7 @@ import 'package:spotube/collections/routes.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:window_manager/window_manager.dart'; class PlayPauseIntent extends Intent { final WidgetRef ref; @@ -134,7 +134,7 @@ class CloseAppAction extends Action { @override invoke(intent) { if (kIsDesktop) { - appWindow.close(); + windowManager.close(); } else { SystemNavigator.pop(); } diff --git a/lib/components/shared/page_window_title_bar.dart b/lib/components/shared/page_window_title_bar.dart index 41b02680..a0248ee1 100644 --- a/lib/components/shared/page_window_title_bar.dart +++ b/lib/components/shared/page_window_title_bar.dart @@ -1,8 +1,8 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:window_manager/window_manager.dart'; class PageWindowTitleBar extends StatefulHookWidget with PreferredSizeWidget { final Widget? leading; @@ -52,7 +52,7 @@ class PageWindowTitleBar extends StatefulHookWidget with PreferredSizeWidget { class _PageWindowTitleBarState extends State { @override Widget build(BuildContext context) { - final isMaximized = useState(kIsDesktop ? appWindow.isMaximized : false); + final isMaximized = useState(null); useEffect(() { if (platform == TargetPlatform.windows && @@ -81,24 +81,38 @@ class _PageWindowTitleBarState extends State { return const SizedBox.shrink(); } + maximizeOrRestore() async { + if (await windowManager.isMaximized()) { + await windowManager.unmaximize(); + isMaximized.value = false; + } else { + await windowManager.maximize(); + isMaximized.value = true; + } + } + return PlatformAppBar( actions: [ ...?widget.actions, if (!kIsMacOS && !kIsMobile) PlatformWindowButtons( - isMaximized: () => isMaximized.value, - onMaximize: () { - appWindow.maximize(); - isMaximized.value = true; + isMaximized: () async => + isMaximized.value ?? await windowManager.isMaximized(), + onMaximize: maximizeOrRestore, + onRestore: maximizeOrRestore, + onMinimize: () { + windowManager.minimize(); }, - onRestore: () { - appWindow.restore(); - isMaximized.value = false; + onClose: () { + windowManager.close(); }, + showCloseButton: true, + showMaximizeButton: true, + showMinimizeButton: true, ), ], onDrag: () { - if (kIsDesktop) appWindow.startDragging(); + if (kIsDesktop) windowManager.startDragging(); }, title: widget.center, toolbarOpacity: widget.toolbarOpacity, diff --git a/lib/main.dart b/lib/main.dart index 5760cc78..20e90e47 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:audio_service/audio_service.dart'; -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:fl_query/fl_query.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -26,30 +25,39 @@ import 'package:spotube/services/mobile_audio_service.dart'; import 'package:spotube/themes/dark_theme.dart'; import 'package:spotube/themes/light_theme.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:window_size/window_size.dart'; final bowl = QueryBowl(); void main() async { + WidgetsFlutterBinding.ensureInitialized(); await Hive.initFlutter(); Hive.registerAdapter(CacheTrackAdapter()); Hive.registerAdapter(CacheTrackEngagementAdapter()); Hive.registerAdapter(CacheTrackSkipSegmentAdapter()); - WidgetsFlutterBinding.ensureInitialized(); if (kIsDesktop) { - doWhenWindowReady(() async { + await windowManager.ensureInitialized(); + WindowOptions windowOptions = const WindowOptions( + center: true, + backgroundColor: Colors.transparent, + titleBarStyle: TitleBarStyle.hidden, + title: "Spotube", + ); + setWindowMinSize(const Size(kReleaseMode ? 1020 : 300, 700)); + await windowManager.waitUntilReadyToShow(windowOptions, () async { final localStorage = await SharedPreferences.getInstance(); final rawSize = localStorage.getString(LocalStorageKeys.windowSizeInfo); final savedSize = rawSize != null ? json.decode(rawSize) : null; + final wasMaximized = savedSize?["maximized"] ?? false; final double? height = savedSize?["height"]; final double? width = savedSize?["width"]; - appWindow.minSize = const Size(kReleaseMode ? 1020 : 300, 700); - appWindow.alignment = Alignment.center; - appWindow.title = "Spotube"; - if (height != null && width != null && height >= 700 && width >= 359) { - appWindow.size = Size(width, height); - } else { - appWindow.maximize(); + await windowManager.setResizable(true); + if (wasMaximized) { + await windowManager.maximize(); + } else if (height != null && width != null) { + await windowManager.setSize(Size(width, height)); } - appWindow.show(); + await windowManager.show(); }); } MobileAudioService? audioServiceHandler; @@ -169,22 +177,24 @@ class SpotubeState extends ConsumerState with WidgetsBindingObserver { } @override - void didChangeMetrics() { + void didChangeMetrics() async { super.didChangeMetrics(); - final windowSameDimension = kIsMobile - ? false - : prevSize?.width == appWindow.size.width && - prevSize?.height == appWindow.size.height; + if (kIsMobile) return; + final size = await windowManager.getSize(); + final windowSameDimension = + prevSize?.width == size.width && prevSize?.height == size.height; - if (localStorage == null || windowSameDimension || kIsMobile) return; + if (localStorage == null || windowSameDimension) return; + final isMaximized = await windowManager.isMaximized(); localStorage!.setString( LocalStorageKeys.windowSizeInfo, jsonEncode({ - 'width': appWindow.isMaximized ? 0.0 : appWindow.size.width, - 'height': appWindow.isMaximized ? 0.0 : appWindow.size.height, + 'maximized': isMaximized, + 'width': size.width, + 'height': size.height, }), ); - prevSize = appWindow.size; + prevSize = await windowManager.getSize(); } TargetPlatform appPlatform = TargetPlatform.android; @@ -220,6 +230,9 @@ class SpotubeState extends ConsumerState with WidgetsBindingObserver { routeInformationProvider: router.routeInformationProvider, debugShowCheckedModeBanner: false, title: 'Spotube', + builder: (context, child) { + return DragToResizeArea(child: child!); + }, androidTheme: lightTheme( accentMaterialColor: accentMaterialColor, backgroundMaterialColor: backgroundMaterialColor, @@ -236,15 +249,6 @@ class SpotubeState extends ConsumerState with WidgetsBindingObserver { macosTheme: macosTheme, macosDarkTheme: macosDarkTheme, themeMode: themeMode, - windowButtonConfig: kIsDesktop - ? PlatformWindowButtonConfig( - isMaximized: () async => Future.value(appWindow.isMaximized), - onClose: () async => appWindow.close(), - onRestore: () async => appWindow.restore(), - onMaximize: () async => appWindow.maximize(), - onMinimize: () async => appWindow.minimize(), - ) - : null, shortcuts: PlatformProperty.all({ ...WidgetsApp.defaultShortcuts.map((key, value) { return MapEntry( diff --git a/lib/services/linux_audio_service.dart b/lib/services/linux_audio_service.dart index ac68b2e2..8cb82306 100644 --- a/lib/services/linux_audio_service.dart +++ b/lib/services/linux_audio_service.dart @@ -1,12 +1,12 @@ import 'dart:io'; -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:dbus/dbus.dart'; import 'package:spotube/provider/dbus_provider.dart'; import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/provider/playback_provider.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; +import 'package:window_manager/window_manager.dart'; class _MprisMediaPlayer2 extends DBusObject { /// Creates a new object to expose on [path]. @@ -79,7 +79,7 @@ class _MprisMediaPlayer2 extends DBusObject { /// Implementation of org.mpris.MediaPlayer2.Quit() Future doQuit() async { - appWindow.close(); + await windowManager.close(); return DBusMethodSuccessResponse(); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index bf0cd78b..73dfb386 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,21 +7,29 @@ #include "generated_plugin_registrant.h" #include -#include #include +#include #include +#include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); - g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin"); - bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar); g_autoptr(FlPluginRegistrar) metadata_god_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MetadataGodPlugin"); metadata_god_plugin_register_with_registrar(metadata_god_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); + screen_retriever_plugin_register_with_registrar(screen_retriever_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); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); + g_autoptr(FlPluginRegistrar) window_size_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); + window_size_plugin_register_with_registrar(window_size_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 0de3bf80..60021f16 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,9 +4,11 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux - bitsdojo_window_linux metadata_god + screen_retriever url_launcher_linux + window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/linux/my_application.cc b/linux/my_application.cc index e1aa9337..759285af 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -1,4 +1,3 @@ -#include #include "my_application.h" #include @@ -48,10 +47,8 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "spotube"); } - auto bdw = bitsdojo_window_from(window); - bdw->setCustomFrame(true); - // gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_realize(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7beb29a8..3e05dc1a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,27 +8,31 @@ import Foundation import audio_service import audio_session import audioplayers_darwin -import bitsdojo_window_macos import connectivity_plus_macos import macos_ui import metadata_god import package_info_plus_macos import path_provider_macos +import screen_retriever import shared_preferences_macos import sqflite import url_launcher_macos +import window_manager +import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) - BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MetadataGodPlugin.register(with: registry.registrar(forPlugin: "MetadataGodPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) + WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) } diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index a5e040aa..319eb28f 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -1,20 +1,21 @@ import Cocoa import FlutterMacOS -import bitsdojo_window_macos +import window_manager -class MainFlutterWindow: BitsdojoWindow { - override func bitsdojo_window_configure() -> UInt { - return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() } - override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } + override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { + super.order(place, relativeTo: otherWin) + hiddenWindowAtLaunch() + } } diff --git a/pubspec.lock b/pubspec.lock index 259d6b19..d870ec78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -190,41 +190,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.3" - bitsdojo_window: - dependency: "direct main" - description: - name: bitsdojo_window - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" - bitsdojo_window_linux: - dependency: transitive - description: - name: bitsdojo_window_linux - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - bitsdojo_window_macos: - dependency: transitive - description: - name: bitsdojo_window_macos - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - bitsdojo_window_platform_interface: - dependency: transitive - description: - name: bitsdojo_window_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2" - bitsdojo_window_windows: - dependency: transitive - description: - name: bitsdojo_window_windows - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" boolean_selector: dependency: transitive description: @@ -1157,6 +1122,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.3" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" scroll_pos: dependency: transitive description: @@ -1484,6 +1456,22 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + window_manager: + dependency: "direct main" + description: + name: window_manager + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + window_size: + dependency: "direct main" + description: + path: "plugins/window_size" + ref: a738913c8ce2c9f47515382d40827e794a334274 + resolved-ref: a738913c8ce2c9f47515382d40827e794a334274 + url: "https://github.com/google/flutter-desktop-embedding.git" + source: git + version: "0.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 68337146..8893a03d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,6 @@ dependencies: spotify: ^0.8.0 url_launcher: ^6.1.7 youtube_explode_dart: ^1.12.1 - bitsdojo_window: ^0.1.5 path: ^1.8.0 path_provider: ^2.0.8 collection: ^1.15.0 @@ -65,6 +64,12 @@ dependencies: adwaita: ^0.5.2 flutter_svg: ^1.1.6 fuzzywuzzy: ^0.2.0 + window_manager: 0.2.8 + window_size: + git: + url: https://github.com/google/flutter-desktop-embedding.git + ref: a738913c8ce2c9f47515382d40827e794a334274 + path: plugins/window_size dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c40e4841..a39133cb 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,23 +7,29 @@ #include "generated_plugin_registrant.h" #include -#include #include #include #include +#include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); - BitsdojoWindowPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); MetadataGodPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MetadataGodPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); + WindowSizePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowSizePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5dca4667..86f316df 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,11 +4,13 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows - bitsdojo_window_windows connectivity_plus_windows metadata_god permission_handler_windows + screen_retriever url_launcher_windows + window_manager + window_size ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index 1ad6625c..b938ff49 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -1,6 +1,3 @@ -#include -auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP); - #include #include #include diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index c10f08dc..d5c04f23 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -117,7 +117,8 @@ bool Win32Window::CreateAndShow(const std::wstring& title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), + WS_OVERLAPPEDWINDOW, // do not add WS_VISIBLE since the window will be shown later Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this);