fix: local track not showing up in queue

This commit is contained in:
Kingkor Roy Tirtho 2024-05-23 16:56:52 +06:00
parent 22caa818f4
commit d82261cb25
8 changed files with 385 additions and 335 deletions

View File

@ -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: [
return Column(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
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,
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)
title: preferences.downloadLocation != location
? Text(location)
: Text(context.l10n.downloads),
trailing: preferences.downloadLocation != location ? Tooltip(
trailing: preferences.downloadLocation != location
? Tooltip(
message: context.l10n.remove_library_location,
child: IconButton(
icon: Icon(SpotubeIcons.folderRemove, color: Colors.red[400]),
onPressed: () => removeLocalLibraryLocation(location),
icon: Icon(SpotubeIcons.folderRemove,
color: Colors.red[400]),
onPressed: () =>
removeLocalLibraryLocation(location),
),
) : null,
)
: null,
onTap: () async {
context.go("/library/local${location == preferences.downloadLocation ? "?downloads=1" : ""}", extra: location);
}
context.go(
"/library/local${location == preferences.downloadLocation ? "?downloads=1" : ""}",
extra: location,
);
}
});
}),
),
),
]
);
]);
}
}

View File

@ -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,15 +316,13 @@ class TrackOptions extends HookConsumerWidget {
),
),
],
children: switch (track.runtimeType) {
LocalTrack() => [
children: [
if (isLocalTrack)
PopSheetEntry(
value: TrackOptionValue.delete,
leading: const Icon(SpotubeIcons.trash),
title: Text(context.l10n.delete),
)
],
_ => [
),
if (mediaQuery.smAndDown)
PopSheetEntry(
value: TrackOptionValue.album,
@ -348,7 +348,7 @@ class TrackOptions extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.queueRemove),
title: Text(context.l10n.remove_from_queue),
),
if (me.asData?.value != null)
if (me.asData?.value != null && !isLocalTrack)
PopSheetEntry(
value: TrackOptionValue.favorite,
leading: favorites.isLiked
@ -363,7 +363,7 @@ class TrackOptions extends HookConsumerWidget {
: context.l10n.save_as_favorite,
),
),
if (auth != null) ...[
if (auth != null && !isLocalTrack) ...[
PopSheetEntry(
value: TrackOptionValue.startRadio,
leading: const Icon(SpotubeIcons.radio),
@ -375,12 +375,13 @@ class TrackOptions extends HookConsumerWidget {
title: Text(context.l10n.add_to_playlist),
),
],
if (userPlaylist && auth != null)
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,
@ -394,6 +395,7 @@ class TrackOptions extends HookConsumerWidget {
: const Icon(SpotubeIcons.download),
title: Text(context.l10n.download_track),
),
if (!isLocalTrack)
PopSheetEntry(
value: TrackOptionValue.blacklist,
leading: const Icon(SpotubeIcons.playlistRemove),
@ -405,11 +407,13 @@ class TrackOptions extends HookConsumerWidget {
: context.l10n.add_to_blacklist,
),
),
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(
@ -419,13 +423,13 @@ class TrackOptions extends HookConsumerWidget {
),
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

View File

@ -195,19 +195,26 @@ class TrackTile extends HookConsumerWidget {
children: [
Expanded(
flex: 6,
child: LinkText(
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,

View File

@ -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';
@ -52,8 +51,8 @@ class LocalLibraryPage extends HookConsumerWidget {
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);
@ -71,10 +70,8 @@ class LocalLibraryPage extends HookConsumerWidget {
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(
@ -83,7 +80,8 @@ class LocalLibraryPage extends HookConsumerWidget {
FilledButton(
onPressed: trackSnapshot.asData?.value != null
? () async {
if (trackSnapshot.asData?.value.isNotEmpty == true) {
if (trackSnapshot.asData?.value.isNotEmpty ==
true) {
if (!isPlaylistPlaying) {
await playLocalTracks(
ref,
@ -97,7 +95,9 @@ class LocalLibraryPage extends HookConsumerWidget {
children: [
Text(context.l10n.play),
Icon(
isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play,
isPlaylistPlaying
? SpotubeIcons.stop
: SpotubeIcons.play,
)
],
),
@ -134,7 +134,8 @@ class LocalLibraryPage extends HookConsumerWidget {
trackSnapshot.when(
data: (tracks) {
final sortedTracks = useMemoized(() {
return ServiceUtils.sortTracks(tracks[location] ?? <LocalTrack>[], sortBy.value);
return ServiceUtils.sortTracks(
tracks[location] ?? <LocalTrack>[], sortBy.value);
}, [sortBy.value, tracks]);
final filteredTracks = useMemoized(() {
@ -180,8 +181,9 @@ class LocalLibraryPage extends HookConsumerWidget {
child: ListView.builder(
controller: controller,
physics: const AlwaysScrollableScrollPhysics(),
itemCount:
trackSnapshot.isLoading ? 5 : filteredTracks.length,
itemCount: trackSnapshot.isLoading
? 5
: filteredTracks.length,
itemBuilder: (context, index) {
if (trackSnapshot.isLoading) {
return TrackTile(
@ -229,8 +231,7 @@ class LocalLibraryPage extends HookConsumerWidget {
Text(error.toString() + stackTrace.toString()),
)
],
)
),
)),
);
}
}

View File

@ -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';

View File

@ -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(),
};
}

View File

@ -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);
}
}

View File

@ -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",