mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-26 11:31:01 +02:00
feat(album): batch download selected tracks
This commit is contained in:
@@ -118,6 +118,12 @@ void showQueuedSnackbar(BuildContext context, int added, int skipped) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
|
||||
bool shouldShowBatchDownloadPicker({
|
||||
required bool forceQualityPicker,
|
||||
required bool askQualityBeforeDownload,
|
||||
required bool allowQualityVariants,
|
||||
}) => forceQualityPicker || askQualityBeforeDownload || allowQualityVariants;
|
||||
|
||||
/// Shared batch "add to queue" flow for detail screens: skips tracks already
|
||||
/// present in download history or the local library, then either shows the
|
||||
/// quality/service picker or resolves a default service, before enqueueing
|
||||
@@ -126,6 +132,9 @@ void showQueuedSnackbar(BuildContext context, int added, int skipped) {
|
||||
/// Set [resolveDefaultService] to false to match screens that pass
|
||||
/// `settings.defaultService` straight through without resolving it against
|
||||
/// enabled extensions (playlist / library-folder screens do this today).
|
||||
/// [forceQualityPicker] lets an explicit multi-select action choose one
|
||||
/// provider and quality for the complete batch. [onQueued] runs only after
|
||||
/// tracks are actually added, not when the picker is merely opened.
|
||||
Future<void> queueTracksSkippingDownloaded(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
@@ -134,6 +143,8 @@ Future<void> queueTracksSkippingDownloaded(
|
||||
String? recommendedService,
|
||||
String? playlistName,
|
||||
bool resolveDefaultService = true,
|
||||
bool forceQualityPicker = false,
|
||||
VoidCallback? onQueued,
|
||||
}) async {
|
||||
if (tracks.isEmpty) return;
|
||||
|
||||
@@ -198,7 +209,11 @@ Future<void> queueTracksSkippingDownloaded(
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.askQualityBeforeDownload || settings.allowQualityVariants) {
|
||||
if (shouldShowBatchDownloadPicker(
|
||||
forceQualityPicker: forceQualityPicker,
|
||||
askQualityBeforeDownload: settings.askQualityBeforeDownload,
|
||||
allowQualityVariants: settings.allowQualityVariants,
|
||||
)) {
|
||||
DownloadServicePicker.show(
|
||||
context,
|
||||
trackName: '${tracksToQueue.length} tracks',
|
||||
@@ -213,6 +228,7 @@ Future<void> queueTracksSkippingDownloaded(
|
||||
qualityOverride: quality,
|
||||
playlistName: playlistName,
|
||||
);
|
||||
onQueued?.call();
|
||||
showQueuedSnackbar(context, tracksToQueue.length, skippedCount);
|
||||
},
|
||||
);
|
||||
@@ -238,6 +254,7 @@ Future<void> queueTracksSkippingDownloaded(
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addMultipleToQueue(tracksToQueue, service, playlistName: playlistName);
|
||||
onQueued?.call();
|
||||
showQueuedSnackbar(context, tracksToQueue.length, skippedCount);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/utils/clickable_metadata.dart';
|
||||
import 'package:spotiflac_android/utils/local_playback.dart';
|
||||
import 'package:spotiflac_android/widgets/audio_quality_badges.dart';
|
||||
import 'package:spotiflac_android/widgets/animation_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/in_library_badge.dart';
|
||||
import 'package:spotiflac_android/widgets/preview_button.dart';
|
||||
import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
|
||||
@@ -22,6 +23,10 @@ class TrackListTile extends ConsumerWidget {
|
||||
final void Function({bool forceQualityPicker}) onDownload;
|
||||
final Widget leading;
|
||||
final bool clickableArtist;
|
||||
final bool isSelectionMode;
|
||||
final bool isSelected;
|
||||
final VoidCallback? onToggleSelection;
|
||||
final VoidCallback? onEnterSelectionMode;
|
||||
|
||||
const TrackListTile({
|
||||
super.key,
|
||||
@@ -30,7 +35,11 @@ class TrackListTile extends ConsumerWidget {
|
||||
required this.onDownload,
|
||||
required this.leading,
|
||||
this.clickableArtist = false,
|
||||
});
|
||||
this.isSelectionMode = false,
|
||||
this.isSelected = false,
|
||||
this.onToggleSelection,
|
||||
this.onEnterSelectionMode,
|
||||
}) : assert(!isSelectionMode || onToggleSelection != null);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -65,13 +74,29 @@ class TrackListTile extends ConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
color: isSelected
|
||||
? colorScheme.primaryContainer.withValues(alpha: 0.3)
|
||||
: Colors.transparent,
|
||||
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
leading: leading,
|
||||
leading: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isSelectionMode) ...[
|
||||
AnimatedSelectionCheckbox(
|
||||
visible: true,
|
||||
selected: isSelected,
|
||||
colorScheme: colorScheme,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
leading,
|
||||
],
|
||||
),
|
||||
title: Text(
|
||||
track.name,
|
||||
maxLines: 1,
|
||||
@@ -83,7 +108,7 @@ class TrackListTile extends ConsumerWidget {
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: clickableArtist
|
||||
child: clickableArtist && !isSelectionMode
|
||||
? ClickableArtistName(
|
||||
artistName: track.artistName,
|
||||
artistId: track.artistId,
|
||||
@@ -112,28 +137,37 @@ class TrackListTile extends ConsumerWidget {
|
||||
],
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PreviewButton(track: track),
|
||||
TrackCollectionQuickActions(
|
||||
track: track,
|
||||
hasLocalPlaybackCandidate: isInHistory || isInLocalLibrary,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _handleTap(
|
||||
context,
|
||||
ref,
|
||||
isQueued: isQueued,
|
||||
isInLocalLibrary: isInLocalLibrary,
|
||||
),
|
||||
onLongPress: () => TrackCollectionQuickActions.showTrackOptionsSheet(
|
||||
context,
|
||||
ref,
|
||||
track,
|
||||
hasLocalPlaybackCandidate: isInHistory || isInLocalLibrary,
|
||||
),
|
||||
trailing: isSelectionMode
|
||||
? null
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PreviewButton(track: track),
|
||||
TrackCollectionQuickActions(
|
||||
track: track,
|
||||
hasLocalPlaybackCandidate:
|
||||
isInHistory || isInLocalLibrary,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: isSelectionMode
|
||||
? onToggleSelection
|
||||
: () => _handleTap(
|
||||
context,
|
||||
ref,
|
||||
isQueued: isQueued,
|
||||
isInLocalLibrary: isInLocalLibrary,
|
||||
),
|
||||
onLongPress: isSelectionMode
|
||||
? null
|
||||
: onEnterSelectionMode ??
|
||||
() => TrackCollectionQuickActions.showTrackOptionsSheet(
|
||||
context,
|
||||
ref,
|
||||
track,
|
||||
hasLocalPlaybackCandidate:
|
||||
isInHistory || isInLocalLibrary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user