From 8ca1aa38a2eee53cdadaccaf2017eccc433ab609 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 1 Oct 2023 13:24:36 +0600 Subject: [PATCH] chore: include interscrollbar in left out pages --- .../player/sibling_tracks_sheet.dart | 56 +- lib/pages/artist/artist.dart | 630 +++++++++--------- lib/pages/search/search.dart | 454 ++++++------- lib/pages/settings/blacklist.dart | 41 +- lib/pages/settings/logs.dart | 79 +-- 5 files changed, 641 insertions(+), 619 deletions(-) diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart index d4857853..6587b8b3 100644 --- a/lib/components/player/sibling_tracks_sheet.dart +++ b/lib/components/player/sibling_tracks_sheet.dart @@ -7,6 +7,7 @@ import 'package:spotify/spotify.dart' hide Offset; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; @@ -17,7 +18,6 @@ import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; import 'package:spotube/provider/youtube_provider.dart'; import 'package:spotube/services/youtube/youtube.dart'; -import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -202,32 +202,36 @@ class SiblingTracksSheet extends HookConsumerWidget { duration: const Duration(milliseconds: 300), transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child), - child: switch (isSearching.value) { - false => ListView.builder( - itemCount: siblings.length, - itemBuilder: (context, index) => - itemBuilder(siblings[index]), - ), - true => FutureBuilder( - future: searchRequest, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Text(snapshot.error.toString()), - ); - } else if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator()); - } + child: InterScrollbar( + child: switch (isSearching.value) { + false => ListView.builder( + itemCount: siblings.length, + itemBuilder: (context, index) => + itemBuilder(siblings[index]), + ), + true => FutureBuilder( + future: searchRequest, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(snapshot.error.toString()), + ); + } else if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator()); + } - return ListView.builder( - itemCount: snapshot.data!.length, - itemBuilder: (context, index) => - itemBuilder(snapshot.data![index]), - ); - }, - ), - }, + return InterScrollbar( + child: ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) => + itemBuilder(snapshot.data![index]), + ), + ); + }, + ), + }, + ), ), ), ), diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index e1bbefcb..67a99d86 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/shimmers/shimmer_artist_profile.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/shared/track_table/track_tile.dart'; @@ -90,365 +91,372 @@ class ArtistPage extends HookConsumerWidget { BlacklistedElement.artist(artistId, data.name!), ); - return SingleChildScrollView( + return InterScrollbar( controller: parentScrollController, - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - runAlignment: WrapAlignment.center, - children: [ - const SizedBox(width: 50), - Padding( - padding: const EdgeInsets.all(16), - child: CircleAvatar( - radius: avatarWidth, - backgroundImage: UniversalImage.imageProvider( - TypeConversionUtils.image_X_UrlString( - data.images, - placeholder: ImagePlaceholder.artist, + child: SingleChildScrollView( + controller: parentScrollController, + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runAlignment: WrapAlignment.center, + children: [ + const SizedBox(width: 50), + Padding( + padding: const EdgeInsets.all(16), + child: CircleAvatar( + radius: avatarWidth, + backgroundImage: UniversalImage.imageProvider( + TypeConversionUtils.image_X_UrlString( + data.images, + placeholder: ImagePlaceholder.artist, + ), ), ), ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - decoration: BoxDecoration( - color: Colors.blue, - borderRadius: - BorderRadius.circular(50)), - child: Text( - data.type!.toUpperCase(), - style: chipTextVariant.copyWith( - color: Colors.white, - ), - ), - ), - if (isBlackListed) ...[ - const SizedBox(width: 5), + Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5), decoration: BoxDecoration( - color: Colors.red[400], + color: Colors.blue, borderRadius: BorderRadius.circular(50)), child: Text( - context.l10n.blacklisted, + data.type!.toUpperCase(), style: chipTextVariant.copyWith( color: Colors.white, ), ), ), - ] - ], - ), - Text( - data.name!, - style: mediaQuery.smAndDown - ? textTheme.headlineSmall - : textTheme.headlineMedium, - ), - Text( - context.l10n.followers( - PrimitiveUtils.toReadableNumber( - data.followers!.total!.toDouble(), + if (isBlackListed) ...[ + const SizedBox(width: 5), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: Colors.red[400], + borderRadius: + BorderRadius.circular(50)), + child: Text( + context.l10n.blacklisted, + style: chipTextVariant.copyWith( + color: Colors.white, + ), + ), + ), + ] + ], + ), + Text( + data.name!, + style: mediaQuery.smAndDown + ? textTheme.headlineSmall + : textTheme.headlineMedium, + ), + Text( + context.l10n.followers( + PrimitiveUtils.toReadableNumber( + data.followers!.total!.toDouble(), + ), + ), + style: textTheme.bodyMedium?.copyWith( + fontWeight: mediaQuery.mdAndUp + ? FontWeight.bold + : null, ), ), - style: textTheme.bodyMedium?.copyWith( - fontWeight: mediaQuery.mdAndUp - ? FontWeight.bold - : null, - ), - ), - const SizedBox(height: 20), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (auth != null) - HookBuilder( - builder: (context) { - final isFollowingQuery = useQueries - .artist - .doIFollow(ref, artistId); + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (auth != null) + HookBuilder( + builder: (context) { + final isFollowingQuery = useQueries + .artist + .doIFollow(ref, artistId); - final followUnfollow = - useCallback(() async { - try { - isFollowingQuery.data! - ? await spotify.me.unfollow( - FollowingType.artist, - [artistId], - ) - : await spotify.me.follow( - FollowingType.artist, - [artistId], - ); - await isFollowingQuery.refresh(); + final followUnfollow = + useCallback(() async { + try { + isFollowingQuery.data! + ? await spotify.me.unfollow( + FollowingType.artist, + [artistId], + ) + : await spotify.me.follow( + FollowingType.artist, + [artistId], + ); + await isFollowingQuery.refresh(); - queryClient - .refreshInfiniteQueryAllPages( - "user-following-artists"); - } finally { - queryClient.refreshQuery( - "user-follows-artists-query/$artistId", + queryClient + .refreshInfiniteQueryAllPages( + "user-following-artists"); + } finally { + queryClient.refreshQuery( + "user-follows-artists-query/$artistId", + ); + } + }, [isFollowingQuery]); + + if (isFollowingQuery.isLoading || + !isFollowingQuery.hasData) { + return const SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator(), ); } - }, [isFollowingQuery]); - if (isFollowingQuery.isLoading || - !isFollowingQuery.hasData) { - return const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(), - ); - } + if (isFollowingQuery.data!) { + return OutlinedButton( + onPressed: followUnfollow, + child: + Text(context.l10n.following), + ); + } - if (isFollowingQuery.data!) { - return OutlinedButton( + return FilledButton( onPressed: followUnfollow, - child: Text(context.l10n.following), + child: Text(context.l10n.follow), + ); + }, + ), + const SizedBox(width: 5), + IconButton( + tooltip: + context.l10n.add_artist_to_blacklist, + icon: Icon( + SpotubeIcons.userRemove, + color: !isBlackListed + ? Colors.red[400] + : Colors.white, + ), + style: IconButton.styleFrom( + backgroundColor: isBlackListed + ? Colors.red[400] + : null, + ), + onPressed: () async { + if (isBlackListed) { + ref + .read(BlackListNotifier + .provider.notifier) + .remove( + BlacklistedElement.artist( + data.id!, data.name!), + ); + } else { + ref + .read(BlackListNotifier + .provider.notifier) + .add( + BlacklistedElement.artist( + data.id!, data.name!), + ); + } + }, + ), + IconButton( + icon: const Icon(SpotubeIcons.share), + onPressed: () async { + if (data.externalUrls?.spotify != + null) { + await Clipboard.setData( + ClipboardData( + text: data.externalUrls!.spotify!, + ), ); } - return FilledButton( - onPressed: followUnfollow, - child: Text(context.l10n.follow), + if (!context.mounted) return; + + scaffoldMessenger.showSnackBar( + SnackBar( + width: 300, + behavior: SnackBarBehavior.floating, + content: Text( + context.l10n.artist_url_copied, + textAlign: TextAlign.center, + ), + ), + ); + }, + ) + ], + ) + ], + ), + ), + ], + ), + const SizedBox(height: 50), + HookBuilder( + builder: (context) { + final topTracksQuery = useQueries.artist.topTracksOf( + ref, + artistId, + ); + + final isPlaylistPlaying = playlist.containsTracks( + topTracksQuery.data ?? [], + ); + + if (topTracksQuery.isLoading || + !topTracksQuery.hasData) { + return const CircularProgressIndicator(); + } else if (topTracksQuery.hasError) { + return Center( + child: Text(topTracksQuery.error.toString()), + ); + } + + final topTracks = topTracksQuery.data!; + + void playPlaylist(List tracks, + {Track? currentTrack}) async { + currentTrack ??= tracks.first; + if (!isPlaylistPlaying) { + playlistNotifier.load( + tracks, + initialIndex: tracks.indexWhere( + (s) => s.id == currentTrack?.id), + autoPlay: true, + ); + } else if (isPlaylistPlaying && + currentTrack.id != null && + currentTrack.id != playlist.activeTrack?.id) { + await playlistNotifier.jumpToTrack(currentTrack); + } + } + + return Column( + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.l10n.top_tracks, + style: theme.textTheme.headlineSmall, + ), + ), + if (!isPlaylistPlaying) + IconButton( + icon: const Icon( + SpotubeIcons.queueAdd, + ), + onPressed: () { + playlistNotifier + .addTracks(topTracks.toList()); + scaffoldMessenger.showSnackBar( + SnackBar( + width: 300, + behavior: SnackBarBehavior.floating, + content: Text( + context.l10n.added_to_queue( + topTracks.length, + ), + textAlign: TextAlign.center, + ), + ), ); }, ), const SizedBox(width: 5), IconButton( - tooltip: - context.l10n.add_artist_to_blacklist, icon: Icon( - SpotubeIcons.userRemove, - color: !isBlackListed - ? Colors.red[400] - : Colors.white, + isPlaylistPlaying + ? SpotubeIcons.stop + : SpotubeIcons.play, + color: Colors.white, ), style: IconButton.styleFrom( - backgroundColor: isBlackListed - ? Colors.red[400] - : null, + backgroundColor: + theme.colorScheme.primary, ), - onPressed: () async { - if (isBlackListed) { - ref - .read(BlackListNotifier - .provider.notifier) - .remove( - BlacklistedElement.artist( - data.id!, data.name!), - ); - } else { - ref - .read(BlackListNotifier - .provider.notifier) - .add( - BlacklistedElement.artist( - data.id!, data.name!), - ); - } - }, - ), - IconButton( - icon: const Icon(SpotubeIcons.share), - onPressed: () async { - if (data.externalUrls?.spotify != null) { - await Clipboard.setData( - ClipboardData( - text: data.externalUrls!.spotify!, - ), - ); - } - - if (!context.mounted) return; - - scaffoldMessenger.showSnackBar( - SnackBar( - width: 300, - behavior: SnackBarBehavior.floating, - content: Text( - context.l10n.artist_url_copied, - textAlign: TextAlign.center, - ), - ), - ); - }, + onPressed: () => + playPlaylist(topTracks.toList()), ) ], - ) + ), + ...topTracks.mapIndexed((i, track) { + return TrackTile( + index: i, + track: track, + onTap: () async { + playPlaylist( + topTracks.toList(), + currentTrack: track, + ); + }, + ); + }), ], - ), + ); + }, + ), + const SizedBox(height: 50), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.l10n.albums, + style: theme.textTheme.headlineSmall, ), - ], - ), - const SizedBox(height: 50), - HookBuilder( - builder: (context) { - final topTracksQuery = useQueries.artist.topTracksOf( - ref, - artistId, - ); - - final isPlaylistPlaying = playlist.containsTracks( - topTracksQuery.data ?? [], - ); - - if (topTracksQuery.isLoading || - !topTracksQuery.hasData) { - return const CircularProgressIndicator(); - } else if (topTracksQuery.hasError) { - return Center( - child: Text(topTracksQuery.error.toString()), + ), + ArtistAlbumList(artistId), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.l10n.fans_also_like, + style: theme.textTheme.headlineSmall, + ), + ), + const SizedBox(height: 10), + HookBuilder( + builder: (context) { + final relatedArtists = + useQueries.artist.relatedArtistsOf( + ref, + artistId, ); - } - final topTracks = topTracksQuery.data!; - - void playPlaylist(List tracks, - {Track? currentTrack}) async { - currentTrack ??= tracks.first; - if (!isPlaylistPlaying) { - playlistNotifier.load( - tracks, - initialIndex: tracks - .indexWhere((s) => s.id == currentTrack?.id), - autoPlay: true, + if (relatedArtists.isLoading || + !relatedArtists.hasData) { + return const CircularProgressIndicator(); + } else if (relatedArtists.hasError) { + return Center( + child: Text(relatedArtists.error.toString()), ); - } else if (isPlaylistPlaying && - currentTrack.id != null && - currentTrack.id != playlist.activeTrack?.id) { - await playlistNotifier.jumpToTrack(currentTrack); } - } - return Column( - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - context.l10n.top_tracks, - style: theme.textTheme.headlineSmall, - ), - ), - if (!isPlaylistPlaying) - IconButton( - icon: const Icon( - SpotubeIcons.queueAdd, - ), - onPressed: () { - playlistNotifier - .addTracks(topTracks.toList()); - scaffoldMessenger.showSnackBar( - SnackBar( - width: 300, - behavior: SnackBarBehavior.floating, - content: Text( - context.l10n.added_to_queue( - topTracks.length, - ), - textAlign: TextAlign.center, - ), - ), - ); - }, - ), - const SizedBox(width: 5), - IconButton( - icon: Icon( - isPlaylistPlaying - ? SpotubeIcons.stop - : SpotubeIcons.play, - color: Colors.white, - ), - style: IconButton.styleFrom( - backgroundColor: theme.colorScheme.primary, - ), - onPressed: () => - playPlaylist(topTracks.toList()), - ) - ], - ), - ...topTracks.mapIndexed((i, track) { - return TrackTile( - index: i, - track: track, - onTap: () async { - playPlaylist( - topTracks.toList(), - currentTrack: track, - ); - }, - ); - }), - ], - ); - }, - ), - 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( - padding: const EdgeInsets.all(8.0), - child: Text( - context.l10n.fans_also_like, - style: theme.textTheme.headlineSmall, - ), - ), - const SizedBox(height: 10), - HookBuilder( - builder: (context) { - final relatedArtists = - useQueries.artist.relatedArtistsOf( - ref, - artistId, - ); - - if (relatedArtists.isLoading || - !relatedArtists.hasData) { - return const CircularProgressIndicator(); - } else if (relatedArtists.hasError) { return Center( - child: Text(relatedArtists.error.toString()), + child: Wrap( + spacing: 20, + runSpacing: 20, + children: relatedArtists.data! + .map((artist) => ArtistCard(artist)) + .toList(), + ), ); - } - - return Center( - child: Wrap( - spacing: 20, - runSpacing: 20, - children: relatedArtists.data! - .map((artist) => ArtistCard(artist)) - .toList(), - ), - ); - }, - ), - ], + }, + ), + ], + ), ), ), ); diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 7ceecd58..fb94fb2c 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -9,6 +9,7 @@ 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'; @@ -103,240 +104,243 @@ class SearchPage extends HookConsumerWidget { } } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (tracks.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - context.l10n.songs, - style: theme.textTheme.titleLarge!, + return InterScrollbar( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tracks.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + context.l10n.songs, + style: theme.textTheme.titleLarge!, + ), ), - ), - if (searchTrack.isLoadingPage) - const CircularProgressIndicator() - else if (searchTrack.hasPageError) - Text( - searchTrack.errors.lastOrNull?.toString() ?? "", - ) - else - ...tracks.mapIndexed((i, track) { - return TrackTile( - index: i, - track: track, - onTap: () async { - final isTrackPlaying = - playlist.activeTrack?.id == track.id; - if (!isTrackPlaying && context.mounted) { - final shouldPlay = (playlist.tracks.length) > 20 - ? await showPromptDialog( - context: context, - title: context.l10n.playing_track( - track.name!, - ), - message: context.l10n.queue_clear_alert( - playlist.tracks.length, - ), - ) - : true; + if (searchTrack.isLoadingPage) + const CircularProgressIndicator() + else if (searchTrack.hasPageError) + Text( + searchTrack.errors.lastOrNull?.toString() ?? "", + ) + else + ...tracks.mapIndexed((i, track) { + return TrackTile( + index: i, + track: track, + onTap: () async { + final isTrackPlaying = + playlist.activeTrack?.id == track.id; + if (!isTrackPlaying && context.mounted) { + final shouldPlay = (playlist.tracks.length) > 20 + ? await showPromptDialog( + context: context, + title: context.l10n.playing_track( + track.name!, + ), + message: context.l10n.queue_clear_alert( + playlist.tracks.length, + ), + ) + : true; - if (shouldPlay) { - await playlistNotifier.load( - [track], - autoPlay: true, - ); - } - } - }, - ); - }), - if (searchTrack.hasNextPage && tracks.isNotEmpty) - Center( - child: TextButton( - onPressed: searchTrack.isRefreshingPage - ? null - : () => searchTrack.fetchNext(), - child: searchTrack.isRefreshingPage - ? const CircularProgressIndicator() - : Text(context.l10n.load_more), - ), - ), - if (playlists.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - context.l10n.playlists, - style: theme.textTheme.titleLarge!, - ), - ), - const SizedBox(height: 10), - ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - scrollbarOrientation: mediaQuery.lgAndUp - ? ScrollbarOrientation.bottom - : ScrollbarOrientation.top, - controller: playlistController, - child: Waypoint( - onTouchEdge: () { - searchPlaylist.fetchNext(); - }, - controller: playlistController, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: playlistController, - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - ...playlists.mapIndexed( - (i, playlist) { - if (i == playlists.length - 1 && - searchPlaylist.hasNextPage) { - return const ShimmerPlaybuttonCard( - count: 1); - } - return PlaylistCard(playlist); - }, - ), - ], - ), - ), - ), - ), - ), - if (searchPlaylist.isLoadingPage) - const CircularProgressIndicator(), - if (searchPlaylist.hasPageError) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - searchPlaylist.errors.lastOrNull?.toString() ?? "", - ), - ), - const SizedBox(height: 20), - if (artists.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - context.l10n.artists, - style: theme.textTheme.titleLarge!, - ), - ), - const SizedBox(height: 10), - ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - controller: artistController, - child: Waypoint( - controller: artistController, - onTouchEdge: () { - searchArtist.fetchNext(); - }, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: artistController, - child: Row( - children: [ - ...artists.mapIndexed( - (i, artist) { - if (i == artists.length - 1 && - searchArtist.hasNextPage) { - return const ShimmerPlaybuttonCard( - count: 1); - } - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 15), - child: ArtistCard(artist), - ); - }, - ), - ], - ), - ), - ), - ), - ), - if (searchArtist.isLoadingPage) - const CircularProgressIndicator(), - if (searchArtist.hasPageError) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - searchArtist.errors.lastOrNull?.toString() ?? "", - ), - ), - const SizedBox(height: 20), - if (albums.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - context.l10n.albums, - style: theme.textTheme.titleLarge!, - ), - ), - const SizedBox(height: 10), - ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - controller: albumController, - child: Waypoint( - controller: albumController, - onTouchEdge: () { - searchAlbum.fetchNext(); - }, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: albumController, - child: Row( - children: [ - ...albums.mapIndexed((i, album) { - if (i == albums.length - 1 && - searchAlbum.hasNextPage) { - return const ShimmerPlaybuttonCard(count: 1); - } - return AlbumCard( - TypeConversionUtils.simpleAlbum_X_Album( - album, - ), + if (shouldPlay) { + await playlistNotifier.load( + [track], + autoPlay: true, ); - }), - ], + } + } + }, + ); + }), + if (searchTrack.hasNextPage && tracks.isNotEmpty) + Center( + child: TextButton( + onPressed: searchTrack.isRefreshingPage + ? null + : () => searchTrack.fetchNext(), + child: searchTrack.isRefreshingPage + ? const CircularProgressIndicator() + : Text(context.l10n.load_more), + ), + ), + if (playlists.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + context.l10n.playlists, + style: theme.textTheme.titleLarge!, + ), + ), + const SizedBox(height: 10), + ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + scrollbarOrientation: mediaQuery.lgAndUp + ? ScrollbarOrientation.bottom + : ScrollbarOrientation.top, + controller: playlistController, + child: Waypoint( + onTouchEdge: () { + searchPlaylist.fetchNext(); + }, + controller: playlistController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: playlistController, + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + ...playlists.mapIndexed( + (i, playlist) { + if (i == playlists.length - 1 && + searchPlaylist.hasNextPage) { + return const ShimmerPlaybuttonCard( + count: 1); + } + return PlaylistCard(playlist); + }, + ), + ], + ), ), ), ), ), - ), - if (searchAlbum.isLoadingPage) - const CircularProgressIndicator(), - if (searchAlbum.hasPageError) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - searchAlbum.errors.lastOrNull?.toString() ?? "", + if (searchPlaylist.isLoadingPage) + const CircularProgressIndicator(), + if (searchPlaylist.hasPageError) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + searchPlaylist.errors.lastOrNull?.toString() ?? "", + ), + ), + const SizedBox(height: 20), + if (artists.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + context.l10n.artists, + style: theme.textTheme.titleLarge!, + ), + ), + const SizedBox(height: 10), + ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + controller: artistController, + child: Waypoint( + controller: artistController, + onTouchEdge: () { + searchArtist.fetchNext(); + }, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: artistController, + child: Row( + children: [ + ...artists.mapIndexed( + (i, artist) { + if (i == artists.length - 1 && + searchArtist.hasNextPage) { + return const ShimmerPlaybuttonCard( + count: 1); + } + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 15), + child: ArtistCard(artist), + ); + }, + ), + ], + ), + ), + ), ), ), - ], + if (searchArtist.isLoadingPage) + const CircularProgressIndicator(), + if (searchArtist.hasPageError) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + searchArtist.errors.lastOrNull?.toString() ?? "", + ), + ), + const SizedBox(height: 20), + if (albums.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + context.l10n.albums, + style: theme.textTheme.titleLarge!, + ), + ), + const SizedBox(height: 10), + ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + controller: albumController, + child: Waypoint( + controller: albumController, + onTouchEdge: () { + searchAlbum.fetchNext(); + }, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: albumController, + child: Row( + children: [ + ...albums.mapIndexed((i, album) { + if (i == albums.length - 1 && + searchAlbum.hasNextPage) { + return const ShimmerPlaybuttonCard( + count: 1); + } + return AlbumCard( + TypeConversionUtils.simpleAlbum_X_Album( + album, + ), + ); + }), + ], + ), + ), + ), + ), + ), + if (searchAlbum.isLoadingPage) + const CircularProgressIndicator(), + if (searchAlbum.hasPageError) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + searchAlbum.errors.lastOrNull?.toString() ?? "", + ), + ), + ], + ), ), ), ), diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index a41a38eb..69800633 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -5,6 +5,7 @@ import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/blacklist_provider.dart'; @@ -56,25 +57,27 @@ class BlackListPage extends HookConsumerWidget { ), ), ), - ListView.builder( - shrinkWrap: true, - itemCount: filteredBlacklist.length, - itemBuilder: (context, index) { - final item = filteredBlacklist.elementAt(index); - return ListTile( - leading: Text("${index + 1}."), - title: Text("${item.name} (${item.type.name})"), - subtitle: Text(item.id), - trailing: IconButton( - icon: Icon(SpotubeIcons.trash, color: Colors.red[400]), - onPressed: () { - ref - .read(BlackListNotifier.provider.notifier) - .remove(filteredBlacklist.elementAt(index)); - }, - ), - ); - }, + InterScrollbar( + child: ListView.builder( + shrinkWrap: true, + itemCount: filteredBlacklist.length, + itemBuilder: (context, index) { + final item = filteredBlacklist.elementAt(index); + return ListTile( + leading: Text("${index + 1}."), + title: Text("${item.name} (${item.type.name})"), + subtitle: Text(item.id), + trailing: IconButton( + icon: Icon(SpotubeIcons.trash, color: Colors.red[400]), + onPressed: () { + ref + .read(BlackListNotifier.provider.notifier) + .remove(filteredBlacklist.elementAt(index)); + }, + ), + ); + }, + ), ), ], ), diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 3bc1319f..91d87fbb 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/settings/section_card_with_heading.dart'; +import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/logger.dart'; @@ -91,47 +92,49 @@ class LogsPage extends HookWidget { ], ), body: SafeArea( - child: ListView.builder( - itemCount: logs.value.length, - itemBuilder: (context, index) { - final log = logs.value[index]; - return Stack( - children: [ - SectionCardWithHeading( - heading: log.date.toString(), - children: [ - Padding( - padding: const EdgeInsets.all(12.0), - child: SelectableText(log.body), - ), - ], - ), - Positioned( - right: 10, - top: 0, - child: IconButton( - icon: const Icon(SpotubeIcons.clipboard), - onPressed: () async { - await Clipboard.setData( - ClipboardData(text: log.body), - ); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.copied_to_clipboard( - log.date.toString(), + child: InterScrollbar( + child: ListView.builder( + itemCount: logs.value.length, + itemBuilder: (context, index) { + final log = logs.value[index]; + return Stack( + children: [ + SectionCardWithHeading( + heading: log.date.toString(), + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: SelectableText(log.body), + ), + ], + ), + Positioned( + right: 10, + top: 0, + child: IconButton( + icon: const Icon(SpotubeIcons.clipboard), + onPressed: () async { + await Clipboard.setData( + ClipboardData(text: log.body), + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.copied_to_clipboard( + log.date.toString(), + ), ), ), - ), - ); - } - }, + ); + } + }, + ), ), - ), - ], - ); - }, + ], + ); + }, + ), ), ), );