From c7817909ce0920fac6d70706bc596e6fa3039a09 Mon Sep 17 00:00:00 2001
From: Amin <23167933+aminsaedi@users.noreply.github.com>
Date: Sun, 29 Oct 2023 05:56:48 -0400
Subject: [PATCH 01/53] Updated README.md file (#847)
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 71589794..d82af783 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ This handy table lists all methods you can use to install Spotube:
-
Then run: sudo apt install Spotube-linux-x86_64.deb
+ Then run: sudo apt install ./Spotube-linux-x86_64.deb
From d056dbf9eeef7033dbc012d0c05800063e820042 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 19:02:39 +0600
Subject: [PATCH 02/53] fix: android invalid download location Download not
starting or not explaining error #720
---
lib/pages/settings/settings.dart | 21 +++++++++++++--------
pubspec.lock | 8 ++++++++
pubspec.yaml | 1 +
3 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart
index e319997a..5632a89a 100644
--- a/lib/pages/settings/settings.dart
+++ b/lib/pages/settings/settings.dart
@@ -1,5 +1,6 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:collection/collection.dart';
+import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -47,15 +48,19 @@ class SettingsPage extends HookConsumerWidget {
}, []);
final pickDownloadLocation = useCallback(() async {
- String? dirStr = await getDirectoryPath(
- initialDirectory: preferences.downloadLocation,
- );
- if (dirStr == null) return;
- if (DesktopTools.platform.isAndroid && dirStr.startsWith("content://")) {
- dirStr =
- "/storage/emulated/0/${Uri.decodeFull(dirStr).split("primary:").last}";
+ if (DesktopTools.platform.isMobile) {
+ final dirStr = await FilePicker.platform.getDirectoryPath(
+ initialDirectory: preferences.downloadLocation,
+ );
+ if (dirStr == null) return;
+ preferences.setDownloadLocation(dirStr);
+ } else {
+ String? dirStr = await getDirectoryPath(
+ initialDirectory: preferences.downloadLocation,
+ );
+ if (dirStr == null) return;
+ preferences.setDownloadLocation(dirStr);
}
- preferences.setDownloadLocation(dirStr);
}, [preferences.downloadLocation]);
return SafeArea(
diff --git a/pubspec.lock b/pubspec.lock
index 3dbc3cbf..bd50225a 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -513,6 +513,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.4"
+ file_picker:
+ dependency: "direct main"
+ description:
+ name: file_picker
+ sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.0"
file_selector:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 04f2d8b8..75b14bc1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -114,6 +114,7 @@ dependencies:
youtube_explode_dart: ^2.0.1
simple_icons: ^7.10.0
audio_service_mpris: ^0.1.0
+ file_picker: ^6.0.0
dev_dependencies:
build_runner: ^2.3.2
From 286ef83e8ec516db70019398d9e3e724437a4172 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 19:16:46 +0600
Subject: [PATCH 03/53] fix: trim login field padding
---
lib/components/desktop_login/login_form.dart | 2 +-
lib/pages/lastfm_login/lastfm_login.dart | 2 +-
lib/provider/authentication_provider.dart | 4 +++-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/components/desktop_login/login_form.dart b/lib/components/desktop_login/login_form.dart
index b9783f87..f2b183f4 100644
--- a/lib/components/desktop_login/login_form.dart
+++ b/lib/components/desktop_login/login_form.dart
@@ -63,7 +63,7 @@ class TokenLoginForm extends HookConsumerWidget {
return;
}
final cookieHeader =
- "sp_dc=${directCodeController.text}; sp_key=${keyCodeController.text}";
+ "sp_dc=${directCodeController.text.trim()}; sp_key=${keyCodeController.text.trim()}";
authenticationNotifier.setCredentials(
await AuthenticationCredentials.fromCookie(
diff --git a/lib/pages/lastfm_login/lastfm_login.dart b/lib/pages/lastfm_login/lastfm_login.dart
index f77d0abb..4280328f 100644
--- a/lib/pages/lastfm_login/lastfm_login.dart
+++ b/lib/pages/lastfm_login/lastfm_login.dart
@@ -108,7 +108,7 @@ class LastFMLoginPage extends HookConsumerWidget {
return;
}
await scrobblerNotifier.login(
- username.text,
+ username.text.trim(),
password.text,
);
router.pop();
diff --git a/lib/provider/authentication_provider.dart b/lib/provider/authentication_provider.dart
index f1cf58ec..cd77e7bb 100644
--- a/lib/provider/authentication_provider.dart
+++ b/lib/provider/authentication_provider.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
+import 'package:fl_query/fl_query.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
@@ -51,7 +52,8 @@ class AuthenticationCredentials {
),
);
} catch (e) {
- if (rootNavigatorKey?.currentContext != null) {
+ if (rootNavigatorKey?.currentContext != null &&
+ await QueryClient.connectivity.isConnected) {
showPromptDialog(
context: rootNavigatorKey!.currentContext!,
title: rootNavigatorKey!.currentContext!.l10n
From 58e569864dddd74c3064624998dfc184046e97eb Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 19:51:53 +0600
Subject: [PATCH 04/53] fix: last track of queue keeps repeating #718
---
lib/components/library/user_local_tracks.dart | 35 +----------------
.../shared/track_table/track_tile.dart | 4 +-
lib/hooks/use_get_storage_perms.dart | 38 +++++++++++++++++++
lib/main.dart | 2 +
.../audio_player/mk_state_player.dart | 3 --
5 files changed, 44 insertions(+), 38 deletions(-)
create mode 100644 lib/hooks/use_get_storage_perms.dart
diff --git a/lib/components/library/user_local_tracks.dart b/lib/components/library/user_local_tracks.dart
index 50ae64be..c7cd0682 100644
--- a/lib/components/library/user_local_tracks.dart
+++ b/lib/components/library/user_local_tracks.dart
@@ -1,7 +1,6 @@
import 'dart:io';
import 'package:catcher_2/catcher_2.dart';
-import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -12,7 +11,6 @@ import 'package:metadata_god/metadata_god.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
-import 'package:permission_handler/permission_handler.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
@@ -22,15 +20,12 @@ import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
import 'package:spotube/components/shared/track_table/track_tile.dart';
import 'package:spotube/extensions/context.dart';
-import 'package:spotube/hooks/use_async_effect.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
-import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
-import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'
- show FfiException;
+import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FfiException;
const supportedAudioTypes = [
"audio/webm",
@@ -162,40 +157,12 @@ class UserLocalTracks extends HookConsumerWidget {
final trackSnapshot = ref.watch(localTracksProvider);
final isPlaylistPlaying =
playlist.containsTracks(trackSnapshot.value ?? []);
- final isMounted = useIsMounted();
final searchController = useTextEditingController();
useValueListenable(searchController);
final searchFocus = useFocusNode();
final isFiltering = useState(false);
- useAsyncEffect(
- () async {
- if (!kIsMobile) return;
-
- final androidInfo = await DeviceInfoPlugin().androidInfo;
-
- final hasNoStoragePerm = androidInfo.version.sdkInt < 33 &&
- !await Permission.storage.isGranted &&
- !await Permission.storage.isLimited;
-
- final hasNoAudioPerm = androidInfo.version.sdkInt >= 33 &&
- !await Permission.audio.isGranted &&
- !await Permission.audio.isLimited;
-
- if (hasNoStoragePerm) {
- await Permission.storage.request();
- if (isMounted()) ref.refresh(localTracksProvider);
- }
- if (hasNoAudioPerm) {
- await Permission.audio.request();
- if (isMounted()) ref.refresh(localTracksProvider);
- }
- },
- null,
- [],
- );
-
return Column(
children: [
Padding(
diff --git a/lib/components/shared/track_table/track_tile.dart b/lib/components/shared/track_table/track_tile.dart
index 0666b7f9..ff1b314b 100644
--- a/lib/components/shared/track_table/track_tile.dart
+++ b/lib/components/shared/track_table/track_tile.dart
@@ -91,7 +91,9 @@ class TrackTile extends HookConsumerWidget {
isLoading.value = true;
await onTap?.call();
} finally {
- isLoading.value = false;
+ if (context.mounted) {
+ isLoading.value = false;
+ }
}
},
onLongPress: onLongPress,
diff --git a/lib/hooks/use_get_storage_perms.dart b/lib/hooks/use_get_storage_perms.dart
new file mode 100644
index 00000000..d83c60f6
--- /dev/null
+++ b/lib/hooks/use_get_storage_perms.dart
@@ -0,0 +1,38 @@
+import 'package:device_info_plus/device_info_plus.dart';
+import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'package:spotube/components/library/user_local_tracks.dart';
+import 'package:spotube/hooks/use_async_effect.dart';
+
+void useGetStoragePermissions(WidgetRef ref) {
+ final isMounted = useIsMounted();
+
+ useAsyncEffect(
+ () async {
+ if (!DesktopTools.platform.isMobile) return;
+
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
+
+ final hasNoStoragePerm = androidInfo.version.sdkInt < 33 &&
+ !await Permission.storage.isGranted &&
+ !await Permission.storage.isLimited;
+
+ final hasNoAudioPerm = androidInfo.version.sdkInt >= 33 &&
+ !await Permission.audio.isGranted &&
+ !await Permission.audio.isLimited;
+
+ if (hasNoStoragePerm) {
+ await Permission.storage.request();
+ if (isMounted()) ref.refresh(localTracksProvider);
+ }
+ if (hasNoAudioPerm) {
+ await Permission.audio.request();
+ if (isMounted()) ref.refresh(localTracksProvider);
+ }
+ },
+ null,
+ [],
+ );
+}
diff --git a/lib/main.dart b/lib/main.dart
index f8c3aa8c..b92dfaf1 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -15,6 +15,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/collections/intents.dart';
import 'package:spotube/hooks/use_disable_battery_optimizations.dart';
+import 'package:spotube/hooks/use_get_storage_perms.dart';
import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/models/matched_track.dart';
@@ -181,6 +182,7 @@ class SpotubeState extends ConsumerState {
}, []);
useDisableBatteryOptimizations();
+ useGetStoragePermissions(ref);
final lightTheme = useMemoized(
() => theme(paletteColor ?? accentMaterialColor, Brightness.light, false),
diff --git a/lib/services/audio_player/mk_state_player.dart b/lib/services/audio_player/mk_state_player.dart
index 386b0a0e..af94a0e8 100644
--- a/lib/services/audio_player/mk_state_player.dart
+++ b/lib/services/audio_player/mk_state_player.dart
@@ -174,9 +174,6 @@ class MkPlayerWithState extends Player {
case PlaylistMode.none:
// Fixes auto-repeating the last track
await super.stop();
- await Future.delayed(const Duration(seconds: 2), () {
- super.open(_playlist!.medias[_playlist!.index], play: false);
- });
break;
default:
}
From 4956bf367baae39c88b5de7c6c136513a14f8ad2 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 19:54:58 +0600
Subject: [PATCH 05/53] fix: shuffle doesn't move active track to top
---
lib/services/audio_player/mk_state_player.dart | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lib/services/audio_player/mk_state_player.dart b/lib/services/audio_player/mk_state_player.dart
index af94a0e8..a556afec 100644
--- a/lib/services/audio_player/mk_state_player.dart
+++ b/lib/services/audio_player/mk_state_player.dart
@@ -96,7 +96,10 @@ class MkPlayerWithState extends Player {
if (shuffle) {
_tempMedias = _playlist!.medias;
final active = _playlist!.medias[_playlist!.index];
- final newMedias = _playlist!.medias.toList()..shuffle();
+ final newMedias = _playlist!.medias.toList()
+ ..shuffle()
+ ..remove(active)
+ ..insert(0, active);
playlist = _playlist!.copyWith(
medias: newMedias,
index: newMedias.indexOf(active),
From 83c0b49da962d9f3d40de9525f90f0b320e8f7b8 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 20:19:03 +0600
Subject: [PATCH 06/53] fix: 0:00 media duration in queue after application
restart #782
---
lib/extensions/track.dart | 41 ++++++++++---------
lib/models/local_track.dart | 20 +--------
lib/models/spotube_track.dart | 20 +--------
.../proxy_playlist/proxy_playlist.dart | 12 +++---
4 files changed, 33 insertions(+), 60 deletions(-)
diff --git a/lib/extensions/track.dart b/lib/extensions/track.dart
index e17e851e..51498b33 100644
--- a/lib/extensions/track.dart
+++ b/lib/extensions/track.dart
@@ -4,26 +4,29 @@ import 'package:spotube/extensions/artist_simple.dart';
extension TrackJson on Track {
Map toJson() {
+ return TrackJson.trackToJson(this);
+ }
+
+ static Map trackToJson(Track track) {
return {
- "album": album?.toJson(),
- "artists": artists?.map((artist) => artist.toJson()).toList(),
- "availableMarkets": availableMarkets?.map((e) => e.name).toList(),
- "discNumber": discNumber,
- "duration": duration.toString(),
- "durationMs": durationMs,
- "explicit": explicit,
- // "externalIds": externalIds,
- // "externalUrls": externalUrls,
- "href": href,
- "id": id,
- "isPlayable": isPlayable,
- // "linkedFrom": linkedFrom,
- "name": name,
- "popularity": popularity,
- "previewUrl": previewUrl,
- "trackNumber": trackNumber,
- "type": type,
- "uri": uri,
+ "album": track.album?.toJson(),
+ "artists": track.artists?.map((artist) => artist.toJson()).toList(),
+ "available_markets": track.availableMarkets?.map((e) => e.name).toList(),
+ "disc_number": track.discNumber,
+ "duration_ms": track.durationMs,
+ "explicit": track.explicit,
+ // "external_ids"track.: externalIds,
+ // "external_urls"track.: externalUrls,
+ "href": track.href,
+ "id": track.id,
+ "is_playable": track.isPlayable,
+ // "linked_from"track.: linkedFrom,
+ "name": track.name,
+ "popularity": track.popularity,
+ "preview_rrl": track.previewUrl,
+ "track_number": track.trackNumber,
+ "type": track.type,
+ "uri": track.uri,
};
}
}
diff --git a/lib/models/local_track.dart b/lib/models/local_track.dart
index e297e974..134cd327 100644
--- a/lib/models/local_track.dart
+++ b/lib/models/local_track.dart
@@ -1,6 +1,5 @@
import 'package:spotify/spotify.dart';
-import 'package:spotube/extensions/album_simple.dart';
-import 'package:spotube/extensions/artist_simple.dart';
+import 'package:spotube/extensions/track.dart';
class LocalTrack extends Track {
final String path;
@@ -38,22 +37,7 @@ class LocalTrack extends Track {
Map toJson() {
return {
- "album": album?.toJson(),
- "artists": artists?.map((artist) => artist.toJson()).toList(),
- "availableMarkets": availableMarkets?.map((m) => m.name),
- "discNumber": discNumber,
- "duration": duration.toString(),
- "durationMs": durationMs,
- "explicit": explicit,
- "href": href,
- "id": id,
- "isPlayable": isPlayable,
- "name": name,
- "popularity": popularity,
- "previewUrl": previewUrl,
- "trackNumber": trackNumber,
- "type": type,
- "uri": uri,
+ ...TrackJson.trackToJson(this),
'path': path,
};
}
diff --git a/lib/models/spotube_track.dart b/lib/models/spotube_track.dart
index a8b94ef5..68641010 100644
--- a/lib/models/spotube_track.dart
+++ b/lib/models/spotube_track.dart
@@ -1,8 +1,7 @@
import 'dart:async';
import 'package:spotify/spotify.dart';
-import 'package:spotube/extensions/album_simple.dart';
-import 'package:spotube/extensions/artist_simple.dart';
+import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/matched_track.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
import 'package:spotube/services/youtube/youtube.dart';
@@ -264,22 +263,7 @@ class SpotubeTrack extends Track {
Map toJson() {
return {
// super values
- "album": album?.toJson(),
- "artists": artists?.map((artist) => artist.toJson()).toList(),
- "availableMarkets": availableMarkets?.map((m) => m.name),
- "discNumber": discNumber,
- "duration": duration.toString(),
- "durationMs": durationMs,
- "explicit": explicit,
- "href": href,
- "id": id,
- "isPlayable": isPlayable,
- "name": name,
- "popularity": popularity,
- "previewUrl": previewUrl,
- "trackNumber": trackNumber,
- "type": type,
- "uri": uri,
+ ...TrackJson.trackToJson(this),
// this values
"ytTrack": ytTrack.toJson(),
"ytUri": ytUri,
diff --git a/lib/provider/proxy_playlist/proxy_playlist.dart b/lib/provider/proxy_playlist/proxy_playlist.dart
index c0563f21..e5dfa7e8 100644
--- a/lib/provider/proxy_playlist/proxy_playlist.dart
+++ b/lib/provider/proxy_playlist/proxy_playlist.dart
@@ -54,12 +54,14 @@ class ProxyPlaylist {
}
}
+ /// To make sure proper instance method is used for JSON serialization
+ /// Otherwise default super.toJson() is used
static Map _makeAppropriateTrackJson(Track track) {
- if (track is LocalTrack) {
- return track.toJson();
- } else {
- return track.toJson();
- }
+ return switch (track.runtimeType) {
+ LocalTrack => track.toJson(),
+ SpotubeTrack => track.toJson(),
+ _ => track.toJson(),
+ };
}
Map toJson() {
From 353ca79be334077c3ac27b4f64e8b4b15eca7175 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 20:59:56 +0600
Subject: [PATCH 07/53] fix: spotube doesn't exit properly, hangs in infinite
loop #768
---
lib/collections/intents.dart | 5 +++--
lib/components/shared/page_window_title_bar.dart | 6 +++---
lib/hooks/use_init_sys_tray.dart | 4 +++-
lib/l10n/app_en.arb | 4 ++--
lib/services/audio_services/linux_audio_service.dart | 3 +--
5 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart
index 8c7ea73b..abccb3ad 100644
--- a/lib/collections/intents.dart
+++ b/lib/collections/intents.dart
@@ -1,6 +1,7 @@
+import 'dart:io';
+
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:spotube/components/player/player_controls.dart';
@@ -115,7 +116,7 @@ class CloseAppAction extends Action {
@override
invoke(intent) {
if (kIsDesktop) {
- DesktopTools.window.close();
+ exit(0);
} else {
SystemNavigator.pop();
}
diff --git a/lib/components/shared/page_window_title_bar.dart b/lib/components/shared/page_window_title_bar.dart
index b1086eed..50d468aa 100644
--- a/lib/components/shared/page_window_title_bar.dart
+++ b/lib/components/shared/page_window_title_bar.dart
@@ -6,7 +6,7 @@ import 'package:spotube/utils/platform.dart';
import 'package:titlebar_buttons/titlebar_buttons.dart';
import 'dart:math';
import 'package:flutter/foundation.dart' show kIsWeb;
-import 'dart:io' show Platform;
+import 'dart:io' show Platform, exit;
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:local_notifier/local_notifier.dart';
@@ -17,7 +17,7 @@ final closeNotification = DesktopTools.createNotification(
LocalNotificationAction(text: 'Close The App'),
],
)?..onClickAction = (value) {
- DesktopTools.window.close();
+ exit(0);
};
class PageWindowTitleBar extends StatefulHookConsumerWidget
@@ -113,7 +113,7 @@ class WindowTitleBarButtons extends HookConsumerWidget {
Future onClose() async {
if (preferences.closeBehavior == CloseBehavior.close) {
- await DesktopTools.window.close();
+ exit(0);
} else {
await DesktopTools.window.hide();
await closeNotification?.show();
diff --git a/lib/hooks/use_init_sys_tray.dart b/lib/hooks/use_init_sys_tray.dart
index e9aa05b6..f342c24a 100644
--- a/lib/hooks/use_init_sys_tray.dart
+++ b/lib/hooks/use_init_sys_tray.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -70,7 +72,7 @@ void useInitSysTray(WidgetRef ref) {
label: "Quit",
name: "quit",
onClicked: (item) async {
- await DesktopTools.window.close();
+ exit(0);
},
),
],
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 9d5be6bb..730f51ea 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -263,8 +263,8 @@
"use_system_title_bar": "Use system title bar",
"crunching_results": "Crunching results...",
"search_to_get_results": "Search to get results",
- "use_amoled_mode": "Use AMOLED mode",
- "pitch_dark_theme": "Pitch black dart theme",
+ "use_amoled_mode": "Pitch black dark theme",
+ "pitch_dark_theme": "AMOLED Mode",
"normalize_audio": "Normalize audio",
"change_cover": "Change cover",
"add_cover": "Add cover",
diff --git a/lib/services/audio_services/linux_audio_service.dart b/lib/services/audio_services/linux_audio_service.dart
index 28370c86..bfe022d6 100644
--- a/lib/services/audio_services/linux_audio_service.dart
+++ b/lib/services/audio_services/linux_audio_service.dart
@@ -86,8 +86,7 @@ class _MprisMediaPlayer2 extends DBusObject {
/// Implementation of org.mpris.MediaPlayer2.Quit()
Future doQuit() async {
- await windowManager.close();
- return DBusMethodSuccessResponse();
+ exit(0);
}
@override
From 1334a62aaea31f97031b3ebf455e94c583f37314 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Sun, 29 Oct 2023 21:38:48 +0600
Subject: [PATCH 08/53] fix: infinite list disappearing for a moment everytime
new page is fetched
---
lib/components/genre/category_card.dart | 4 ++-
lib/pages/home/personalized.dart | 3 ++-
lib/pages/search/search.dart | 11 ++++++---
pubspec.lock | 33 +++++++++++--------------
pubspec.yaml | 23 +++--------------
5 files changed, 31 insertions(+), 43 deletions(-)
diff --git a/lib/components/genre/category_card.dart b/lib/components/genre/category_card.dart
index 42654ed9..1aa33cd6 100644
--- a/lib/components/genre/category_card.dart
+++ b/lib/components/genre/category_card.dart
@@ -28,7 +28,9 @@ class CategoryCard extends HookConsumerWidget {
category.id!,
);
- if (playlistQuery.hasErrors && !playlistQuery.hasPageData) {
+ if (playlistQuery.hasErrors &&
+ !playlistQuery.hasPageData &&
+ !playlistQuery.isLoadingNextPage) {
return const SizedBox.shrink();
}
final playlists = playlistQuery.pages.expand(
diff --git a/lib/pages/home/personalized.dart b/lib/pages/home/personalized.dart
index d6192592..f7e942be 100644
--- a/lib/pages/home/personalized.dart
+++ b/lib/pages/home/personalized.dart
@@ -131,7 +131,8 @@ class PersonalizedPage extends HookConsumerWidget {
child: ListView(
controller: controller,
children: [
- if (!featuredPlaylistsQuery.hasPageData)
+ if (!featuredPlaylistsQuery.hasPageData &&
+ !featuredPlaylistsQuery.isLoadingNextPage)
const ShimmerCategories()
else
PersonalizedItemCard(
diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart
index 19a9aafa..c192eb7b 100644
--- a/lib/pages/search/search.dart
+++ b/lib/pages/search/search.dart
@@ -222,7 +222,8 @@ class SearchPage extends HookConsumerWidget {
),
),
if (!searchPlaylist.hasPageData &&
- !searchPlaylist.hasPageError)
+ !searchPlaylist.hasPageError &&
+ !searchPlaylist.isLoadingNextPage)
const CircularProgressIndicator(),
if (searchPlaylist.hasPageError)
Padding(
@@ -280,7 +281,9 @@ class SearchPage extends HookConsumerWidget {
),
),
),
- if (!searchArtist.hasPageData && !searchArtist.hasPageError)
+ if (!searchArtist.hasPageData &&
+ !searchArtist.hasPageError &&
+ !searchArtist.isLoadingNextPage)
const CircularProgressIndicator(),
if (searchArtist.hasPageError)
Padding(
@@ -336,7 +339,9 @@ class SearchPage extends HookConsumerWidget {
),
),
),
- if (!searchAlbum.hasPageData && !searchAlbum.hasPageError)
+ if (!searchAlbum.hasPageData &&
+ !searchAlbum.hasPageError &&
+ !searchAlbum.isLoadingNextPage)
const CircularProgressIndicator(),
if (searchAlbum.hasPageError)
Padding(
diff --git a/pubspec.lock b/pubspec.lock
index bd50225a..15a50f41 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -596,30 +596,27 @@ packages:
fl_query:
dependency: "direct main"
description:
- path: "packages/fl_query"
- ref: HEAD
- resolved-ref: a817713a0bb0c486e908e9ed74467c4f7f58bea7
- url: "https://github.com/KRTirtho/fl-query.git"
- source: git
- version: "1.0.0-alpha.5"
+ name: fl_query
+ sha256: daee5ab0ed8899baa201b89b5813107df5258144a9e2bcf192dbcf922c57d985
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
fl_query_devtools:
dependency: "direct main"
description:
- path: "packages/fl_query_devtools"
- ref: HEAD
- resolved-ref: a817713a0bb0c486e908e9ed74467c4f7f58bea7
- url: "https://github.com/KRTirtho/fl-query.git"
- source: git
- version: "0.1.0-alpha.3"
+ name: fl_query_devtools
+ sha256: "2ae8905fd4a95f1d245a1b54057c31c8d27fc961223bcb7ce13088bcf6595059"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.0"
fl_query_hooks:
dependency: "direct main"
description:
- path: "packages/fl_query_hooks"
- ref: HEAD
- resolved-ref: a817713a0bb0c486e908e9ed74467c4f7f58bea7
- url: "https://github.com/KRTirtho/fl-query.git"
- source: git
- version: "1.0.0-alpha.5"
+ name: fl_query_hooks
+ sha256: "6c88b3bfbdc3e1330931b927903929d7351f86fc63266ac93b3acb9f133a09a9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
fluentui_system_icons:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 75b14bc1..64b2b6a3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -32,18 +32,9 @@ dependencies:
duration: ^3.0.12
envied: ^0.3.0
file_selector: ^1.0.1
- fl_query:
- git:
- url: https://github.com/KRTirtho/fl-query.git
- path: packages/fl_query
- fl_query_hooks:
- git:
- url: https://github.com/KRTirtho/fl-query.git
- path: packages/fl_query_hooks
- fl_query_devtools:
- git:
- url: https://github.com/KRTirtho/fl-query.git
- path: packages/fl_query_devtools
+ fl_query: ^1.0.0
+ fl_query_hooks: ^1.0.0
+ fl_query_devtools: ^0.1.0
fluentui_system_icons: ^1.1.189
flutter:
sdk: flutter
@@ -135,14 +126,6 @@ dev_dependencies:
dependency_overrides:
http: ^1.1.0
system_tray: 2.0.2
- fl_query:
- git:
- url: https://github.com/KRTirtho/fl-query.git
- path: packages/fl_query
- fl_query_hooks:
- git:
- url: https://github.com/KRTirtho/fl-query.git
- path: packages/fl_query_hooks
flutter:
generate: true
From ac0e2e74d803a902b0abef94f674f68ffcd81fd3 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 11:43:17 +0600
Subject: [PATCH 09/53] refactor: extract settings section to separate files
---
lib/pages/home/genres.dart | 3 +-
lib/pages/settings/sections/about.dart | 84 +++
lib/pages/settings/sections/appearance.dart | 109 ++++
lib/pages/settings/sections/desktop.dart | 54 ++
lib/pages/settings/sections/developers.dart | 27 +
lib/pages/settings/sections/downloads.dart | 51 ++
.../settings/sections/language_region.dart | 74 +++
lib/pages/settings/sections/playback.dart | 218 +++++++
lib/pages/settings/settings.dart | 541 +-----------------
lib/utils/persisted_state_notifier.dart | 36 +-
pubspec.lock | 4 +-
11 files changed, 653 insertions(+), 548 deletions(-)
create mode 100644 lib/pages/settings/sections/about.dart
create mode 100644 lib/pages/settings/sections/appearance.dart
create mode 100644 lib/pages/settings/sections/desktop.dart
create mode 100644 lib/pages/settings/sections/developers.dart
create mode 100644 lib/pages/settings/sections/downloads.dart
create mode 100644 lib/pages/settings/sections/language_region.dart
create mode 100644 lib/pages/settings/sections/playback.dart
diff --git a/lib/pages/home/genres.dart b/lib/pages/home/genres.dart
index b4e3c664..db1c58c5 100644
--- a/lib/pages/home/genres.dart
+++ b/lib/pages/home/genres.dart
@@ -74,7 +74,8 @@ class GenrePage extends HookConsumerWidget {
searchController: searchController,
searchFocus: searchFocus,
),
- if (!categoriesQuery.hasPageData)
+ if (!categoriesQuery.hasPageData &&
+ !categoriesQuery.isLoadingNextPage)
const ShimmerCategories()
else
Expanded(
diff --git a/lib/pages/settings/sections/about.dart b/lib/pages/settings/sections/about.dart
new file mode 100644
index 00000000..0340b27c
--- /dev/null
+++ b/lib/pages/settings/sections/about.dart
@@ -0,0 +1,84 @@
+import 'package:auto_size_text/auto_size_text.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:spotube/collections/env.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+
+class SettingsAboutSection extends HookConsumerWidget {
+ const SettingsAboutSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.about,
+ children: [
+ AdaptiveListTile(
+ leading: const Icon(
+ SpotubeIcons.heart,
+ color: Colors.pink,
+ ),
+ title: SizedBox(
+ height: 50,
+ width: 200,
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: AutoSizeText(
+ context.l10n.u_love_spotube,
+ maxLines: 1,
+ style: const TextStyle(
+ color: Colors.pink,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ),
+ trailing: (context, update) => FilledButton(
+ style: ButtonStyle(
+ backgroundColor: MaterialStatePropertyAll(Colors.red[100]),
+ foregroundColor:
+ const MaterialStatePropertyAll(Colors.pinkAccent),
+ padding: const MaterialStatePropertyAll(EdgeInsets.all(15)),
+ ),
+ onPressed: () {
+ launchUrlString(
+ "https://opencollective.com/spotube",
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Icon(SpotubeIcons.heart),
+ const SizedBox(width: 5),
+ Text(context.l10n.please_sponsor),
+ ],
+ ),
+ ),
+ ),
+ if (Env.enableUpdateChecker)
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.update),
+ title: Text(context.l10n.check_for_updates),
+ value: preferences.checkUpdate,
+ onChanged: (checked) => preferences.setCheckUpdate(checked),
+ ),
+ ListTile(
+ leading: const Icon(SpotubeIcons.info),
+ title: Text(context.l10n.about_spotube),
+ trailing: const Icon(SpotubeIcons.angleRight),
+ onTap: () {
+ GoRouter.of(context).push("/settings/about");
+ },
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/appearance.dart b/lib/pages/settings/sections/appearance.dart
new file mode 100644
index 00000000..f4b097e8
--- /dev/null
+++ b/lib/pages/settings/sections/appearance.dart
@@ -0,0 +1,109 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+
+class SettingsAppearanceSection extends HookConsumerWidget {
+ const SettingsAppearanceSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+ final pickColorScheme = useCallback(() {
+ return () => showDialog(
+ context: context,
+ builder: (context) {
+ return const ColorSchemePickerDialog();
+ });
+ }, []);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.appearance,
+ children: [
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.dashboard),
+ title: Text(context.l10n.layout_mode),
+ subtitle: Text(context.l10n.override_layout_settings),
+ value: preferences.layoutMode,
+ onChanged: (value) {
+ if (value != null) {
+ preferences.setLayoutMode(value);
+ }
+ },
+ options: [
+ DropdownMenuItem(
+ value: LayoutMode.adaptive,
+ child: Text(context.l10n.adaptive),
+ ),
+ DropdownMenuItem(
+ value: LayoutMode.compact,
+ child: Text(context.l10n.compact),
+ ),
+ DropdownMenuItem(
+ value: LayoutMode.extended,
+ child: Text(context.l10n.extended),
+ ),
+ ],
+ ),
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.darkMode),
+ title: Text(context.l10n.theme),
+ value: preferences.themeMode,
+ options: [
+ DropdownMenuItem(
+ value: ThemeMode.dark,
+ child: Text(context.l10n.dark),
+ ),
+ DropdownMenuItem(
+ value: ThemeMode.light,
+ child: Text(context.l10n.light),
+ ),
+ DropdownMenuItem(
+ value: ThemeMode.system,
+ child: Text(context.l10n.system),
+ ),
+ ],
+ onChanged: (value) {
+ if (value != null) {
+ preferences.setThemeMode(value);
+ }
+ },
+ ),
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.amoled),
+ title: Text(context.l10n.use_amoled_mode),
+ subtitle: Text(context.l10n.pitch_dark_theme),
+ value: preferences.amoledDarkTheme,
+ onChanged: preferences.setAmoledDarkTheme,
+ ),
+ ListTile(
+ leading: const Icon(SpotubeIcons.palette),
+ title: Text(context.l10n.accent_color),
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 15,
+ vertical: 5,
+ ),
+ trailing: ColorTile.compact(
+ color: preferences.accentColorScheme,
+ onPressed: pickColorScheme(),
+ isActive: true,
+ ),
+ onTap: pickColorScheme(),
+ ),
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.colorSync),
+ title: Text(context.l10n.sync_album_color),
+ subtitle: Text(context.l10n.sync_album_color_description),
+ value: preferences.albumColorSync,
+ onChanged: preferences.setAlbumColorSync,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/desktop.dart b/lib/pages/settings/sections/desktop.dart
new file mode 100644
index 00000000..d12bcb41
--- /dev/null
+++ b/lib/pages/settings/sections/desktop.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+
+class SettingsDesktopSection extends HookConsumerWidget {
+ const SettingsDesktopSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.desktop,
+ children: [
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.close),
+ title: Text(context.l10n.close_behavior),
+ value: preferences.closeBehavior,
+ options: [
+ DropdownMenuItem(
+ value: CloseBehavior.close,
+ child: Text(context.l10n.close),
+ ),
+ DropdownMenuItem(
+ value: CloseBehavior.minimizeToTray,
+ child: Text(context.l10n.minimize_to_tray),
+ ),
+ ],
+ onChanged: (value) {
+ if (value != null) {
+ preferences.setCloseBehavior(value);
+ }
+ },
+ ),
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.tray),
+ title: Text(context.l10n.show_tray_icon),
+ value: preferences.showSystemTrayIcon,
+ onChanged: preferences.setShowSystemTrayIcon,
+ ),
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.window),
+ title: Text(context.l10n.use_system_title_bar),
+ value: preferences.systemTitleBar,
+ onChanged: preferences.setSystemTitleBar,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/developers.dart b/lib/pages/settings/sections/developers.dart
new file mode 100644
index 00000000..4b5f58a6
--- /dev/null
+++ b/lib/pages/settings/sections/developers.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:go_router/go_router.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/extensions/context.dart';
+
+class SettingsDevelopersSection extends HookWidget {
+ const SettingsDevelopersSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SectionCardWithHeading(
+ heading: context.l10n.developers,
+ children: [
+ ListTile(
+ leading: const Icon(SpotubeIcons.logs),
+ title: Text(context.l10n.logs),
+ trailing: const Icon(SpotubeIcons.angleRight),
+ onTap: () {
+ GoRouter.of(context).push("/settings/logs");
+ },
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/downloads.dart b/lib/pages/settings/sections/downloads.dart
new file mode 100644
index 00000000..1f157037
--- /dev/null
+++ b/lib/pages/settings/sections/downloads.dart
@@ -0,0 +1,51 @@
+import 'package:file_picker/file_picker.dart';
+import 'package:file_selector/file_selector.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+
+class SettingsDownloadsSection extends HookConsumerWidget {
+ const SettingsDownloadsSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+
+ final pickDownloadLocation = useCallback(() async {
+ if (DesktopTools.platform.isMobile) {
+ final dirStr = await FilePicker.platform.getDirectoryPath(
+ initialDirectory: preferences.downloadLocation,
+ );
+ if (dirStr == null) return;
+ preferences.setDownloadLocation(dirStr);
+ } else {
+ String? dirStr = await getDirectoryPath(
+ initialDirectory: preferences.downloadLocation,
+ );
+ if (dirStr == null) return;
+ preferences.setDownloadLocation(dirStr);
+ }
+ }, [preferences.downloadLocation]);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.downloads,
+ children: [
+ ListTile(
+ leading: const Icon(SpotubeIcons.download),
+ title: Text(context.l10n.download_location),
+ subtitle: Text(preferences.downloadLocation),
+ trailing: FilledButton(
+ onPressed: pickDownloadLocation,
+ child: const Icon(SpotubeIcons.folder),
+ ),
+ onTap: pickDownloadLocation,
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/language_region.dart b/lib/pages/settings/sections/language_region.dart
new file mode 100644
index 00000000..64c56224
--- /dev/null
+++ b/lib/pages/settings/sections/language_region.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:spotify/spotify.dart';
+import 'package:spotube/collections/language_codes.dart';
+import 'package:spotube/collections/spotify_markets.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
+import 'package:spotube/extensions/constrains.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/l10n/l10n.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+
+class SettingsLanguageRegionSection extends HookConsumerWidget {
+ const SettingsLanguageRegionSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+ final mediaQuery = MediaQuery.of(context);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.language_region,
+ children: [
+ AdaptiveSelectTile(
+ value: preferences.locale,
+ onChanged: (locale) {
+ if (locale == null) return;
+ preferences.setLocale(locale);
+ },
+ title: Text(context.l10n.language),
+ secondary: const Icon(SpotubeIcons.language),
+ options: [
+ DropdownMenuItem(
+ value: const Locale("system", "system"),
+ child: Text(context.l10n.system_default),
+ ),
+ for (final locale in L10n.all)
+ DropdownMenuItem(
+ value: locale,
+ child: Builder(builder: (context) {
+ final isoCodeName = LanguageLocals.getDisplayLanguage(
+ locale.languageCode,
+ );
+ return Text(
+ "${isoCodeName.name} (${isoCodeName.nativeName})",
+ );
+ }),
+ ),
+ ],
+ ),
+ AdaptiveSelectTile(
+ breakLayout: mediaQuery.lgAndUp,
+ secondary: const Icon(SpotubeIcons.shoppingBag),
+ title: Text(context.l10n.market_place_region),
+ subtitle: Text(context.l10n.recommendation_country),
+ value: preferences.recommendationMarket,
+ onChanged: (value) {
+ if (value == null) return;
+ preferences.setRecommendationMarket(value);
+ },
+ options: spotifyMarkets
+ .map(
+ (country) => DropdownMenuItem(
+ value: country.$1,
+ child: Text(country.$2),
+ ),
+ )
+ .toList(),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart
new file mode 100644
index 00000000..cf7e33e9
--- /dev/null
+++ b/lib/pages/settings/sections/playback.dart
@@ -0,0 +1,218 @@
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:google_fonts/google_fonts.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:piped_client/piped_client.dart';
+import 'package:spotube/collections/spotube_icons.dart';
+import 'package:spotube/components/settings/section_card_with_heading.dart';
+import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
+import 'package:spotube/extensions/context.dart';
+import 'package:spotube/models/matched_track.dart';
+import 'package:spotube/provider/piped_instances_provider.dart';
+import 'package:spotube/provider/user_preferences_provider.dart';
+
+class SettingsPlaybackSection extends HookConsumerWidget {
+ const SettingsPlaybackSection({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context, ref) {
+ final preferences = ref.watch(userPreferencesProvider);
+ final theme = Theme.of(context);
+
+ return SectionCardWithHeading(
+ heading: context.l10n.playback,
+ children: [
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.audioQuality),
+ title: Text(context.l10n.audio_quality),
+ value: preferences.audioQuality,
+ options: [
+ DropdownMenuItem(
+ value: AudioQuality.high,
+ child: Text(context.l10n.high),
+ ),
+ DropdownMenuItem(
+ value: AudioQuality.low,
+ child: Text(context.l10n.low),
+ ),
+ ],
+ onChanged: (value) {
+ if (value != null) {
+ preferences.setAudioQuality(value);
+ }
+ },
+ ),
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.api),
+ title: Text(context.l10n.youtube_api_type),
+ value: preferences.youtubeApiType,
+ options: YoutubeApiType.values
+ .map((e) => DropdownMenuItem(
+ value: e,
+ child: Text(e.label),
+ ))
+ .toList(),
+ onChanged: (value) {
+ if (value == null) return;
+ preferences.setYoutubeApiType(value);
+ },
+ ),
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ child: preferences.youtubeApiType == YoutubeApiType.youtube
+ ? const SizedBox.shrink()
+ : Consumer(builder: (context, ref, child) {
+ final instanceList = ref.watch(pipedInstancesFutureProvider);
+
+ return instanceList.when(
+ data: (data) {
+ return AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.piped),
+ title: Text(context.l10n.piped_instance),
+ subtitle: RichText(
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: context.l10n.piped_description,
+ style: theme.textTheme.bodyMedium,
+ ),
+ const TextSpan(text: "\n"),
+ TextSpan(
+ text: context.l10n.piped_warning,
+ style: theme.textTheme.labelMedium,
+ )
+ ],
+ ),
+ ),
+ value: preferences.pipedInstance,
+ showValueWhenUnfolded: false,
+ options: data
+ .sortedBy((e) => e.name)
+ .map(
+ (e) => DropdownMenuItem(
+ value: e.apiUrl,
+ child: RichText(
+ text: TextSpan(
+ children: [
+ TextSpan(
+ text: "${e.name.trim()}\n",
+ style: theme.textTheme.labelLarge,
+ ),
+ TextSpan(
+ text: e.locations
+ .map(countryCodeToEmoji)
+ .join(""),
+ style: GoogleFonts.notoColorEmoji(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ )
+ .toList(),
+ onChanged: (value) {
+ if (value != null) {
+ preferences.setPipedInstance(value);
+ }
+ },
+ );
+ },
+ loading: () => const Center(
+ child: CircularProgressIndicator(),
+ ),
+ error: (error, stackTrace) => Text(error.toString()),
+ );
+ }),
+ ),
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ child: preferences.youtubeApiType == YoutubeApiType.youtube
+ ? const SizedBox.shrink()
+ : AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.search),
+ title: Text(context.l10n.search_mode),
+ value: preferences.searchMode,
+ options: SearchMode.values
+ .map((e) => DropdownMenuItem(
+ value: e,
+ child: Text(e.label),
+ ))
+ .toList(),
+ onChanged: (value) {
+ if (value == null) return;
+ preferences.setSearchMode(value);
+ },
+ ),
+ ),
+ AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ child: preferences.searchMode == SearchMode.youtubeMusic &&
+ preferences.youtubeApiType == YoutubeApiType.piped
+ ? const SizedBox.shrink()
+ : SwitchListTile(
+ secondary: const Icon(SpotubeIcons.skip),
+ title: Text(context.l10n.skip_non_music),
+ value: preferences.skipNonMusic,
+ onChanged: (state) {
+ preferences.setSkipNonMusic(state);
+ },
+ ),
+ ),
+ ListTile(
+ leading: const Icon(SpotubeIcons.playlistRemove),
+ title: Text(context.l10n.blacklist),
+ subtitle: Text(context.l10n.blacklist_description),
+ onTap: () {
+ GoRouter.of(context).push("/settings/blacklist");
+ },
+ trailing: const Icon(SpotubeIcons.angleRight),
+ ),
+ SwitchListTile(
+ secondary: const Icon(SpotubeIcons.normalize),
+ title: Text(context.l10n.normalize_audio),
+ value: preferences.normalizeAudio,
+ onChanged: preferences.setNormalizeAudio,
+ ),
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.stream),
+ title: Text(context.l10n.streaming_music_codec),
+ value: preferences.streamMusicCodec,
+ showValueWhenUnfolded: false,
+ options: MusicCodec.values
+ .map((e) => DropdownMenuItem(
+ value: e,
+ child: Text(
+ e.label,
+ style: theme.textTheme.labelMedium,
+ ),
+ ))
+ .toList(),
+ onChanged: (value) {
+ if (value == null) return;
+ preferences.setStreamMusicCodec(value);
+ },
+ ),
+ AdaptiveSelectTile(
+ secondary: const Icon(SpotubeIcons.file),
+ title: Text(context.l10n.download_music_codec),
+ value: preferences.downloadMusicCodec,
+ showValueWhenUnfolded: false,
+ options: MusicCodec.values
+ .map((e) => DropdownMenuItem(
+ value: e,
+ child: Text(
+ e.label,
+ style: theme.textTheme.labelMedium,
+ ),
+ ))
+ .toList(),
+ onChanged: (value) {
+ if (value == null) return;
+ preferences.setDownloadMusicCodec(value);
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart
index 5632a89a..5b377a1f 100644
--- a/lib/pages/settings/settings.dart
+++ b/lib/pages/settings/settings.dart
@@ -1,34 +1,19 @@
-import 'package:auto_size_text/auto_size_text.dart';
-import 'package:collection/collection.dart';
-import 'package:file_picker/file_picker.dart';
-import 'package:file_selector/file_selector.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
-import 'package:flutter_hooks/flutter_hooks.dart';
-import 'package:go_router/go_router.dart';
-import 'package:google_fonts/google_fonts.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:piped_client/piped_client.dart';
-import 'package:spotify/spotify.dart';
-import 'package:spotube/collections/env.dart';
-import 'package:spotube/collections/language_codes.dart';
-import 'package:spotube/collections/spotube_icons.dart';
-import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
-import 'package:spotube/components/settings/section_card_with_heading.dart';
-import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
-import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
-import 'package:spotube/collections/spotify_markets.dart';
-import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
-import 'package:spotube/l10n/l10n.dart';
-import 'package:spotube/models/matched_track.dart';
+import 'package:spotube/pages/settings/sections/about.dart';
import 'package:spotube/pages/settings/sections/accounts.dart';
+import 'package:spotube/pages/settings/sections/appearance.dart';
+import 'package:spotube/pages/settings/sections/desktop.dart';
+import 'package:spotube/pages/settings/sections/developers.dart';
+import 'package:spotube/pages/settings/sections/downloads.dart';
+import 'package:spotube/pages/settings/sections/language_region.dart';
+import 'package:spotube/pages/settings/sections/playback.dart';
import 'package:spotube/provider/user_preferences_provider.dart';
-import 'package:spotube/provider/piped_instances_provider.dart';
-import 'package:url_launcher/url_launcher_string.dart';
class SettingsPage extends HookConsumerWidget {
const SettingsPage({Key? key}) : super(key: key);
@@ -36,32 +21,6 @@ class SettingsPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider);
- final theme = Theme.of(context);
- final mediaQuery = MediaQuery.of(context);
-
- final pickColorScheme = useCallback(() {
- return () => showDialog(
- context: context,
- builder: (context) {
- return const ColorSchemePickerDialog();
- });
- }, []);
-
- final pickDownloadLocation = useCallback(() async {
- if (DesktopTools.platform.isMobile) {
- final dirStr = await FilePicker.platform.getDirectoryPath(
- initialDirectory: preferences.downloadLocation,
- );
- if (dirStr == null) return;
- preferences.setDownloadLocation(dirStr);
- } else {
- String? dirStr = await getDirectoryPath(
- initialDirectory: preferences.downloadLocation,
- );
- if (dirStr == null) return;
- preferences.setDownloadLocation(dirStr);
- }
- }, [preferences.downloadLocation]);
return SafeArea(
bottom: false,
@@ -80,486 +39,14 @@ class SettingsPage extends HookConsumerWidget {
child: ListView(
children: [
const SettingsAccountSection(),
- SectionCardWithHeading(
- heading: context.l10n.language_region,
- children: [
- AdaptiveSelectTile(
- value: preferences.locale,
- onChanged: (locale) {
- if (locale == null) return;
- preferences.setLocale(locale);
- },
- title: Text(context.l10n.language),
- secondary: const Icon(SpotubeIcons.language),
- options: [
- DropdownMenuItem(
- value: const Locale("system", "system"),
- child: Text(context.l10n.system_default),
- ),
- for (final locale in L10n.all)
- DropdownMenuItem(
- value: locale,
- child: Builder(builder: (context) {
- final isoCodeName =
- LanguageLocals.getDisplayLanguage(
- locale.languageCode,
- );
- return Text(
- "${isoCodeName.name} (${isoCodeName.nativeName})",
- );
- }),
- ),
- ],
- ),
- AdaptiveSelectTile(
- breakLayout: mediaQuery.lgAndUp,
- secondary: const Icon(SpotubeIcons.shoppingBag),
- title: Text(context.l10n.market_place_region),
- subtitle: Text(context.l10n.recommendation_country),
- value: preferences.recommendationMarket,
- onChanged: (value) {
- if (value == null) return;
- preferences.setRecommendationMarket(value);
- },
- options: spotifyMarkets
- .map(
- (country) => DropdownMenuItem(
- value: country.$1,
- child: Text(country.$2),
- ),
- )
- .toList(),
- ),
- ],
- ),
- SectionCardWithHeading(
- heading: context.l10n.appearance,
- children: [
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.dashboard),
- title: Text(context.l10n.layout_mode),
- subtitle:
- Text(context.l10n.override_layout_settings),
- value: preferences.layoutMode,
- onChanged: (value) {
- if (value != null) {
- preferences.setLayoutMode(value);
- }
- },
- options: [
- DropdownMenuItem(
- value: LayoutMode.adaptive,
- child: Text(context.l10n.adaptive),
- ),
- DropdownMenuItem(
- value: LayoutMode.compact,
- child: Text(context.l10n.compact),
- ),
- DropdownMenuItem(
- value: LayoutMode.extended,
- child: Text(context.l10n.extended),
- ),
- ],
- ),
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.darkMode),
- title: Text(context.l10n.theme),
- value: preferences.themeMode,
- options: [
- DropdownMenuItem(
- value: ThemeMode.dark,
- child: Text(context.l10n.dark),
- ),
- DropdownMenuItem(
- value: ThemeMode.light,
- child: Text(context.l10n.light),
- ),
- DropdownMenuItem(
- value: ThemeMode.system,
- child: Text(context.l10n.system),
- ),
- ],
- onChanged: (value) {
- if (value != null) {
- preferences.setThemeMode(value);
- }
- },
- ),
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.amoled),
- title: Text(context.l10n.use_amoled_mode),
- subtitle: Text(context.l10n.pitch_dark_theme),
- value: preferences.amoledDarkTheme,
- onChanged: preferences.setAmoledDarkTheme,
- ),
- ListTile(
- leading: const Icon(SpotubeIcons.palette),
- title: Text(context.l10n.accent_color),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 15,
- vertical: 5,
- ),
- trailing: ColorTile.compact(
- color: preferences.accentColorScheme,
- onPressed: pickColorScheme(),
- isActive: true,
- ),
- onTap: pickColorScheme(),
- ),
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.colorSync),
- title: Text(context.l10n.sync_album_color),
- subtitle:
- Text(context.l10n.sync_album_color_description),
- value: preferences.albumColorSync,
- onChanged: preferences.setAlbumColorSync,
- ),
- ],
- ),
- SectionCardWithHeading(
- heading: context.l10n.playback,
- children: [
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.audioQuality),
- title: Text(context.l10n.audio_quality),
- value: preferences.audioQuality,
- options: [
- DropdownMenuItem(
- value: AudioQuality.high,
- child: Text(context.l10n.high),
- ),
- DropdownMenuItem(
- value: AudioQuality.low,
- child: Text(context.l10n.low),
- ),
- ],
- onChanged: (value) {
- if (value != null) {
- preferences.setAudioQuality(value);
- }
- },
- ),
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.api),
- title: Text(context.l10n.youtube_api_type),
- value: preferences.youtubeApiType,
- options: YoutubeApiType.values
- .map((e) => DropdownMenuItem(
- value: e,
- child: Text(e.label),
- ))
- .toList(),
- onChanged: (value) {
- if (value == null) return;
- preferences.setYoutubeApiType(value);
- },
- ),
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- child: preferences.youtubeApiType ==
- YoutubeApiType.youtube
- ? const SizedBox.shrink()
- : Consumer(builder: (context, ref, child) {
- final instanceList =
- ref.watch(pipedInstancesFutureProvider);
-
- return instanceList.when(
- data: (data) {
- return AdaptiveSelectTile(
- secondary:
- const Icon(SpotubeIcons.piped),
- title:
- Text(context.l10n.piped_instance),
- subtitle: RichText(
- text: TextSpan(
- children: [
- TextSpan(
- text: context
- .l10n.piped_description,
- style: theme
- .textTheme.bodyMedium,
- ),
- const TextSpan(text: "\n"),
- TextSpan(
- text: context
- .l10n.piped_warning,
- style: theme
- .textTheme.labelMedium,
- )
- ],
- ),
- ),
- value: preferences.pipedInstance,
- showValueWhenUnfolded: false,
- options: data
- .sortedBy((e) => e.name)
- .map(
- (e) => DropdownMenuItem(
- value: e.apiUrl,
- child: RichText(
- text: TextSpan(
- children: [
- TextSpan(
- text:
- "${e.name.trim()}\n",
- style: theme.textTheme
- .labelLarge,
- ),
- TextSpan(
- text: e.locations
- .map(
- countryCodeToEmoji)
- .join(""),
- style: GoogleFonts
- .notoColorEmoji(),
- ),
- ],
- ),
- ),
- ),
- )
- .toList(),
- onChanged: (value) {
- if (value != null) {
- preferences
- .setPipedInstance(value);
- }
- },
- );
- },
- loading: () => const Center(
- child: CircularProgressIndicator(),
- ),
- error: (error, stackTrace) =>
- Text(error.toString()),
- );
- }),
- ),
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- child: preferences.youtubeApiType ==
- YoutubeApiType.youtube
- ? const SizedBox.shrink()
- : AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.search),
- title: Text(context.l10n.search_mode),
- value: preferences.searchMode,
- options: SearchMode.values
- .map((e) => DropdownMenuItem(
- value: e,
- child: Text(e.label),
- ))
- .toList(),
- onChanged: (value) {
- if (value == null) return;
- preferences.setSearchMode(value);
- },
- ),
- ),
- AnimatedSwitcher(
- duration: const Duration(milliseconds: 300),
- child: preferences.searchMode ==
- SearchMode.youtubeMusic &&
- preferences.youtubeApiType ==
- YoutubeApiType.piped
- ? const SizedBox.shrink()
- : SwitchListTile(
- secondary: const Icon(SpotubeIcons.skip),
- title: Text(context.l10n.skip_non_music),
- value: preferences.skipNonMusic,
- onChanged: (state) {
- preferences.setSkipNonMusic(state);
- },
- ),
- ),
- ListTile(
- leading: const Icon(SpotubeIcons.playlistRemove),
- title: Text(context.l10n.blacklist),
- subtitle: Text(context.l10n.blacklist_description),
- onTap: () {
- GoRouter.of(context).push("/settings/blacklist");
- },
- trailing: const Icon(SpotubeIcons.angleRight),
- ),
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.normalize),
- title: Text(context.l10n.normalize_audio),
- value: preferences.normalizeAudio,
- onChanged: preferences.setNormalizeAudio,
- ),
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.stream),
- title: Text(context.l10n.streaming_music_codec),
- value: preferences.streamMusicCodec,
- showValueWhenUnfolded: false,
- options: MusicCodec.values
- .map((e) => DropdownMenuItem(
- value: e,
- child: Text(
- e.label,
- style: theme.textTheme.labelMedium,
- ),
- ))
- .toList(),
- onChanged: (value) {
- if (value == null) return;
- preferences.setStreamMusicCodec(value);
- },
- ),
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.file),
- title: Text(context.l10n.download_music_codec),
- value: preferences.downloadMusicCodec,
- showValueWhenUnfolded: false,
- options: MusicCodec.values
- .map((e) => DropdownMenuItem(
- value: e,
- child: Text(
- e.label,
- style: theme.textTheme.labelMedium,
- ),
- ))
- .toList(),
- onChanged: (value) {
- if (value == null) return;
- preferences.setDownloadMusicCodec(value);
- },
- ),
- ],
- ),
- SectionCardWithHeading(
- heading: context.l10n.downloads,
- children: [
- ListTile(
- leading: const Icon(SpotubeIcons.download),
- title: Text(context.l10n.download_location),
- subtitle: Text(preferences.downloadLocation),
- trailing: FilledButton(
- onPressed: pickDownloadLocation,
- child: const Icon(SpotubeIcons.folder),
- ),
- onTap: pickDownloadLocation,
- ),
- ],
- ),
+ const SettingsLanguageRegionSection(),
+ const SettingsAppearanceSection(),
+ const SettingsPlaybackSection(),
+ const SettingsDownloadsSection(),
if (DesktopTools.platform.isDesktop)
- SectionCardWithHeading(
- heading: context.l10n.desktop,
- children: [
- AdaptiveSelectTile(
- secondary: const Icon(SpotubeIcons.close),
- title: Text(context.l10n.close_behavior),
- value: preferences.closeBehavior,
- options: [
- DropdownMenuItem(
- value: CloseBehavior.close,
- child: Text(context.l10n.close),
- ),
- DropdownMenuItem(
- value: CloseBehavior.minimizeToTray,
- child: Text(context.l10n.minimize_to_tray),
- ),
- ],
- onChanged: (value) {
- if (value != null) {
- preferences.setCloseBehavior(value);
- }
- },
- ),
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.tray),
- title: Text(context.l10n.show_tray_icon),
- value: preferences.showSystemTrayIcon,
- onChanged: preferences.setShowSystemTrayIcon,
- ),
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.window),
- title: Text(context.l10n.use_system_title_bar),
- value: preferences.systemTitleBar,
- onChanged: preferences.setSystemTitleBar,
- ),
- ],
- ),
- if (!kIsWeb)
- SectionCardWithHeading(
- heading: context.l10n.developers,
- children: [
- ListTile(
- leading: const Icon(SpotubeIcons.logs),
- title: Text(context.l10n.logs),
- trailing: const Icon(SpotubeIcons.angleRight),
- onTap: () {
- GoRouter.of(context).push("/settings/logs");
- },
- )
- ],
- ),
- SectionCardWithHeading(
- heading: context.l10n.about,
- children: [
- AdaptiveListTile(
- leading: const Icon(
- SpotubeIcons.heart,
- color: Colors.pink,
- ),
- title: SizedBox(
- height: 50,
- width: 200,
- child: Align(
- alignment: Alignment.centerLeft,
- child: AutoSizeText(
- context.l10n.u_love_spotube,
- maxLines: 1,
- style: const TextStyle(
- color: Colors.pink,
- fontWeight: FontWeight.bold,
- ),
- ),
- ),
- ),
- trailing: (context, update) => FilledButton(
- style: ButtonStyle(
- backgroundColor:
- MaterialStatePropertyAll(Colors.red[100]),
- foregroundColor: const MaterialStatePropertyAll(
- Colors.pinkAccent),
- padding: const MaterialStatePropertyAll(
- EdgeInsets.all(15)),
- ),
- onPressed: () {
- launchUrlString(
- "https://opencollective.com/spotube",
- mode: LaunchMode.externalApplication,
- );
- },
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(SpotubeIcons.heart),
- const SizedBox(width: 5),
- Text(context.l10n.please_sponsor),
- ],
- ),
- ),
- ),
- if (Env.enableUpdateChecker)
- SwitchListTile(
- secondary: const Icon(SpotubeIcons.update),
- title: Text(context.l10n.check_for_updates),
- value: preferences.checkUpdate,
- onChanged: (checked) =>
- preferences.setCheckUpdate(checked),
- ),
- ListTile(
- leading: const Icon(SpotubeIcons.info),
- title: Text(context.l10n.about_spotube),
- trailing: const Icon(SpotubeIcons.angleRight),
- onTap: () {
- GoRouter.of(context).push("/settings/about");
- },
- )
- ],
- ),
+ const SettingsDesktopSection(),
+ if (!kIsWeb) const SettingsDevelopersSection(),
+ const SettingsAboutSection(),
Center(
child: FilledButton(
onPressed: preferences.reset,
diff --git a/lib/utils/persisted_state_notifier.dart b/lib/utils/persisted_state_notifier.dart
index 2937bff9..218cd64a 100644
--- a/lib/utils/persisted_state_notifier.dart
+++ b/lib/utils/persisted_state_notifier.dart
@@ -59,32 +59,32 @@ abstract class PersistedStateNotifier extends StateNotifier {
static Future read(String key) async {
final localStorage = await SharedPreferences.getInstance();
- if (kIsMacOS || kIsIOS) {
+ if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
+ return localStorage.getString(key);
+ }
+
+ try {
+ await localStorage.setBool(kIsUsingEncryption, true);
+ return await secureStorage.read(key: key);
+ } catch (e) {
+ await localStorage.setBool(kIsUsingEncryption, false);
return localStorage.getString(key);
- } else {
- try {
- await localStorage.setBool(kIsUsingEncryption, true);
- return await secureStorage.read(key: key);
- } catch (e) {
- await localStorage.setBool(kIsUsingEncryption, false);
- return localStorage.getString(key);
- }
}
}
static Future write(String key, String value) async {
final localStorage = await SharedPreferences.getInstance();
- if (kIsMacOS || kIsIOS) {
+ if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
await localStorage.setString(key, value);
return;
- } else {
- try {
- await localStorage.setBool(kIsUsingEncryption, true);
- await secureStorage.write(key: key, value: value);
- } catch (e) {
- await localStorage.setBool(kIsUsingEncryption, false);
- await localStorage.setString(key, value);
- }
+ }
+
+ try {
+ await localStorage.setBool(kIsUsingEncryption, true);
+ await secureStorage.write(key: key, value: value);
+ } catch (e) {
+ await localStorage.setBool(kIsUsingEncryption, false);
+ await localStorage.setString(key, value);
}
}
diff --git a/pubspec.lock b/pubspec.lock
index 15a50f41..9c0161c6 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -517,10 +517,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
- sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620"
+ sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
url: "https://pub.dev"
source: hosted
- version: "6.0.0"
+ version: "6.1.1"
file_selector:
dependency: "direct main"
description:
From 574406dd5fc410914b27e7fce374323696845012 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 12:11:06 +0600
Subject: [PATCH 10/53] fix(playbutton_card): annoying animation
---
lib/components/shared/playbutton_card.dart | 245 +++++++++---------
.../proxy_playlist/next_fetcher_mixin.dart | 1 -
2 files changed, 122 insertions(+), 124 deletions(-)
diff --git a/lib/components/shared/playbutton_card.dart b/lib/components/shared/playbutton_card.dart
index c9daa267..91c185c7 100644
--- a/lib/components/shared/playbutton_card.dart
+++ b/lib/components/shared/playbutton_card.dart
@@ -8,7 +8,6 @@ import 'package:spotube/components/shared/hover_builder.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
-import 'package:spotube/utils/platform.dart';
final htmlTagRegexp = RegExp(r"<[^>]*>", caseSensitive: true);
@@ -59,9 +58,9 @@ class PlaybuttonCard extends HookWidget {
);
final end = useBreakpointValue(
- xs: 15,
- sm: 15,
- others: 20,
+ xs: 10,
+ sm: 10,
+ others: 15,
);
final textsHeight = useState(
@@ -84,28 +83,29 @@ class PlaybuttonCard extends HookWidget {
return null;
}, [textsKey]);
- return Stack(
- children: [
- Container(
- constraints: BoxConstraints(maxWidth: size),
- margin: margin,
- child: Material(
- color: Color.lerp(
- theme.colorScheme.surfaceVariant,
- theme.colorScheme.surface,
- useBrightnessValue(.9, .7),
- ),
- borderRadius: radius,
- shadowColor: theme.colorScheme.background,
- elevation: 3,
- child: InkWell(
- mouseCursor: SystemMouseCursors.click,
- onTap: onTap,
- borderRadius: radius,
- splashFactory: theme.splashFactory,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
+ return Container(
+ constraints: BoxConstraints(maxWidth: size),
+ margin: margin,
+ child: Material(
+ color: Color.lerp(
+ theme.colorScheme.surfaceVariant,
+ theme.colorScheme.surface,
+ useBrightnessValue(.9, .7),
+ ),
+ borderRadius: radius,
+ shadowColor: theme.colorScheme.background,
+ elevation: 3,
+ child: InkWell(
+ mouseCursor: SystemMouseCursors.click,
+ onTap: onTap,
+ borderRadius: radius,
+ splashFactory: theme.splashFactory,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Stack(
+ clipBehavior: Clip.none,
children: [
Padding(
padding: const EdgeInsets.only(
@@ -121,115 +121,114 @@ class PlaybuttonCard extends HookWidget {
),
),
),
- Column(
- key: textsKey,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(height: 15),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12.0),
- child: AutoSizeText(
- title,
- maxLines: 1,
- minFontSize: theme.textTheme.bodyMedium!.fontSize!,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- if (cleanDescription != null)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12.0),
- child: AutoSizeText(
- cleanDescription,
- maxLines: 2,
- style: theme.textTheme.bodySmall?.copyWith(
- color:
- theme.colorScheme.onSurface.withOpacity(.5),
+ if (isOwner)
+ Positioned(
+ top: 15,
+ left: 15,
+ child: AnimatedSize(
+ duration: const Duration(milliseconds: 150),
+ alignment: Alignment.centerLeft,
+ curve: Curves.easeInExpo,
+ child: HoverBuilder(builder: (context, isHovered) {
+ return Container(
+ padding: const EdgeInsets.all(4),
+ decoration: BoxDecoration(
+ color: Colors.blueAccent,
+ borderRadius: BorderRadius.circular(20),
),
- overflow: TextOverflow.ellipsis,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Icon(
+ SpotubeIcons.user,
+ color: Colors.white,
+ size: 16,
+ ),
+ if (isHovered)
+ Text(
+ "Owned by you",
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: Colors.white,
+ ),
+ ),
+ ],
+ ),
+ );
+ }),
+ ),
+ ),
+ Positioned(
+ right: end,
+ bottom: -15,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (!isPlaying)
+ IconButton(
+ style: IconButton.styleFrom(
+ backgroundColor: theme.colorScheme.background,
+ foregroundColor: theme.colorScheme.primary,
+ minimumSize: const Size.square(10),
+ ),
+ icon: const Icon(SpotubeIcons.queueAdd),
+ onPressed: isLoading ? null : onAddToQueuePressed,
),
+ const SizedBox(height: 5),
+ IconButton(
+ style: IconButton.styleFrom(
+ backgroundColor: theme.colorScheme.primaryContainer,
+ foregroundColor: theme.colorScheme.primary,
+ minimumSize: const Size.square(10),
+ ),
+ icon: isLoading
+ ? SizedBox.fromSize(
+ size: const Size.square(15),
+ child: const CircularProgressIndicator(
+ strokeWidth: 2),
+ )
+ : isPlaying
+ ? const Icon(SpotubeIcons.pause)
+ : const Icon(SpotubeIcons.play),
+ onPressed: isLoading ? null : onPlaybuttonPressed,
),
- const SizedBox(height: 10),
- ],
+ ],
+ ),
),
],
),
- ),
- ),
- ),
- if (isOwner)
- Positioned(
- top: 15,
- left: 25,
- child: AnimatedSize(
- duration: const Duration(milliseconds: 150),
- alignment: Alignment.centerLeft,
- curve: Curves.easeInExpo,
- child: HoverBuilder(builder: (context, isHovered) {
- return Container(
- padding: const EdgeInsets.all(4),
- decoration: BoxDecoration(
- color: Colors.blueAccent,
- borderRadius: BorderRadius.circular(20),
+ Column(
+ key: textsKey,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 15),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: AutoSizeText(
+ title,
+ maxLines: 1,
+ minFontSize: theme.textTheme.bodyMedium!.fontSize!,
+ overflow: TextOverflow.ellipsis,
+ ),
),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(
- SpotubeIcons.user,
- color: Colors.white,
- size: 16,
- ),
- if (isHovered)
- Text(
- "Owned by you",
- style: theme.textTheme.bodySmall?.copyWith(
- color: Colors.white,
- ),
+ if (cleanDescription != null)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: AutoSizeText(
+ cleanDescription,
+ maxLines: 2,
+ style: theme.textTheme.bodySmall?.copyWith(
+ color: theme.colorScheme.onSurface.withOpacity(.5),
),
- ],
- ),
- );
- }),
- ),
- ),
- AnimatedPositioned(
- duration: const Duration(milliseconds: 300),
- right: end,
- bottom: textsHeight.value - (kIsMobile ? 5 : 10),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (!isPlaying)
- IconButton(
- style: IconButton.styleFrom(
- backgroundColor: theme.colorScheme.background,
- foregroundColor: theme.colorScheme.primary,
- minimumSize: const Size.square(10),
- ),
- icon: const Icon(SpotubeIcons.queueAdd),
- onPressed: isLoading ? null : onAddToQueuePressed,
- ),
- const SizedBox(height: 5),
- IconButton(
- style: IconButton.styleFrom(
- backgroundColor: theme.colorScheme.primaryContainer,
- foregroundColor: theme.colorScheme.primary,
- minimumSize: const Size.square(10),
- ),
- icon: isLoading
- ? SizedBox.fromSize(
- size: const Size.square(15),
- child: const CircularProgressIndicator(strokeWidth: 2),
- )
- : isPlaying
- ? const Icon(SpotubeIcons.pause)
- : const Icon(SpotubeIcons.play),
- onPressed: isLoading ? null : onPlaybuttonPressed,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ const SizedBox(height: 10),
+ ],
),
],
),
),
- ],
+ ),
);
}
}
diff --git a/lib/provider/proxy_playlist/next_fetcher_mixin.dart b/lib/provider/proxy_playlist/next_fetcher_mixin.dart
index 61b86d8c..f6776234 100644
--- a/lib/provider/proxy_playlist/next_fetcher_mixin.dart
+++ b/lib/provider/proxy_playlist/next_fetcher_mixin.dart
@@ -1,5 +1,4 @@
import 'package:collection/collection.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/local_track.dart';
From 1d77556157d158600f29cf2ea5f26c567607dec7 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 12:26:27 +0600
Subject: [PATCH 11/53] fix: check for unsynced lyrics and error handling for
timed lyrics query
---
lib/pages/lyrics/synced_lyrics.dart | 49 ++++++++++++++++++++++++++---
1 file changed, 45 insertions(+), 4 deletions(-)
diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart
index dab2103d..5f2afbc9 100644
--- a/lib/pages/lyrics/synced_lyrics.dart
+++ b/lib/pages/lyrics/synced_lyrics.dart
@@ -45,6 +45,11 @@ class SyncedLyrics extends HookConsumerWidget {
final lyricValue = timedLyricsQuery.data;
+ final isUnSyncLyric = useMemoized(
+ () => lyricValue?.lyrics.every((l) => l.time == Duration.zero),
+ [lyricValue],
+ );
+
final lyricsMap = useMemoized(
() =>
lyricValue?.lyrics
@@ -72,6 +77,9 @@ class SyncedLyrics extends HookConsumerWidget {
: textTheme.headlineMedium?.copyWith(fontSize: 25))
?.copyWith(color: palette.titleTextColor);
+ var bodyTextTheme = textTheme.bodyLarge?.copyWith(
+ color: palette.bodyTextColor,
+ );
return Stack(
children: [
Column(
@@ -93,7 +101,9 @@ class SyncedLyrics extends HookConsumerWidget {
: textTheme.titleLarge,
),
),
- if (lyricValue != null && lyricValue.lyrics.isNotEmpty)
+ if (lyricValue != null &&
+ lyricValue.lyrics.isNotEmpty &&
+ isUnSyncLyric == false)
Expanded(
child: ListView.builder(
controller: controller,
@@ -102,7 +112,7 @@ class SyncedLyrics extends HookConsumerWidget {
final lyricSlice = lyricValue.lyrics[index];
final isActive = lyricSlice.time.inSeconds == currentTime;
- if (isActive) {
+ if (isActive && isUnSyncLyric == true) {
controller.scrollToIndex(
index,
preferPosition: AutoScrollPosition.middle,
@@ -173,8 +183,39 @@ class SyncedLyrics extends HookConsumerWidget {
),
),
if (playlist.activeTrack != null &&
- (lyricValue == null || lyricValue.lyrics.isEmpty == true))
- const Expanded(child: ShimmerLyrics()),
+ (timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing))
+ const Expanded(child: ShimmerLyrics())
+ else if (playlist.activeTrack != null &&
+ (timedLyricsQuery.hasError))
+ Text(
+ "Sorry, no Lyrics were found for `${playlist.activeTrack?.name}` :'(\n${timedLyricsQuery.error.toString()}",
+ style: bodyTextTheme,
+ )
+ else if (isUnSyncLyric == true)
+ Expanded(
+ child: Center(
+ child: RichText(
+ textAlign: TextAlign.center,
+ text: TextSpan(
+ style: bodyTextTheme,
+ children: [
+ const TextSpan(
+ text:
+ "Synced lyrics is not available for this song. Please use the",
+ ),
+ TextSpan(
+ text: " Plain Lyrics ",
+ style: textTheme.bodyLarge?.copyWith(
+ color: palette.bodyTextColor,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const TextSpan(text: "tab instead."),
+ ],
+ ),
+ ),
+ ),
+ ),
],
),
Align(
From 5633367397812148f6d712d06e97a4f84033f968 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 12:32:21 +0600
Subject: [PATCH 12/53] fix(album_card): show loading state during adding track
to queue/play
---
lib/components/album/album_card.dart | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/components/album/album_card.dart b/lib/components/album/album_card.dart
index d8f8d85b..93b4cefc 100644
--- a/lib/components/album/album_card.dart
+++ b/lib/components/album/album_card.dart
@@ -51,7 +51,8 @@ class AlbumCard extends HookConsumerWidget {
),
margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()),
isPlaying: isPlaylistPlaying,
- isLoading: isPlaylistPlaying && playlist.isFetching == true,
+ isLoading: (isPlaylistPlaying && playlist.isFetching == true) ||
+ updating.value,
title: album.name!,
description:
"${album.albumType?.formatted} • ${TypeConversionUtils.artists_X_String(album.artists ?? [])}",
@@ -92,7 +93,7 @@ class AlbumCard extends HookConsumerWidget {
"album-tracks/${album.id}",
() {
return spotify.albums
- .getTracks(album.id!)
+ .tracks(album.id!)
.all()
.then((value) => value.toList());
},
From 487c2ed6bdc4af33006ba52532eb4eaaa261dceb Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 14:41:15 +0600
Subject: [PATCH 13/53] fix: user_playlists layout, track tile index,
---
lib/components/genre/category_card.dart | 69 ++++++++------
lib/components/library/user_playlists.dart | 94 ++++++++++---------
.../shared/track_table/track_tile.dart | 2 +-
lib/pages/home/personalized.dart | 68 ++++++++------
4 files changed, 133 insertions(+), 100 deletions(-)
diff --git a/lib/components/genre/category_card.dart b/lib/components/genre/category_card.dart
index 1aa33cd6..a8d67771 100644
--- a/lib/components/genre/category_card.dart
+++ b/lib/components/genre/category_card.dart
@@ -1,6 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart' hide Page;
+import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -8,6 +9,7 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/waypoint.dart';
+import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/services/queries/queries.dart';
@@ -28,16 +30,23 @@ class CategoryCard extends HookConsumerWidget {
category.id!,
);
+ final playlists = useMemoized(
+ () => playlistQuery.pages.expand(
+ (page) {
+ return page.items?.where((i) => i != null) ?? const Iterable.empty();
+ },
+ ).toList(),
+ [playlistQuery.pages],
+ );
+
if (playlistQuery.hasErrors &&
!playlistQuery.hasPageData &&
!playlistQuery.isLoadingNextPage) {
return const SizedBox.shrink();
}
- final playlists = playlistQuery.pages.expand(
- (page) {
- return page.items?.where((i) => i != null) ?? const Iterable.empty();
- },
- ).toList();
+
+ final mediaQuery = MediaQuery.of(context);
+
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@@ -48,29 +57,35 @@ class CategoryCard extends HookConsumerWidget {
category.name!,
style: Theme.of(context).textTheme.titleMedium,
),
- ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.touch,
- PointerDeviceKind.mouse,
- },
- ),
- child: Waypoint(
- controller: scrollController,
- onTouchEdge: playlistQuery.fetchNext,
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- controller: scrollController,
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- ...playlists.map((playlist) => PlaylistCard(playlist)),
- if (playlistQuery.hasNextPage)
- const ShimmerPlaybuttonCard(count: 1),
- ],
- ),
+ SizedBox(
+ height: mediaQuery.smAndDown ? 226 : 266,
+ child: ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ dragDevices: {
+ PointerDeviceKind.touch,
+ PointerDeviceKind.mouse,
+ },
),
+ child: ListView.builder(
+ controller: scrollController,
+ scrollDirection: Axis.horizontal,
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ itemCount: playlists.length + 1,
+ itemBuilder: (context, index) {
+ if (index == playlists.length) {
+ if (!playlistQuery.hasNextPage) {
+ return const SizedBox.shrink();
+ }
+ return Waypoint(
+ controller: scrollController,
+ onTouchEdge: playlistQuery.fetchNext,
+ isGrid: true,
+ child: const ShimmerPlaybuttonCard(),
+ );
+ }
+ final playlist = playlists[index];
+ return PlaylistCard(playlist);
+ }),
),
),
],
diff --git a/lib/components/library/user_playlists.dart b/lib/components/library/user_playlists.dart
index 8ed3e73d..ecf4fa12 100644
--- a/lib/components/library/user_playlists.dart
+++ b/lib/components/library/user_playlists.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart' hide Image;
+import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:collection/collection.dart';
@@ -8,7 +9,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
-import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/playlist/playlist_card.dart';
@@ -80,20 +80,13 @@ class UserPlaylists extends HookConsumerWidget {
return RefreshIndicator(
onRefresh: playlistsQuery.refresh,
- child: InterScrollbar(
- controller: controller,
- child: SingleChildScrollView(
+ child: SafeArea(
+ child: CustomScrollView(
controller: controller,
- physics: const AlwaysScrollableScrollPhysics(),
- child: Waypoint(
- controller: controller,
- onTouchEdge: () {
- if (playlistsQuery.hasNextPage) {
- playlistsQuery.fetchNext();
- }
- },
- child: SafeArea(
+ slivers: [
+ SliverToBoxAdapter(
child: Column(
+ mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(10),
@@ -103,42 +96,53 @@ class UserPlaylists extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.filter),
),
),
- AnimatedCrossFade(
- duration: const Duration(milliseconds: 300),
- crossFadeState: !playlistsQuery.hasPageData &&
- !playlistsQuery.hasPageError &&
- !playlistsQuery.isLoadingNextPage
- ? CrossFadeState.showFirst
- : CrossFadeState.showSecond,
- firstChild:
- const Center(child: ShimmerPlaybuttonCard(count: 7)),
- secondChild: Wrap(
- runSpacing: 10,
- alignment: WrapAlignment.center,
- children: [
- Row(
- children: [
- const SizedBox(width: 10),
- const PlaylistCreateDialogButton(),
- const SizedBox(width: 10),
- ElevatedButton.icon(
- icon: const Icon(SpotubeIcons.magic),
- label: Text(context.l10n.generate_playlist),
- onPressed: () {
- GoRouter.of(context).push("/library/generate");
- },
- ),
- const SizedBox(width: 10),
- ],
- ),
- ...playlists.map((playlist) => PlaylistCard(playlist))
- ],
- ),
+ Row(
+ children: [
+ const SizedBox(width: 10),
+ const PlaylistCreateDialogButton(),
+ const SizedBox(width: 10),
+ ElevatedButton.icon(
+ icon: const Icon(SpotubeIcons.magic),
+ label: Text(context.l10n.generate_playlist),
+ onPressed: () {
+ GoRouter.of(context).push("/library/generate");
+ },
+ ),
+ const SizedBox(width: 10),
+ ],
),
],
),
),
- ),
+ const SliverToBoxAdapter(
+ child: SizedBox(height: 10),
+ ),
+ SliverGrid.builder(
+ itemCount: playlists.length + 1,
+ gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
+ maxCrossAxisExtent: 200,
+ mainAxisExtent: DesktopTools.platform.isMobile ? 225 : 250,
+ crossAxisSpacing: 8,
+ mainAxisSpacing: 8,
+ ),
+ itemBuilder: (context, index) {
+ if (index == playlists.length) {
+ if (!playlistsQuery.hasNextPage) {
+ return const SizedBox.shrink();
+ }
+
+ return Waypoint(
+ controller: controller,
+ isGrid: true,
+ onTouchEdge: playlistsQuery.fetchNext,
+ child: const ShimmerPlaybuttonCard(count: 1),
+ );
+ }
+
+ return PlaylistCard(playlists[index]);
+ },
+ )
+ ],
),
),
);
diff --git a/lib/components/shared/track_table/track_tile.dart b/lib/components/shared/track_table/track_tile.dart
index ff1b314b..4980f96b 100644
--- a/lib/components/shared/track_table/track_tile.dart
+++ b/lib/components/shared/track_table/track_tile.dart
@@ -113,7 +113,7 @@ class TrackTile extends HookConsumerWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Text(
- '$index',
+ '${(index ?? 0) + 1}',
maxLines: 1,
style: theme.textTheme.bodySmall,
textAlign: TextAlign.center,
diff --git a/lib/pages/home/personalized.dart b/lib/pages/home/personalized.dart
index f7e942be..4f0b655f 100644
--- a/lib/pages/home/personalized.dart
+++ b/lib/pages/home/personalized.dart
@@ -10,6 +10,7 @@ import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/waypoint.dart';
+import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/authentication_provider.dart';
@@ -38,6 +39,7 @@ class PersonalizedItemCard extends HookWidget {
@override
Widget build(BuildContext context) {
final scrollController = useScrollController();
+ final mediaQuery = MediaQuery.of(context);
return Padding(
padding: const EdgeInsets.all(8.0),
@@ -52,36 +54,48 @@ class PersonalizedItemCard extends HookWidget {
style: Theme.of(context).textTheme.titleLarge,
),
),
- ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.touch,
- PointerDeviceKind.mouse,
- },
- ),
- child: Scrollbar(
- controller: scrollController,
- interactive: false,
- child: Waypoint(
+ SizedBox(
+ height: mediaQuery.smAndDown ? 226 : 266,
+ child: ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ dragDevices: {
+ PointerDeviceKind.touch,
+ PointerDeviceKind.mouse,
+ },
+ ),
+ child: Scrollbar(
controller: scrollController,
- onTouchEdge: hasNextPage ? onFetchMore : null,
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- controller: scrollController,
- physics: const AlwaysScrollableScrollPhysics(),
+ interactive: false,
+ child: ListView.builder(
+ itemCount: (playlists?.length ?? albums?.length)! + 1,
padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- ...?playlists?.map((playlist) => PlaylistCard(playlist)),
- ...?albums?.map(
- (album) => AlbumCard(
- TypeConversionUtils.simpleAlbum_X_Album(album),
+ scrollDirection: Axis.horizontal,
+ itemBuilder: (context, index) {
+ if (index == (playlists?.length ?? albums?.length)!) {
+ if (!hasNextPage) return const SizedBox.shrink();
+
+ return Waypoint(
+ controller: scrollController,
+ onTouchEdge: onFetchMore,
+ isGrid: true,
+ child: const ShimmerPlaybuttonCard(count: 1),
+ );
+ }
+
+ final item = playlists == null
+ ? albums!.elementAt(index)
+ : playlists!.elementAt(index);
+
+ if (playlists == null) {
+ return AlbumCard(
+ TypeConversionUtils.simpleAlbum_X_Album(
+ item as AlbumSimple,
),
- ),
- if (hasNextPage) const ShimmerPlaybuttonCard(count: 1),
- ],
- ),
+ );
+ }
+
+ return PlaylistCard(item as PlaylistSimple);
+ },
),
),
),
From 6b8ae88db4105039c6cbd40bc032a45febab7f63 Mon Sep 17 00:00:00 2001
From: Kingkor Roy Tirtho
Date: Wed, 8 Nov 2023 17:07:20 +0600
Subject: [PATCH 14/53] refactor: horizontal playbutton layout to use ListView
and breakdown search page into sections
---
lib/components/album/album_card.dart | 10 +-
lib/components/artist/artist_album_list.dart | 49 +--
lib/components/genre/category_card.dart | 63 +---
.../horizontal_playbutton_card_view.dart | 96 ++++++
lib/pages/artist/artist.dart | 7 -
lib/pages/home/genres.dart | 18 +-
lib/pages/home/personalized.dart | 125 +------
lib/pages/search/search.dart | 308 ++----------------
lib/pages/search/sections/albums.dart | 39 +++
lib/pages/search/sections/artists.dart | 37 +++
lib/pages/search/sections/playlists.dart | 35 ++
lib/pages/search/sections/tracks.dart | 98 ++++++
12 files changed, 374 insertions(+), 511 deletions(-)
create mode 100644 lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart
create mode 100644 lib/pages/search/sections/albums.dart
create mode 100644 lib/pages/search/sections/artists.dart
create mode 100644 lib/pages/search/sections/playlists.dart
create mode 100644 lib/pages/search/sections/tracks.dart
diff --git a/lib/components/album/album_card.dart b/lib/components/album/album_card.dart
index 93b4cefc..945f8ecf 100644
--- a/lib/components/album/album_card.dart
+++ b/lib/components/album/album_card.dart
@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/playbutton_card.dart';
-import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
@@ -34,13 +33,6 @@ class AlbumCard extends HookConsumerWidget {
[playlist, album.id],
);
- final marginH = useBreakpointValue(
- xs: 10,
- sm: 10,
- md: 15,
- others: 20,
- );
-
final updating = useState(false);
final spotify = ref.watch(spotifyProvider);
@@ -49,7 +41,7 @@ class AlbumCard extends HookConsumerWidget {
album.images,
placeholder: ImagePlaceholder.collection,
),
- margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()),
+ margin: const EdgeInsets.symmetric(horizontal: 10),
isPlaying: isPlaylistPlaying,
isLoading: (isPlaylistPlaying && playlist.isFetching == true) ||
updating.value,
diff --git a/lib/components/artist/artist_album_list.dart b/lib/components/artist/artist_album_list.dart
index 8fa9be87..e075cd60 100644
--- a/lib/components/artist/artist_album_list.dart
+++ b/lib/components/artist/artist_album_list.dart
@@ -1,11 +1,9 @@
-import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
-import 'package:spotube/components/album/album_card.dart';
-import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
-import 'package:spotube/components/shared/waypoint.dart';
+import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
+import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/services/queries/queries.dart';
@@ -20,7 +18,6 @@ class ArtistAlbumList extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
- final scrollController = useScrollController();
final albumsQuery = useQueries.artist.albumsOf(ref, artistId);
final albums = useMemoized(() {
@@ -29,40 +26,16 @@ class ArtistAlbumList extends HookConsumerWidget {
.toList();
}, [albumsQuery.pages]);
- final hasNextPage = albumsQuery.pages.isEmpty
- ? false
- : (albumsQuery.pages.last.items?.length ?? 0) == 5;
+ final theme = Theme.of(context);
- return Column(
- children: [
- ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.touch,
- PointerDeviceKind.mouse,
- },
- ),
- child: Scrollbar(
- interactive: false,
- controller: scrollController,
- child: Waypoint(
- controller: scrollController,
- onTouchEdge: albumsQuery.fetchNext,
- child: SingleChildScrollView(
- controller: scrollController,
- scrollDirection: Axis.horizontal,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- ...albums.map((album) => AlbumCard(album)),
- if (hasNextPage) const ShimmerPlaybuttonCard(count: 1),
- ],
- ),
- ),
- ),
- ),
- ),
- ],
+ return HorizontalPlaybuttonCardView(
+ hasNextPage: albumsQuery.hasNextPage,
+ items: albums,
+ onFetchMore: albumsQuery.fetchNext,
+ title: Text(
+ context.l10n.albums,
+ style: theme.textTheme.headlineSmall,
+ ),
);
}
}
diff --git a/lib/components/genre/category_card.dart b/lib/components/genre/category_card.dart
index a8d67771..d5809b5d 100644
--- a/lib/components/genre/category_card.dart
+++ b/lib/components/genre/category_card.dart
@@ -1,15 +1,10 @@
-import 'dart:ui';
-
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart' hide Page;
-import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
-import 'package:spotube/components/playlist/playlist_card.dart';
-import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
-import 'package:spotube/components/shared/waypoint.dart';
-import 'package:spotube/extensions/constrains.dart';
+import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/services/queries/queries.dart';
@@ -24,7 +19,6 @@ class CategoryCard extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
- final scrollController = useScrollController();
final playlistQuery = useQueries.category.playlistsOf(
ref,
category.id!,
@@ -33,7 +27,8 @@ class CategoryCard extends HookConsumerWidget {
final playlists = useMemoized(
() => playlistQuery.pages.expand(
(page) {
- return page.items?.where((i) => i != null) ?? const Iterable.empty();
+ return page.items?.whereNotNull() ??
+ const Iterable.empty();
},
).toList(),
[playlistQuery.pages],
@@ -45,51 +40,11 @@ class CategoryCard extends HookConsumerWidget {
return const SizedBox.shrink();
}
- final mediaQuery = MediaQuery.of(context);
-
- return Padding(
- padding: const EdgeInsets.all(8.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- category.name!,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- SizedBox(
- height: mediaQuery.smAndDown ? 226 : 266,
- child: ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.touch,
- PointerDeviceKind.mouse,
- },
- ),
- child: ListView.builder(
- controller: scrollController,
- scrollDirection: Axis.horizontal,
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- itemCount: playlists.length + 1,
- itemBuilder: (context, index) {
- if (index == playlists.length) {
- if (!playlistQuery.hasNextPage) {
- return const SizedBox.shrink();
- }
- return Waypoint(
- controller: scrollController,
- onTouchEdge: playlistQuery.fetchNext,
- isGrid: true,
- child: const ShimmerPlaybuttonCard(),
- );
- }
- final playlist = playlists[index];
- return PlaylistCard(playlist);
- }),
- ),
- ),
- ],
- ),
+ return HorizontalPlaybuttonCardView(
+ title: Text(category.name!),
+ hasNextPage: playlistQuery.hasNextPage,
+ items: playlists,
+ onFetchMore: playlistQuery.fetchNext,
);
}
}
diff --git a/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart
new file mode 100644
index 00000000..a415d721
--- /dev/null
+++ b/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart
@@ -0,0 +1,96 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:spotify/spotify.dart';
+import 'package:spotube/components/album/album_card.dart';
+import 'package:spotube/components/artist/artist_card.dart';
+import 'package:spotube/components/playlist/playlist_card.dart';
+import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
+import 'package:spotube/components/shared/waypoint.dart';
+import 'package:spotube/hooks/use_breakpoint_value.dart';
+
+class HorizontalPlaybuttonCardView extends HookWidget {
+ final Widget title;
+ final List items;
+ final VoidCallback onFetchMore;
+ final bool hasNextPage;
+ const HorizontalPlaybuttonCardView({
+ required this.title,
+ required this.items,
+ required this.hasNextPage,
+ required this.onFetchMore,
+ Key? key,
+ }) : assert(
+ items is List ||
+ items is List ||
+ items is List,
+ ),
+ super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final ThemeData(:textTheme) = Theme.of(context);
+ final scrollController = useScrollController();
+ final height = useBreakpointValue(
+ xs: 226,
+ sm: 226,
+ md: 236,
+ others: 266,
+ );
+
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ DefaultTextStyle(
+ style: textTheme.titleMedium!,
+ child: title,
+ ),
+ SizedBox(
+ height: height,
+ child: ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ dragDevices: {
+ PointerDeviceKind.touch,
+ PointerDeviceKind.mouse,
+ },
+ ),
+ child: ListView.builder(
+ controller: scrollController,
+ scrollDirection: Axis.horizontal,
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ itemCount: items.length + 1,
+ itemBuilder: (context, index) {
+ if (index == items.length) {
+ if (!hasNextPage) {
+ return const SizedBox.shrink();
+ }
+ return Waypoint(
+ controller: scrollController,
+ onTouchEdge: onFetchMore,
+ isGrid: true,
+ child: const ShimmerPlaybuttonCard(),
+ );
+ }
+ final item = items[index];
+
+ return switch (item.runtimeType) {
+ PlaylistSimple => PlaylistCard(item as PlaylistSimple),
+ Album => AlbumCard(item as Album),
+ Artist => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: ArtistCard(item as Artist),
+ ),
+ _ => const SizedBox.shrink(),
+ };
+ }),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart
index 67a99d86..2f169583 100644
--- a/lib/pages/artist/artist.dart
+++ b/lib/pages/artist/artist.dart
@@ -410,13 +410,6 @@ class ArtistPage extends HookConsumerWidget {
},
),
const SizedBox(height: 50),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- context.l10n.albums,
- style: theme.textTheme.headlineSmall,
- ),
- ),
ArtistAlbumList(artistId),
const SizedBox(height: 20),
Padding(
diff --git a/lib/pages/home/genres.dart b/lib/pages/home/genres.dart
index db1c58c5..076305f2 100644
--- a/lib/pages/home/genres.dart
+++ b/lib/pages/home/genres.dart
@@ -85,15 +85,19 @@ class GenrePage extends HookConsumerWidget {
controller: scrollController,
itemCount: categories.length,
itemBuilder: (context, index) {
- return AnimatedCrossFade(
- crossFadeState: searchController.text.isEmpty &&
+ return AnimatedSwitcher(
+ transitionBuilder: (child, animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: child,
+ );
+ },
+ duration: const Duration(milliseconds: 300),
+ child: searchController.text.isEmpty &&
index == categories.length - 1 &&
categoriesQuery.hasNextPage
- ? CrossFadeState.showFirst
- : CrossFadeState.showSecond,
- duration: const Duration(milliseconds: 300),
- firstChild: const ShimmerCategories(),
- secondChild: CategoryCard(categories[index]),
+ ? const ShimmerCategories()
+ : CategoryCard(categories[index]),
);
},
),
diff --git a/lib/pages/home/personalized.dart b/lib/pages/home/personalized.dart
index 4f0b655f..bbffbc11 100644
--- a/lib/pages/home/personalized.dart
+++ b/lib/pages/home/personalized.dart
@@ -1,111 +1,16 @@
-import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
-import 'package:spotube/components/album/album_card.dart';
-import 'package:spotube/components/playlist/playlist_card.dart';
+import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
-import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
-import 'package:spotube/components/shared/waypoint.dart';
-import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
-import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
-class PersonalizedItemCard extends HookWidget {
- final Iterable? playlists;
- final Iterable? albums;
- final String title;
- final bool hasNextPage;
- final void Function() onFetchMore;
-
- PersonalizedItemCard({
- this.playlists,
- this.albums,
- required this.title,
- required this.hasNextPage,
- required this.onFetchMore,
- Key? key,
- }) : assert(playlists == null || albums == null),
- super(key: key);
-
- final logger = getLogger(PersonalizedItemCard);
-
- @override
- Widget build(BuildContext context) {
- final scrollController = useScrollController();
- final mediaQuery = MediaQuery.of(context);
-
- return Padding(
- padding: const EdgeInsets.all(8.0),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Text(
- title,
- style: Theme.of(context).textTheme.titleLarge,
- ),
- ),
- SizedBox(
- height: mediaQuery.smAndDown ? 226 : 266,
- child: ScrollConfiguration(
- behavior: ScrollConfiguration.of(context).copyWith(
- dragDevices: {
- PointerDeviceKind.touch,
- PointerDeviceKind.mouse,
- },
- ),
- child: Scrollbar(
- controller: scrollController,
- interactive: false,
- child: ListView.builder(
- itemCount: (playlists?.length ?? albums?.length)! + 1,
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- scrollDirection: Axis.horizontal,
- itemBuilder: (context, index) {
- if (index == (playlists?.length ?? albums?.length)!) {
- if (!hasNextPage) return const SizedBox.shrink();
-
- return Waypoint(
- controller: scrollController,
- onTouchEdge: onFetchMore,
- isGrid: true,
- child: const ShimmerPlaybuttonCard(count: 1),
- );
- }
-
- final item = playlists == null
- ? albums!.elementAt(index)
- : playlists!.elementAt(index);
-
- if (playlists == null) {
- return AlbumCard(
- TypeConversionUtils.simpleAlbum_X_Album(
- item as AlbumSimple,
- ),
- );
- }
-
- return PlaylistCard(item as PlaylistSimple);
- },
- ),
- ),
- ),
- ),
- ],
- ),
- );
- }
-}
-
class PersonalizedPage extends HookConsumerWidget {
const PersonalizedPage({Key? key}) : super(key: key);
@@ -133,10 +38,12 @@ class PersonalizedPage extends HookConsumerWidget {
.whereType>()
.expand((page) => page.items ?? const [])
.where((album) {
- return album.artists
- ?.any((artist) => userArtists.contains(artist.id!)) ==
- true;
- }),
+ return album.artists
+ ?.any((artist) => userArtists.contains(artist.id!)) ==
+ true;
+ })
+ .map((album) => TypeConversionUtils.simpleAlbum_X_Album(album))
+ .toList(),
[newReleases.pages],
);
@@ -149,18 +56,18 @@ class PersonalizedPage extends HookConsumerWidget {
!featuredPlaylistsQuery.isLoadingNextPage)
const ShimmerCategories()
else
- PersonalizedItemCard(
- playlists: playlists,
- title: context.l10n.featured,
+ HorizontalPlaybuttonCardView(
+ items: playlists.toList(),
+ title: Text(context.l10n.featured),
hasNextPage: featuredPlaylistsQuery.hasNextPage,
onFetchMore: featuredPlaylistsQuery.fetchNext,
),
if (auth != null &&
newReleases.hasPageData &&
userArtistsQuery.hasData)
- PersonalizedItemCard(
- albums: albums,
- title: context.l10n.new_releases,
+ HorizontalPlaybuttonCardView(
+ items: albums,
+ title: Text(context.l10n.new_releases),
hasNextPage: newReleases.hasNextPage,
onFetchMore: newReleases.fetchNext,
),
@@ -172,9 +79,9 @@ class PersonalizedPage extends HookConsumerWidget {
.cast() ??
[];
if (playlists.isEmpty) return const SizedBox.shrink();
- return PersonalizedItemCard(
- playlists: playlists,
- title: item["name"] ?? "",
+ return HorizontalPlaybuttonCardView(
+ items: playlists,
+ title: Text(item["name"] ?? ""),
hasNextPage: false,
onFetchMore: () {},
);
diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart
index c192eb7b..d659e8e3 100644
--- a/lib/pages/search/search.dart
+++ b/lib/pages/search/search.dart
@@ -1,30 +1,24 @@
import 'dart:async';
-import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
-import 'package:spotube/components/album/album_card.dart';
-import 'package:spotube/components/shared/dialogs/prompt_dialog.dart';
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
-import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
-import 'package:spotube/components/shared/track_table/track_tile.dart';
-import 'package:spotube/components/shared/waypoint.dart';
-import 'package:spotube/components/artist/artist_card.dart';
-import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
+import 'package:spotube/pages/search/sections/albums.dart';
+import 'package:spotube/pages/search/sections/artists.dart';
+import 'package:spotube/pages/search/sections/playlists.dart';
+import 'package:spotube/pages/search/sections/tracks.dart';
import 'package:spotube/provider/authentication_provider.dart';
-import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/platform.dart';
-import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:collection/collection.dart';
final searchTermStateProvider = StateProvider((ref) => "");
@@ -38,9 +32,6 @@ class SearchPage extends HookConsumerWidget {
ref.watch(AuthenticationNotifier.provider);
final authenticationNotifier =
ref.watch(AuthenticationNotifier.provider.notifier);
- final albumController = useScrollController();
- final playlistController = useScrollController();
- final artistController = useScrollController();
final mediaQuery = MediaQuery.of(context);
final searchTerm = ref.watch(searchTermStateProvider);
@@ -80,283 +71,26 @@ class SearchPage extends HookConsumerWidget {
searchTerm.isNotEmpty;
final resultWidget = HookBuilder(
- builder: (context) {
- final playlist = ref.watch(ProxyPlaylistNotifier.provider);
- final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
- List albums = [];
- List artists = [];
- List