import 'dart:async'; import 'dart:developer'; import 'package:drift/drift.dart'; import 'package:get/get.dart' hide Value; import 'package:rhythm_box/providers/database.dart'; import 'package:rhythm_box/services/artist.dart'; import 'package:rhythm_box/services/database/database.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; import 'package:spotify/spotify.dart'; class ScrobblerProvider extends GetxController { final StreamController _scrobbleController = StreamController.broadcast(); final Rxn scrobbler = Rxn(null); late StreamSubscription _databaseSubscription; late StreamSubscription _scrobbleSubscription; static String apiKey = 'd2a75393e1141d0c9486eb77cc7b8892'; static String apiSecret = '3ac3a5231a2e8a0dc98577c246101b78'; @override void onInit() { super.onInit(); _initialize(); } Future _initialize() async { final database = Get.find().database; final loginInfo = await (database.select(database.scrobblerTable) ..where((t) => t.id.equals(0))) .getSingleOrNull(); _databaseSubscription = database.select(database.scrobblerTable).watch().listen((event) async { if (event.isNotEmpty) { try { scrobbler.value = Scrobblenaut( lastFM: await LastFM.authenticateWithPasswordHash( apiKey: apiKey, apiSecret: apiSecret, username: event.first.username, passwordHash: event.first.passwordHash.value, ), ); } catch (e, stack) { log('[Scrobble] Error: $e; Trace:\n$stack'); scrobbler.value = null; } } else { scrobbler.value = null; } }); _scrobbleSubscription = _scrobbleController.stream.listen((track) async { try { await scrobbler.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) { log('[Scrobble] Error: $e; Trace:\n$stackTrace'); } }); if (loginInfo == null) { scrobbler.value = null; return; } scrobbler.value = Scrobblenaut( lastFM: await LastFM.authenticateWithPasswordHash( apiKey: apiKey, apiSecret: apiSecret, username: loginInfo.username, passwordHash: loginInfo.passwordHash.value, ), ); } Future login(String username, String password) async { final database = Get.find().database; final lastFm = await LastFM.authenticate( apiKey: apiKey, apiSecret: apiSecret, username: username, password: password, ); if (!lastFm.isAuth) throw Exception('Invalid credentials'); await database.into(database.scrobblerTable).insert( ScrobblerTableCompanion.insert( id: const Value(0), username: username, passwordHash: DecryptedText(lastFm.passwordHash!), ), ); scrobbler.value = Scrobblenaut(lastFM: lastFm); } Future logout() async { scrobbler.value = null; final database = Get.find().database; await database.delete(database.scrobblerTable).go(); } void scrobble(Track track) { _scrobbleController.add(track); } Future love(Track track) async { await scrobbler.value?.track.love( artist: track.artists!.asString(), track: track.name!, ); } Future unlove(Track track) async { await scrobbler.value?.track.unLove( artist: track.artists!.asString(), track: track.name!, ); } @override void onClose() { _databaseSubscription.cancel(); _scrobbleSubscription.cancel(); _scrobbleController.close(); super.onClose(); } }