release: v3.1.0 - fix Separate Singles, extension metadata, YTMusic parsing

Fixes:
- Fix Separate Singles not working (#54) - albumType not extracted from Deezer API
- Fix extension artist/album metadata missing provider IDs and cover URLs
- Fix YTMusic extension not extracting album name and duration from search
- Fix extension collection screens setState after dispose
- Fix search source chips referencing removed badge props

Changes:
- Deezer convertTrack now includes album_type from record_type
- Track creation preserves albumType and source throughout download flow
- Go exports include provider_id in album/artist responses
- Version bump to 3.1.0+59
This commit is contained in:
zarzet
2026-01-15 18:54:01 +07:00
parent 82440affac
commit 1a90887465
11 changed files with 103 additions and 20 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
/// App version and info constants
/// Update version here only - all other files will reference this
class AppInfo {
static const String version = '3.0.1';
static const String buildNumber = '58';
static const String version = '3.1.0';
static const String buildNumber = '59';
static const String fullVersion = '$version+$buildNumber';
+7 -1
View File
@@ -1461,6 +1461,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final data = trackData;
_log.d('Track data keys: ${data.keys.toList()}');
_log.d('ISRC from API: ${data['isrc']}');
_log.d('album_type from API: ${data['album_type']}');
trackToDownload = Track(
id: (data['spotify_id'] as String?) ?? trackToDownload.id,
name: (data['name'] as String?) ?? trackToDownload.name,
@@ -1482,9 +1483,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
releaseDate: data['release_date'] as String?,
deezerId: rawId,
availability: trackToDownload.availability,
// Preserve albumType from API response or original track
albumType: (data['album_type'] as String?) ?? trackToDownload.albumType,
source: trackToDownload.source,
);
_log.d(
'Metadata enriched: Track ${trackToDownload.trackNumber}, Disc ${trackToDownload.discNumber}, ISRC ${trackToDownload.isrc}',
'Metadata enriched: Track ${trackToDownload.trackNumber}, Disc ${trackToDownload.discNumber}, ISRC ${trackToDownload.isrc}, AlbumType ${trackToDownload.albumType}',
);
} else {
_log.w('Unexpected track data type: ${trackData.runtimeType}');
@@ -1720,6 +1724,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
releaseDate: backendYear ?? trackToDownload.releaseDate,
deezerId: trackToDownload.deezerId,
availability: trackToDownload.availability,
albumType: trackToDownload.albumType,
source: trackToDownload.source,
);
}
+4 -1
View File
@@ -455,6 +455,8 @@ class TrackNotifier extends Notifier<TrackState> {
trackNumber: track.trackNumber,
discNumber: track.discNumber,
releaseDate: track.releaseDate,
albumType: track.albumType,
source: track.source,
availability: ServiceAvailability(
tidal: availability['tidal'] as bool? ?? false,
qobuz: availability['qobuz'] as bool? ?? false,
@@ -552,9 +554,10 @@ class TrackNotifier extends Notifier<TrackState> {
name: data['name'] as String? ?? '',
releaseDate: data['release_date'] as String? ?? '',
totalTracks: data['total_tracks'] as int? ?? 0,
coverUrl: data['images'] as String?,
coverUrl: (data['cover_url'] ?? data['images'])?.toString(),
albumType: data['album_type'] as String? ?? 'album',
artists: data['artists'] as String? ?? '',
providerId: data['provider_id']?.toString(),
);
}
+6
View File
@@ -1431,6 +1431,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
widget.extensionId,
widget.albumId,
);
if (!mounted) return;
if (result == null) {
setState(() {
@@ -1457,6 +1458,7 @@ class _ExtensionAlbumScreenState extends ConsumerState<ExtensionAlbumScreen> {
_isLoading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = 'Error: $e';
_isLoading = false;
@@ -1567,6 +1569,7 @@ class _ExtensionPlaylistScreenState extends ConsumerState<ExtensionPlaylistScree
widget.extensionId,
widget.playlistId,
);
if (!mounted) return;
if (result == null) {
setState(() {
@@ -1593,6 +1596,7 @@ class _ExtensionPlaylistScreenState extends ConsumerState<ExtensionPlaylistScree
_isLoading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = 'Error: $e';
_isLoading = false;
@@ -1702,6 +1706,7 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
widget.extensionId,
widget.artistId,
);
if (!mounted) return;
if (result == null) {
setState(() {
@@ -1728,6 +1733,7 @@ class _ExtensionArtistScreenState extends ConsumerState<ExtensionArtistScreen> {
_isLoading = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = 'Error: $e';
_isLoading = false;
@@ -845,8 +845,6 @@ class _MetadataSourceSelector extends ConsumerWidget {
_SourceChip(
icon: Icons.graphic_eq,
label: 'Deezer',
badge: 'Free',
badgeColor: colorScheme.tertiary,
// Not selected if extension is active
isSelected: currentSource == 'deezer' && !hasExtensionSearch,
onTap: () {
@@ -861,8 +859,6 @@ class _MetadataSourceSelector extends ConsumerWidget {
_SourceChip(
icon: Icons.music_note,
label: 'Spotify',
badge: 'API Key',
badgeColor: colorScheme.secondary,
// Not selected if extension is active
isSelected: currentSource == 'spotify' && !hasExtensionSearch,
onTap: () {