From b8ffb9b75f27076060104aedabe0ed15e11bdce0 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 12 Jan 2025 14:16:18 +0600 Subject: [PATCH] feat: show placeholder images where there is no item or on empty page --- lib/collections/assets.gen.dart | 13 ++ .../fallbacks/anonymous_fallback.dart | 12 +- .../playbutton_view/playbutton_view.dart | 100 +++++++--- .../track_presentation/presentation_list.dart | 27 +++ lib/l10n/app_en.arb | 7 +- lib/modules/getting_started/blur_card.dart | 7 +- lib/modules/home/sections/featured.dart | 17 ++ .../home/sections/genres/genre_card.dart | 29 +-- lib/modules/library/user_albums.dart | 52 +++-- lib/modules/library/user_artists.dart | 107 ++++++---- lib/modules/stats/top/albums.dart | 22 ++- lib/modules/stats/top/artists.dart | 22 ++- lib/modules/stats/top/tracks.dart | 22 ++- lib/pages/album/album.dart | 2 +- .../getting_started/getting_started.dart | 70 +++---- .../getting_started/sections/greeting.dart | 30 +-- .../getting_started/sections/playback.dart | 108 +++++------ .../getting_started/sections/region.dart | 162 +++++++++------- .../getting_started/sections/support.dart | 116 ++++++----- lib/pages/playlist/playlist.dart | 2 +- lib/pages/search/search.dart | 19 +- pubspec.lock | 30 +-- pubspec.yaml | 10 + untranslated_messages.json | 182 +++++++++++++++--- 24 files changed, 765 insertions(+), 403 deletions(-) diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index 004001f2..98b67c80 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -20,6 +20,17 @@ class $AssetsBackgroundsGen { List get values => [xmasEffect]; } +class $AssetsIllustrationsGen { + const $AssetsIllustrationsGen(); + + /// File path: assets/illustrations/fixing_bugs.png + AssetGenImage get fixingBugs => + const AssetGenImage('assets/illustrations/fixing_bugs.png'); + + /// List of all assets + List get values => [fixingBugs]; +} + class $AssetsLogosGen { const $AssetsLogosGen(); @@ -140,6 +151,8 @@ class Assets { AssetGenImage('assets/bengali-patterns-bg.jpg'); static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); + static const $AssetsIllustrationsGen illustrations = + $AssetsIllustrationsGen(); static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg'); static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png'); static const AssetGenImage likedTracks = diff --git a/lib/components/fallbacks/anonymous_fallback.dart b/lib/components/fallbacks/anonymous_fallback.dart index cd7a218f..373e0454 100644 --- a/lib/components/fallbacks/anonymous_fallback.dart +++ b/lib/components/fallbacks/anonymous_fallback.dart @@ -1,9 +1,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/pages/settings/settings.dart'; import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/service_utils.dart'; class AnonymousFallback extends ConsumerWidget { @@ -25,9 +28,16 @@ class AnonymousFallback extends ConsumerWidget { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, + spacing: 10, children: [ + Undraw( + illustration: kIsMobile + ? UndrawIllustration.accessDenied + : UndrawIllustration.secureLogin, + height: 200 * context.theme.scaling, + color: context.theme.colorScheme.primary, + ), Text(context.l10n.not_logged_in), - const SizedBox(height: 10), Button.primary( child: Text(context.l10n.login_with_spotify), onPressed: () => ServiceUtils.pushNamed(context, SettingsPage.name), diff --git a/lib/components/playbutton_view/playbutton_view.dart b/lib/components/playbutton_view/playbutton_view.dart index dd8dc371..46e67e25 100644 --- a/lib/components/playbutton_view/playbutton_view.dart +++ b/lib/components/playbutton_view/playbutton_view.dart @@ -1,4 +1,5 @@ import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; @@ -7,6 +8,7 @@ import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/components/waypoint.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; const _dummyPlaybuttonCard = PlaybuttonCard( @@ -99,38 +101,59 @@ class PlaybuttonView extends StatelessWidget { const SliverGap(10), // Toggle between grid and list view switch ((isGrid.value, isLoading)) { - (true, _) => SliverGrid.builder( - itemCount: isLoading ? 6 : itemCount + 1, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 150 * scale, - mainAxisExtent: 225 * scale, - crossAxisSpacing: 12 * scale, - mainAxisSpacing: 12 * scale, - ), - itemBuilder: (context, index) { - if (isLoading) { - return const Skeletonizer( - enabled: true, - child: _dummyPlaybuttonCard, - ); - } - - if (index == itemCount) { - if (!hasMore) return const SizedBox.shrink(); - return Waypoint( - controller: controller, - isGrid: true, - onTouchEdge: onRequestMore, - child: const Skeletonizer( - enabled: true, - child: _dummyPlaybuttonCard, + (true, _) => !isLoading && itemCount == 0 + ? SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 8), + sliver: SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.taken, + color: Theme.of(context).colorScheme.primary, + ), + Text( + context.l10n.nothing_found, + textAlign: TextAlign.center, + ).muted().small() + ], ), - ); - } + ), + ) + : SliverGrid.builder( + itemCount: isLoading ? 6 : itemCount + 1, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150 * scale, + mainAxisExtent: 225 * scale, + crossAxisSpacing: 12 * scale, + mainAxisSpacing: 12 * scale, + ), + itemBuilder: (context, index) { + if (isLoading) { + return const Skeletonizer( + enabled: true, + child: _dummyPlaybuttonCard, + ); + } - return gridItemBuilder(context, index); - }, - ), + if (index == itemCount) { + if (!hasMore) return const SizedBox.shrink(); + return Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: onRequestMore, + child: const Skeletonizer( + enabled: true, + child: _dummyPlaybuttonCard, + ), + ); + } + + return gridItemBuilder(context, index); + }, + ), (false, true) => Skeletonizer.sliver( enabled: true, child: SliverList( @@ -150,6 +173,23 @@ class PlaybuttonView extends StatelessWidget { onFetchData: onRequestMore, hasReachedMax: !hasMore, isLoading: isLoading, + emptyBuilder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.taken, + color: Theme.of(context).colorScheme.primary, + ), + Text( + context.l10n.nothing_found, + textAlign: TextAlign.center, + ).muted().small() + ], + ); + }, ), } ], diff --git a/lib/components/track_presentation/presentation_list.dart b/lib/components/track_presentation/presentation_list.dart index 55b4c46d..dda7dffa 100644 --- a/lib/components/track_presentation/presentation_list.dart +++ b/lib/components/track_presentation/presentation_list.dart @@ -1,6 +1,8 @@ import 'package:flutter/services.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; @@ -8,6 +10,7 @@ import 'package:spotube/components/track_presentation/presentation_state.dart'; import 'package:spotube/components/track_presentation/use_track_tile_play_callback.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/components/track_presentation/use_is_user_playlist.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -25,6 +28,30 @@ class PresentationListSection extends HookConsumerWidget { final onTileTap = useTrackTilePlayCallback(ref); + if (state.presentationTracks.isEmpty && !options.pagination.isLoading) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Undraw( + illustration: UndrawIllustration.dreamer, + color: context.theme.colorScheme.primary, + height: 200 * context.theme.scaling, + ), + Text( + isUserPlaylist + ? context.l10n.no_tracks_added_yet + : context.l10n.no_tracks, + textAlign: TextAlign.center, + ).muted().small(), + ], + ), + ), + ); + } + return SliverInfiniteList( isLoading: options.pagination.isLoading, onFetchData: options.pagination.onFetchMore, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c95ec54f..9a0f1814 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -409,5 +409,10 @@ "add_all_to_queue": "Add all to queue", "play_all_next": "Play all next", "pause": "Pause", - "view_all": "View all" + "view_all": "View all", + "no_tracks_added_yet": "Looks like you haven't added any tracks yet", + "no_tracks": "Looks like there are no tracks here", + "no_tracks_listened_yet": "Looks like you haven't listened to anything yet", + "not_following_artists": "You're not following any artists", + "no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet" } \ No newline at end of file diff --git a/lib/modules/getting_started/blur_card.dart b/lib/modules/getting_started/blur_card.dart index db887013..6434c0a3 100644 --- a/lib/modules/getting_started/blur_card.dart +++ b/lib/modules/getting_started/blur_card.dart @@ -1,7 +1,5 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; class BlurCard extends HookConsumerWidget { final Widget child; @@ -18,8 +16,7 @@ class BlurCard extends HookConsumerWidget { clipBehavior: Clip.antiAlias, child: SizedBox( width: double.infinity, - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), + child: SurfaceCard( child: Padding( padding: const EdgeInsets.all(16.0), child: child, diff --git a/lib/modules/home/sections/featured.dart b/lib/modules/home/sections/featured.dart index 9ccc8908..a339bd43 100644 --- a/lib/modules/home/sections/featured.dart +++ b/lib/modules/home/sections/featured.dart @@ -1,5 +1,7 @@ +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; @@ -15,6 +17,21 @@ class HomeFeaturedSection extends HookConsumerWidget { final featuredPlaylistsNotifier = ref.watch(featuredPlaylistsProvider.notifier); + if (featuredPlaylists.hasError) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Undraw( + illustration: UndrawIllustration.fixingBugs, + height: 200 * context.theme.scaling, + color: context.theme.colorScheme.primary, + ), + Text(context.l10n.something_went_wrong).small().muted(), + const Gap(8), + ], + ); + } + return Skeletonizer( enabled: featuredPlaylists.isLoading, child: HorizontalPlaybuttonCardView( diff --git a/lib/modules/home/sections/genres/genre_card.dart b/lib/modules/home/sections/genres/genre_card.dart index 722e6644..617d7392 100644 --- a/lib/modules/home/sections/genres/genre_card.dart +++ b/lib/modules/home/sections/genres/genre_card.dart @@ -89,23 +89,24 @@ class GenreSectionCard extends HookConsumerWidget { ), ], ), - Expanded( - child: Skeleton.ignore( - child: Skeletonizer( - enabled: playlists?.isLoading ?? false, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: playlistsData.length, - separatorBuilder: (context, index) => const Gap(12), - itemBuilder: (context, index) { - final playlist = playlistsData.elementAt(index); + if (playlists?.hasError != true) + Expanded( + child: Skeleton.ignore( + child: Skeletonizer( + enabled: playlists?.isLoading ?? false, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: playlistsData.length, + separatorBuilder: (context, index) => const Gap(12), + itemBuilder: (context, index) { + final playlist = playlistsData.elementAt(index); - return GenreSectionCardPlaylistCard(playlist: playlist); - }, + return GenreSectionCardPlaylistCard(playlist: playlist); + }, + ), ), ), - ), - ) + ) ], ), ), diff --git a/lib/modules/library/user_albums.dart b/lib/modules/library/user_albums.dart index a3f14fba..8d55cf80 100644 --- a/lib/modules/library/user_albums.dart +++ b/lib/modules/library/user_albums.dart @@ -1,9 +1,11 @@ +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:collection/collection.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; @@ -75,21 +77,45 @@ class UserAlbums extends HookConsumerWidget { ), ), const SliverGap(10), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 8), - sliver: PlaybuttonView( - controller: controller, - itemCount: albums.length, - hasMore: albumsQuery.asData?.value.hasMore == true, - isLoading: albumsQuery.isLoading, - onRequestMore: albumsQueryNotifier.fetchMore, - gridItemBuilder: (context, index) => AlbumCard( - albums[index], + if (albums.isEmpty && + !albumsQuery.isLoading && + searchText.value.isEmpty) + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 8), + sliver: SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.followMeDrone, + color: Theme.of(context).colorScheme.primary, + ), + Text( + context.l10n.not_following_artists, + textAlign: TextAlign.center, + ).muted().small() + ], + ), + ), + ) + else + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 8), + sliver: PlaybuttonView( + controller: controller, + itemCount: albums.length, + hasMore: albumsQuery.asData?.value.hasMore == true, + isLoading: albumsQuery.isLoading, + onRequestMore: albumsQueryNotifier.fetchMore, + gridItemBuilder: (context, index) => AlbumCard( + albums[index], + ), + listItemBuilder: (context, index) => + AlbumCard.tile(albums[index]), ), - listItemBuilder: (context, index) => - AlbumCard.tile(albums[index]), ), - ), const SliverSafeArea(sliver: SliverGap(10)), ], ), diff --git a/lib/modules/library/user_artists.dart b/lib/modules/library/user_artists.dart index 576298ce..fd23f426 100644 --- a/lib/modules/library/user_artists.dart +++ b/lib/modules/library/user_artists.dart @@ -1,9 +1,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:collection/collection.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; @@ -79,45 +81,82 @@ class UserArtists extends HookConsumerWidget { ), ), const SliverGap(10), - SliverLayoutBuilder(builder: (context, constrains) { - return SliverGrid.builder( - itemCount: filteredArtists.isEmpty - ? 6 - : filteredArtists.length + 1, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: constrains.smAndDown ? 225 : 250, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemBuilder: (context, index) { - if (filteredArtists.isNotEmpty && - index == filteredArtists.length) { - if (artistQuery.asData?.value.hasMore != true) { - return const SizedBox.shrink(); + if (filteredArtists.isNotEmpty) + SliverLayoutBuilder(builder: (context, constrains) { + return SliverGrid.builder( + itemCount: filteredArtists.length + 1, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisExtent: constrains.smAndDown ? 225 : 250, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemBuilder: (context, index) { + if (filteredArtists.isNotEmpty && + index == filteredArtists.length) { + if (artistQuery.asData?.value.hasMore != true) { + return const SizedBox.shrink(); + } + + return Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: artistQueryNotifier.fetchMore, + child: Skeletonizer( + enabled: true, + child: ArtistCard(FakeData.artist), + ), + ); } - return Waypoint( - controller: controller, - isGrid: true, - onTouchEdge: artistQueryNotifier.fetchMore, - child: Skeletonizer( - enabled: true, - child: ArtistCard(FakeData.artist), + return Skeletonizer( + enabled: artistQuery.isLoading, + child: ArtistCard( + filteredArtists.elementAtOrNull(index) ?? + FakeData.artist, ), ); - } - - return Skeletonizer( - enabled: artistQuery.isLoading, - child: ArtistCard( - filteredArtists.elementAtOrNull(index) ?? - FakeData.artist, + }, + ); + }) + else if (filteredArtists.isEmpty && + searchText.value.isEmpty && + !artistQuery.isLoading) + SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.followMeDrone, + color: Theme.of(context).colorScheme.primary, ), - ); - }, - ); - }), + Text( + context.l10n.not_following_artists, + textAlign: TextAlign.center, + ).muted().small() + ], + ), + ) + else + SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.taken, + color: Theme.of(context).colorScheme.primary, + ), + Text( + context.l10n.nothing_found, + textAlign: TextAlign.center, + ).muted().small() + ], + ), + ), const SliverSafeArea(sliver: SliverGap(10)), ], ), diff --git a/lib/modules/stats/top/albums.dart b/lib/modules/stats/top/albums.dart index e401340e..09bf755c 100644 --- a/lib/modules/stats/top/albums.dart +++ b/lib/modules/stats/top/albums.dart @@ -1,5 +1,7 @@ -import 'package:flutter/material.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/modules/stats/common/album_item.dart'; @@ -31,6 +33,24 @@ class TopAlbums extends HookConsumerWidget { isLoading: topAlbums.isLoading && !topAlbums.isLoadingNextPage, hasReachedMax: topAlbums.asData?.value.hasMore ?? true, itemCount: albumsData.length, + emptyBuilder: (context) => Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Gap(50), + Undraw( + illustration: UndrawIllustration.happyMusic, + color: context.theme.colorScheme.primary, + height: 200 * context.theme.scaling, + ), + Text( + context.l10n.no_tracks_listened_yet, + textAlign: TextAlign.center, + ).muted().small(), + ], + ), + ), itemBuilder: (context, index) { final album = albumsData[index]; return StatsAlbumItem( diff --git a/lib/modules/stats/top/artists.dart b/lib/modules/stats/top/artists.dart index 3e4e098d..c53c34fd 100644 --- a/lib/modules/stats/top/artists.dart +++ b/lib/modules/stats/top/artists.dart @@ -1,6 +1,8 @@ -import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/modules/stats/common/artist_item.dart'; @@ -35,6 +37,24 @@ class TopArtists extends HookConsumerWidget { isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, hasReachedMax: topTracks.asData?.value.hasMore ?? true, itemCount: artistsData.length, + emptyBuilder: (context) => Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Gap(50), + Undraw( + illustration: UndrawIllustration.happyMusic, + color: context.theme.colorScheme.primary, + height: 200 * context.theme.scaling, + ), + Text( + context.l10n.no_tracks_listened_yet, + textAlign: TextAlign.center, + ).muted().small(), + ], + ), + ), itemBuilder: (context, index) { final artist = artistsData[index]; return StatsArtistItem( diff --git a/lib/modules/stats/top/tracks.dart b/lib/modules/stats/top/tracks.dart index 7fba220d..c4015431 100644 --- a/lib/modules/stats/top/tracks.dart +++ b/lib/modules/stats/top/tracks.dart @@ -1,5 +1,7 @@ -import 'package:flutter/material.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/formatters.dart'; import 'package:spotube/modules/stats/common/track_item.dart'; @@ -33,6 +35,24 @@ class TopTracks extends HookConsumerWidget { isLoading: topTracks.isLoading && !topTracks.isLoadingNextPage, hasReachedMax: topTracks.asData?.value.hasMore ?? true, itemCount: tracksData.length, + emptyBuilder: (context) => Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Gap(50), + Undraw( + illustration: UndrawIllustration.happyMusic, + color: context.theme.colorScheme.primary, + height: 200 * context.theme.scaling, + ), + Text( + context.l10n.no_tracks_listened_yet, + textAlign: TextAlign.center, + ).muted().small(), + ], + ), + ), itemBuilder: (context, index) { final track = tracksData[index]; return StatsTrackItem( diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 4a10268b..bc013574 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -35,7 +35,7 @@ class AlbumPage extends HookConsumerWidget { tracks: tracks.asData?.value.items ?? [], pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, - isLoading: tracks.isLoadingNextPage, + isLoading: tracks.isLoading || tracks.isLoadingNextPage, onFetchMore: () async { await tracksNotifier.fetchMore(); }, diff --git a/lib/pages/getting_started/getting_started.dart b/lib/pages/getting_started/getting_started.dart index 6a8141d0..2931a782 100644 --- a/lib/pages/getting_started/getting_started.dart +++ b/lib/pages/getting_started/getting_started.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; @@ -8,8 +8,6 @@ import 'package:spotube/pages/getting_started/sections/greeting.dart'; import 'package:spotube/pages/getting_started/sections/playback.dart'; import 'package:spotube/pages/getting_started/sections/region.dart'; import 'package:spotube/pages/getting_started/sections/support.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/themes/theme.dart'; class GettingStarting extends HookConsumerWidget { static const name = "getting_started"; @@ -18,12 +16,6 @@ class GettingStarting extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final preferences = ref.watch(userPreferencesProvider); - final themeData = theme( - preferences.accentColorScheme, - Brightness.dark, - preferences.amoledDarkTheme, - ); final pageController = usePageController(); final onNext = useCallback(() { @@ -40,11 +32,11 @@ class GettingStarting extends HookConsumerWidget { ); }, [pageController]); - return Theme( - data: themeData, - child: Scaffold( - appBar: TitleBar( + return Scaffold( + headers: [ + TitleBar( backgroundColor: Colors.transparent, + surfaceBlur: 0, trailing: [ ListenableBuilder( listenable: pageController, @@ -54,7 +46,7 @@ class GettingStarting extends HookConsumerWidget { child: pageController.hasClients && (pageController.page == 0 || pageController.page == 3) ? const SizedBox() - : TextButton( + : Button.secondary( onPressed: () { pageController.animateToPage( 3, @@ -62,43 +54,33 @@ class GettingStarting extends HookConsumerWidget { curve: Curves.easeInOut, ); }, - child: Text( - context.l10n.skip_this_nonsense, - style: TextStyle( - decoration: TextDecoration.underline, - decorationColor: themeData.colorScheme.primary, - ), - ), + child: Text(context.l10n.skip_this_nonsense), ), ); }, ), ], ), - extendBodyBehindAppBar: true, - body: DecoratedBox( - decoration: BoxDecoration( - image: DecorationImage( - image: Assets.bengaliPatternsBg.provider(), - fit: BoxFit.cover, - colorFilter: const ColorFilter.mode( - Colors.black38, - BlendMode.srcOver, - ), + ], + floatingHeader: true, + child: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: Assets.bengaliPatternsBg.provider(), + fit: BoxFit.cover, + ), + ), + child: PageView( + controller: pageController, + children: [ + GettingStartedPageGreetingSection(onNext: onNext), + GettingStartedPageLanguageRegionSection(onNext: onNext), + GettingStartedPagePlaybackSection( + onNext: onNext, + onPrevious: onPrevious, ), - ), - child: PageView( - controller: pageController, - children: [ - GettingStartedPageGreetingSection(onNext: onNext), - GettingStartedPageLanguageRegionSection(onNext: onNext), - GettingStartedPagePlaybackSection( - onNext: onNext, - onPrevious: onPrevious, - ), - const GettingStartedScreenSupportSection(), - ], - ), + const GettingStartedScreenSupportSection(), + ], ), ), ); diff --git a/lib/pages/getting_started/sections/greeting.dart b/lib/pages/getting_started/sections/greeting.dart index 6d649351..4b9c0a89 100644 --- a/lib/pages/getting_started/sections/greeting.dart +++ b/lib/pages/getting_started/sections/greeting.dart @@ -1,10 +1,9 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/utils/platform.dart'; class GettingStartedPageGreetingSection extends HookConsumerWidget { @@ -13,8 +12,6 @@ class GettingStartedPageGreetingSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final ThemeData(:textTheme) = Theme.of(context); - return Center( child: BlurCard( child: Column( @@ -22,30 +19,19 @@ class GettingStartedPageGreetingSection extends HookConsumerWidget { children: [ Assets.spotubeLogoPng.image(height: 200), const Gap(24), - Text( - "Spotube", - style: - textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), + const Text("Spotube").semiBold().h4(), const Gap(4), Text( kIsMobile ? context.l10n.freedom_of_music_palm : context.l10n.freedom_of_music, textAlign: TextAlign.center, - style: textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w300, - fontStyle: FontStyle.italic, - ), - ), + ).light().large().italic(), const Gap(84), - Directionality( - textDirection: TextDirection.rtl, - child: FilledButton.icon( - onPressed: onNext, - icon: const Icon(SpotubeIcons.angleRight), - label: Text(context.l10n.get_started), - ), + Button.primary( + onPressed: onNext, + trailing: const Icon(SpotubeIcons.angleRight), + child: Text(context.l10n.get_started), ), ], ), diff --git a/lib/pages/getting_started/sections/playback.dart b/lib/pages/getting_started/sections/playback.dart index dbf0bda2..bf12d426 100644 --- a/lib/pages/getting_started/sections/playback.dart +++ b/lib/pages/getting_started/sections/playback.dart @@ -1,9 +1,9 @@ -import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; @@ -14,14 +14,14 @@ final audioSourceToIconMap = { AudioSource.youtube: const Icon( SpotubeIcons.youtube, color: Colors.red, - size: 30, + size: 20, ), - AudioSource.piped: const Icon(SpotubeIcons.piped, size: 30), + AudioSource.piped: const Icon(SpotubeIcons.piped, size: 20), AudioSource.invidious: ClipRRect( - borderRadius: BorderRadius.circular(48), - child: Assets.invidious.image(width: 48, height: 48), + borderRadius: BorderRadius.circular(26), + child: Assets.invidious.image(width: 26, height: 26), ), - AudioSource.jiosaavn: Assets.jiosaavn.image(width: 48, height: 48), + AudioSource.jiosaavn: Assets.jiosaavn.image(width: 20, height: 20), }; class GettingStartedPagePlaybackSection extends HookConsumerWidget { @@ -36,8 +36,6 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final ThemeData(:textTheme, :colorScheme, :dividerColor) = - Theme.of(context); final preferences = ref.watch(userPreferencesProvider); final preferencesNotifier = ref.read(userPreferencesProvider.notifier); @@ -62,76 +60,56 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { children: [ const Icon(SpotubeIcons.album, size: 16), const Gap(8), - Text(context.l10n.playback, style: textTheme.titleMedium), + Text(context.l10n.playback).semiBold().large(), ], ), const Gap(16), - ListTile( - title: Text( - context.l10n.select_audio_source, - style: textTheme.titleMedium, - ), + Align( + alignment: Alignment.centerLeft, + child: Text(context.l10n.select_audio_source).semiBold().large(), ), const Gap(16), - ToggleButtons( - isSelected: [ - for (final source in AudioSource.values) - preferences.audioSource == source, - ], - onPressed: (index) { - preferencesNotifier.setAudioSource(AudioSource.values[index]); + Select( + value: preferences.audioSource, + onChanged: (value) { + if (value == null) return; + preferencesNotifier.setAudioSource(value); }, - borderRadius: BorderRadius.circular(8), + placeholder: Text(preferences.audioSource.name.capitalize()), + itemBuilder: (context, value) => Row( + mainAxisSize: MainAxisSize.min, + spacing: 6, + children: [ + audioSourceToIconMap[value]!, + Text(value.name.capitalize()), + ], + ), children: [ for (final source in AudioSource.values) - SizedBox.square( - dimension: 84, - child: Column( + SelectItemButton( + value: source, + child: Row( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + spacing: 6, children: [ audioSourceToIconMap[source]!, - const Gap(8), - Text( - source.name.capitalize(), - style: textTheme.bodySmall!.copyWith( - color: preferences.audioSource == source - ? colorScheme.primary - : null, - ), - ), + Text(source.name.capitalize()), ], ), ), ], ), - ListTile( - title: Align( - alignment: switch (preferences.audioSource) { - AudioSource.youtube => Alignment.centerLeft, - AudioSource.piped || - AudioSource.invidious => - Alignment.center, - AudioSource.jiosaavn => Alignment.centerRight, - }, - child: Text( - audioSourceToDescription[preferences.audioSource]!, - style: textTheme.bodySmall?.copyWith( - color: dividerColor, - ), - ), - ), - ), const Gap(16), - ListTile( + Text( + audioSourceToDescription[preferences.audioSource]!, + ).small().muted(), + const Gap(16), + ButtonTile( title: Text(context.l10n.endless_playback), subtitle: Text( context.l10n.endless_playback_description, - style: textTheme.bodySmall?.copyWith( - color: dividerColor, - ), - ), - onTap: () { + ).small().muted(), + onPressed: () { preferencesNotifier .setEndlessPlayback(!preferences.endlessPlayback); }, @@ -146,17 +124,17 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - FilledButton.icon( - icon: const Icon(SpotubeIcons.angleLeft), - label: Text(context.l10n.previous), + Button.secondary( + leading: const Icon(SpotubeIcons.angleLeft), onPressed: onPrevious, + child: Text(context.l10n.previous), ), Directionality( textDirection: TextDirection.rtl, - child: FilledButton.icon( - icon: const Icon(SpotubeIcons.angleRight), - label: Text(context.l10n.next), + child: Button.primary( + leading: const Icon(SpotubeIcons.angleRight), onPressed: onNext, + child: Text(context.l10n.next), ), ), ], diff --git a/lib/pages/getting_started/sections/region.dart b/lib/pages/getting_started/sections/region.dart index 9e31a273..19507fe9 100644 --- a/lib/pages/getting_started/sections/region.dart +++ b/lib/pages/getting_started/sections/region.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.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'; @@ -16,7 +16,6 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final ThemeData(:textTheme, :dividerColor) = Theme.of(context); final preferences = ref.watch(userPreferencesProvider); return SafeArea( @@ -32,92 +31,119 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { size: 16, ), const SizedBox(width: 8), - Text( - context.l10n.language_region, - style: textTheme.titleMedium, - ), + Text(context.l10n.language_region).semiBold(), ], ), - const Gap(48), + const Gap(30), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text( - context.l10n.choose_your_region, - style: textTheme.titleSmall, - ), + Text(context.l10n.choose_your_region).semiBold(), Text( context.l10n.choose_your_region_description, - style: textTheme.bodySmall?.copyWith( - color: dividerColor, - ), - ), + ).small().muted(), const Gap(16), - DropdownMenu( - initialSelection: preferences.market, - onSelected: (value) { - if (value == null) return; - ref - .read(userPreferencesProvider.notifier) - .setRecommendationMarket(value); - }, - hintText: preferences.market.name, - label: Text(context.l10n.market_place_region), - inputDecorationTheme: - const InputDecorationTheme(isDense: true), - dropdownMenuEntries: [ - for (final market in spotifyMarkets) - DropdownMenuEntry( - value: market.$1, - label: market.$2, - ), - ], + Text(context.l10n.market_place_region).small(), + const Gap(8), + SizedBox( + width: double.infinity, + child: Select( + value: preferences.market, + onChanged: (value) { + if (value == null) return; + ref + .read(userPreferencesProvider.notifier) + .setRecommendationMarket(value); + }, + placeholder: Text(preferences.market.name), + itemBuilder: (context, value) => Text( + spotifyMarkets + .firstWhere((element) => element.$1 == value) + .$2, + ), + searchPlaceholder: Text(context.l10n.search), + searchFilter: (item, query) { + final market = spotifyMarkets + .firstWhere((element) => element.$1 == item) + .$2 + .toLowerCase(); + + return market.contains(query.toLowerCase()) ? 1 : 0; + }, + children: [ + for (final market in spotifyMarkets) + SelectItemButton( + value: market.$1, + child: Text(market.$2), + ), + ], + ), ), const Gap(36), Text( context.l10n.choose_your_language, - style: textTheme.titleSmall, - ), + ).semiBold(), const Gap(16), - DropdownMenu( - initialSelection: preferences.locale, - onSelected: (locale) { - if (locale == null) return; - ref - .read(userPreferencesProvider.notifier) - .setLocale(locale); - }, - hintText: context.l10n.system_default, - label: Text(context.l10n.language), - inputDecorationTheme: - const InputDecorationTheme(isDense: true), - dropdownMenuEntries: [ - DropdownMenuEntry( - value: const Locale("system", "system"), - label: context.l10n.system_default, - ), - for (final locale in L10n.all) - DropdownMenuEntry( - value: locale, - label: LanguageLocals.getDisplayLanguage( - locale.languageCode) - .toString(), + Text(context.l10n.language).small(), + const Gap(8), + SizedBox( + width: double.infinity, + child: Select( + value: preferences.locale, + onChanged: (locale) { + if (locale == null) return; + ref + .read(userPreferencesProvider.notifier) + .setLocale(locale); + }, + placeholder: Text(context.l10n.system_default), + itemBuilder: (context, value) => + value.languageCode == "system" + ? Text(context.l10n.system_default) + : Text( + LanguageLocals.getDisplayLanguage( + value.languageCode) + .toString(), + ), + searchPlaceholder: Text(context.l10n.search), + searchFilter: (locale, query) { + final language = LanguageLocals.getDisplayLanguage( + locale.languageCode) + .toString(); + + return language + .toLowerCase() + .contains(query.toLowerCase()) + ? 1 + : 0; + }, + children: [ + SelectItemButton( + value: const Locale("system", "system"), + child: Text(context.l10n.system_default), ), - ], + for (final locale in L10n.all) + SelectItemButton( + value: locale, + child: Text( + LanguageLocals.getDisplayLanguage( + locale.languageCode) + .toString(), + ), + ), + ], + ), ), ], ), const Gap(48), Align( alignment: Alignment.centerRight, - child: Directionality( - textDirection: TextDirection.rtl, - child: FilledButton.icon( - icon: const Icon(SpotubeIcons.angleRight), - label: Text(context.l10n.next), - onPressed: onNext, - ), + child: Button.primary( + trailing: const Icon(SpotubeIcons.angleRight), + onPressed: onNext, + child: Text(context.l10n.next), ), ), ], diff --git a/lib/pages/getting_started/sections/support.dart b/lib/pages/getting_started/sections/support.dart index f09a585d..640b0b38 100644 --- a/lib/pages/getting_started/sections/support.dart +++ b/lib/pages/getting_started/sections/support.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; -import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; @@ -16,7 +15,6 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final ThemeData(:textTheme, :colorScheme) = Theme.of(context); final onLogin = useLoginCallback(ref); return Center( @@ -34,9 +32,8 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { const SizedBox(width: 8), Text( context.l10n.help_project_grow, - style: - textTheme.titleMedium?.copyWith(color: Colors.pink), - ), + style: const TextStyle(color: Colors.pink), + ).semiBold(), ], ), const Gap(16), @@ -46,38 +43,57 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - FilledButton.icon( - icon: const Icon(SpotubeIcons.github), - label: Text(context.l10n.contribute_on_github), - style: FilledButton.styleFrom( - backgroundColor: Colors.black, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( + Button( + leading: const Icon(SpotubeIcons.github), + style: ButtonVariance.primary.copyWith( + decoration: (context, states, value) { + if (states.isNotEmpty) { + return ButtonVariance.primary + .decoration(context, states); + } + + return BoxDecoration( + color: Colors.black, borderRadius: BorderRadius.circular(8), - ), - ), + ); + }), onPressed: () async { await launchUrlString( "https://github.com/KRTirtho/spotube", mode: LaunchMode.externalApplication, ); }, + child: Text( + context.l10n.contribute_on_github, + style: const TextStyle(color: Colors.white), + ), ), if (!Env.hideDonations) ...[ const Gap(16), - FilledButton.icon( - icon: const Icon(SpotubeIcons.openCollective), - label: Text(context.l10n.donate_on_open_collective), - style: FilledButton.styleFrom( - backgroundColor: const Color(0xff4cb7f6), - foregroundColor: Colors.white, - ), + Button( + leading: const Icon(SpotubeIcons.openCollective), + style: ButtonVariance.primary.copyWith( + decoration: (context, states, value) { + if (states.isNotEmpty) { + return ButtonVariance.primary + .decoration(context, states); + } + + return BoxDecoration( + color: const Color(0xff4cb7f6), + borderRadius: BorderRadius.circular(8), + ); + }), onPressed: () async { await launchUrlString( "https://opencollective.com/spotube", mode: LaunchMode.externalApplication, ); }, + child: Text( + context.l10n.donate_on_open_collective, + style: const TextStyle(color: Colors.white), + ), ), ] ], @@ -91,42 +107,40 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - gradient: LinearGradient( - colors: [ - colorScheme.primary, - colorScheme.secondary, - ], - ), - ), - child: TextButton.icon( - icon: const Icon(SpotubeIcons.anonymous), - label: Text(context.l10n.browse_anonymously), - style: TextButton.styleFrom( - foregroundColor: Colors.white, - ), - onPressed: () async { - await KVStoreService.setDoneGettingStarted(true); - if (context.mounted) { - context.goNamed(HomePage.name); - } - }, - ), + Button.secondary( + leading: const Icon(SpotubeIcons.anonymous), + onPressed: () async { + await KVStoreService.setDoneGettingStarted(true); + if (context.mounted) { + context.goNamed(HomePage.name); + } + }, + child: Text(context.l10n.browse_anonymously), ), const Gap(16), - FilledButton.icon( - icon: const Icon(SpotubeIcons.spotify), - label: Text(context.l10n.connect_with_spotify), - style: FilledButton.styleFrom( - backgroundColor: const Color(0xff1db954), - foregroundColor: Colors.white, + Button.primary( + leading: const Icon(SpotubeIcons.spotify), + style: ButtonVariance.primary.copyWith( + decoration: (context, states, value) { + if (states.isNotEmpty) { + return ButtonVariance.primary + .decoration(context, states); + } + + return BoxDecoration( + color: const Color(0xff1db954), + borderRadius: BorderRadius.circular(8), + ); + }, ), onPressed: () async { await KVStoreService.setDoneGettingStarted(true); await onLogin(); }, + child: Text( + context.l10n.connect_with_spotify, + style: const TextStyle(color: Colors.white), + ), ), ], ), diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index da28c83c..b610b1d4 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -53,7 +53,7 @@ class PlaylistPage extends HookConsumerWidget { ), pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, - isLoading: tracks.isLoadingNextPage, + isLoading: tracks.isLoading || tracks.isLoadingNextPage, onFetchMore: tracksNotifier.fetchMore, onRefresh: () async { ref.invalidate(playlistTracksProvider(playlist.id!)); diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index d0f59ce2..efd46ebb 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -1,4 +1,5 @@ import 'package:flutter/services.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotify/spotify.dart'; @@ -159,21 +160,13 @@ class SearchPage extends HookConsumerWidget { SizedBox( height: mediaQuery.height * 0.2, ), - Icon( - SpotubeIcons.web, - size: 120, - color: theme.colorScheme.foreground - .withOpacity(0.7), + Undraw( + illustration: UndrawIllustration.explore, + color: theme.colorScheme.primary, + height: 200 * theme.scaling, ), const SizedBox(height: 20), - Text( - context.l10n.search_to_get_results, - style: theme.typography.h3.copyWith( - fontWeight: FontWeight.w900, - color: theme.colorScheme.foreground - .withOpacity(0.5), - ), - ), + Text(context.l10n.search_to_get_results).large(), ], ), (false, true) => Container( diff --git a/pubspec.lock b/pubspec.lock index fbcdfd52..2666460c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -963,18 +963,26 @@ packages: source: hosted version: "1.1.1" flutter_svg: - dependency: transitive + dependency: "direct overridden" description: name: flutter_svg - sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "1.1.6" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_undraw: + dependency: "direct main" + description: + name: flutter_undraw + sha256: "17fe2738231c502171f984c003f6e40979de1a2550ef2debdd29fec27ae006ea" + url: "https://pub.dev" + source: hosted + version: "0.2.0" flutter_web_plugins: dependency: transitive description: flutter @@ -1662,14 +1670,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - path_drawing: - dependency: transitive - description: - name: path_drawing - sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 - url: "https://pub.dev" - source: hosted - version: "1.0.1" path_parsing: dependency: transitive description: @@ -2525,6 +2525,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + url: "https://pub.dev" + source: hosted + version: "1.1.15" vector_graphics_codec: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7bbf15a1..657de0c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: flutter_riverpod: ^2.5.1 flutter_secure_storage: ^9.0.0 flutter_sharing_intent: ^1.1.0 + flutter_undraw: ^0.2.0 form_builder_validators: ^11.1.1 form_validator: ^2.1.1 freezed_annotation: ^2.4.1 @@ -163,6 +164,7 @@ dependency_overrides: path: packages/bonsoir_android web: ^1.1.0 meta: 1.16.0 + flutter_svg: ^2.0.17 flutter: generate: true @@ -174,6 +176,14 @@ flutter: - assets/backgrounds/ - assets/patterns/ - LICENSE + - packages/flutter_undraw/assets/undraw/access_denied.svg + - packages/flutter_undraw/assets/undraw/fixing_bugs.svg + - packages/flutter_undraw/assets/undraw/secure_login.svg + - packages/flutter_undraw/assets/undraw/explore.svg + - packages/flutter_undraw/assets/undraw/dreamer.svg + - packages/flutter_undraw/assets/undraw/happy_music.svg + - packages/flutter_undraw/assets/undraw/follow_me_drone.svg + - packages/flutter_undraw/assets/undraw/taken.svg fonts: - family: GeistSans fonts: diff --git a/untranslated_messages.json b/untranslated_messages.json index ddaf9ab0..b00b1fd4 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -9,7 +9,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "bn": [ @@ -22,7 +27,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ca": [ @@ -35,7 +45,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "cs": [ @@ -48,7 +63,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "de": [ @@ -61,7 +81,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "es": [ @@ -74,7 +99,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "eu": [ @@ -87,7 +117,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "fa": [ @@ -100,7 +135,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "fi": [ @@ -113,7 +153,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "fr": [ @@ -126,7 +171,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "hi": [ @@ -139,7 +189,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "id": [ @@ -152,7 +207,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "it": [ @@ -165,7 +225,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ja": [ @@ -178,7 +243,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ka": [ @@ -191,7 +261,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ko": [ @@ -204,7 +279,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ne": [ @@ -217,7 +297,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "nl": [ @@ -230,7 +315,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "pl": [ @@ -243,7 +333,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "pt": [ @@ -256,7 +351,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "ru": [ @@ -269,7 +369,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "th": [ @@ -282,7 +387,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "tr": [ @@ -295,7 +405,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "uk": [ @@ -308,7 +423,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "vi": [ @@ -321,7 +441,12 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ], "zh": [ @@ -334,6 +459,11 @@ "add_all_to_queue", "play_all_next", "pause", - "view_all" + "view_all", + "no_tracks_added_yet", + "no_tracks", + "no_tracks_listened_yet", + "not_following_artists", + "no_favorite_albums_yet" ] }