diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index ad70117d..bfb7a2e3 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -2,7 +2,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter/material.dart' show showModalBottomSheet, ListTile, SafeArea, Column, Navigator; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -10,14 +9,16 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; +import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; import 'package:spotube/components/fallbacks/not_found.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; -import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; +import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/modules/player/player_queue_actions.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; @@ -62,7 +63,7 @@ class PlayerQueue extends HookConsumerWidget { final isSearching = useState(false); - final tracks = playlist.tracks; + final tracks = playlist.tracks; final filteredTracks = useMemoized( () { @@ -136,157 +137,136 @@ class PlayerQueue extends HookConsumerWidget { surfaceOpacity: 0, child: searchBar, ) - else - selectionMode.value - ? AppBar( - backgroundColor: Colors.transparent, - surfaceBlur: 0, - surfaceOpacity: 0, - leading: [ - IconButton.ghost( - icon: const Icon(SpotubeIcons.close), + else if (selectionMode.value) + AppBar( + backgroundColor: Colors.transparent, + surfaceBlur: 0, + surfaceOpacity: 0, + leading: [ + IconButton.ghost( + icon: const Icon(SpotubeIcons.close), + onPressed: () { + selectedTrackIds.value = {}; + selectionMode.value = false; + }, + ) + ], + title: SizedBox( + height: 30, + child: AutoSizeText( + '${selectedTrackIds.value.length} selected', + maxLines: 1, + ), + ), + trailing: [ + PlayerQueueActionButton( + builder: (context, close) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Gap(12), + ButtonTile( + style: const ButtonStyle.ghost(), + leading: + const Icon(SpotubeIcons.selectionCheck), + title: Text(context.l10n.select_all), onPressed: () { - selectedTrackIds.value = {}; - selectionMode.value = false; + selectedTrackIds.value = + filteredTracks.map((t) => t.id).toSet(); + Navigator.pop(context); }, - ) - ], - title: SizedBox( - height: 30, - child: AutoSizeText( - '${selectedTrackIds.value.length} selected', - maxLines: 1, ), - ), - trailing: [ - IconButton.ghost( - icon: const Icon(SpotubeIcons.moreHorizontal), + ButtonTile( + style: const ButtonStyle.ghost(), + leading: const Icon(SpotubeIcons.playlistAdd), + title: Text(context.l10n.add_to_playlist), onPressed: () async { - await showModalBottomSheet( + final selected = filteredTracks + .where((t) => + selectedTrackIds.value.contains(t.id)) + .toList(); + close(); + if (selected.isEmpty) return; + final res = await showDialog( context: context, - builder: (context) { - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon( - SpotubeIcons.selectionCheck), - title: Text( - context.l10n.select_all), - onTap: () { - selectedTrackIds.value = - filteredTracks - .map((t) => t.id) - .toSet(); - Navigator.pop(context); - }, - ), - ListTile( - leading: const Icon( - SpotubeIcons.playlistAdd), - title: Text( - context.l10n.add_to_playlist), - onTap: () async { - final selected = filteredTracks - .where((t) => selectedTrackIds - .value - .contains(t.id)) - .toList(); - Navigator.pop(context); - if (selected.isEmpty) return; - final res = await showDialog< - bool?>( - context: context, - builder: (context) => - PlaylistAddTrackDialog( - tracks: selected, - openFromPlaylist: null, - ), - ); - if (res == true) { - selectedTrackIds.value = {}; - selectionMode.value = false; - } - }, - ), - ListTile( - leading: - const Icon(SpotubeIcons.trash), - title: Text( - context.l10n.remove_from_queue), - onTap: () async { - final ids = selectedTrackIds - .value - .toList(); - Navigator.pop(context); - if (ids.isEmpty) return; - await Future.wait(ids - .map((id) => onRemove(id))); - selectedTrackIds.value = {}; - selectionMode.value = false; - }, - ), - ListTile( - leading: const Icon( - SpotubeIcons.close), - title: Text(context.l10n.cancel), - onTap: () { - Navigator.pop(context); - }, - ), - ], - ), - ); - }, + builder: (context) => + PlaylistAddTrackDialog( + tracks: selected, + openFromPlaylist: null, + ), ); + if (res == true) { + selectedTrackIds.value = {}; + selectionMode.value = false; + } }, ), - ], - ) - : AppBar( - trailingGap: 0, - backgroundColor: Colors.transparent, - surfaceBlur: 0, - surfaceOpacity: 0, - title: mediaQuery.mdAndUp || !isSearching.value - ? SizedBox( - height: 30, - child: AutoSizeText( - context.l10n.tracks_in_queue(tracks.length), - maxLines: 1, - ), - ) - : null, - trailing: [ - if (mediaQuery.mdAndUp) searchBar - else - IconButton.ghost( - icon: const Icon(SpotubeIcons.filter), - onPressed: () { - isSearching.value = !isSearching.value; - }, - ), - if (!isSearching.value) ...[ - const SizedBox(width: 10), - Tooltip( - tooltip: TooltipContainer( - child: Text(context.l10n.clear_all)) - .call, - child: IconButton.outline( - icon: const Icon(SpotubeIcons.playlistRemove), - onPressed: () { - onStop(); - closeDrawer(context); - }, - ), - ), - const Gap(5), - if (mediaQuery.smAndDown) - const BackButton(icon: SpotubeIcons.angleDown), - ], + ButtonTile( + style: const ButtonStyle.ghost(), + leading: const Icon(SpotubeIcons.trash), + title: Text(context.l10n.remove_from_queue), + onPressed: () async { + final ids = selectedTrackIds.value.toList(); + close(); + if (ids.isEmpty) return; + await Future.wait( + ids.map((id) => onRemove(id))); + if (context.mounted) { + selectedTrackIds.value = {}; + selectionMode.value = false; + } + }, + ), + const Gap(12), ], ), + ), + ], + ) + else + AppBar( + trailingGap: 0, + backgroundColor: Colors.transparent, + surfaceBlur: 0, + surfaceOpacity: 0, + title: mediaQuery.mdAndUp || !isSearching.value + ? SizedBox( + height: 30, + child: AutoSizeText( + context.l10n.tracks_in_queue(tracks.length), + maxLines: 1, + ), + ) + : null, + trailing: [ + if (mediaQuery.mdAndUp) + searchBar + else + IconButton.ghost( + icon: const Icon(SpotubeIcons.filter), + onPressed: () { + isSearching.value = !isSearching.value; + }, + ), + if (!isSearching.value) ...[ + const SizedBox(width: 10), + Tooltip( + tooltip: TooltipContainer( + child: Text(context.l10n.clear_all)) + .call, + child: IconButton.outline( + icon: const Icon(SpotubeIcons.playlistRemove), + onPressed: () { + onStop(); + closeDrawer(context); + }, + ), + ), + const Gap(5), + if (mediaQuery.smAndDown) + const BackButton(icon: SpotubeIcons.angleDown), + ], + ], + ), const Divider(), Expanded( child: InterScrollbar( @@ -331,9 +311,9 @@ class PlayerQueue extends HookConsumerWidget { selectionMode: selectionMode.value, selected: selectedTrackIds.value.contains(track.id), - onChanged: selectionMode.value - ? (_) => toggleSelection(track.id) - : null, + onChanged: selectionMode.value + ? (_) => toggleSelection(track.id) + : null, onTap: () async { if (selectionMode.value) { toggleSelection(track.id); diff --git a/lib/modules/player/player_queue_actions.dart b/lib/modules/player/player_queue_actions.dart new file mode 100644 index 00000000..3d1666c2 --- /dev/null +++ b/lib/modules/player/player_queue_actions.dart @@ -0,0 +1,44 @@ +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/extensions/constrains.dart'; + +class PlayerQueueActionButton extends StatelessWidget { + final Widget Function(BuildContext context, VoidCallback close) builder; + + const PlayerQueueActionButton({ + super.key, + required this.builder, + }); + + @override + Widget build(BuildContext context) { + return IconButton.ghost( + onPressed: () { + final mediaQuery = MediaQuery.sizeOf(context); + + if (mediaQuery.lgAndUp) { + showDropdown( + context: context, + builder: (context) { + return SizedBox( + width: 220 * context.theme.scaling, + child: Card( + padding: EdgeInsets.zero, + child: builder(context, () => closeOverlay(context)), + ), + ); + }, + ); + } else { + openSheet( + context: context, + builder: (context) => builder(context, () => closeSheet(context)), + position: OverlayPosition.bottom, + ); + } + }, + icon: const Icon(SpotubeIcons.moreHorizontal), + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 22c09458..5860e0d6 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -50,7 +50,6 @@ class MetadataPlugin { sharedPreferences, config.slug, ), - createYoutubeEngine: () => throw UnimplementedError(), onNavigatorPush: (route) { return rootNavigatorKey.currentContext?.router .pushWidget(Builder(builder: (context) { diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 8f5a71fe..63e83265 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -12,11 +12,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -41,6 +43,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); + irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); g_autoptr(FlPluginRegistrar) local_notifier_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin"); local_notifier_plugin_register_with_registrar(local_notifier_registrar); @@ -56,6 +61,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); + 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) system_theme_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin"); system_theme_plugin_register_with_registrar(system_theme_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e174b0ef..541826e6 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -9,11 +9,13 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux flutter_timezone gtk + irondash_engine_context local_notifier media_kit_libs_linux open_file_linux screen_retriever_linux sqlite3_flutter_libs + super_native_extensions system_theme tray_manager url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2931c1b4..d211f518 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -18,6 +18,7 @@ import flutter_inappwebview_macos import flutter_new_pipe_extractor import flutter_secure_storage_macos import flutter_timezone +import irondash_engine_context import local_notifier import media_kit_libs_macos_audio import open_file_mac @@ -27,6 +28,7 @@ import screen_retriever_macos import shared_preferences_foundation import sqflite_darwin import sqlite3_flutter_libs +import super_native_extensions import system_theme import tray_manager import url_launcher_macos @@ -46,6 +48,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterNewPipeExtractorPlugin.register(with: registry.registrar(forPlugin: "FlutterNewPipeExtractorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) + IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) @@ -55,6 +58,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index a67d7b2b..7e53e91c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2863,4 +2863,4 @@ packages: version: "1.0.0" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.1" \ No newline at end of file + flutter: ">=3.35.1" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 95f52491..ac2fd1e0 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -15,11 +15,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -44,6 +46,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); FlutterTimezonePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); + IrondashEngineContextPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); LocalNotifierPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalNotifierPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( @@ -54,6 +58,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + SuperNativeExtensionsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); SystemThemePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SystemThemePlugin")); TrayManagerPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index abbe763a..53cd3667 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -12,11 +12,13 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_new_pipe_extractor flutter_secure_storage_windows flutter_timezone + irondash_engine_context local_notifier media_kit_libs_windows_audio permission_handler_windows screen_retriever_windows sqlite3_flutter_libs + super_native_extensions system_theme tray_manager url_launcher_windows