66545bf8c3
Models: - history_event.dart (HistoryEvent + HistoryPage envelope) - quarantine_mine.dart (QuarantineMineRow for /api/quarantine/mine) - Renamed Page<T> -> Paged<T> to avoid collision with Material's Page Endpoints: - library_lists.dart: listArtists / listAlbums (paged) - me.dart: history + quarantineMine - likes.dart: extended with listTracks/Albums/Artists (paged) UI: - library_screen.dart: TabBar over 5 tabs - Artists tab: 3-col grid - Albums tab: 2-col grid - History tab: list with relative-time formatting (matches HistoryRow.svelte) - Liked tab: 3 stacked sections (artists scroll-row, albums scroll-row, tracks list) - Hidden tab: list of quarantined tracks with reason pill - /library route + library_music icon on home AppBar First page only for v1 (limit 50). Infinite scroll deferred.
45 lines
1.4 KiB
Dart
45 lines
1.4 KiB
Dart
// Mirrors the server's Page[T] envelope used by paged endpoints
|
|
// (/api/search, /api/library/artists, /api/library/albums,
|
|
// /api/likes/*). Class is named `Paged<T>` rather than `Page<T>`
|
|
// to avoid ambiguous imports with Flutter Material's Navigator-2.0
|
|
// `Page` class.
|
|
//
|
|
// Wire shape from internal/api/types.go Page[T]:
|
|
// { items: T[], total: int, limit: int, offset: int }
|
|
//
|
|
// Dart generics can't auto-infer T from JSON, so callers pass an
|
|
// itemFromJson function alongside the raw map. Parse logic stays in
|
|
// each entity's own fromJson; this class only handles the envelope.
|
|
class Paged<T> {
|
|
const Paged({
|
|
required this.items,
|
|
required this.total,
|
|
required this.limit,
|
|
required this.offset,
|
|
});
|
|
|
|
final List<T> items;
|
|
final int total;
|
|
final int limit;
|
|
final int offset;
|
|
|
|
factory Paged.fromJson(
|
|
Map<String, dynamic> j,
|
|
T Function(Map<String, dynamic>) itemFromJson,
|
|
) {
|
|
final raw = (j['items'] as List?) ?? const [];
|
|
return Paged<T>(
|
|
items: raw
|
|
.map((e) => itemFromJson((e as Map).cast<String, dynamic>()))
|
|
.toList(growable: false),
|
|
total: (j['total'] as num?)?.toInt() ?? 0,
|
|
limit: (j['limit'] as num?)?.toInt() ?? 0,
|
|
offset: (j['offset'] as num?)?.toInt() ?? 0,
|
|
);
|
|
}
|
|
|
|
/// Convenience for endpoints that always return the first page or
|
|
/// when the caller just wants the items.
|
|
bool get hasMore => offset + items.length < total;
|
|
}
|