chore: track view play not working properly

This commit is contained in:
Kingkor Roy Tirtho 2023-11-27 19:34:18 +06:00
parent 64080ef273
commit 722dd86810
5 changed files with 296 additions and 269 deletions

View File

@ -25,8 +25,6 @@ class TrackViewBodySection extends HookConsumerWidget {
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final props = InheritedTrackView.of(context); final props = InheritedTrackView.of(context);
final trackViewState = ref.watch(trackViewProvider(props.tracks));
final searchController = useTextEditingController(); final searchController = useTextEditingController();
final searchFocus = useFocusNode(); final searchFocus = useFocusNode();
@ -35,12 +33,19 @@ class TrackViewBodySection extends HookConsumerWidget {
final isFiltering = useState(false); 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(() { final tracks = useMemoized(() {
List<Track> filteredTracks; List<Track> filteredTracks;
if (searchQuery.isEmpty) { if (searchQuery.isEmpty) {
filteredTracks = props.tracks; filteredTracks = uniqTracks;
} else { } else {
filteredTracks = props.tracks filteredTracks = uniqTracks
.map((e) => (weightedRatio(e.name!, searchQuery), e)) .map((e) => (weightedRatio(e.name!, searchQuery), e))
.sorted((a, b) => b.$1.compareTo(a.$1)) .sorted((a, b) => b.$1.compareTo(a.$1))
.where((e) => e.$1 > 50) .where((e) => e.$1 > 50)
@ -48,7 +53,7 @@ class TrackViewBodySection extends HookConsumerWidget {
.toList(); .toList();
} }
return ServiceUtils.sortTracks(filteredTracks, trackViewState.sortBy); return ServiceUtils.sortTracks(filteredTracks, trackViewState.sortBy);
}, [trackViewState.sortBy, searchQuery, props.tracks]); }, [trackViewState.sortBy, searchQuery, uniqTracks]);
final isUserPlaylist = useIsUserPlaylist(ref, props.collectionId); final isUserPlaylist = useIsUserPlaylist(ref, props.collectionId);
@ -106,8 +111,9 @@ class TrackViewBodySection extends HookConsumerWidget {
if (isActive || playlist.tracks.contains(track)) { if (isActive || playlist.tracks.contains(track)) {
await playlistNotifier.jumpToTrack(track); await playlistNotifier.jumpToTrack(track);
} else { } else {
final tracks = await props.pagination.onFetchAll();
await playlistNotifier.load( await playlistNotifier.load(
props.tracks, tracks,
initialIndex: index, initialIndex: index,
autoPlay: true, autoPlay: true,
); );

View File

@ -9,17 +9,21 @@ extension FetchAllTracks on InfiniteQuery<List<Track>, dynamic, int> {
return pages.expand((page) => page).toList(); return pages.expand((page) => page).toList();
} }
final tracks = await getAllTracks(); final tracks = await getAllTracks();
final pagedTracks = tracks.fold(
<int, List<Track>>{}, final numOfPages = (tracks.length / 20).round();
(acc, element) {
final index = acc.length; final Map<int, List<Track>> pagedTracks = {};
final groupIndex = index ~/ 20;
final group = acc[groupIndex] ?? []; for (var i = 0; i < numOfPages; i++) {
group.add(element); if (i == numOfPages - 1) {
acc[groupIndex] = group; final pageTracks = tracks.sublist(i * 20);
return acc; pagedTracks[i] = pageTracks;
}, break;
); }
final pageTracks = tracks.sublist(i * 20, (i + 1) * 20);
pagedTracks[i] = pageTracks;
}
for (final group in pagedTracks.entries) { for (final group in pagedTracks.entries) {
setPageData(group.key, group.value); setPageData(group.key, group.value);

View File

@ -38,9 +38,9 @@ class ArtistPageFooter extends HookConsumerWidget {
BlendMode.darken, BlendMode.darken,
), ),
image: UniversalImage.imageProvider( image: UniversalImage.imageProvider(
summary.data!.originalimage?.source_ ?? artistImage, summary.data!.thumbnail?.source_ ?? artistImage,
height: summary.data!.originalimage?.height.toDouble(), height: summary.data!.thumbnail?.height.toDouble(),
width: summary.data!.originalimage?.width.toDouble(), width: summary.data!.thumbnail?.width.toDouble(),
), ),
fit: BoxFit.cover, fit: BoxFit.cover,
alignment: Alignment.center, alignment: Alignment.center,

View File

@ -242,267 +242,284 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
}, },
); );
final controller = useScrollController();
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: PageWindowTitleBar(
leading: const BackButton(), leading: const BackButton(),
title: Text(context.l10n.generate_playlist), title: Text(context.l10n.generate_playlist),
centerTitle: true, centerTitle: true,
), ),
body: Center( body: Scrollbar(
child: ConstrainedBox( controller: controller,
constraints: BoxConstraints(maxWidth: Breakpoints.lg), child: Center(
child: SliderTheme( child: ConstrainedBox(
data: const SliderThemeData( constraints: BoxConstraints(maxWidth: Breakpoints.lg),
overlayShape: RoundSliderOverlayShape(), child: SliderTheme(
), data: const SliderThemeData(
child: SafeArea( overlayShape: RoundSliderOverlayShape(),
child: LayoutBuilder(builder: (context, constrains) { ),
return ListView( child: SafeArea(
padding: const EdgeInsets.all(16), child: LayoutBuilder(builder: (context, constrains) {
children: [ return ScrollConfiguration(
ValueListenableBuilder( behavior: ScrollConfiguration.of(context)
valueListenable: limit, .copyWith(scrollbars: false),
builder: (context, value, child) { child: ListView(
return Column( controller: controller,
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16),
children: [ children: [
Text( ValueListenableBuilder(
context.l10n.number_of_tracks_generate, valueListenable: limit,
style: textTheme.titleMedium, builder: (context, value, child) {
), return Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Text(
width: 40, context.l10n.number_of_tracks_generate,
height: 40, style: textTheme.titleMedium,
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( Row(
child: Slider( children: [
value: value.toDouble(), Container(
min: 10, width: 40,
max: 100, height: 40,
divisions: 9, alignment: Alignment.center,
label: value.round().toString(), decoration: BoxDecoration(
onChanged: (value) { color: theme.colorScheme.primary,
limit.value = value.round(); 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)
const SizedBox(height: 16), Row(
if (constrains.mdAndUp) crossAxisAlignment: CrossAxisAlignment.start,
Row( children: [
crossAxisAlignment: CrossAxisAlignment.start, Expanded(
children: [ child: countrySelector,
Expanded( ),
child: countrySelector, const SizedBox(width: 16),
), Expanded(
const SizedBox(width: 16), child: genreSelector,
Expanded( ),
child: genreSelector, ],
), )
else ...[
countrySelector,
const SizedBox(height: 16),
genreSelector,
], ],
) const SizedBox(height: 16),
else ...[ if (constrains.mdAndUp)
countrySelector, Row(
const SizedBox(height: 16), crossAxisAlignment: CrossAxisAlignment.start,
genreSelector, children: [
], Expanded(
const SizedBox(height: 16), child: artistAutoComplete,
if (constrains.mdAndUp) ),
Row( const SizedBox(width: 16),
crossAxisAlignment: CrossAxisAlignment.start, Expanded(
children: [ child: tracksAutocomplete,
Expanded( ),
child: artistAutoComplete, ],
), )
const SizedBox(width: 16), else ...[
Expanded( artistAutoComplete,
child: tracksAutocomplete, const SizedBox(height: 16),
), tracksAutocomplete,
], ],
) const SizedBox(height: 16),
else ...[ RecommendationAttributeDials(
artistAutoComplete, title: Text(context.l10n.acousticness),
const SizedBox(height: 16), values: acousticness.value,
tracksAutocomplete, onChanged: (value) {
], acousticness.value = value;
const SizedBox(height: 16), },
RecommendationAttributeDials( ),
title: Text(context.l10n.acousticness), RecommendationAttributeDials(
values: acousticness.value, title: Text(context.l10n.danceability),
onChanged: (value) { values: danceability.value,
acousticness.value = 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,
);
},
),
],
);
}),
), ),
), ),
), ),

View File

@ -53,7 +53,7 @@ class PlaylistPage extends HookConsumerWidget {
), ),
pagination: PaginationProps.fromQuery( pagination: PaginationProps.fromQuery(
tracksQuery, tracksQuery,
onFetchAll: () async { onFetchAll: () {
return tracksQuery.fetchAllTracks( return tracksQuery.fetchAllTracks(
getAllTracks: () async { getAllTracks: () async {
final res = await spotify.playlists final res = await spotify.playlists