147d6e280e
Major version bumps (riverpod 2->3, go_router 14->17, just_audio 0.9->0.10, flutter_secure_storage 9->10, google_fonts 6->8, flutter_lints 4->6). package_info_plus held at 8.x due to win32 conflict with secure_storage 10.x. Riverpod 3 breaks: AsyncValue.valueOrNull -> .value, StateProvider replaced with Notifier subclass. just_audio 0.10: ConcatenatingAudioSource -> setAudioSources. Search slice: - models/page.dart (generic Page<T> envelope) - models/search_response.dart (3-facet wrapper) - api/endpoints/search.dart - search/search_provider.dart (debounced 250ms) - search/search_screen.dart (TextField + 3 horizontal/vertical sections) - Wired /search route + search button on home AppBar
43 lines
1.3 KiB
Dart
43 lines
1.3 KiB
Dart
// Mirrors the server's Page<T> envelope used by paged endpoints
|
|
// (/api/search, /api/library/artists, /api/library/albums,
|
|
// /api/library/history, /api/me/likes/*).
|
|
//
|
|
// 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 Page<T> {
|
|
const Page({
|
|
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 Page.fromJson(
|
|
Map<String, dynamic> j,
|
|
T Function(Map<String, dynamic>) itemFromJson,
|
|
) {
|
|
final raw = (j['items'] as List?) ?? const [];
|
|
return Page<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;
|
|
}
|