mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
chore: fix artist top tracks play button not showing loading indicator
This commit is contained in:
parent
2e48ac380b
commit
69d50eec35
@ -101,7 +101,9 @@ extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject {
|
|||||||
albumArtist: artists.map((a) => a.name).join(", "),
|
albumArtist: artists.map((a) => a.name).join(", "),
|
||||||
year: album.releaseDate == null
|
year: album.releaseDate == null
|
||||||
? 1970
|
? 1970
|
||||||
: DateTime.parse(album.releaseDate!).year,
|
: DateTime.tryParse(album.releaseDate!)?.year ??
|
||||||
|
int.tryParse(album.releaseDate!) ??
|
||||||
|
1970,
|
||||||
durationMs: durationMs.toDouble(),
|
durationMs: durationMs.toDouble(),
|
||||||
fileSize: BigInt.from(fileLength),
|
fileSize: BigInt.from(fileLength),
|
||||||
picture: imageBytes != null
|
picture: imageBytes != null
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -19,6 +21,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final isLoading = useState(false);
|
||||||
|
|
||||||
final playlist = ref.watch(audioPlayerProvider);
|
final playlist = ref.watch(audioPlayerProvider);
|
||||||
final playlistNotifier = ref.watch(audioPlayerProvider.notifier);
|
final playlistNotifier = ref.watch(audioPlayerProvider.notifier);
|
||||||
@ -40,46 +43,54 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
final topTracks = topTracksQuery.asData?.value.items ??
|
final topTracks = topTracksQuery.asData?.value.items ??
|
||||||
List.generate(10, (index) => FakeData.track);
|
List.generate(10, (index) => FakeData.track);
|
||||||
|
|
||||||
void playPlaylist(List<SpotubeFullTrackObject> tracks,
|
void playPlaylist(
|
||||||
{SpotubeTrackObject? currentTrack}) async {
|
List<SpotubeFullTrackObject> tracks, {
|
||||||
|
SpotubeTrackObject? currentTrack,
|
||||||
|
}) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
currentTrack ??= tracks.first;
|
currentTrack ??= tracks.first;
|
||||||
|
try {
|
||||||
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
|
|
||||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
if (isRemoteDevice == null) return;
|
||||||
|
|
||||||
if (isRemoteDevice == null) return;
|
if (isRemoteDevice) {
|
||||||
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
|
final remotePlaylist = ref.read(queueProvider);
|
||||||
|
|
||||||
if (isRemoteDevice) {
|
final isPlaylistPlaying = remotePlaylist.containsTracks(tracks);
|
||||||
final remotePlayback = ref.read(connectProvider.notifier);
|
|
||||||
final remotePlaylist = ref.read(queueProvider);
|
|
||||||
|
|
||||||
final isPlaylistPlaying = remotePlaylist.containsTracks(tracks);
|
if (!isPlaylistPlaying) {
|
||||||
|
await remotePlayback.load(
|
||||||
if (!isPlaylistPlaying) {
|
WebSocketLoadEventData.playlist(
|
||||||
await remotePlayback.load(
|
tracks: tracks,
|
||||||
WebSocketLoadEventData.playlist(
|
collection: null,
|
||||||
tracks: tracks,
|
initialIndex:
|
||||||
collection: null,
|
tracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (isPlaylistPlaying &&
|
||||||
|
currentTrack.id != remotePlaylist.activeTrack?.id) {
|
||||||
|
final index = playlist.tracks
|
||||||
|
.toList()
|
||||||
|
.indexWhere((s) => s.id == currentTrack!.id);
|
||||||
|
await remotePlayback.jumpTo(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
playlistNotifier.load(
|
||||||
|
tracks,
|
||||||
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
),
|
autoPlay: true,
|
||||||
);
|
);
|
||||||
} else if (isPlaylistPlaying &&
|
} else if (isPlaylistPlaying &&
|
||||||
currentTrack.id != remotePlaylist.activeTrack?.id) {
|
currentTrack.id != playlist.activeTrack?.id) {
|
||||||
final index = playlist.tracks
|
await playlistNotifier.jumpToTrack(currentTrack);
|
||||||
.toList()
|
}
|
||||||
.indexWhere((s) => s.id == currentTrack!.id);
|
|
||||||
await remotePlayback.jumpTo(index);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!isPlaylistPlaying) {
|
|
||||||
playlistNotifier.load(
|
|
||||||
tracks,
|
|
||||||
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
|
||||||
autoPlay: true,
|
|
||||||
);
|
|
||||||
} else if (isPlaylistPlaying &&
|
|
||||||
currentTrack.id != playlist.activeTrack?.id) {
|
|
||||||
await playlistNotifier.jumpToTrack(currentTrack);
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +131,19 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
IconButton.primary(
|
IconButton.primary(
|
||||||
shape: ButtonShape.circle,
|
shape: ButtonShape.circle,
|
||||||
enabled: !isPlaylistPlaying,
|
enabled: !isPlaylistPlaying && !isLoading.value,
|
||||||
icon: Skeleton.keep(
|
icon: isLoading.value
|
||||||
child: Icon(
|
? CircularProgressIndicator(
|
||||||
isPlaylistPlaying ? SpotubeIcons.pause : SpotubeIcons.play,
|
size: 20 * context.theme.scaling,
|
||||||
),
|
color: theme.colorScheme.primaryForeground,
|
||||||
),
|
)
|
||||||
|
: Skeleton.keep(
|
||||||
|
child: Icon(
|
||||||
|
isPlaylistPlaying
|
||||||
|
? SpotubeIcons.pause
|
||||||
|
: SpotubeIcons.play,
|
||||||
|
),
|
||||||
|
),
|
||||||
onPressed: () => playPlaylist(topTracks.toList()),
|
onPressed: () => playPlaylist(topTracks.toList()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -98,315 +98,311 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
headers: [
|
headers: [
|
||||||
TitleBar(
|
TitleBar(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 10,
|
horizontal: 10,
|
||||||
vertical: 0,
|
vertical: 0,
|
||||||
),
|
),
|
||||||
surfaceBlur: 0,
|
surfaceBlur: 0,
|
||||||
leading: const [BackButton()],
|
leading: const [BackButton()],
|
||||||
title: Column(
|
title: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isDownloads
|
isDownloads
|
||||||
? context.l10n.downloads
|
? context.l10n.downloads
|
||||||
: isCache
|
: isCache
|
||||||
? context.l10n.cache_folder.capitalize()
|
? context.l10n.cache_folder.capitalize()
|
||||||
: location,
|
: location,
|
||||||
),
|
),
|
||||||
FutureBuilder<String>(
|
FutureBuilder<String>(
|
||||||
future: directorySize,
|
future: directorySize,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Text(
|
return Text(
|
||||||
"${(snapshot.data ?? 0)} GB",
|
"${(snapshot.data ?? 0)} GB",
|
||||||
).xSmall().muted();
|
).xSmall().muted();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
trailingGap: 10,
|
|
||||||
trailing: [
|
|
||||||
if (isCache) ...[
|
|
||||||
IconButton.outline(
|
|
||||||
size: ButtonSize.small,
|
|
||||||
icon: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(SpotubeIcons.delete),
|
|
||||||
Text(context.l10n.clear_cache)
|
|
||||||
],
|
|
||||||
).xSmall().iconSmall(),
|
|
||||||
onPressed: () async {
|
|
||||||
final accepted = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text(context.l10n.clear_cache_confirmation),
|
|
||||||
actions: [
|
|
||||||
Button.outline(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop(false);
|
|
||||||
},
|
|
||||||
child: Text(context.l10n.decline),
|
|
||||||
),
|
|
||||||
Button.destructive(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(context).pop(true);
|
|
||||||
},
|
|
||||||
child: Text(context.l10n.accept),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (accepted ?? false) return;
|
|
||||||
|
|
||||||
final cacheDir = Directory(
|
|
||||||
await UserPreferencesNotifier.getMusicCacheDir(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cacheDir.existsSync()) {
|
|
||||||
await cacheDir.delete(recursive: true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton.outline(
|
|
||||||
size: ButtonSize.small,
|
|
||||||
icon: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(SpotubeIcons.export),
|
|
||||||
Text(
|
|
||||||
context.l10n.export,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
).xSmall().iconSmall(),
|
|
||||||
onPressed: () async {
|
|
||||||
final exportPath =
|
|
||||||
await FilePicker.platform.getDirectoryPath();
|
|
||||||
|
|
||||||
if (exportPath == null) return;
|
|
||||||
final exportDirectory = Directory(exportPath);
|
|
||||||
|
|
||||||
if (!exportDirectory.existsSync()) {
|
|
||||||
await exportDirectory.create(recursive: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
final cacheDir = Directory(
|
|
||||||
await UserPreferencesNotifier.getMusicCacheDir());
|
|
||||||
|
|
||||||
if (!context.mounted) return;
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return LocalFolderCacheExportDialog(
|
|
||||||
cacheDir: cacheDir,
|
|
||||||
exportDir: exportDirectory,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
backgroundColor: Colors.transparent,
|
||||||
child: LayoutBuilder(
|
trailingGap: 10,
|
||||||
builder: (context, constraints) => Column(
|
trailing: [
|
||||||
|
if (isCache) ...[
|
||||||
|
IconButton.outline(
|
||||||
|
size: ButtonSize.small,
|
||||||
|
icon: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const Icon(SpotubeIcons.delete),
|
||||||
padding: const EdgeInsets.all(8.0),
|
Text(context.l10n.clear_cache)
|
||||||
child: Row(
|
],
|
||||||
children: [
|
).xSmall().iconSmall(),
|
||||||
const Gap(5),
|
onPressed: () async {
|
||||||
Button.primary(
|
final accepted = await showDialog<bool>(
|
||||||
onPressed: trackSnapshot.asData?.value != null
|
context: context,
|
||||||
? () async {
|
builder: (context) => AlertDialog(
|
||||||
if (trackSnapshot
|
title: Text(context.l10n.clear_cache_confirmation),
|
||||||
.asData?.value.isNotEmpty ==
|
actions: [
|
||||||
true) {
|
Button.outline(
|
||||||
if (!isPlaylistPlaying) {
|
onPressed: () {
|
||||||
await playLocalTracks(
|
Navigator.of(context).pop(false);
|
||||||
ref,
|
},
|
||||||
trackSnapshot
|
child: Text(context.l10n.decline),
|
||||||
.asData!.value[location] ??
|
),
|
||||||
[],
|
Button.destructive(
|
||||||
);
|
onPressed: () async {
|
||||||
}
|
Navigator.of(context).pop(true);
|
||||||
}
|
},
|
||||||
}
|
child: Text(context.l10n.accept),
|
||||||
: null,
|
),
|
||||||
leading: Icon(
|
],
|
||||||
isPlaylistPlaying
|
),
|
||||||
? SpotubeIcons.stop
|
);
|
||||||
: SpotubeIcons.play,
|
|
||||||
),
|
if (accepted ?? false) return;
|
||||||
child: Text(context.l10n.play),
|
|
||||||
),
|
final cacheDir = Directory(
|
||||||
const Spacer(),
|
await UserPreferencesNotifier.getMusicCacheDir(),
|
||||||
if (constraints.smAndDown)
|
);
|
||||||
ExpandableSearchButton(
|
|
||||||
isFiltering: isFiltering.value,
|
if (cacheDir.existsSync()) {
|
||||||
onPressed: (value) => isFiltering.value = value,
|
await cacheDir.delete(recursive: true);
|
||||||
searchFocus: searchFocus,
|
}
|
||||||
)
|
},
|
||||||
else
|
),
|
||||||
ConstrainedBox(
|
IconButton.outline(
|
||||||
constraints: BoxConstraints(
|
size: ButtonSize.small,
|
||||||
maxWidth: 300 * scale,
|
icon: Column(
|
||||||
maxHeight: 38 * scale,
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: [
|
||||||
child: ExpandableSearchField(
|
const Icon(SpotubeIcons.export),
|
||||||
isFiltering: true,
|
Text(
|
||||||
onChangeFiltering: (value) {},
|
context.l10n.export,
|
||||||
searchController: searchController,
|
)
|
||||||
searchFocus: searchFocus,
|
],
|
||||||
),
|
).xSmall().iconSmall(),
|
||||||
),
|
onPressed: () async {
|
||||||
const Gap(5),
|
final exportPath =
|
||||||
SortTracksDropdown(
|
await FilePicker.platform.getDirectoryPath();
|
||||||
value: sortBy.value,
|
|
||||||
onChanged: (value) {
|
if (exportPath == null) return;
|
||||||
sortBy.value = value;
|
final exportDirectory = Directory(exportPath);
|
||||||
},
|
|
||||||
),
|
if (!exportDirectory.existsSync()) {
|
||||||
const Gap(5),
|
await exportDirectory.create(recursive: true);
|
||||||
IconButton.outline(
|
}
|
||||||
icon: const Icon(SpotubeIcons.refresh),
|
|
||||||
onPressed: () {
|
final cacheDir = Directory(
|
||||||
ref.invalidate(localTracksProvider);
|
await UserPreferencesNotifier.getMusicCacheDir());
|
||||||
},
|
|
||||||
)
|
if (!context.mounted) return;
|
||||||
],
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return LocalFolderCacheExportDialog(
|
||||||
|
cacheDir: cacheDir,
|
||||||
|
exportDir: exportDirectory,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) => Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Gap(5),
|
||||||
|
Button.primary(
|
||||||
|
onPressed: trackSnapshot.asData?.value != null
|
||||||
|
? () async {
|
||||||
|
if (trackSnapshot.asData?.value.isNotEmpty ==
|
||||||
|
true) {
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
await playLocalTracks(
|
||||||
|
ref,
|
||||||
|
trackSnapshot.asData!.value[location] ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
leading: Icon(
|
||||||
|
isPlaylistPlaying
|
||||||
|
? SpotubeIcons.stop
|
||||||
|
: SpotubeIcons.play,
|
||||||
|
),
|
||||||
|
child: Text(context.l10n.play),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (constraints.smAndDown)
|
||||||
|
ExpandableSearchButton(
|
||||||
|
isFiltering: isFiltering.value,
|
||||||
|
onPressed: (value) => isFiltering.value = value,
|
||||||
|
searchFocus: searchFocus,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: 300 * scale,
|
||||||
|
maxHeight: 38 * scale,
|
||||||
|
),
|
||||||
|
child: ExpandableSearchField(
|
||||||
|
isFiltering: true,
|
||||||
|
onChangeFiltering: (value) {},
|
||||||
|
searchController: searchController,
|
||||||
|
searchFocus: searchFocus,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ExpandableSearchField(
|
const Gap(5),
|
||||||
searchController: searchController,
|
SortTracksDropdown(
|
||||||
searchFocus: searchFocus,
|
value: sortBy.value,
|
||||||
isFiltering: isFiltering.value,
|
onChanged: (value) {
|
||||||
onChangeFiltering: (value) => isFiltering.value = value,
|
sortBy.value = value;
|
||||||
),
|
},
|
||||||
HookBuilder(builder: (context) {
|
),
|
||||||
return trackSnapshot.when(
|
const Gap(5),
|
||||||
data: (tracks) {
|
IconButton.outline(
|
||||||
final sortedTracks = useMemoized(() {
|
icon: const Icon(SpotubeIcons.refresh),
|
||||||
return ServiceUtils.sortTracks(
|
onPressed: () {
|
||||||
tracks[location] ??
|
ref.invalidate(localTracksProvider);
|
||||||
<SpotubeLocalTrackObject>[],
|
},
|
||||||
sortBy.value);
|
)
|
||||||
}, [sortBy.value, tracks]);
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ExpandableSearchField(
|
||||||
|
searchController: searchController,
|
||||||
|
searchFocus: searchFocus,
|
||||||
|
isFiltering: isFiltering.value,
|
||||||
|
onChangeFiltering: (value) => isFiltering.value = value,
|
||||||
|
),
|
||||||
|
HookBuilder(builder: (context) {
|
||||||
|
return trackSnapshot.when(
|
||||||
|
data: (tracks) {
|
||||||
|
final sortedTracks = useMemoized(() {
|
||||||
|
return ServiceUtils.sortTracks(
|
||||||
|
tracks[location] ?? <SpotubeLocalTrackObject>[],
|
||||||
|
sortBy.value);
|
||||||
|
}, [sortBy.value, tracks]);
|
||||||
|
|
||||||
final filteredTracks = useMemoized(() {
|
final filteredTracks = useMemoized(() {
|
||||||
if (searchController.text.isEmpty) {
|
if (searchController.text.isEmpty) {
|
||||||
return sortedTracks;
|
return sortedTracks;
|
||||||
}
|
}
|
||||||
return sortedTracks
|
return sortedTracks
|
||||||
.map((e) => (
|
.map((e) => (
|
||||||
weightedRatio(
|
weightedRatio(
|
||||||
"${e.name} - ${e.artists.asString()}",
|
"${e.name} - ${e.artists.asString()}",
|
||||||
searchController.text,
|
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 Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Undraw(
|
|
||||||
illustration: UndrawIllustration.empty,
|
|
||||||
height: 200 * scale,
|
|
||||||
color: context.theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
const Gap(10),
|
|
||||||
Text(
|
|
||||||
context.l10n.nothing_found,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).muted().small()
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
e,
|
||||||
}
|
))
|
||||||
|
.toList()
|
||||||
|
.sorted(
|
||||||
|
(a, b) => b.$1.compareTo(a.$1),
|
||||||
|
)
|
||||||
|
.where((e) => e.$1 > 50)
|
||||||
|
.map((e) => e.$2)
|
||||||
|
.toList()
|
||||||
|
.toList();
|
||||||
|
}, [searchController.text, sortedTracks]);
|
||||||
|
|
||||||
return Expanded(
|
if (!trackSnapshot.isLoading && filteredTracks.isEmpty) {
|
||||||
child: material.RefreshIndicator.adaptive(
|
return Expanded(
|
||||||
onRefresh: () async {
|
child: Column(
|
||||||
ref.invalidate(localTracksProvider);
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
},
|
children: [
|
||||||
child: InterScrollbar(
|
Undraw(
|
||||||
controller: controller,
|
illustration: UndrawIllustration.empty,
|
||||||
child: Skeletonizer(
|
height: 200 * scale,
|
||||||
enabled: trackSnapshot.isLoading,
|
color: context.theme.colorScheme.primary,
|
||||||
child: ListView.builder(
|
),
|
||||||
controller: controller,
|
const Gap(10),
|
||||||
physics:
|
Text(
|
||||||
const AlwaysScrollableScrollPhysics(),
|
context.l10n.nothing_found,
|
||||||
itemCount: trackSnapshot.isLoading
|
textAlign: TextAlign.center,
|
||||||
? 5
|
).muted().small()
|
||||||
: filteredTracks.length,
|
],
|
||||||
itemBuilder: (context, index) {
|
),
|
||||||
if (trackSnapshot.isLoading) {
|
);
|
||||||
return TrackTile(
|
}
|
||||||
playlist: playlist,
|
|
||||||
track: FakeData.track,
|
|
||||||
index: index,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final track = filteredTracks[index];
|
return Expanded(
|
||||||
return TrackTile(
|
child: material.RefreshIndicator.adaptive(
|
||||||
index: index,
|
onRefresh: () async {
|
||||||
playlist: playlist,
|
ref.invalidate(localTracksProvider);
|
||||||
track: track,
|
},
|
||||||
userPlaylist: false,
|
child: InterScrollbar(
|
||||||
onTap: () async {
|
controller: controller,
|
||||||
await playLocalTracks(
|
child: Skeletonizer(
|
||||||
ref,
|
enabled: trackSnapshot.isLoading,
|
||||||
sortedTracks,
|
child: ListView.builder(
|
||||||
currentTrack: track,
|
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,
|
||||||
loading: () => Expanded(
|
);
|
||||||
child: Skeletonizer(
|
}
|
||||||
enabled: true,
|
|
||||||
child: ListView.builder(
|
final track = filteredTracks[index];
|
||||||
itemCount: 5,
|
return TrackTile(
|
||||||
itemBuilder: (context, index) => TrackTile(
|
|
||||||
track: FakeData.track,
|
|
||||||
index: index,
|
index: index,
|
||||||
playlist: playlist,
|
playlist: playlist,
|
||||||
),
|
track: track,
|
||||||
),
|
userPlaylist: false,
|
||||||
|
onTap: () async {
|
||||||
|
await playLocalTracks(
|
||||||
|
ref,
|
||||||
|
sortedTracks,
|
||||||
|
currentTrack: track,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
error: (error, stackTrace) =>
|
),
|
||||||
Text(error.toString() + stackTrace.toString()),
|
),
|
||||||
);
|
);
|
||||||
})
|
},
|
||||||
],
|
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()),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,6 +393,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
Future<SourcedTrack> refreshStream() async {
|
Future<SourcedTrack> refreshStream() async {
|
||||||
List<TrackSource> validStreams = [];
|
List<TrackSource> validStreams = [];
|
||||||
|
|
||||||
|
final stringBuffer = StringBuffer();
|
||||||
for (final source in sources) {
|
for (final source in sources) {
|
||||||
final res = await globalDio.head(
|
final res = await globalDio.head(
|
||||||
source.url,
|
source.url,
|
||||||
@ -400,11 +401,17 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
Options(validateStatus: (status) => status != null && status < 500),
|
Options(validateStatus: (status) => status != null && status < 500),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
stringBuffer.writeln(
|
||||||
|
"[${query.id}] ${res.statusCode} ${source.quality} ${source.codec} ${source.bitrate}",
|
||||||
|
);
|
||||||
|
|
||||||
if (res.statusCode! < 400) {
|
if (res.statusCode! < 400) {
|
||||||
validStreams.add(source);
|
validStreams.add(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppLogger.log.d(stringBuffer.toString());
|
||||||
|
|
||||||
if (validStreams.isEmpty) {
|
if (validStreams.isEmpty) {
|
||||||
final manifest =
|
final manifest =
|
||||||
await ref.read(youtubeEngineProvider).getStreamManifest(info.id);
|
await ref.read(youtubeEngineProvider).getStreamManifest(info.id);
|
||||||
|
Loading…
Reference in New Issue
Block a user