mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-21 02:17:13 +02:00
feat: propagate download cancellation through entire pipeline, add MusicBrainz album artist fallback, and allow disabling home feed
- Add reference-counted cancel entries to prevent premature cleanup when multiple operations share the same itemID - Propagate cancellation to DownloadTrack, DownloadWithFallback, DownloadWithExtensionsJSON, extension providers, and ISRC search - Fetch album artist from MusicBrainz when missing during download and re-enrich - Make ALBUMARTIST tag nullable to avoid writing artistName as album artist - Add home feed 'Off' option in extension settings - Skip deezer in download provider priority sanitization
This commit is contained in:
@@ -397,7 +397,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
artistName: (data['artists'] ?? data['artist'] ?? '').toString(),
|
||||
albumName: (data['album_name'] ?? data['album'] ?? album?.name ?? '')
|
||||
.toString(),
|
||||
albumArtist: data['album_artist']?.toString() ?? widget.artistName,
|
||||
albumArtist: normalizeOptionalString(data['album_artist']?.toString()),
|
||||
artistId:
|
||||
(data['artist_id'] ?? data['artistId'])?.toString() ??
|
||||
widget.artistId,
|
||||
@@ -1124,7 +1124,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
|
||||
name: (data['title'] ?? data['name'] ?? '').toString(),
|
||||
artistName: artistName,
|
||||
albumName: album.name,
|
||||
albumArtist: widget.artistName,
|
||||
albumArtist: null,
|
||||
artistId: widget.artistId,
|
||||
albumId: album.id.isNotEmpty ? album.id : null,
|
||||
coverUrl: album.coverUrl,
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:spotiflac_android/services/cover_cache_manager.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/models/track.dart';
|
||||
import 'package:spotiflac_android/providers/track_provider.dart';
|
||||
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
||||
@@ -376,6 +377,12 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
}
|
||||
|
||||
void _fetchExploreIfNeeded() {
|
||||
if (ref.read(settingsProvider).homeFeedProvider ==
|
||||
AppSettings.homeFeedProviderOff) {
|
||||
ref.read(exploreProvider.notifier).clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final extState = ref.read(extensionProvider);
|
||||
final exploreState = ref.read(exploreProvider);
|
||||
final hasHomeFeedExtension = extState.extensions.any(
|
||||
@@ -1259,6 +1266,11 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
(s) => s.extensions.any((e) => e.enabled && e.hasHomeFeed),
|
||||
),
|
||||
);
|
||||
final homeFeedDisabled = ref.watch(
|
||||
settingsProvider.select(
|
||||
(s) => s.homeFeedProvider == AppSettings.homeFeedProviderOff,
|
||||
),
|
||||
);
|
||||
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final searchText = _urlController.text.trim();
|
||||
@@ -1285,6 +1297,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
!hasActualResults &&
|
||||
!isLoading &&
|
||||
!showRecentAccess &&
|
||||
!homeFeedDisabled &&
|
||||
(hasHomeFeedExtension || hasExploreContent) &&
|
||||
hasExploreContent;
|
||||
|
||||
@@ -1536,6 +1549,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
),
|
||||
|
||||
if (hasHomeFeedExtension &&
|
||||
!homeFeedDisabled &&
|
||||
!hasActualResults &&
|
||||
!isLoading &&
|
||||
exploreLoading)
|
||||
@@ -4687,7 +4701,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
|
||||
name: (data['name'] ?? '').toString(),
|
||||
artistName: (data['artists'] ?? data['artist'] ?? '').toString(),
|
||||
albumName: (data['album_name'] ?? widget.albumName).toString(),
|
||||
albumArtist: (data['album_artist'] ?? _artistName)?.toString(),
|
||||
albumArtist: normalizeOptionalString(data['album_artist']?.toString()),
|
||||
artistId:
|
||||
(data['artist_id'] ?? data['artistId'])?.toString() ?? _artistId,
|
||||
albumId: data['album_id']?.toString() ?? widget.albumId,
|
||||
|
||||
@@ -891,7 +891,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist ?? item.artistName,
|
||||
'album_artist': item.albumArtist ?? '',
|
||||
'track_number': item.trackNumber ?? 0,
|
||||
'disc_number': item.discNumber ?? 0,
|
||||
'release_date': item.releaseDate ?? '',
|
||||
|
||||
@@ -5136,7 +5136,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist ?? item.artistName,
|
||||
'album_artist': item.albumArtist ?? '',
|
||||
'track_number': item.trackNumber ?? 0,
|
||||
'disc_number': item.discNumber ?? 0,
|
||||
'release_date': item.releaseDate ?? '',
|
||||
|
||||
@@ -859,9 +859,13 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
.toList();
|
||||
|
||||
final hasAnyProvider = homeFeedProviders.isNotEmpty;
|
||||
final homeFeedDisabled =
|
||||
settings.homeFeedProvider == AppSettings.homeFeedProviderOff;
|
||||
|
||||
String currentProviderName = context.l10n.extensionsHomeFeedAuto;
|
||||
if (settings.homeFeedProvider != null &&
|
||||
if (homeFeedDisabled) {
|
||||
currentProviderName = context.l10n.extensionsHomeFeedOff;
|
||||
} else if (settings.homeFeedProvider != null &&
|
||||
settings.homeFeedProvider!.isNotEmpty) {
|
||||
final ext = homeFeedProviders
|
||||
.where((e) => e.id == settings.homeFeedProvider)
|
||||
@@ -873,23 +877,19 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: !hasAnyProvider
|
||||
? null
|
||||
: () => _showHomeFeedProviderPicker(
|
||||
context,
|
||||
ref,
|
||||
settings,
|
||||
homeFeedProviders,
|
||||
),
|
||||
onTap: () => _showHomeFeedProviderPicker(
|
||||
context,
|
||||
ref,
|
||||
settings,
|
||||
homeFeedProviders,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.explore_outlined,
|
||||
color: !hasAnyProvider
|
||||
? colorScheme.outline
|
||||
: colorScheme.onSurfaceVariant,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
@@ -898,13 +898,11 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.extensionsHomeFeedProvider,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: !hasAnyProvider ? colorScheme.outline : null,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
!hasAnyProvider
|
||||
!hasAnyProvider && !homeFeedDisabled
|
||||
? context.l10n.extensionsNoHomeFeedExtensions
|
||||
: currentProviderName,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
@@ -914,12 +912,7 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: !hasAnyProvider
|
||||
? colorScheme.outline
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
Icon(Icons.chevron_right, color: colorScheme.onSurfaceVariant),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -982,6 +975,31 @@ class _HomeFeedProviderSelector extends ConsumerWidget {
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: Icon(Icons.block, color: colorScheme.error),
|
||||
title: Text(ctx.l10n.extensionsHomeFeedOff),
|
||||
subtitle: Text(ctx.l10n.extensionsHomeFeedOffSubtitle),
|
||||
trailing:
|
||||
settings.homeFeedProvider == AppSettings.homeFeedProviderOff
|
||||
? Icon(Icons.check_circle, color: colorScheme.primary)
|
||||
: Icon(Icons.circle_outlined, color: colorScheme.outline),
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setHomeFeedProvider(AppSettings.homeFeedProviderOff);
|
||||
ref.read(exploreProvider.notifier).clear();
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
if (homeFeedProviders.isEmpty)
|
||||
ListTile(
|
||||
enabled: false,
|
||||
leading: Icon(
|
||||
Icons.extension_off,
|
||||
color: colorScheme.outline,
|
||||
),
|
||||
title: Text(ctx.l10n.extensionsNoHomeFeedExtensions),
|
||||
),
|
||||
...homeFeedProviders.map(
|
||||
(ext) => ListTile(
|
||||
leading: Icon(Icons.extension, color: colorScheme.secondary),
|
||||
|
||||
@@ -2674,7 +2674,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
'track_name': trackName,
|
||||
'artist_name': artistName,
|
||||
'album_name': albumName,
|
||||
'album_artist': albumArtist ?? artistName,
|
||||
'album_artist': albumArtist ?? '',
|
||||
'track_number': trackNumber ?? 0,
|
||||
'total_tracks': totalTracks ?? 0,
|
||||
'disc_number': discNumber ?? 0,
|
||||
|
||||
Reference in New Issue
Block a user