init
This commit is contained in:
commit
d205277cdd
182 changed files with 22978 additions and 0 deletions
98
lib/state/favorites.dart
Normal file
98
lib/state/favorites.dart
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../subsonic/models.dart';
|
||||
import '../subsonic/subsonic_client.dart';
|
||||
|
||||
/// Client-side mirror of the server's stars + ratings, so toggles feel instant
|
||||
/// (optimistic update, reverted on failure). Hydrated from `getStarred2`.
|
||||
class FavoritesState {
|
||||
const FavoritesState({
|
||||
this.songIds = const {},
|
||||
this.albumIds = const {},
|
||||
this.artistIds = const {},
|
||||
this.ratings = const {},
|
||||
});
|
||||
|
||||
final Set<String> songIds;
|
||||
final Set<String> albumIds;
|
||||
final Set<String> artistIds;
|
||||
final Map<String, int> ratings;
|
||||
|
||||
bool isSongStarred(String id) => songIds.contains(id);
|
||||
int ratingFor(String id) => ratings[id] ?? 0;
|
||||
|
||||
FavoritesState copyWith({
|
||||
Set<String>? songIds,
|
||||
Set<String>? albumIds,
|
||||
Set<String>? artistIds,
|
||||
Map<String, int>? ratings,
|
||||
}) =>
|
||||
FavoritesState(
|
||||
songIds: songIds ?? this.songIds,
|
||||
albumIds: albumIds ?? this.albumIds,
|
||||
artistIds: artistIds ?? this.artistIds,
|
||||
ratings: ratings ?? this.ratings,
|
||||
);
|
||||
}
|
||||
|
||||
class FavoritesController extends StateNotifier<FavoritesState> {
|
||||
FavoritesController(this._clientGetter) : super(const FavoritesState()) {
|
||||
if (_clientGetter() != null) hydrate();
|
||||
}
|
||||
|
||||
final SubsonicClient? Function() _clientGetter;
|
||||
|
||||
Future<void> hydrate() async {
|
||||
final client = _clientGetter();
|
||||
if (client == null) return;
|
||||
try {
|
||||
final starred = await client.getStarred2();
|
||||
final ratings = <String, int>{};
|
||||
for (final s in starred.songs) {
|
||||
if (s.userRating != null) ratings[s.id] = s.userRating!;
|
||||
}
|
||||
state = FavoritesState(
|
||||
songIds: starred.songs.map((s) => s.id).toSet(),
|
||||
albumIds: starred.albums.map((a) => a.id).toSet(),
|
||||
artistIds: starred.artists.map((a) => a.id).toSet(),
|
||||
ratings: ratings,
|
||||
);
|
||||
} catch (_) {
|
||||
// Leave current state on failure.
|
||||
}
|
||||
}
|
||||
|
||||
void clear() => state = const FavoritesState();
|
||||
|
||||
Future<void> toggleSong(Song song) async {
|
||||
final client = _clientGetter();
|
||||
if (client == null) return;
|
||||
final wasStarred = state.isSongStarred(song.id);
|
||||
final next = Set<String>.from(state.songIds);
|
||||
wasStarred ? next.remove(song.id) : next.add(song.id);
|
||||
state = state.copyWith(songIds: next); // optimistic
|
||||
try {
|
||||
await client.setStarred(starred: !wasStarred, songId: song.id);
|
||||
} catch (_) {
|
||||
// Revert on failure.
|
||||
final reverted = Set<String>.from(state.songIds);
|
||||
wasStarred ? reverted.add(song.id) : reverted.remove(song.id);
|
||||
state = state.copyWith(songIds: reverted);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> rateSong(String songId, int rating) async {
|
||||
final client = _clientGetter();
|
||||
if (client == null) return;
|
||||
final previous = state.ratings[songId] ?? 0;
|
||||
final next = Map<String, int>.from(state.ratings)..[songId] = rating;
|
||||
state = state.copyWith(ratings: next); // optimistic
|
||||
try {
|
||||
await client.setRating(songId, rating);
|
||||
} catch (_) {
|
||||
final reverted = Map<String, int>.from(state.ratings)
|
||||
..[songId] = previous;
|
||||
state = state.copyWith(ratings: reverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue