From 722dd86810ea076c0e540ff5cd108fb5f2df2a0f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 27 Nov 2023 19:34:18 +0600 Subject: [PATCH] chore: track view play not working properly --- .../sections/body/track_view_body.dart | 18 +- lib/extensions/infinite_query.dart | 26 +- lib/pages/artist/section/footer.dart | 6 +- .../playlist_generate/playlist_generate.dart | 513 +++++++++--------- lib/pages/playlist/playlist.dart | 2 +- 5 files changed, 296 insertions(+), 269 deletions(-) diff --git a/lib/components/shared/tracks_view/sections/body/track_view_body.dart b/lib/components/shared/tracks_view/sections/body/track_view_body.dart index 486e4405..1c3ba3fb 100644 --- a/lib/components/shared/tracks_view/sections/body/track_view_body.dart +++ b/lib/components/shared/tracks_view/sections/body/track_view_body.dart @@ -25,8 +25,6 @@ class TrackViewBodySection extends HookConsumerWidget { final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final props = InheritedTrackView.of(context); - final trackViewState = ref.watch(trackViewProvider(props.tracks)); - final searchController = useTextEditingController(); final searchFocus = useFocusNode(); @@ -35,12 +33,19 @@ class TrackViewBodySection extends HookConsumerWidget { final isFiltering = useState(false); + final uniqTracks = useMemoized(() { + final trackIds = props.tracks.map((e) => e.id).toSet(); + return props.tracks.where((e) => trackIds.remove(e.id)).toList(); + }, [props.tracks]); + + final trackViewState = ref.watch(trackViewProvider(uniqTracks)); + final tracks = useMemoized(() { List filteredTracks; if (searchQuery.isEmpty) { - filteredTracks = props.tracks; + filteredTracks = uniqTracks; } else { - filteredTracks = props.tracks + filteredTracks = uniqTracks .map((e) => (weightedRatio(e.name!, searchQuery), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) .where((e) => e.$1 > 50) @@ -48,7 +53,7 @@ class TrackViewBodySection extends HookConsumerWidget { .toList(); } return ServiceUtils.sortTracks(filteredTracks, trackViewState.sortBy); - }, [trackViewState.sortBy, searchQuery, props.tracks]); + }, [trackViewState.sortBy, searchQuery, uniqTracks]); final isUserPlaylist = useIsUserPlaylist(ref, props.collectionId); @@ -106,8 +111,9 @@ class TrackViewBodySection extends HookConsumerWidget { if (isActive || playlist.tracks.contains(track)) { await playlistNotifier.jumpToTrack(track); } else { + final tracks = await props.pagination.onFetchAll(); await playlistNotifier.load( - props.tracks, + tracks, initialIndex: index, autoPlay: true, ); diff --git a/lib/extensions/infinite_query.dart b/lib/extensions/infinite_query.dart index 90dcf73e..2181ab3c 100644 --- a/lib/extensions/infinite_query.dart +++ b/lib/extensions/infinite_query.dart @@ -9,17 +9,21 @@ extension FetchAllTracks on InfiniteQuery, dynamic, int> { return pages.expand((page) => page).toList(); } final tracks = await getAllTracks(); - final pagedTracks = tracks.fold( - >{}, - (acc, element) { - final index = acc.length; - final groupIndex = index ~/ 20; - final group = acc[groupIndex] ?? []; - group.add(element); - acc[groupIndex] = group; - return acc; - }, - ); + + final numOfPages = (tracks.length / 20).round(); + + final Map> pagedTracks = {}; + + for (var i = 0; i < numOfPages; i++) { + if (i == numOfPages - 1) { + final pageTracks = tracks.sublist(i * 20); + pagedTracks[i] = pageTracks; + break; + } + + final pageTracks = tracks.sublist(i * 20, (i + 1) * 20); + pagedTracks[i] = pageTracks; + } for (final group in pagedTracks.entries) { setPageData(group.key, group.value); diff --git a/lib/pages/artist/section/footer.dart b/lib/pages/artist/section/footer.dart index 3c0db8a5..b01ef705 100644 --- a/lib/pages/artist/section/footer.dart +++ b/lib/pages/artist/section/footer.dart @@ -38,9 +38,9 @@ class ArtistPageFooter extends HookConsumerWidget { BlendMode.darken, ), image: UniversalImage.imageProvider( - summary.data!.originalimage?.source_ ?? artistImage, - height: summary.data!.originalimage?.height.toDouble(), - width: summary.data!.originalimage?.width.toDouble(), + summary.data!.thumbnail?.source_ ?? artistImage, + height: summary.data!.thumbnail?.height.toDouble(), + width: summary.data!.thumbnail?.width.toDouble(), ), fit: BoxFit.cover, alignment: Alignment.center, diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index 4b8dddaf..802b28d3 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -242,267 +242,284 @@ class PlaylistGeneratorPage extends HookConsumerWidget { }, ); + final controller = useScrollController(); + return Scaffold( appBar: PageWindowTitleBar( leading: const BackButton(), title: Text(context.l10n.generate_playlist), centerTitle: true, ), - body: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: Breakpoints.lg), - child: SliderTheme( - data: const SliderThemeData( - overlayShape: RoundSliderOverlayShape(), - ), - child: SafeArea( - child: LayoutBuilder(builder: (context, constrains) { - return ListView( - padding: const EdgeInsets.all(16), - children: [ - ValueListenableBuilder( - valueListenable: limit, - builder: (context, value, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.number_of_tracks_generate, - style: textTheme.titleMedium, - ), - Row( + body: Scrollbar( + controller: controller, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: Breakpoints.lg), + child: SliderTheme( + data: const SliderThemeData( + overlayShape: RoundSliderOverlayShape(), + ), + child: SafeArea( + child: LayoutBuilder(builder: (context, constrains) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: ListView( + controller: controller, + padding: const EdgeInsets.all(16), + children: [ + ValueListenableBuilder( + valueListenable: limit, + builder: (context, value, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 40, - height: 40, - alignment: Alignment.center, - decoration: BoxDecoration( - color: theme.colorScheme.primary, - shape: BoxShape.circle, - ), - child: Text( - value.round().toString(), - style: textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.primaryContainer, - ), - ), + Text( + context.l10n.number_of_tracks_generate, + style: textTheme.titleMedium, ), - Expanded( - child: Slider( - value: value.toDouble(), - min: 10, - max: 100, - divisions: 9, - label: value.round().toString(), - onChanged: (value) { - limit.value = value.round(); - }, - ), + Row( + children: [ + Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + shape: BoxShape.circle, + ), + child: Text( + value.round().toString(), + style: textTheme.bodyLarge?.copyWith( + color: theme + .colorScheme.primaryContainer, + ), + ), + ), + Expanded( + child: Slider( + value: value.toDouble(), + min: 10, + max: 100, + divisions: 9, + label: value.round().toString(), + onChanged: (value) { + limit.value = value.round(); + }, + ), + ) + ], ) ], - ) - ], - ); - }, - ), - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: countrySelector, - ), - const SizedBox(width: 16), - Expanded( - child: genreSelector, - ), + ); + }, + ), + const SizedBox(height: 16), + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: countrySelector, + ), + const SizedBox(width: 16), + Expanded( + child: genreSelector, + ), + ], + ) + else ...[ + countrySelector, + const SizedBox(height: 16), + genreSelector, ], - ) - else ...[ - countrySelector, - const SizedBox(height: 16), - genreSelector, - ], - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: artistAutoComplete, - ), - const SizedBox(width: 16), - Expanded( - child: tracksAutocomplete, - ), + const SizedBox(height: 16), + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: artistAutoComplete, + ), + const SizedBox(width: 16), + Expanded( + child: tracksAutocomplete, + ), + ], + ) + else ...[ + artistAutoComplete, + const SizedBox(height: 16), + tracksAutocomplete, ], - ) - else ...[ - artistAutoComplete, - const SizedBox(height: 16), - tracksAutocomplete, - ], - const SizedBox(height: 16), - RecommendationAttributeDials( - title: Text(context.l10n.acousticness), - values: acousticness.value, - onChanged: (value) { - acousticness.value = value; - }, + const SizedBox(height: 16), + RecommendationAttributeDials( + title: Text(context.l10n.acousticness), + values: acousticness.value, + onChanged: (value) { + acousticness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.danceability), + values: danceability.value, + onChanged: (value) { + danceability.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.energy), + values: energy.value, + onChanged: (value) { + energy.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.instrumentalness), + values: instrumentalness.value, + onChanged: (value) { + instrumentalness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.liveness), + values: liveness.value, + onChanged: (value) { + liveness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.loudness), + values: loudness.value, + onChanged: (value) { + loudness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.speechiness), + values: speechiness.value, + onChanged: (value) { + speechiness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.valence), + values: valence.value, + onChanged: (value) { + valence.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.popularity), + values: popularity.value, + base: 100, + onChanged: (value) { + popularity.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.key), + values: key.value, + base: 11, + onChanged: (value) { + key.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.duration), + values: ( + max: durationMs.value.max / 1000, + target: durationMs.value.target / 1000, + min: durationMs.value.min / 1000, + ), + onChanged: (value) { + durationMs.value = ( + max: value.max * 1000, + target: value.target * 1000, + min: value.min * 1000, + ); + }, + presets: { + context.l10n.short: (min: 50, target: 90, max: 120), + context.l10n.medium: ( + min: 120, + target: 180, + max: 200 + ), + context.l10n.long: (min: 480, target: 560, max: 640) + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.tempo), + values: tempo.value, + onChanged: (value) { + tempo.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.mode), + values: mode.value, + onChanged: (value) { + mode.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.time_signature), + values: timeSignature.value, + onChanged: (value) { + timeSignature.value = value; + }, + ), + const SizedBox(height: 20), + FilledButton.icon( + icon: const Icon(SpotubeIcons.magic), + label: Text(context.l10n.generate_playlist), + onPressed: artists.value.isEmpty && + tracks.value.isEmpty && + genres.value.isEmpty + ? null + : () { + final PlaylistGenerateResultRouteState + routeState = ( + seeds: ( + artists: artists.value + .map((a) => a.id!) + .toList(), + tracks: tracks.value + .map((t) => t.id!) + .toList(), + genres: genres.value + ), + market: market.value, + limit: limit.value, + parameters: ( + acousticness: acousticness.value, + danceability: danceability.value, + energy: energy.value, + instrumentalness: instrumentalness.value, + liveness: liveness.value, + loudness: loudness.value, + speechiness: speechiness.value, + valence: valence.value, + popularity: popularity.value, + key: key.value, + duration_ms: durationMs.value, + tempo: tempo.value, + mode: mode.value, + time_signature: timeSignature.value, + ) + ); + GoRouter.of(context).push( + "/library/generate/result", + extra: routeState, + ); + }, + ), + ], ), - RecommendationAttributeDials( - title: Text(context.l10n.danceability), - values: danceability.value, - onChanged: (value) { - danceability.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.energy), - values: energy.value, - onChanged: (value) { - energy.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.instrumentalness), - values: instrumentalness.value, - onChanged: (value) { - instrumentalness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.liveness), - values: liveness.value, - onChanged: (value) { - liveness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.loudness), - values: loudness.value, - onChanged: (value) { - loudness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.speechiness), - values: speechiness.value, - onChanged: (value) { - speechiness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.valence), - values: valence.value, - onChanged: (value) { - valence.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.popularity), - values: popularity.value, - base: 100, - onChanged: (value) { - popularity.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.key), - values: key.value, - base: 11, - onChanged: (value) { - key.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.duration), - values: ( - max: durationMs.value.max / 1000, - target: durationMs.value.target / 1000, - min: durationMs.value.min / 1000, - ), - onChanged: (value) { - durationMs.value = ( - max: value.max * 1000, - target: value.target * 1000, - min: value.min * 1000, - ); - }, - presets: { - context.l10n.short: (min: 50, target: 90, max: 120), - context.l10n.medium: (min: 120, target: 180, max: 200), - context.l10n.long: (min: 480, target: 560, max: 640) - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.tempo), - values: tempo.value, - onChanged: (value) { - tempo.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.mode), - values: mode.value, - onChanged: (value) { - mode.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.time_signature), - values: timeSignature.value, - onChanged: (value) { - timeSignature.value = value; - }, - ), - const SizedBox(height: 20), - FilledButton.icon( - icon: const Icon(SpotubeIcons.magic), - label: Text(context.l10n.generate_playlist), - onPressed: artists.value.isEmpty && - tracks.value.isEmpty && - genres.value.isEmpty - ? null - : () { - final PlaylistGenerateResultRouteState - routeState = ( - seeds: ( - artists: - artists.value.map((a) => a.id!).toList(), - tracks: - tracks.value.map((t) => t.id!).toList(), - genres: genres.value - ), - market: market.value, - limit: limit.value, - parameters: ( - acousticness: acousticness.value, - danceability: danceability.value, - energy: energy.value, - instrumentalness: instrumentalness.value, - liveness: liveness.value, - loudness: loudness.value, - speechiness: speechiness.value, - valence: valence.value, - popularity: popularity.value, - key: key.value, - duration_ms: durationMs.value, - tempo: tempo.value, - mode: mode.value, - time_signature: timeSignature.value, - ) - ); - GoRouter.of(context).push( - "/library/generate/result", - extra: routeState, - ); - }, - ), - ], - ); - }), + ); + }), + ), ), ), ), diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index ab39b225..29601a09 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -53,7 +53,7 @@ class PlaylistPage extends HookConsumerWidget { ), pagination: PaginationProps.fromQuery( tracksQuery, - onFetchAll: () async { + onFetchAll: () { return tracksQuery.fetchAllTracks( getAllTracks: () async { final res = await spotify.playlists