import 'dart:async'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/vars.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/services/logger/logger.dart'; class ScrobblerNotifier extends AsyncNotifier { final StreamController _scrobbleController = StreamController.broadcast(); AppDatabase get db => getIt.get(); @override build() async { final loginInfo = await (db.select(db.scrobblerTable) ..where((t) => t.id.equals(0))) .getSingleOrNull(); final subscription = db.select(db.scrobblerTable).watch().listen((event) async { try { if (event.isNotEmpty) { state = await AsyncValue.guard( () async => Scrobblenaut( lastFM: await LastFM.authenticateWithPasswordHash( apiKey: Env.lastFmApiKey, apiSecret: Env.lastFmApiSecret, username: event.first.username, passwordHash: event.first.passwordHash.value, ), ), ); } else { state = const AsyncValue.data(null); } } catch (e, stack) { AppLogger.reportError(e, stack); } }); final scrobblerSubscription = _scrobbleController.stream.listen((track) async { try { await state.asData?.value?.track.scrobble( artist: track.artists!.first.name!, track: track.name!, album: track.album!.name!, chosenByUser: true, duration: track.duration, timestamp: DateTime.now().toUtc(), trackNumber: track.trackNumber, ); } catch (e, stackTrace) { AppLogger.reportError(e, stackTrace); } }); ref.onDispose(() { subscription.cancel(); scrobblerSubscription.cancel(); }); if (loginInfo == null) { return null; } return Scrobblenaut( lastFM: await LastFM.authenticateWithPasswordHash( apiKey: Env.lastFmApiKey, apiSecret: Env.lastFmApiSecret, username: loginInfo.username, passwordHash: loginInfo.passwordHash.value, ), ); } Future login( String username, String password, ) async { final lastFm = await LastFM.authenticate( apiKey: Env.lastFmApiKey, apiSecret: Env.lastFmApiSecret, username: username, password: password, ); if (!lastFm.isAuth) throw Exception("Invalid credentials"); await db.into(db.scrobblerTable).insert( ScrobblerTableCompanion.insert( id: const Value(0), username: username, passwordHash: DecryptedText(lastFm.passwordHash!), ), ); } Future logout() async { state = const AsyncValue.data(null); await db.delete(db.scrobblerTable).go(); } void scrobble(Track track) { _scrobbleController.add(track); } Future love(Track track) async { await state.asData?.value?.track.love( artist: track.artists!.asString(), track: track.name!, ); } Future unlove(Track track) async { await state.asData?.value?.track.unLove( artist: track.artists!.asString(), track: track.name!, ); } } final scrobblerProvider = AsyncNotifierProvider( () => ScrobblerNotifier(), );