Skip to content

Commit

Permalink
feat: sort tracks in playlist, album and local tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Oct 13, 2022
1 parent 91d5d10 commit cb4bd25
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 26 deletions.
20 changes: 15 additions & 5 deletions lib/components/Album/AlbumView.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Shared/HeartButton.dart';
import 'package:spotube/components/Shared/TrackCollectionView.dart';
import 'package:spotube/components/Shared/TracksTableView.dart';
import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:spotube/models/CurrentPlaylist.dart';
import 'package:spotube/provider/Auth.dart';
Expand All @@ -18,22 +20,28 @@ class AlbumView extends HookConsumerWidget {
final AlbumSimple album;
const AlbumView(this.album, {Key? key}) : super(key: key);

Future<void> playPlaylist(Playback playback, List<Track> tracks,
{Track? currentTrack}) async {
currentTrack ??= tracks.first;
Future<void> playPlaylist(
Playback playback,
List<Track> tracks,
WidgetRef ref, {
Track? currentTrack,
}) async {
final sortBy = ref.read(trackCollectionSortState(album.id!));
final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy);
currentTrack ??= sortedTracks.first;
final isPlaylistPlaying = playback.playlist?.id == album.id;
if (!isPlaylistPlaying) {
await playback.playPlaylist(
CurrentPlaylist(
tracks: tracks,
tracks: sortedTracks,
id: album.id!,
name: album.name!,
thumbnail: TypeConversionUtils.image_X_UrlString(
album.images,
placeholder: ImagePlaceholder.collection,
),
),
tracks.indexWhere((s) => s.id == currentTrack?.id),
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
);
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
Expand Down Expand Up @@ -82,6 +90,7 @@ class AlbumView extends HookConsumerWidget {
.map((track) =>
TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList(),
ref,
);
} else if (isAlbumPlaying && track != null) {
playPlaylist(
Expand All @@ -91,6 +100,7 @@ class AlbumView extends HookConsumerWidget {
TypeConversionUtils.simpleTrack_X_Track(track, album))
.toList(),
currentTrack: track,
ref,
);
} else {
playback.stop();
Expand Down
31 changes: 28 additions & 3 deletions lib/components/Library/UserLocalTracks.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:mime/mime.dart';
Expand All @@ -10,6 +12,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/LoaderShimmers/ShimmerTrackTile.dart';
import 'package:spotube/components/Shared/SortTracksDropdown.dart';
import 'package:spotube/components/Shared/TrackTile.dart';
import 'package:spotube/hooks/useAsyncEffect.dart';
import 'package:spotube/models/CurrentPlaylist.dart';
Expand All @@ -18,6 +21,7 @@ import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';

const supportedAudioTypes = [
Expand All @@ -37,6 +41,15 @@ const imgMimeToExt = {
"image/gif": ".gif",
};

enum SortBy {
none,
ascending,
descending,
artist,
album,
dateAdded,
}

final localTracksProvider = FutureProvider<List<Track>>((ref) async {
try {
if (kIsWeb) return [];
Expand Down Expand Up @@ -132,6 +145,7 @@ class UserLocalTracks extends HookConsumerWidget {

@override
Widget build(BuildContext context, ref) {
final sortBy = useState<SortBy>(SortBy.none);
final playback = ref.watch(playbackProvider);
final isPlaylistPlaying = playback.playlist?.id == "local";
final trackSnapshot = ref.watch(localTracksProvider);
Expand Down Expand Up @@ -176,6 +190,13 @@ class UserLocalTracks extends HookConsumerWidget {
: null,
),
const Spacer(),
SortTracksDropdown(
value: sortBy.value,
onChanged: (value) {
if (value != null) sortBy.value = value;
},
),
const SizedBox(width: 10),
ElevatedButton(
child: const Icon(Icons.refresh_rounded),
onPressed: () {
Expand All @@ -187,11 +208,15 @@ class UserLocalTracks extends HookConsumerWidget {
),
trackSnapshot.when(
data: (tracks) {
final sortedTracks = useMemoized(() {
return ServiceUtils.sortTracks(tracks, sortBy.value);
}, [sortBy.value, tracks]);

return Expanded(
child: ListView.builder(
itemCount: tracks.length,
itemCount: sortedTracks.length,
itemBuilder: (context, index) {
final track = tracks[index];
final track = sortedTracks[index];
return TrackTile(
playback,
duration:
Expand All @@ -204,7 +229,7 @@ class UserLocalTracks extends HookConsumerWidget {
onTrackPlayButtonPressed: (currentTrack) {
return playLocalTracks(
playback,
tracks,
sortedTracks,
currentTrack: track,
);
},
Expand Down
24 changes: 15 additions & 9 deletions lib/components/Playlist/PlaylistView.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/components/Shared/HeartButton.dart';
import 'package:spotube/components/Shared/TrackCollectionView.dart';
import 'package:spotube/components/Shared/TracksTableView.dart';
import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/hooks/usePaletteColor.dart';
import 'package:spotube/models/CurrentPlaylist.dart';
Expand All @@ -16,30 +17,37 @@ import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/SpotifyRequests.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';

class PlaylistView extends HookConsumerWidget {
final logger = getLogger(PlaylistView);
final PlaylistSimple playlist;
PlaylistView(this.playlist, {Key? key}) : super(key: key);

playPlaylist(Playback playback, List<Track> tracks,
{Track? currentTrack}) async {
currentTrack ??= tracks.first;
Future<void> playPlaylist(
Playback playback,
List<Track> tracks,
WidgetRef ref, {
Track? currentTrack,
}) async {
final sortBy = ref.read(trackCollectionSortState(playlist.id!));
final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy);
currentTrack ??= sortedTracks.first;
final isPlaylistPlaying =
playback.playlist?.id != null && playback.playlist?.id == playlist.id;
if (!isPlaylistPlaying) {
await playback.playPlaylist(
CurrentPlaylist(
tracks: tracks,
tracks: sortedTracks,
id: playlist.id!,
name: playlist.name!,
thumbnail: TypeConversionUtils.image_X_UrlString(
playlist.images,
placeholder: ImagePlaceholder.collection,
),
),
tracks.indexWhere((s) => s.id == currentTrack?.id),
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
);
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
Expand Down Expand Up @@ -85,14 +93,12 @@ class PlaylistView extends HookConsumerWidget {
onPlay: ([track]) {
if (tracksSnapshot.asData?.value != null) {
if (!isPlaylistPlaying) {
playPlaylist(
playback,
tracksSnapshot.asData!.value,
);
playPlaylist(playback, tracksSnapshot.asData!.value, ref);
} else if (isPlaylistPlaying && track != null) {
playPlaylist(
playback,
tracksSnapshot.asData!.value,
ref,
currentTrack: track,
);
} else {
Expand Down
54 changes: 54 additions & 0 deletions lib/components/Shared/SortTracksDropdown.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:spotube/components/Library/UserLocalTracks.dart';

class SortTracksDropdown extends StatelessWidget {
final SortBy? value;
final void Function(SortBy)? onChanged;
const SortTracksDropdown({
this.onChanged,
this.value,
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return PopupMenuButton<SortBy>(
itemBuilder: (context) {
return [
PopupMenuItem(
value: SortBy.none,
enabled: value != SortBy.none,
child: const Text("None"),
),
PopupMenuItem(
value: SortBy.ascending,
enabled: value != SortBy.ascending,
child: const Text("Sort by A-Z"),
),
PopupMenuItem(
value: SortBy.descending,
enabled: value != SortBy.descending,
child: const Text("Sort by Z-A"),
),
PopupMenuItem(
value: SortBy.dateAdded,
enabled: value != SortBy.dateAdded,
child: const Text("Sort by Date"),
),
PopupMenuItem(
value: SortBy.artist,
enabled: value != SortBy.artist,
child: const Text("Sort by Artist"),
),
PopupMenuItem(
value: SortBy.album,
enabled: value != SortBy.album,
child: const Text("Sort by Album"),
),
];
},
onSelected: onChanged,
icon: const Icon(Icons.sort_rounded),
);
}
}
36 changes: 29 additions & 7 deletions lib/components/Shared/TracksTableView.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Library/UserLocalTracks.dart';
import 'package:spotube/components/Shared/DownloadConfirmationDialog.dart';
import 'package:spotube/components/Shared/NotFound.dart';
import 'package:spotube/components/Shared/SortTracksDropdown.dart';
import 'package:spotube/components/Shared/TrackTile.dart';
import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/provider/Downloader.dart';
import 'package:spotube/provider/Playback.dart';
import 'package:spotube/utils/primitive_utils.dart';
import 'package:spotube/utils/service_utils.dart';

final trackCollectionSortState =
StateProvider.family<SortBy, String>((ref, _) => SortBy.none);

class TracksTableView extends HookConsumerWidget {
final void Function(Track currentTrack)? onTrackPlayButtonPressed;
Expand Down Expand Up @@ -39,26 +45,34 @@ class TracksTableView extends HookConsumerWidget {

final selected = useState<List<String>>([]);
final showCheck = useState<bool>(false);
final sortBy = ref.watch(trackCollectionSortState(playlistId ?? ''));

final sortedTracks = useMemoized(
() {
return ServiceUtils.sortTracks(tracks, sortBy);
},
[tracks, sortBy],
);

final selectedTracks = useMemoized(
() => tracks.where(
() => sortedTracks.where(
(track) => selected.value.contains(track.id),
),
[tracks],
[sortedTracks],
);

final children = tracks.isEmpty
final children = sortedTracks.isEmpty
? [const NotFound(vertical: true)]
: [
if (heading != null) heading!,
Row(
children: [
Checkbox(
value: selected.value.length == tracks.length,
value: selected.value.length == sortedTracks.length,
onChanged: (checked) {
if (!showCheck.value) showCheck.value = true;
if (checked == true) {
selected.value = tracks.map((s) => s.id!).toList();
selected.value = sortedTracks.map((s) => s.id!).toList();
} else {
selected.value = [];
showCheck.value = false;
Expand Down Expand Up @@ -104,11 +118,20 @@ class TracksTableView extends HookConsumerWidget {
Text("Time", style: tableHeadStyle),
const SizedBox(width: 10),
],
SortTracksDropdown(
value: sortBy,
onChanged: (value) {
ref
.read(trackCollectionSortState(playlistId ?? '').state)
.state = value;
},
),
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
enabled: selected.value.isNotEmpty,
value: "download",
child: Row(
children: [
const Icon(Icons.file_download_outlined),
Expand All @@ -117,7 +140,6 @@ class TracksTableView extends HookConsumerWidget {
),
],
),
value: "download",
),
];
},
Expand All @@ -144,7 +166,7 @@ class TracksTableView extends HookConsumerWidget {
),
],
),
...tracks.asMap().entries.map((track) {
...sortedTracks.asMap().entries.map((track) {
String duration =
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
return InkWell(
Expand Down
4 changes: 2 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ class _SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
localStorage!.setString(
LocalStorageKeys.windowSizeInfo,
jsonEncode({
'width': appWindow.isMaximized ? 0 : appWindow.size.width,
'height': appWindow.isMaximized ? 0 : appWindow.size.height,
'width': appWindow.isMaximized ? 0.0 : appWindow.size.width,
'height': appWindow.isMaximized ? 0.0 : appWindow.size.height,
}),
);
prevSize = appWindow.size;
Expand Down
Loading

0 comments on commit cb4bd25

Please sign in to comment.