feat(flutter/likes): LikeButton with optimistic toggle + rollback

LikedIdsController loads /api/likes/ids once and mutates the local set
on toggle. Failed mutations roll the set back so the icon never lies.
Wired into ArtistDetail header, AlbumDetail header, and per-track in
the album track list.
This commit is contained in:
2026-05-02 17:34:59 -04:00
parent 6ec7cea362
commit 44e2623b3c
6 changed files with 238 additions and 3 deletions
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../api/endpoints/likes.dart';
import '../likes/like_button.dart';
import '../theme/theme_extension.dart';
import 'library_providers.dart';
import 'widgets/track_row.dart';
@@ -43,11 +45,15 @@ class AlbumDetailScreen extends ConsumerWidget {
onPressed: () { /* play wired in Task 18 */ },
),
),
// LikeButton goes here in Task 16.
LikeButton(kind: LikeKind.album, id: r.album.id, size: 28),
]),
),
for (final t in r.tracks)
TrackRow(track: t, onTap: () { /* play wired in Task 18 */ }),
TrackRow(
track: t,
onTap: () { /* play wired in Task 18 */ },
trailing: LikeButton(kind: LikeKind.track, id: t.id),
),
]),
),
);
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../api/endpoints/likes.dart';
import '../likes/like_button.dart';
import '../models/album.dart';
import '../theme/theme_extension.dart';
import 'library_providers.dart';
@@ -50,7 +52,7 @@ class ArtistDetailScreen extends ConsumerWidget {
onPressed: () { /* play wired in Task 18 */ },
),
),
// LikeButton goes here in Task 16.
LikeButton(kind: LikeKind.artist, id: a.id, size: 28),
]),
),
const Padding(