diff --git a/lib/components/Home/Home.dart b/lib/components/Home/Home.dart index b2052f1c..b7527b89 100644 --- a/lib/components/Home/Home.dart +++ b/lib/components/Home/Home.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:oauth2/oauth2.dart' show AuthorizationException; import 'package:spotify/spotify.dart' hide Image, Player, Search; import 'package:spotube/components/Category/CategoryCard.dart'; @@ -17,16 +16,11 @@ import 'package:spotube/components/Search/Search.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Library/UserLibrary.dart'; -import 'package:spotube/helpers/get-random-element.dart'; -import 'package:spotube/helpers/oauth-login.dart'; import 'package:spotube/hooks/useBreakpointValue.dart'; import 'package:spotube/hooks/useHotKeys.dart'; import 'package:spotube/hooks/usePagingController.dart'; import 'package:spotube/hooks/useSharedPreferences.dart'; -import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/Logger.dart'; -import 'package:spotube/models/generated_secrets.dart'; -import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/UserPreferences.dart'; @@ -48,7 +42,6 @@ class Home extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - Auth auth = ref.watch(authProvider); String recommendationMarket = ref.watch(userPreferencesProvider.select( (value) => (value.recommendationMarket), )); @@ -98,70 +91,12 @@ class Home extends HookConsumerWidget { }, [recommendationMarket]); useEffect(() { - if (localStorage == null) return null; - final String? clientId = - localStorage.getString(LocalStorageKeys.clientId); - final String? clientSecret = - localStorage.getString(LocalStorageKeys.clientSecret); - final String? accessToken = - localStorage.getString(LocalStorageKeys.accessToken); - final String? refreshToken = - localStorage.getString(LocalStorageKeys.refreshToken); - final String? expirationStr = - localStorage.getString(LocalStorageKeys.expiration); - try { - final DateTime? expiration = - expirationStr != null ? DateTime.parse(expirationStr) : null; - final anonCred = getRandomElement(spotifySecrets); - SpotifyApiCredentials apiCredentials = - clientId != null && clientSecret != null - ? SpotifyApiCredentials( - clientId, - clientSecret, - accessToken: accessToken, - refreshToken: refreshToken, - expiration: expiration, - scopes: spotifyScopes, - ) - : SpotifyApiCredentials( - anonCred["clientId"], - anonCred["clientSecret"], - ); - - SpotifyApi spotify = SpotifyApi(apiCredentials); - if (clientId != null && clientSecret != null) { - spotify.getCredentials().then((credentials) { - if (credentials.accessToken?.isNotEmpty == true) { - auth.setAuthState( - clientId: clientId, - clientSecret: clientSecret, - accessToken: - credentials.accessToken, // accessToken can be new/refreshed - refreshToken: refreshToken, - expiration: credentials.expiration, - isLoggedIn: true, - ); - } - pagingController.addPageRequestListener(listener); - // the world is full of surprises and the previously working - // fine pageRequestListener now doesn't notify the listeners - // automatically after assigning a listener. So doing it manually - pagingController.notifyPageRequestListeners(0); - }).catchError((e, stack) { - if (e is AuthorizationException) { - oauthLogin( - auth, - clientId: clientId, - clientSecret: clientSecret, - ); - } - logger.e("useEffect.spotify.getCredentials", e, stack); - }); - } else { - pagingController.addPageRequestListener(listener); - pagingController.notifyPageRequestListeners(0); - } + pagingController.addPageRequestListener(listener); + // the world is full of surprises and the previously working + // fine pageRequestListener now doesn't notify the listeners + // automatically after assigning a listener. So doing it manually + pagingController.notifyPageRequestListeners(0); } catch (e, stack) { logger.e("initState", e, stack); } diff --git a/lib/helpers/oauth-login.dart b/lib/helpers/oauth-login.dart index c89197d8..12cd5499 100644 --- a/lib/helpers/oauth-login.dart +++ b/lib/helpers/oauth-login.dart @@ -26,7 +26,7 @@ Future oauthLogin(Auth auth, if (responseUri != null) { final SpotifyApi spotify = SpotifyApi.fromAuthCodeGrant(grant, responseUri); - var credentials = await spotify.getCredentials(); + final credentials = await spotify.getCredentials(); if (credentials.accessToken != null) { accessToken = credentials.accessToken; await localStorage.setString( @@ -56,7 +56,6 @@ Future oauthLogin(Auth auth, accessToken: accessToken, refreshToken: refreshToken, expiration: expiration, - isLoggedIn: true, ); } catch (e, stack) { logger.e("oauthLogin", e, stack); diff --git a/lib/helpers/server_ipc.dart b/lib/helpers/server_ipc.dart index 2647c6a0..c8f0de2e 100644 --- a/lib/helpers/server_ipc.dart +++ b/lib/helpers/server_ipc.dart @@ -8,7 +8,7 @@ final logger = getLogger("ServerIPC"); Future connectIpc(String authUri, String redirectUri) async { try { logger.i("[Launching]: $authUri"); - await launch(authUri); + await launchUrl(Uri.parse(authUri)); HttpServer server = await HttpServer.bind(InternetAddress.loopbackIPv4, 4304); diff --git a/lib/models/LocalStorageKeys.dart b/lib/models/LocalStorageKeys.dart index 0fedb95a..43e3b012 100644 --- a/lib/models/LocalStorageKeys.dart +++ b/lib/models/LocalStorageKeys.dart @@ -3,10 +3,10 @@ abstract class LocalStorageKeys { static String recommendationMarket = 'recommendation_market'; static String ytSearchFormate = 'youtube_search_format'; - static String clientId = 'client_id'; - static String clientSecret = 'client_secret'; - static String accessToken = 'access_token'; - static String refreshToken = 'refresh_token'; + static String clientId = 'clientId'; + static String clientSecret = 'clientSecret'; + static String accessToken = 'accessToken'; + static String refreshToken = 'refreshToken'; static String expiration = "expiration"; static String geniusAccessToken = "genius_access_token"; diff --git a/lib/provider/Auth.dart b/lib/provider/Auth.dart index 48e24567..cceee530 100644 --- a/lib/provider/Auth.dart +++ b/lib/provider/Auth.dart @@ -1,26 +1,32 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:async'; -class Auth with ChangeNotifier { +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotube/utils/PersistedChangeNotifier.dart'; + +class Auth extends PersistedChangeNotifier { String? _clientId; String? _clientSecret; String? _accessToken; String? _refreshToken; DateTime? _expiration; - bool _isLoggedIn = false; + Auth() : super(); String? get clientId => _clientId; String? get clientSecret => _clientSecret; String? get accessToken => _accessToken; String? get refreshToken => _refreshToken; DateTime? get expiration => _expiration; - bool get isLoggedIn => _isLoggedIn; + bool get isAnonymous => - !_isLoggedIn && _clientId == null && _clientSecret == null; + _clientId == null && + _clientSecret == null && + accessToken == null && + refreshToken == null; + + bool get isLoggedIn => !isAnonymous && _expiration != null; void setAuthState({ - bool? isLoggedIn, bool safe = true, String? clientId, String? clientSecret, @@ -31,7 +37,6 @@ class Auth with ChangeNotifier { if (safe) { if (clientId != null) _clientId = clientId; if (clientSecret != null) _clientSecret = clientSecret; - if (isLoggedIn != null) _isLoggedIn = isLoggedIn; if (refreshToken != null) _refreshToken = refreshToken; if (accessToken != null) _accessToken = accessToken; if (expiration != null) _expiration = expiration; @@ -43,6 +48,7 @@ class Auth with ChangeNotifier { _expiration = expiration; } notifyListeners(); + updatePersistence(); } logout() { @@ -51,14 +57,34 @@ class Auth with ChangeNotifier { _accessToken = null; _refreshToken = null; _expiration = null; - _isLoggedIn = false; notifyListeners(); + updatePersistence(); } @override String toString() { return "Auth(clientId: $clientId, clientSecret: $clientSecret, accessToken: $accessToken, refreshToken: $refreshToken, expiration: $expiration, isLoggedIn: $isLoggedIn)"; } + + @override + FutureOr loadFromLocal(Map map) { + _clientId = map["clientId"].isNotEmpty ? map["clientId"] : null; + _clientSecret = map["clientSecret"].isNotEmpty ? map["clientSecret"] : null; + _accessToken = map["accessToken"]; + _refreshToken = map["refreshToken"]; + _expiration = DateTime.tryParse(map["expiration"]); + } + + @override + FutureOr> toMap() { + return { + "clientId": _clientId, + "clientSecret": _clientSecret, + "accessToken": _accessToken, + "refreshToken": _refreshToken, + "expiration": _expiration.toString(), + }; + } } -var authProvider = ChangeNotifierProvider((ref) => Auth()); +final authProvider = ChangeNotifierProvider((ref) => Auth()); diff --git a/lib/provider/SpotifyDI.dart b/lib/provider/SpotifyDI.dart index 4383d3a2..db07a9fa 100644 --- a/lib/provider/SpotifyDI.dart +++ b/lib/provider/SpotifyDI.dart @@ -1,13 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Home/Home.dart'; import 'package:spotube/helpers/get-random-element.dart'; -import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/generated_secrets.dart'; import 'package:spotube/provider/Auth.dart'; -var spotifyProvider = Provider((ref) { +final spotifyProvider = Provider((ref) { Auth authState = ref.watch(authProvider); final anonCred = getRandomElement(spotifySecrets); SpotifyApiCredentials apiCredentials = authState.isAnonymous @@ -26,20 +24,13 @@ var spotifyProvider = Provider((ref) { return SpotifyApi( apiCredentials, - onCredentialsRefreshed: (credentials) async { - SharedPreferences localStorage = await SharedPreferences.getInstance(); - localStorage.setString( - LocalStorageKeys.refreshToken, - credentials.refreshToken!, - ); - localStorage.setString( - LocalStorageKeys.accessToken, - credentials.accessToken!, - ); - localStorage.setString(LocalStorageKeys.clientId, credentials.clientId!); - localStorage.setString( - LocalStorageKeys.clientSecret, - credentials.clientSecret!, + onCredentialsRefreshed: (credentials) { + authState.setAuthState( + clientId: credentials.clientId, + clientSecret: credentials.clientSecret, + accessToken: credentials.accessToken, + refreshToken: credentials.refreshToken, + expiration: credentials.expiration, ); }, ); diff --git a/lib/utils/PersistedChangeNotifier.dart b/lib/utils/PersistedChangeNotifier.dart new file mode 100644 index 00000000..e888bbdd --- /dev/null +++ b/lib/utils/PersistedChangeNotifier.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +abstract class PersistedChangeNotifier extends ChangeNotifier { + late SharedPreferences _localStorage; + PersistedChangeNotifier() { + SharedPreferences.getInstance().then((value) => _localStorage = value).then( + (_) async { + final persistedMap = (await toMap()) + .entries + .toList() + .fold>({}, (acc, entry) { + if (entry.value != null) { + if (entry.value is bool) { + acc[entry.key] = _localStorage.getBool(entry.key) ?? false; + } else if (entry.value is int) { + acc[entry.key] = _localStorage.getInt(entry.key) ?? -1; + } else if (entry.value is double) { + acc[entry.key] = _localStorage.getDouble(entry.key) ?? -1.0; + } else if (entry.value is String) { + acc[entry.key] = _localStorage.getString(entry.key) ?? ""; + } + } else { + acc[entry.key] = _localStorage.get(entry.key); + } + return acc; + }); + await loadFromLocal(persistedMap); + notifyListeners(); + }, + ); + } + + FutureOr loadFromLocal(Map map); + + FutureOr> toMap(); + + Future updatePersistence() async { + for (final entry in (await toMap()).entries) { + if (entry.value is bool) { + await _localStorage.setBool(entry.key, entry.value); + } else if (entry.value is int) { + await _localStorage.setInt(entry.key, entry.value); + } else if (entry.value is double) { + await _localStorage.setDouble(entry.key, entry.value); + } else if (entry.value is String) { + await _localStorage.setString(entry.key, entry.value); + } + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 1125967c..a3d5cbf9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -724,11 +724,9 @@ packages: spotify: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: e36eb5884de44d39b310d0878779a697048061bd - url: "https://github.com/KRTirtho/spotify-dart.git" - source: git + path: "../spotify-dart" + relative: true + source: path version: "0.7.0" sqflite: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index c7e48629..d59d3fb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,8 @@ dependencies: http: ^0.13.4 shared_preferences: ^2.0.11 spotify: - git: https://github.com/KRTirtho/spotify-dart.git + # git: https://github.com/KRTirtho/spotify-dart.git + path: ../spotify-dart url_launcher: ^6.0.17 youtube_explode_dart: ^1.10.8 infinite_scroll_pagination: ^3.1.0