mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
feat: sort tracks in playlist, album and local tracks
This commit is contained in:
parent
91d5d1003b
commit
cb4bd25df1
@ -6,7 +6,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Shared/HeartButton.dart';
|
import 'package:spotube/components/Shared/HeartButton.dart';
|
||||||
import 'package:spotube/components/Shared/TrackCollectionView.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/useBreakpoints.dart';
|
||||||
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:spotube/models/CurrentPlaylist.dart';
|
import 'package:spotube/models/CurrentPlaylist.dart';
|
||||||
import 'package:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
@ -18,14 +20,20 @@ class AlbumView extends HookConsumerWidget {
|
|||||||
final AlbumSimple album;
|
final AlbumSimple album;
|
||||||
const AlbumView(this.album, {Key? key}) : super(key: key);
|
const AlbumView(this.album, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
Future<void> playPlaylist(Playback playback, List<Track> tracks,
|
Future<void> playPlaylist(
|
||||||
{Track? currentTrack}) async {
|
Playback playback,
|
||||||
currentTrack ??= tracks.first;
|
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;
|
final isPlaylistPlaying = playback.playlist?.id == album.id;
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
await playback.playPlaylist(
|
await playback.playPlaylist(
|
||||||
CurrentPlaylist(
|
CurrentPlaylist(
|
||||||
tracks: tracks,
|
tracks: sortedTracks,
|
||||||
id: album.id!,
|
id: album.id!,
|
||||||
name: album.name!,
|
name: album.name!,
|
||||||
thumbnail: TypeConversionUtils.image_X_UrlString(
|
thumbnail: TypeConversionUtils.image_X_UrlString(
|
||||||
@ -33,7 +41,7 @@ class AlbumView extends HookConsumerWidget {
|
|||||||
placeholder: ImagePlaceholder.collection,
|
placeholder: ImagePlaceholder.collection,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tracks.indexWhere((s) => s.id == currentTrack?.id),
|
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
);
|
);
|
||||||
} else if (isPlaylistPlaying &&
|
} else if (isPlaylistPlaying &&
|
||||||
currentTrack.id != null &&
|
currentTrack.id != null &&
|
||||||
@ -82,6 +90,7 @@ class AlbumView extends HookConsumerWidget {
|
|||||||
.map((track) =>
|
.map((track) =>
|
||||||
TypeConversionUtils.simpleTrack_X_Track(track, album))
|
TypeConversionUtils.simpleTrack_X_Track(track, album))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
ref,
|
||||||
);
|
);
|
||||||
} else if (isAlbumPlaying && track != null) {
|
} else if (isAlbumPlaying && track != null) {
|
||||||
playPlaylist(
|
playPlaylist(
|
||||||
@ -91,6 +100,7 @@ class AlbumView extends HookConsumerWidget {
|
|||||||
TypeConversionUtils.simpleTrack_X_Track(track, album))
|
TypeConversionUtils.simpleTrack_X_Track(track, album))
|
||||||
.toList(),
|
.toList(),
|
||||||
currentTrack: track,
|
currentTrack: track,
|
||||||
|
ref,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
playback.stop();
|
playback.stop();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:metadata_god/metadata_god.dart';
|
import 'package:metadata_god/metadata_god.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
@ -10,6 +12,7 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/LoaderShimmers/ShimmerTrackTile.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/components/Shared/TrackTile.dart';
|
||||||
import 'package:spotube/hooks/useAsyncEffect.dart';
|
import 'package:spotube/hooks/useAsyncEffect.dart';
|
||||||
import 'package:spotube/models/CurrentPlaylist.dart';
|
import 'package:spotube/models/CurrentPlaylist.dart';
|
||||||
@ -18,6 +21,7 @@ import 'package:spotube/provider/Playback.dart';
|
|||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
const supportedAudioTypes = [
|
const supportedAudioTypes = [
|
||||||
@ -37,6 +41,15 @@ const imgMimeToExt = {
|
|||||||
"image/gif": ".gif",
|
"image/gif": ".gif",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum SortBy {
|
||||||
|
none,
|
||||||
|
ascending,
|
||||||
|
descending,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
dateAdded,
|
||||||
|
}
|
||||||
|
|
||||||
final localTracksProvider = FutureProvider<List<Track>>((ref) async {
|
final localTracksProvider = FutureProvider<List<Track>>((ref) async {
|
||||||
try {
|
try {
|
||||||
if (kIsWeb) return [];
|
if (kIsWeb) return [];
|
||||||
@ -132,6 +145,7 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final sortBy = useState<SortBy>(SortBy.none);
|
||||||
final playback = ref.watch(playbackProvider);
|
final playback = ref.watch(playbackProvider);
|
||||||
final isPlaylistPlaying = playback.playlist?.id == "local";
|
final isPlaylistPlaying = playback.playlist?.id == "local";
|
||||||
final trackSnapshot = ref.watch(localTracksProvider);
|
final trackSnapshot = ref.watch(localTracksProvider);
|
||||||
@ -176,6 +190,13 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
SortTracksDropdown(
|
||||||
|
value: sortBy.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) sortBy.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: const Icon(Icons.refresh_rounded),
|
child: const Icon(Icons.refresh_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -187,11 +208,15 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
trackSnapshot.when(
|
trackSnapshot.when(
|
||||||
data: (tracks) {
|
data: (tracks) {
|
||||||
|
final sortedTracks = useMemoized(() {
|
||||||
|
return ServiceUtils.sortTracks(tracks, sortBy.value);
|
||||||
|
}, [sortBy.value, tracks]);
|
||||||
|
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: tracks.length,
|
itemCount: sortedTracks.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final track = tracks[index];
|
final track = sortedTracks[index];
|
||||||
return TrackTile(
|
return TrackTile(
|
||||||
playback,
|
playback,
|
||||||
duration:
|
duration:
|
||||||
@ -204,7 +229,7 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
onTrackPlayButtonPressed: (currentTrack) {
|
onTrackPlayButtonPressed: (currentTrack) {
|
||||||
return playLocalTracks(
|
return playLocalTracks(
|
||||||
playback,
|
playback,
|
||||||
tracks,
|
sortedTracks,
|
||||||
currentTrack: track,
|
currentTrack: track,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/components/Shared/HeartButton.dart';
|
import 'package:spotube/components/Shared/HeartButton.dart';
|
||||||
import 'package:spotube/components/Shared/TrackCollectionView.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/useBreakpoints.dart';
|
||||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||||
import 'package:spotube/models/CurrentPlaylist.dart';
|
import 'package:spotube/models/CurrentPlaylist.dart';
|
||||||
@ -16,6 +17,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class PlaylistView extends HookConsumerWidget {
|
class PlaylistView extends HookConsumerWidget {
|
||||||
@ -23,15 +25,21 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
playPlaylist(Playback playback, List<Track> tracks,
|
Future<void> playPlaylist(
|
||||||
{Track? currentTrack}) async {
|
Playback playback,
|
||||||
currentTrack ??= tracks.first;
|
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 =
|
final isPlaylistPlaying =
|
||||||
playback.playlist?.id != null && playback.playlist?.id == playlist.id;
|
playback.playlist?.id != null && playback.playlist?.id == playlist.id;
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
await playback.playPlaylist(
|
await playback.playPlaylist(
|
||||||
CurrentPlaylist(
|
CurrentPlaylist(
|
||||||
tracks: tracks,
|
tracks: sortedTracks,
|
||||||
id: playlist.id!,
|
id: playlist.id!,
|
||||||
name: playlist.name!,
|
name: playlist.name!,
|
||||||
thumbnail: TypeConversionUtils.image_X_UrlString(
|
thumbnail: TypeConversionUtils.image_X_UrlString(
|
||||||
@ -39,7 +47,7 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
placeholder: ImagePlaceholder.collection,
|
placeholder: ImagePlaceholder.collection,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tracks.indexWhere((s) => s.id == currentTrack?.id),
|
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
);
|
);
|
||||||
} else if (isPlaylistPlaying &&
|
} else if (isPlaylistPlaying &&
|
||||||
currentTrack.id != null &&
|
currentTrack.id != null &&
|
||||||
@ -85,14 +93,12 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
onPlay: ([track]) {
|
onPlay: ([track]) {
|
||||||
if (tracksSnapshot.asData?.value != null) {
|
if (tracksSnapshot.asData?.value != null) {
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
playPlaylist(
|
playPlaylist(playback, tracksSnapshot.asData!.value, ref);
|
||||||
playback,
|
|
||||||
tracksSnapshot.asData!.value,
|
|
||||||
);
|
|
||||||
} else if (isPlaylistPlaying && track != null) {
|
} else if (isPlaylistPlaying && track != null) {
|
||||||
playPlaylist(
|
playPlaylist(
|
||||||
playback,
|
playback,
|
||||||
tracksSnapshot.asData!.value,
|
tracksSnapshot.asData!.value,
|
||||||
|
ref,
|
||||||
currentTrack: track,
|
currentTrack: track,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
54
lib/components/Shared/SortTracksDropdown.dart
Normal file
54
lib/components/Shared/SortTracksDropdown.dart
Normal file
@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,19 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.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/DownloadConfirmationDialog.dart';
|
||||||
import 'package:spotube/components/Shared/NotFound.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/components/Shared/TrackTile.dart';
|
||||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||||
import 'package:spotube/provider/Downloader.dart';
|
import 'package:spotube/provider/Downloader.dart';
|
||||||
import 'package:spotube/provider/Playback.dart';
|
import 'package:spotube/provider/Playback.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.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 {
|
class TracksTableView extends HookConsumerWidget {
|
||||||
final void Function(Track currentTrack)? onTrackPlayButtonPressed;
|
final void Function(Track currentTrack)? onTrackPlayButtonPressed;
|
||||||
@ -39,26 +45,34 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
|
|
||||||
final selected = useState<List<String>>([]);
|
final selected = useState<List<String>>([]);
|
||||||
final showCheck = useState<bool>(false);
|
final showCheck = useState<bool>(false);
|
||||||
|
final sortBy = ref.watch(trackCollectionSortState(playlistId ?? ''));
|
||||||
|
|
||||||
final selectedTracks = useMemoized(
|
final sortedTracks = useMemoized(
|
||||||
() => tracks.where(
|
() {
|
||||||
(track) => selected.value.contains(track.id),
|
return ServiceUtils.sortTracks(tracks, sortBy);
|
||||||
),
|
},
|
||||||
[tracks],
|
[tracks, sortBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
final children = tracks.isEmpty
|
final selectedTracks = useMemoized(
|
||||||
|
() => sortedTracks.where(
|
||||||
|
(track) => selected.value.contains(track.id),
|
||||||
|
),
|
||||||
|
[sortedTracks],
|
||||||
|
);
|
||||||
|
|
||||||
|
final children = sortedTracks.isEmpty
|
||||||
? [const NotFound(vertical: true)]
|
? [const NotFound(vertical: true)]
|
||||||
: [
|
: [
|
||||||
if (heading != null) heading!,
|
if (heading != null) heading!,
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: selected.value.length == tracks.length,
|
value: selected.value.length == sortedTracks.length,
|
||||||
onChanged: (checked) {
|
onChanged: (checked) {
|
||||||
if (!showCheck.value) showCheck.value = true;
|
if (!showCheck.value) showCheck.value = true;
|
||||||
if (checked == true) {
|
if (checked == true) {
|
||||||
selected.value = tracks.map((s) => s.id!).toList();
|
selected.value = sortedTracks.map((s) => s.id!).toList();
|
||||||
} else {
|
} else {
|
||||||
selected.value = [];
|
selected.value = [];
|
||||||
showCheck.value = false;
|
showCheck.value = false;
|
||||||
@ -104,11 +118,20 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
Text("Time", style: tableHeadStyle),
|
Text("Time", style: tableHeadStyle),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
|
SortTracksDropdown(
|
||||||
|
value: sortBy,
|
||||||
|
onChanged: (value) {
|
||||||
|
ref
|
||||||
|
.read(trackCollectionSortState(playlistId ?? '').state)
|
||||||
|
.state = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
enabled: selected.value.isNotEmpty,
|
enabled: selected.value.isNotEmpty,
|
||||||
|
value: "download",
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.file_download_outlined),
|
const Icon(Icons.file_download_outlined),
|
||||||
@ -117,7 +140,6 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
value: "download",
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
@ -144,7 +166,7 @@ class TracksTableView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...tracks.asMap().entries.map((track) {
|
...sortedTracks.asMap().entries.map((track) {
|
||||||
String duration =
|
String duration =
|
||||||
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||||
return InkWell(
|
return InkWell(
|
||||||
|
@ -173,8 +173,8 @@ class _SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
|
|||||||
localStorage!.setString(
|
localStorage!.setString(
|
||||||
LocalStorageKeys.windowSizeInfo,
|
LocalStorageKeys.windowSizeInfo,
|
||||||
jsonEncode({
|
jsonEncode({
|
||||||
'width': appWindow.isMaximized ? 0 : appWindow.size.width,
|
'width': appWindow.isMaximized ? 0.0 : appWindow.size.width,
|
||||||
'height': appWindow.isMaximized ? 0 : appWindow.size.height,
|
'height': appWindow.isMaximized ? 0.0 : appWindow.size.height,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
prevSize = appWindow.size;
|
prevSize = appWindow.size;
|
||||||
|
@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
||||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
import 'package:spotube/models/Logger.dart';
|
import 'package:spotube/models/Logger.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@ -393,4 +394,31 @@ abstract class ServiceUtils {
|
|||||||
static void navigate(BuildContext context, String location, {Object? extra}) {
|
static void navigate(BuildContext context, String location, {Object? extra}) {
|
||||||
GoRouter.of(context).push(location, extra: extra);
|
GoRouter.of(context).push(location, extra: extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<T> sortTracks<T extends Track>(List<T> tracks, SortBy sortBy) {
|
||||||
|
if (sortBy == SortBy.none) return tracks;
|
||||||
|
return List<T>.from(tracks)
|
||||||
|
..sort((a, b) {
|
||||||
|
switch (sortBy) {
|
||||||
|
case SortBy.album:
|
||||||
|
return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0;
|
||||||
|
case SortBy.artist:
|
||||||
|
return a.artists?.first.name
|
||||||
|
?.compareTo(b.artists?.first.name ?? "") ??
|
||||||
|
0;
|
||||||
|
case SortBy.ascending:
|
||||||
|
return a.name?.compareTo(b.name ?? "") ?? 0;
|
||||||
|
case SortBy.dateAdded:
|
||||||
|
final aDate =
|
||||||
|
double.parse(a.album?.releaseDate?.split("-").first ?? "2069");
|
||||||
|
final bDate =
|
||||||
|
double.parse(b.album?.releaseDate?.split("-").first ?? "2069");
|
||||||
|
return aDate.compareTo(bDate);
|
||||||
|
case SortBy.descending:
|
||||||
|
return b.name?.compareTo(a.name ?? "") ?? 0;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user