mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
feat: show matching tracks in queue as well
This commit is contained in:
parent
79b9c57932
commit
27fcedc252
@ -1,23 +1,22 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
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/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/components/library/user_downloads/download_item.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/services/download_manager/download_manager.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
class UserDownloads extends HookConsumerWidget {
|
||||
const UserDownloads({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
ref.watch(downloadManagerProvider);
|
||||
final downloadManager = ref.watch(downloadManagerProvider.notifier);
|
||||
final downloadManager = ref.watch(downloadManagerProvider);
|
||||
|
||||
final history = [
|
||||
...downloadManager.$history,
|
||||
...downloadManager.$backHistory,
|
||||
];
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -52,116 +51,9 @@ class UserDownloads extends HookConsumerWidget {
|
||||
Expanded(
|
||||
child: SafeArea(
|
||||
child: ListView.builder(
|
||||
itemCount: downloadManager.$downloadCount,
|
||||
itemCount: history.length,
|
||||
itemBuilder: (context, index) {
|
||||
final track = downloadManager.$history.elementAt(index);
|
||||
return HookBuilder(builder: (context) {
|
||||
final taskStatus = useListenable(
|
||||
useMemoized(
|
||||
() => downloadManager.getStatusNotifier(track),
|
||||
[track],
|
||||
),
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
title: Text(track.name ?? ''),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: UniversalImage(
|
||||
height: 40,
|
||||
width: 40,
|
||||
path: TypeConversionUtils.image_X_UrlString(
|
||||
track.album?.images,
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: taskStatus == null
|
||||
? null
|
||||
: switch (taskStatus.value) {
|
||||
DownloadStatus.downloading =>
|
||||
HookBuilder(builder: (context) {
|
||||
final taskProgress = useListenable(useMemoized(
|
||||
() => downloadManager
|
||||
.getProgressNotifier(track),
|
||||
[track],
|
||||
));
|
||||
return SizedBox(
|
||||
width: 140,
|
||||
child: Row(
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: taskProgress?.value ?? 0,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.pause),
|
||||
onPressed: () {
|
||||
downloadManager.pause(track);
|
||||
}),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.cancel(track);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
DownloadStatus.paused => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.play),
|
||||
onPressed: () {
|
||||
downloadManager.resume(track);
|
||||
}),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.cancel(track);
|
||||
})
|
||||
],
|
||||
),
|
||||
DownloadStatus.failed ||
|
||||
DownloadStatus.canceled =>
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
SpotubeIcons.error,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.refresh),
|
||||
onPressed: () {
|
||||
downloadManager.retry(track);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DownloadStatus.completed =>
|
||||
Icon(SpotubeIcons.done, color: Colors.green[400]),
|
||||
DownloadStatus.queued => IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.removeFromQueue(track);
|
||||
}),
|
||||
},
|
||||
subtitle: TypeConversionUtils.artists_X_ClickableArtists(
|
||||
track.artists ?? <Artist>[],
|
||||
mainAxisAlignment: WrapAlignment.start,
|
||||
),
|
||||
);
|
||||
});
|
||||
return DownloadItem(track: history[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
145
lib/components/library/user_downloads/download_item.dart
Normal file
145
lib/components/library/user_downloads/download_item.dart
Normal file
@ -0,0 +1,145 @@
|
||||
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/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/services/download_manager/download_status.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
class DownloadItem extends HookConsumerWidget {
|
||||
final Track track;
|
||||
const DownloadItem({
|
||||
Key? key,
|
||||
required this.track,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final downloadManager = ref.watch(downloadManagerProvider);
|
||||
|
||||
final taskStatus = useState<DownloadStatus?>(null);
|
||||
|
||||
useEffect(() {
|
||||
if (track is! SpotubeTrack) return null;
|
||||
final notifier = downloadManager.getStatusNotifier(track as SpotubeTrack);
|
||||
|
||||
taskStatus.value = notifier?.value;
|
||||
listener() {
|
||||
taskStatus.value = notifier?.value;
|
||||
}
|
||||
|
||||
downloadManager
|
||||
.getStatusNotifier(track as SpotubeTrack)
|
||||
?.addListener(listener);
|
||||
|
||||
return () {
|
||||
downloadManager
|
||||
.getStatusNotifier(track as SpotubeTrack)
|
||||
?.removeListener(listener);
|
||||
};
|
||||
}, [track]);
|
||||
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: UniversalImage(
|
||||
height: 40,
|
||||
width: 40,
|
||||
path: TypeConversionUtils.image_X_UrlString(
|
||||
track.album?.images,
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(track.name ?? ''),
|
||||
subtitle: TypeConversionUtils.artists_X_ClickableArtists(
|
||||
track.artists ?? <Artist>[],
|
||||
mainAxisAlignment: WrapAlignment.start,
|
||||
),
|
||||
trailing: taskStatus.value == null || track is! SpotubeTrack
|
||||
? Text(
|
||||
context.l10n.querying_info,
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
)
|
||||
: switch (taskStatus.value!) {
|
||||
DownloadStatus.downloading => HookBuilder(builder: (context) {
|
||||
final taskProgress = useListenable(useMemoized(
|
||||
() => downloadManager
|
||||
.getProgressNotifier(track as SpotubeTrack),
|
||||
[track],
|
||||
));
|
||||
return SizedBox(
|
||||
width: 140,
|
||||
child: Row(
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: taskProgress?.value ?? 0,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.pause),
|
||||
onPressed: () {
|
||||
downloadManager.pause(track as SpotubeTrack);
|
||||
}),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.cancel(track as SpotubeTrack);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
DownloadStatus.paused => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.play),
|
||||
onPressed: () {
|
||||
downloadManager.resume(track as SpotubeTrack);
|
||||
}),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.cancel(track as SpotubeTrack);
|
||||
})
|
||||
],
|
||||
),
|
||||
DownloadStatus.failed || DownloadStatus.canceled => SizedBox(
|
||||
width: 100,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
SpotubeIcons.error,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(SpotubeIcons.refresh),
|
||||
onPressed: () {
|
||||
downloadManager.retry(track as SpotubeTrack);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DownloadStatus.completed =>
|
||||
Icon(SpotubeIcons.done, color: Colors.green[400]),
|
||||
DownloadStatus.queued => IconButton(
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
downloadManager.removeFromQueue(track as SpotubeTrack);
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "API প্রকার",
|
||||
"ok": "ঠিক আছে",
|
||||
"failed_to_encrypt": "এনক্রিপ্ট করা ব্যর্থ হয়েছে",
|
||||
"encryption_failed_warning": "Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে"
|
||||
"encryption_failed_warning": "Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে",
|
||||
"querying_info": "তথ্য অনুসন্ধান করা হচ্ছে"
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "API-Typ",
|
||||
"ok": "OK",
|
||||
"failed_to_encrypt": "Verschlüsselung fehlgeschlagen",
|
||||
"encryption_failed_warning": "Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben"
|
||||
"encryption_failed_warning": "Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben",
|
||||
"querying_info": "Abfrageinformationen..."
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "API Type",
|
||||
"ok": "Ok",
|
||||
"failed_to_encrypt": "Failed to encrypt",
|
||||
"encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed"
|
||||
"encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed",
|
||||
"querying_info": "Querying info..."
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "Tipo de API de YouTube",
|
||||
"ok": "OK",
|
||||
"failed_to_encrypt": "Error al cifrar",
|
||||
"encryption_failed_warning": "Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc"
|
||||
"encryption_failed_warning": "Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc",
|
||||
"querying_info": "Consultando información..."
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "Type d'API",
|
||||
"ok": "OK",
|
||||
"failed_to_encrypt": "Échec de la cryptage",
|
||||
"encryption_failed_warning": "Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc"
|
||||
"encryption_failed_warning": "Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc",
|
||||
"querying_info": "Interrogation des info..."
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "API प्रकार",
|
||||
"ok": "ठीक है",
|
||||
"failed_to_encrypt": "एन्क्रिप्ट करने में विफल रहा",
|
||||
"encryption_failed_warning": "Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है"
|
||||
"encryption_failed_warning": "Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है",
|
||||
"querying_info": "जानकारी प्राप्त करना"
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "APIの種類",
|
||||
"ok": "分かりました",
|
||||
"failed_to_encrypt": "暗号化に失敗しました",
|
||||
"encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください"
|
||||
"encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください",
|
||||
"querying_info": "情報を取得中..."
|
||||
}
|
||||
@ -253,5 +253,6 @@
|
||||
"youtube_api_type": "API 类型",
|
||||
"ok": "确定",
|
||||
"failed_to_encrypt": "加密失败",
|
||||
"encryption_failed_warning": "Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务"
|
||||
"encryption_failed_warning": "Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务",
|
||||
"querying_info": "正在查询信息..."
|
||||
}
|
||||
@ -19,7 +19,7 @@ import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
class DownloadManagerProvider extends ChangeNotifier {
|
||||
DownloadManagerProvider({required this.ref})
|
||||
: $history = <SpotubeTrack>{},
|
||||
backHistory = <Track>{},
|
||||
$backHistory = <Track>{},
|
||||
dl = DownloadManager() {
|
||||
dl.statusStream.listen((event) async {
|
||||
final (:request, :status) = event;
|
||||
@ -100,7 +100,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
|
||||
final Set<SpotubeTrack> $history;
|
||||
// these are the tracks which metadata hasn't been fetched yet
|
||||
final Set<Track> backHistory;
|
||||
final Set<Track> $backHistory;
|
||||
final DownloadManager dl;
|
||||
|
||||
/// Spotify Images are always JPEGs
|
||||
@ -133,7 +133,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
bool isActive(Track track) {
|
||||
if (backHistory.contains(track)) return true;
|
||||
if ($backHistory.contains(track)) return true;
|
||||
|
||||
final spotubeTrack = mapToSpotubeTrack(track);
|
||||
|
||||
@ -170,10 +170,10 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
$history.add(track);
|
||||
}
|
||||
} else {
|
||||
backHistory.add(track);
|
||||
$backHistory.add(track);
|
||||
final spotubeTrack =
|
||||
await SpotubeTrack.fetchFromTrack(track, yt).then((d) {
|
||||
backHistory.remove(track);
|
||||
$backHistory.remove(track);
|
||||
return d;
|
||||
});
|
||||
final downloadTask = await dl.addDownload(spotubeTrack.ytUri, savePath);
|
||||
@ -186,12 +186,24 @@ class DownloadManagerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> batchAddToQueue(List<Track> tracks) async {
|
||||
backHistory.addAll(
|
||||
$backHistory.addAll(
|
||||
tracks.where((element) => element is! SpotubeTrack),
|
||||
);
|
||||
notifyListeners();
|
||||
for (final track in tracks) {
|
||||
try {
|
||||
if (track == tracks.first) {
|
||||
await addToQueue(track);
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
} else {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 5),
|
||||
() => addToQueue(track),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Catcher.reportCheckedError(e, StackTrace.current);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user