Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/models/song.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Song {
final double? replayGainTrackPeak;
final double? replayGainAlbumPeak;
final List<ArtistRef>? artistParticipants;
final DateTime? created;

Song({
required this.id,
Expand All @@ -51,6 +52,7 @@ class Song {
this.replayGainTrackPeak,
this.replayGainAlbumPeak,
this.artistParticipants,
this.created,
});

factory Song.fromJson(Map<String, dynamic> json) {
Expand Down Expand Up @@ -81,6 +83,9 @@ class Song {
replayGainTrackPeak: (replayGain?['trackPeak'] as num?)?.toDouble(),
replayGainAlbumPeak: (replayGain?['albumPeak'] as num?)?.toDouble(),
artistParticipants: ArtistRef.parseList(json['artists']),
created: json['created'] != null
? DateTime.tryParse(json['created'].toString())
: null,
);
}

Expand Down Expand Up @@ -111,6 +116,7 @@ class Song {
},
if (artistParticipants != null)
'artists': artistParticipants!.map((a) => a.toJson()).toList(),
'created': created?.toIso8601String(),
};
}

Expand Down Expand Up @@ -146,6 +152,7 @@ class Song {
double? replayGainTrackPeak,
double? replayGainAlbumPeak,
List<ArtistRef>? artistParticipants,
DateTime? created,
}) {
return Song(
id: id ?? this.id,
Expand All @@ -172,6 +179,7 @@ class Song {
replayGainTrackPeak: replayGainTrackPeak ?? this.replayGainTrackPeak,
replayGainAlbumPeak: replayGainAlbumPeak ?? this.replayGainAlbumPeak,
artistParticipants: artistParticipants ?? this.artistParticipants,
created: created ?? this.created,
);
}
}
28 changes: 27 additions & 1 deletion lib/providers/library_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,34 @@ class LibraryProvider extends ChangeNotifier {
offset += pageSize;
}

final previousAlbums = _cachedAllAlbums;
final previousSongs = _cachedAllSongs;
final Map<String, Song> songById = {};
var failedAlbumLoads = 0;
for (final album in allAlbums) {
try {
final albumSongs = await _subsonicService.getAlbumSongs(album.id);
for (final song in albumSongs) {
songById[song.id] = song;
}
} catch (e) {
failedAlbumLoads++;
debugPrint('Error loading album ${album.id}: $e');
}
}

if (failedAlbumLoads > 0) {
_cachedAllAlbums = previousAlbums;
_cachedAllSongs = previousSongs;
debugPrint(
'Background refresh incomplete: $failedAlbumLoads album(s) failed; keeping previous cache.',
);
notifyListeners();
return;
}

_cachedAllAlbums = allAlbums;
_cachedAllSongs = [];
_cachedAllSongs = songById.values.toList();
_lastCacheUpdate = DateTime.now();

await _saveCachedData();
Expand Down
10 changes: 8 additions & 2 deletions lib/screens/all_songs_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,14 @@ class _AllSongsScreenState extends State<AllSongsScreen> {
);
break;
case SongSortOption.recentlyAdded:

_sortedSongs = List.from(_songs.reversed);
_sortedSongs.sort((a, b) {
final aCreated = a.created;
final bCreated = b.created;
if (aCreated == null && bCreated == null) return 0;
if (aCreated == null) return 1;
if (bCreated == null) return -1;
return bCreated.compareTo(aCreated);
});
break;
}
}
Expand Down
16 changes: 16 additions & 0 deletions test/models/song_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ void main() {
expect(song.album, isNull);
expect(song.artist, isNull);
expect(song.duration, isNull);
expect(song.created, isNull);
});

test('should parse created field from JSON', () {
final json = {
'id': '123',
'title': 'Test Song',
'created': '2026-03-18T13:37:27.257653751Z',
};

final song = Song.fromJson(json);

expect(song.created, isNotNull);
expect(song.created!.year, 2026);
expect(song.created!.month, 3);
expect(song.created!.day, 18);
});

test('should convert Song to JSON', () {
Expand Down