Files
minstrel/flutter_client/lib/api/endpoints/likes.dart
T
bvandeusen 66545bf8c3 feat(flutter): library screen with 5 tabs (Artists/Albums/History/Liked/Hidden)
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.
2026-05-08 14:40:26 -04:00

71 lines
2.2 KiB
Dart

import 'package:dio/dio.dart';
import '../../models/album.dart';
import '../../models/artist.dart';
import '../../models/page.dart';
import '../../models/track.dart';
enum LikeKind { artist, album, track }
extension LikeKindPath on LikeKind {
String get path => switch (this) {
LikeKind.artist => 'artists',
LikeKind.album => 'albums',
LikeKind.track => 'tracks',
};
}
class LikesApi {
LikesApi(this._dio);
final Dio _dio;
Future<void> like(LikeKind kind, String id) async {
await _dio.post<void>('/api/likes/${kind.path}/$id');
}
Future<void> unlike(LikeKind kind, String id) async {
await _dio.delete<void>('/api/likes/${kind.path}/$id');
}
/// GET /api/likes/tracks?limit=N&offset=N. Paged.
Future<Paged<TrackRef>> listTracks({int limit = 50, int offset = 0}) async {
final r = await _dio.get<Map<String, dynamic>>(
'/api/likes/tracks',
queryParameters: {'limit': limit, 'offset': offset},
);
return Paged.fromJson(r.data ?? const {}, TrackRef.fromJson);
}
Future<Paged<AlbumRef>> listAlbums({int limit = 50, int offset = 0}) async {
final r = await _dio.get<Map<String, dynamic>>(
'/api/likes/albums',
queryParameters: {'limit': limit, 'offset': offset},
);
return Paged.fromJson(r.data ?? const {}, AlbumRef.fromJson);
}
Future<Paged<ArtistRef>> listArtists({int limit = 50, int offset = 0}) async {
final r = await _dio.get<Map<String, dynamic>>(
'/api/likes/artists',
queryParameters: {'limit': limit, 'offset': offset},
);
return Paged.fromJson(r.data ?? const {}, ArtistRef.fromJson);
}
/// Returns sets of {artists, albums, tracks} the user has liked.
/// Server response keys (verified in internal/api/likes.go
/// `likedIDsResponse`): `artist_ids`, `album_ids`, `track_ids`.
Future<({Set<String> artists, Set<String> albums, Set<String> tracks})>
ids() async {
final r = await _dio.get<Map<String, dynamic>>('/api/likes/ids');
final body = r.data ?? const <String, dynamic>{};
Set<String> set(String key) =>
((body[key] as List?) ?? const []).map((e) => e.toString()).toSet();
return (
artists: set('artist_ids'),
albums: set('album_ids'),
tracks: set('track_ids'),
);
}
}