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:
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../api/endpoints/likes.dart';
|
||||
import '../theme/theme_extension.dart';
|
||||
import 'likes_provider.dart';
|
||||
|
||||
class LikeButton extends ConsumerWidget {
|
||||
const LikeButton({
|
||||
required this.kind,
|
||||
required this.id,
|
||||
this.size = 22,
|
||||
super.key,
|
||||
});
|
||||
final LikeKind kind;
|
||||
final String id;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
final state = ref.watch(likedIdsProvider);
|
||||
final liked = state.maybeWhen(
|
||||
data: (s) => s.has(kind, id),
|
||||
orElse: () => false,
|
||||
);
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
liked ? Icons.favorite : Icons.favorite_border,
|
||||
color: liked ? fs.accent : fs.ash,
|
||||
size: size,
|
||||
),
|
||||
onPressed: () => ref.read(likedIdsProvider.notifier).toggle(kind, id),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user