mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
fix: local track not showing up in queue
This commit is contained in:
parent
22caa818f4
commit
d82261cb25
@ -6,32 +6,20 @@ import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:metadata_god/metadata_god.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/fake.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
|
||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||
import 'package:spotube/extensions/artist_simple.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/track.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
// ignore: depend_on_referenced_packages
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FfiException;
|
||||
|
||||
@ -63,7 +51,8 @@ enum SortBy {
|
||||
album,
|
||||
}
|
||||
|
||||
final localTracksProvider = FutureProvider<Map<String, List<LocalTrack>>>((ref) async {
|
||||
final localTracksProvider =
|
||||
FutureProvider<Map<String, List<LocalTrack>>>((ref) async {
|
||||
try {
|
||||
if (kIsWeb) return {};
|
||||
final Map<String, List<LocalTrack>> tracks = {};
|
||||
@ -82,7 +71,6 @@ final localTracksProvider = FutureProvider<Map<String, List<LocalTrack>>>((ref)
|
||||
for (var location in [downloadLocation, ...localLibraryLocations]) {
|
||||
if (location.isEmpty) continue;
|
||||
final entities = <FileSystemEntity>[];
|
||||
final dir = Directory(location);
|
||||
if (await Directory(location).exists()) {
|
||||
entities.addAll(Directory(location).listSync(recursive: true));
|
||||
}
|
||||
@ -110,7 +98,11 @@ final localTracksProvider = FutureProvider<Map<String, List<LocalTrack>>>((ref)
|
||||
);
|
||||
}
|
||||
|
||||
return {"metadata": metadata, "file": file, "art": imageFile.path};
|
||||
return {
|
||||
"metadata": metadata,
|
||||
"file": file,
|
||||
"art": imageFile.path
|
||||
};
|
||||
} catch (e, stack) {
|
||||
if (e is FfiException) {
|
||||
return {"file": file};
|
||||
@ -152,7 +144,6 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
|
||||
@ -163,69 +154,74 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
);
|
||||
if (dirStr == null) return;
|
||||
if (preferences.localLibraryLocation.contains(dirStr)) return;
|
||||
preferencesNotifier.setLocalLibraryLocation([...preferences.localLibraryLocation, dirStr]);
|
||||
preferencesNotifier.setLocalLibraryLocation(
|
||||
[...preferences.localLibraryLocation, dirStr]);
|
||||
} else {
|
||||
String? dirStr = await getDirectoryPath(
|
||||
initialDirectory: preferences.downloadLocation,
|
||||
);
|
||||
if (dirStr == null) return;
|
||||
if (preferences.localLibraryLocation.contains(dirStr)) return;
|
||||
preferencesNotifier.setLocalLibraryLocation([...preferences.localLibraryLocation, dirStr]);
|
||||
preferencesNotifier.setLocalLibraryLocation(
|
||||
[...preferences.localLibraryLocation, dirStr]);
|
||||
}
|
||||
}, [preferences.localLibraryLocation]);
|
||||
|
||||
final removeLocalLibraryLocation = useCallback((String location) {
|
||||
if (!preferences.localLibraryLocation.contains(location)) return;
|
||||
preferencesNotifier.setLocalLibraryLocation([...preferences.localLibraryLocation]..remove(location));
|
||||
preferencesNotifier.setLocalLibraryLocation(
|
||||
[...preferences.localLibraryLocation]..remove(location),
|
||||
);
|
||||
}, [preferences.localLibraryLocation]);
|
||||
|
||||
// This is just to pre-load the tracks.
|
||||
// For now, this gets all of them.
|
||||
ref.watch(localTracksProvider);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
return Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
TextButton.icon(
|
||||
icon: const Icon(SpotubeIcons.folderAdd),
|
||||
label: Text(context.l10n.add_library_location),
|
||||
onPressed: addLocalLibraryLocation,
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: preferences.localLibraryLocation.length+1,
|
||||
child: Row(children: [
|
||||
const SizedBox(width: 5),
|
||||
TextButton.icon(
|
||||
icon: const Icon(SpotubeIcons.folderAdd),
|
||||
label: Text(context.l10n.add_library_location),
|
||||
onPressed: addLocalLibraryLocation,
|
||||
)
|
||||
])),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: preferences.localLibraryLocation.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
late final String location;
|
||||
if (index == 0) {
|
||||
location = preferences.downloadLocation;
|
||||
} else {
|
||||
location = preferences.localLibraryLocation[index-1];
|
||||
location = preferences.localLibraryLocation[index - 1];
|
||||
}
|
||||
return ListTile(
|
||||
title: preferences.downloadLocation != location ? Text(location)
|
||||
: Text(context.l10n.downloads),
|
||||
trailing: preferences.downloadLocation != location ? Tooltip(
|
||||
message: context.l10n.remove_library_location,
|
||||
child: IconButton(
|
||||
icon: Icon(SpotubeIcons.folderRemove, color: Colors.red[400]),
|
||||
onPressed: () => removeLocalLibraryLocation(location),
|
||||
),
|
||||
) : null,
|
||||
onTap: () async {
|
||||
context.go("/library/local${location == preferences.downloadLocation ? "?downloads=1" : ""}", extra: location);
|
||||
}
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
]
|
||||
);
|
||||
title: preferences.downloadLocation != location
|
||||
? Text(location)
|
||||
: Text(context.l10n.downloads),
|
||||
trailing: preferences.downloadLocation != location
|
||||
? Tooltip(
|
||||
message: context.l10n.remove_library_location,
|
||||
child: IconButton(
|
||||
icon: Icon(SpotubeIcons.folderRemove,
|
||||
color: Colors.red[400]),
|
||||
onPressed: () =>
|
||||
removeLocalLibraryLocation(location),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
onTap: () async {
|
||||
context.go(
|
||||
"/library/local${location == preferences.downloadLocation ? "?downloads=1" : ""}",
|
||||
extra: location,
|
||||
);
|
||||
});
|
||||
}),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -197,6 +197,8 @@ class TrackOptions extends HookConsumerWidget {
|
||||
return downloadManager.getProgressNotifier(spotubeTrack);
|
||||
});
|
||||
|
||||
final isLocalTrack = track is LocalTrack;
|
||||
|
||||
final adaptivePopSheetList = AdaptivePopSheetList<TrackOptionValue>(
|
||||
onSelected: (value) async {
|
||||
switch (value) {
|
||||
@ -314,118 +316,120 @@ class TrackOptions extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
children: switch (track.runtimeType) {
|
||||
LocalTrack() => [
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.delete,
|
||||
leading: const Icon(SpotubeIcons.trash),
|
||||
title: Text(context.l10n.delete),
|
||||
)
|
||||
],
|
||||
_ => [
|
||||
if (mediaQuery.smAndDown)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.album,
|
||||
leading: const Icon(SpotubeIcons.album),
|
||||
title: Text(context.l10n.go_to_album),
|
||||
subtitle: Text(track.album!.name!),
|
||||
),
|
||||
if (!playlist.containsTrack(track)) ...[
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.addToQueue,
|
||||
leading: const Icon(SpotubeIcons.queueAdd),
|
||||
title: Text(context.l10n.add_to_queue),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.playNext,
|
||||
leading: const Icon(SpotubeIcons.lightning),
|
||||
title: Text(context.l10n.play_next),
|
||||
),
|
||||
] else
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.removeFromQueue,
|
||||
enabled: playlist.activeTrack?.id != track.id,
|
||||
leading: const Icon(SpotubeIcons.queueRemove),
|
||||
title: Text(context.l10n.remove_from_queue),
|
||||
),
|
||||
if (me.asData?.value != null)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.favorite,
|
||||
leading: favorites.isLiked
|
||||
? const Icon(
|
||||
SpotubeIcons.heartFilled,
|
||||
color: Colors.pink,
|
||||
)
|
||||
: const Icon(SpotubeIcons.heart),
|
||||
title: Text(
|
||||
favorites.isLiked
|
||||
? context.l10n.remove_from_favorites
|
||||
: context.l10n.save_as_favorite,
|
||||
),
|
||||
),
|
||||
if (auth != null) ...[
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.startRadio,
|
||||
leading: const Icon(SpotubeIcons.radio),
|
||||
title: Text(context.l10n.start_a_radio),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.addToPlaylist,
|
||||
leading: const Icon(SpotubeIcons.playlistAdd),
|
||||
title: Text(context.l10n.add_to_playlist),
|
||||
),
|
||||
],
|
||||
if (userPlaylist && auth != null)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.removeFromPlaylist,
|
||||
leading: const Icon(SpotubeIcons.removeFilled),
|
||||
title: Text(context.l10n.remove_from_playlist),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.download,
|
||||
enabled: !isInQueue,
|
||||
leading: isInQueue
|
||||
? HookBuilder(builder: (context) {
|
||||
final progress = useListenable(progressNotifier!);
|
||||
return CircularProgressIndicator(
|
||||
value: progress.value,
|
||||
);
|
||||
})
|
||||
: const Icon(SpotubeIcons.download),
|
||||
title: Text(context.l10n.download_track),
|
||||
children: [
|
||||
if (isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.delete,
|
||||
leading: const Icon(SpotubeIcons.trash),
|
||||
title: Text(context.l10n.delete),
|
||||
),
|
||||
if (mediaQuery.smAndDown)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.album,
|
||||
leading: const Icon(SpotubeIcons.album),
|
||||
title: Text(context.l10n.go_to_album),
|
||||
subtitle: Text(track.album!.name!),
|
||||
),
|
||||
if (!playlist.containsTrack(track)) ...[
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.addToQueue,
|
||||
leading: const Icon(SpotubeIcons.queueAdd),
|
||||
title: Text(context.l10n.add_to_queue),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.playNext,
|
||||
leading: const Icon(SpotubeIcons.lightning),
|
||||
title: Text(context.l10n.play_next),
|
||||
),
|
||||
] else
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.removeFromQueue,
|
||||
enabled: playlist.activeTrack?.id != track.id,
|
||||
leading: const Icon(SpotubeIcons.queueRemove),
|
||||
title: Text(context.l10n.remove_from_queue),
|
||||
),
|
||||
if (me.asData?.value != null && !isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.favorite,
|
||||
leading: favorites.isLiked
|
||||
? const Icon(
|
||||
SpotubeIcons.heartFilled,
|
||||
color: Colors.pink,
|
||||
)
|
||||
: const Icon(SpotubeIcons.heart),
|
||||
title: Text(
|
||||
favorites.isLiked
|
||||
? context.l10n.remove_from_favorites
|
||||
: context.l10n.save_as_favorite,
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.blacklist,
|
||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||
iconColor: !isBlackListed ? Colors.red[400] : null,
|
||||
textColor: !isBlackListed ? Colors.red[400] : null,
|
||||
title: Text(
|
||||
isBlackListed
|
||||
? context.l10n.remove_from_blacklist
|
||||
: context.l10n.add_to_blacklist,
|
||||
),
|
||||
),
|
||||
if (auth != null && !isLocalTrack) ...[
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.startRadio,
|
||||
leading: const Icon(SpotubeIcons.radio),
|
||||
title: Text(context.l10n.start_a_radio),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.addToPlaylist,
|
||||
leading: const Icon(SpotubeIcons.playlistAdd),
|
||||
title: Text(context.l10n.add_to_playlist),
|
||||
),
|
||||
],
|
||||
if (userPlaylist && auth != null && !isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.removeFromPlaylist,
|
||||
leading: const Icon(SpotubeIcons.removeFilled),
|
||||
title: Text(context.l10n.remove_from_playlist),
|
||||
),
|
||||
if (!isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.download,
|
||||
enabled: !isInQueue,
|
||||
leading: isInQueue
|
||||
? HookBuilder(builder: (context) {
|
||||
final progress = useListenable(progressNotifier!);
|
||||
return CircularProgressIndicator(
|
||||
value: progress.value,
|
||||
);
|
||||
})
|
||||
: const Icon(SpotubeIcons.download),
|
||||
title: Text(context.l10n.download_track),
|
||||
),
|
||||
if (!isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.blacklist,
|
||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||
iconColor: !isBlackListed ? Colors.red[400] : null,
|
||||
textColor: !isBlackListed ? Colors.red[400] : null,
|
||||
title: Text(
|
||||
isBlackListed
|
||||
? context.l10n.remove_from_blacklist
|
||||
: context.l10n.add_to_blacklist,
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.share,
|
||||
leading: const Icon(SpotubeIcons.share),
|
||||
title: Text(context.l10n.share),
|
||||
),
|
||||
if (!isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.share,
|
||||
leading: const Icon(SpotubeIcons.share),
|
||||
title: Text(context.l10n.share),
|
||||
),
|
||||
if (!isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.songlink,
|
||||
leading: Assets.logos.songlinkTransparent.image(
|
||||
width: 22,
|
||||
height: 22,
|
||||
color: colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.songlink,
|
||||
leading: Assets.logos.songlinkTransparent.image(
|
||||
width: 22,
|
||||
height: 22,
|
||||
color: colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
title: Text(context.l10n.song_link),
|
||||
),
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.details,
|
||||
leading: const Icon(SpotubeIcons.info),
|
||||
title: Text(context.l10n.details),
|
||||
),
|
||||
]
|
||||
},
|
||||
title: Text(context.l10n.song_link),
|
||||
),
|
||||
if (!isLocalTrack)
|
||||
PopSheetEntry(
|
||||
value: TrackOptionValue.details,
|
||||
leading: const Icon(SpotubeIcons.info),
|
||||
title: Text(context.l10n.details),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
//! This is the most ANTI pattern I've ever done, but it works
|
||||
|
@ -195,19 +195,26 @@ class TrackTile extends HookConsumerWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: LinkText(
|
||||
track.name!,
|
||||
"/track/${track.id}",
|
||||
push: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
child: switch (track) {
|
||||
LocalTrack() => Text(
|
||||
track.name!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
_ => LinkText(
|
||||
track.name!,
|
||||
"/track/${track.id}",
|
||||
push: true,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
},
|
||||
),
|
||||
if (constrains.mdAndUp) ...[
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: switch (track.runtimeType) {
|
||||
child: switch (track) {
|
||||
LocalTrack() => Text(
|
||||
track.album!.name!,
|
||||
maxLines: 1,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||
@ -46,14 +45,14 @@ class LocalLibraryPage extends HookConsumerWidget {
|
||||
await playback.jumpToTrack(currentTrack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final sortBy = useState<SortBy>(SortBy.none);
|
||||
final playlist = ref.watch(proxyPlaylistProvider);
|
||||
final trackSnapshot = ref.watch(localTracksProvider);
|
||||
final isPlaylistPlaying =
|
||||
playlist.containsTracks(trackSnapshot.asData?.value.values.flattened.toList() ?? []);
|
||||
final isPlaylistPlaying = playlist.containsTracks(
|
||||
trackSnapshot.asData?.value.values.flattened.toList() ?? []);
|
||||
|
||||
final searchController = useTextEditingController();
|
||||
useValueListenable(searchController);
|
||||
@ -61,176 +60,178 @@ class LocalLibraryPage extends HookConsumerWidget {
|
||||
final isFiltering = useState(false);
|
||||
|
||||
final controller = useScrollController();
|
||||
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
leading: const BackButton(),
|
||||
centerTitle: true,
|
||||
title: Text(isDownloads ? context.l10n.downloads : location),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Column(
|
||||
children: [
|
||||
const SizedBox(height: 56),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
FilledButton(
|
||||
onPressed: trackSnapshot.asData?.value != null
|
||||
? () async {
|
||||
if (trackSnapshot.asData?.value.isNotEmpty == true) {
|
||||
if (!isPlaylistPlaying) {
|
||||
await playLocalTracks(
|
||||
ref,
|
||||
trackSnapshot.asData!.value[location] ?? [],
|
||||
);
|
||||
appBar: PageWindowTitleBar(
|
||||
leading: const BackButton(),
|
||||
centerTitle: true,
|
||||
title: Text(isDownloads ? context.l10n.downloads : location),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 5),
|
||||
FilledButton(
|
||||
onPressed: trackSnapshot.asData?.value != null
|
||||
? () async {
|
||||
if (trackSnapshot.asData?.value.isNotEmpty ==
|
||||
true) {
|
||||
if (!isPlaylistPlaying) {
|
||||
await playLocalTracks(
|
||||
ref,
|
||||
trackSnapshot.asData!.value[location] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(context.l10n.play),
|
||||
Icon(
|
||||
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
|
||||
)
|
||||
],
|
||||
: null,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(context.l10n.play),
|
||||
Icon(
|
||||
isPlaylistPlaying
|
||||
? SpotubeIcons.stop
|
||||
: SpotubeIcons.play,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
ExpandableSearchButton(
|
||||
isFiltering: isFiltering.value,
|
||||
onPressed: (value) => isFiltering.value = value,
|
||||
searchFocus: searchFocus,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SortTracksDropdown(
|
||||
value: sortBy.value,
|
||||
onChanged: (value) {
|
||||
sortBy.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
FilledButton(
|
||||
child: const Icon(SpotubeIcons.refresh),
|
||||
onPressed: () {
|
||||
ref.invalidate(localTracksProvider);
|
||||
},
|
||||
)
|
||||
],
|
||||
const Spacer(),
|
||||
ExpandableSearchButton(
|
||||
isFiltering: isFiltering.value,
|
||||
onPressed: (value) => isFiltering.value = value,
|
||||
searchFocus: searchFocus,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SortTracksDropdown(
|
||||
value: sortBy.value,
|
||||
onChanged: (value) {
|
||||
sortBy.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
FilledButton(
|
||||
child: const Icon(SpotubeIcons.refresh),
|
||||
onPressed: () {
|
||||
ref.invalidate(localTracksProvider);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ExpandableSearchField(
|
||||
searchController: searchController,
|
||||
searchFocus: searchFocus,
|
||||
isFiltering: isFiltering.value,
|
||||
onChangeFiltering: (value) => isFiltering.value = value,
|
||||
),
|
||||
trackSnapshot.when(
|
||||
data: (tracks) {
|
||||
final sortedTracks = useMemoized(() {
|
||||
return ServiceUtils.sortTracks(tracks[location] ?? <LocalTrack>[], sortBy.value);
|
||||
}, [sortBy.value, tracks]);
|
||||
ExpandableSearchField(
|
||||
searchController: searchController,
|
||||
searchFocus: searchFocus,
|
||||
isFiltering: isFiltering.value,
|
||||
onChangeFiltering: (value) => isFiltering.value = value,
|
||||
),
|
||||
trackSnapshot.when(
|
||||
data: (tracks) {
|
||||
final sortedTracks = useMemoized(() {
|
||||
return ServiceUtils.sortTracks(
|
||||
tracks[location] ?? <LocalTrack>[], sortBy.value);
|
||||
}, [sortBy.value, tracks]);
|
||||
|
||||
final filteredTracks = useMemoized(() {
|
||||
if (searchController.text.isEmpty) {
|
||||
return sortedTracks;
|
||||
final filteredTracks = useMemoized(() {
|
||||
if (searchController.text.isEmpty) {
|
||||
return sortedTracks;
|
||||
}
|
||||
return sortedTracks
|
||||
.map((e) => (
|
||||
weightedRatio(
|
||||
"${e.name} - ${e.artists?.asString() ?? ""}",
|
||||
searchController.text,
|
||||
),
|
||||
e,
|
||||
))
|
||||
.toList()
|
||||
.sorted(
|
||||
(a, b) => b.$1.compareTo(a.$1),
|
||||
)
|
||||
.where((e) => e.$1 > 50)
|
||||
.map((e) => e.$2)
|
||||
.toList()
|
||||
.toList();
|
||||
}, [searchController.text, sortedTracks]);
|
||||
|
||||
if (!trackSnapshot.isLoading && filteredTracks.isEmpty) {
|
||||
return const Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [NotFound()],
|
||||
),
|
||||
);
|
||||
}
|
||||
return sortedTracks
|
||||
.map((e) => (
|
||||
weightedRatio(
|
||||
"${e.name} - ${e.artists?.asString() ?? ""}",
|
||||
searchController.text,
|
||||
),
|
||||
e,
|
||||
))
|
||||
.toList()
|
||||
.sorted(
|
||||
(a, b) => b.$1.compareTo(a.$1),
|
||||
)
|
||||
.where((e) => e.$1 > 50)
|
||||
.map((e) => e.$2)
|
||||
.toList()
|
||||
.toList();
|
||||
}, [searchController.text, sortedTracks]);
|
||||
|
||||
if (!trackSnapshot.isLoading && filteredTracks.isEmpty) {
|
||||
return const Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [NotFound()],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
ref.invalidate(localTracksProvider);
|
||||
},
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: Skeletonizer(
|
||||
enabled: trackSnapshot.isLoading,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount:
|
||||
trackSnapshot.isLoading ? 5 : filteredTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (trackSnapshot.isLoading) {
|
||||
return TrackTile(
|
||||
playlist: playlist,
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
|
||||
final track = filteredTracks[index];
|
||||
return TrackTile(
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
userPlaylist: false,
|
||||
onTap: () async {
|
||||
await playLocalTracks(
|
||||
ref,
|
||||
sortedTracks,
|
||||
currentTrack: track,
|
||||
return Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
ref.invalidate(localTracksProvider);
|
||||
},
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: Skeletonizer(
|
||||
enabled: trackSnapshot.isLoading,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: trackSnapshot.isLoading
|
||||
? 5
|
||||
: filteredTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (trackSnapshot.isLoading) {
|
||||
return TrackTile(
|
||||
playlist: playlist,
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
final track = filteredTracks[index];
|
||||
return TrackTile(
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
userPlaylist: false,
|
||||
onTap: () async {
|
||||
await playLocalTracks(
|
||||
ref,
|
||||
sortedTracks,
|
||||
currentTrack: track,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => Expanded(
|
||||
child: Skeletonizer(
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) => TrackTile(
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
);
|
||||
},
|
||||
loading: () => Expanded(
|
||||
child: Skeletonizer(
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) => TrackTile(
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
error: (error, stackTrace) =>
|
||||
Text(error.toString() + stackTrace.toString()),
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
error: (error, stackTrace) =>
|
||||
Text(error.toString() + stackTrace.toString()),
|
||||
)
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// ignore_for_file: invalid_use_of_protected_member
|
||||
// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -45,7 +45,14 @@ class ProxyPlaylist {
|
||||
}
|
||||
|
||||
bool containsTrack(TrackSimple track) {
|
||||
return tracks.firstWhereOrNull((element) => element.id == track.id) != null;
|
||||
return tracks.firstWhereOrNull((element) {
|
||||
if (element is LocalTrack && track is LocalTrack) {
|
||||
return element.path == track.path;
|
||||
}
|
||||
|
||||
return element.id == track.id;
|
||||
}) !=
|
||||
null;
|
||||
}
|
||||
|
||||
bool containsTracks(Iterable<TrackSimple> tracks) {
|
||||
@ -65,8 +72,8 @@ class ProxyPlaylist {
|
||||
/// Otherwise default super.toJson() is used
|
||||
static Map<String, dynamic> _makeAppropriateTrackJson(Track track) {
|
||||
return switch (track.runtimeType) {
|
||||
LocalTrack() => track.toJson(),
|
||||
SourcedTrack() => track.toJson(),
|
||||
LocalTrack() => (track as LocalTrack).toJson(),
|
||||
SourcedTrack() => (track as SourcedTrack).toJson(),
|
||||
_ => track.toJson(),
|
||||
};
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import 'package:media_kit/media_kit.dart' as mk;
|
||||
|
||||
import 'package:spotube/services/audio_player/loop_mode.dart';
|
||||
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||
|
||||
part 'audio_players_streams_mixin.dart';
|
||||
part 'audio_player_impl.dart';
|
||||
@ -30,12 +31,18 @@ class SpotubeMedia extends mk.Media {
|
||||
: "http://${InternetAddress.loopbackIPv4.address}:${PlaybackServer.port}/stream/${track.id}",
|
||||
extras: {
|
||||
...?extras,
|
||||
"track": track.toJson(),
|
||||
"track": switch (track) {
|
||||
LocalTrack() => track.toJson(),
|
||||
SourcedTrack() => track.toJson(),
|
||||
_ => track.toJson(),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
factory SpotubeMedia.fromMedia(mk.Media media) {
|
||||
final track = Track.fromJson(media.extras?["track"]);
|
||||
final track = media.uri.startsWith("http")
|
||||
? Track.fromJson(media.extras?["track"])
|
||||
: LocalTrack.fromJson(media.extras?["track"]);
|
||||
return SpotubeMedia(track);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,13 @@
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
"remove_library_location",
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
@ -48,6 +55,13 @@
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
"remove_library_location",
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
@ -62,6 +76,13 @@
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
"remove_library_location",
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
@ -76,6 +97,13 @@
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"ka": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
"remove_library_location",
|
||||
"local_tab"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"local_library",
|
||||
"add_library_location",
|
||||
|
Loading…
Reference in New Issue
Block a user