mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-21 07:26:51 +02:00
refactor: simplify setup flow and update collection actions
This commit is contained in:
@@ -826,7 +826,7 @@ class _AlbumTrackItem extends ConsumerWidget {
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
if (isInLocalLibrary) ...[
|
||||
if (isInLocalLibrary || isInHistory) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
|
||||
@@ -798,7 +798,7 @@ class _LibraryTracksFolderScreenState
|
||||
_buildShuffleButton(entries),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
_buildDownloadAllCenterButton(context, entries),
|
||||
_buildPlayAllCenterButton(entries),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -831,7 +831,7 @@ class _LibraryTracksFolderScreenState
|
||||
);
|
||||
}
|
||||
|
||||
// ── Shuffle / Download buttons ──
|
||||
// ── Shuffle / Play buttons ──
|
||||
|
||||
Widget _buildShuffleButton(List<CollectionTrackEntry> entries) {
|
||||
return Container(
|
||||
@@ -854,15 +854,12 @@ class _LibraryTracksFolderScreenState
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDownloadAllCenterButton(
|
||||
BuildContext context,
|
||||
List<CollectionTrackEntry> entries,
|
||||
) {
|
||||
Widget _buildPlayAllCenterButton(List<CollectionTrackEntry> entries) {
|
||||
final tracks = entries.map((e) => e.track).toList(growable: false);
|
||||
return FilledButton.icon(
|
||||
onPressed: tracks.isEmpty ? null : () => _downloadAll(context, tracks),
|
||||
icon: const Icon(Icons.download_rounded, size: 18),
|
||||
label: Text(context.l10n.downloadAllCount(tracks.length)),
|
||||
onPressed: tracks.isEmpty ? null : () => _playAll(tracks),
|
||||
icon: const Icon(Icons.play_arrow_rounded, size: 18),
|
||||
label: Text(context.l10n.playAllCount(tracks.length)),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
@@ -885,65 +882,15 @@ class _LibraryTracksFolderScreenState
|
||||
});
|
||||
}
|
||||
|
||||
void _downloadAll(BuildContext context, List<Track> tracks) {
|
||||
void _playAll(List<Track> tracks) {
|
||||
if (tracks.isEmpty) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
final colorScheme = Theme.of(dialogContext).colorScheme;
|
||||
return AlertDialog(
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
title: const Text('Download All'),
|
||||
content: Text('Download ${tracks.length} tracks?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text(context.l10n.dialogCancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(dialogContext);
|
||||
_executeDownloadAll(context, tracks);
|
||||
},
|
||||
child: const Text('Download'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _executeDownloadAll(BuildContext context, List<Track> tracks) {
|
||||
final settings = ref.read(settingsProvider);
|
||||
if (settings.askQualityBeforeDownload) {
|
||||
DownloadServicePicker.show(
|
||||
context,
|
||||
trackName: '${tracks.length} tracks',
|
||||
artistName: '',
|
||||
onSelect: (quality, service) {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addMultipleToQueue(tracks, service, qualityOverride: quality);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.l10n.snackbarAddedTracksToQueue(tracks.length),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
ref.read(playbackProvider.notifier).playTrackList(tracks).catchError((e) {
|
||||
if (!mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('Cannot play local tracks: $e')),
|
||||
);
|
||||
} else {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addMultipleToQueue(tracks, settings.defaultService);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _showCoverOptionsSheet(BuildContext context, bool hasCustomCover) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'package:spotiflac_android/providers/library_collections_provider.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/providers/playback_provider.dart';
|
||||
import 'package:spotiflac_android/widgets/download_service_picker.dart';
|
||||
import 'package:spotiflac_android/widgets/playlist_picker_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
|
||||
|
||||
class PlaylistScreen extends ConsumerStatefulWidget {
|
||||
@@ -308,7 +308,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
const SizedBox(width: 12),
|
||||
_buildDownloadAllCenterButton(context),
|
||||
const SizedBox(width: 12),
|
||||
_buildShufflePlayButton(),
|
||||
_buildAddToPlaylistButton(context),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -505,26 +505,16 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShufflePlayButton() {
|
||||
Widget _buildAddToPlaylistButton(BuildContext context) {
|
||||
return _buildCircleButton(
|
||||
icon: Icons.shuffle_rounded,
|
||||
tooltip: 'Shuffle Play',
|
||||
onPressed: _tracks.isEmpty ? null : _shufflePlayLocal,
|
||||
icon: Icons.playlist_add,
|
||||
tooltip: 'Add to Playlist',
|
||||
onPressed: _tracks.isEmpty
|
||||
? null
|
||||
: () => showAddTracksToPlaylistSheet(context, ref, _tracks),
|
||||
);
|
||||
}
|
||||
|
||||
void _shufflePlayLocal() {
|
||||
if (_tracks.isEmpty) return;
|
||||
final shuffled = [..._tracks]..shuffle();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
ref.read(playbackProvider.notifier).playTrackList(shuffled).catchError((e) {
|
||||
if (!mounted) return;
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('Cannot shuffle play local tracks: $e')),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _confirmDownloadAll(BuildContext context) {
|
||||
if (_tracks.isEmpty) return;
|
||||
showDialog(
|
||||
@@ -717,7 +707,7 @@ class _PlaylistTrackItem extends ConsumerWidget {
|
||||
style: TextStyle(color: colorScheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
if (isInLocalLibrary) ...[
|
||||
if (isInLocalLibrary || isInHistory) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
|
||||
@@ -31,11 +31,7 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
bool _isLoading = false;
|
||||
int _androidSdkVersion = 0;
|
||||
|
||||
// Mode selection
|
||||
String _selectedMode = 'downloader';
|
||||
|
||||
// We add 1 for the Welcome step
|
||||
int get _totalSteps => (_androidSdkVersion >= 33 ? 4 : 3) + 1;
|
||||
int get _totalSteps => _androidSdkVersion >= 33 ? 4 : 3;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -466,8 +462,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
return _notificationPermissionGranted;
|
||||
case 2:
|
||||
return _selectedDirectory != null;
|
||||
case 3:
|
||||
return true; // Mode selection always has a default
|
||||
}
|
||||
} else {
|
||||
switch (logicStep) {
|
||||
@@ -475,8 +469,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
return _storagePermissionGranted;
|
||||
case 1:
|
||||
return _selectedDirectory != null;
|
||||
case 2:
|
||||
return true; // Mode selection always has a default
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -553,7 +545,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
if (_androidSdkVersion >= 33)
|
||||
_buildNotificationStep(colorScheme),
|
||||
_buildDirectoryStep(colorScheme),
|
||||
_buildModeSelectionStep(colorScheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -747,38 +738,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModeSelectionStep(ColorScheme colorScheme) {
|
||||
return _StepLayout(
|
||||
title: context.l10n.setupModeSelectionTitle,
|
||||
description: context.l10n.setupModeSelectionDescription,
|
||||
icon: Icons.tune,
|
||||
child: Column(
|
||||
children: [
|
||||
_ModeCard(
|
||||
icon: Icons.download,
|
||||
title: context.l10n.setupModeDownloaderTitle,
|
||||
features: [
|
||||
context.l10n.setupModeDownloaderFeature1,
|
||||
context.l10n.setupModeDownloaderFeature2,
|
||||
context.l10n.setupModeDownloaderFeature3,
|
||||
],
|
||||
isSelected: _selectedMode == 'downloader',
|
||||
onTap: () => setState(() => _selectedMode = 'downloader'),
|
||||
colorScheme: colorScheme,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.setupModeChangeableLater,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _StepLayout extends StatelessWidget {
|
||||
@@ -888,126 +847,3 @@ class _SuccessCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ModeCard extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final List<String> features;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final ColorScheme colorScheme;
|
||||
|
||||
const _ModeCard({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.features,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
required this.colorScheme,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? colorScheme.primaryContainer
|
||||
: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? colorScheme.primary
|
||||
: colorScheme.outlineVariant,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Icon(
|
||||
isSelected
|
||||
? Icons.radio_button_checked
|
||||
: Icons.radio_button_unchecked,
|
||||
size: 22,
|
||||
color: isSelected
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 22,
|
||||
color: isSelected
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSelected
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...features.map(
|
||||
(feature) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'\u2022 ',
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
feature,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color: isSelected
|
||||
? colorScheme.onPrimaryContainer
|
||||
: colorScheme.onSurfaceVariant,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user