Re initiating ipc login is now no longer required for https://github.com/rinukkusu/spotify-dart/pull/118

This commit is contained in:
Kingkor Roy Tirtho 2022-05-26 13:13:34 +06:00
parent 31551ac1dc
commit 5accf5c56c
9 changed files with 113 additions and 110 deletions

View File

@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.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:spotify/spotify.dart' hide Image, Player, Search;
import 'package:spotube/components/Category/CategoryCard.dart'; 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/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Player/Player.dart'; import 'package:spotube/components/Player/Player.dart';
import 'package:spotube/components/Library/UserLibrary.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/useBreakpointValue.dart';
import 'package:spotube/hooks/useHotKeys.dart'; import 'package:spotube/hooks/useHotKeys.dart';
import 'package:spotube/hooks/usePagingController.dart'; import 'package:spotube/hooks/usePagingController.dart';
import 'package:spotube/hooks/useSharedPreferences.dart'; import 'package:spotube/hooks/useSharedPreferences.dart';
import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/models/Logger.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/SpotifyDI.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
@ -48,7 +42,6 @@ class Home extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Auth auth = ref.watch(authProvider);
String recommendationMarket = ref.watch(userPreferencesProvider.select( String recommendationMarket = ref.watch(userPreferencesProvider.select(
(value) => (value.recommendationMarket), (value) => (value.recommendationMarket),
)); ));
@ -98,70 +91,12 @@ class Home extends HookConsumerWidget {
}, [recommendationMarket]); }, [recommendationMarket]);
useEffect(() { 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 { 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); pagingController.addPageRequestListener(listener);
// the world is full of surprises and the previously working // the world is full of surprises and the previously working
// fine pageRequestListener now doesn't notify the listeners // fine pageRequestListener now doesn't notify the listeners
// automatically after assigning a listener. So doing it manually // automatically after assigning a listener. So doing it manually
pagingController.notifyPageRequestListeners(0); 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);
}
} catch (e, stack) { } catch (e, stack) {
logger.e("initState", e, stack); logger.e("initState", e, stack);
} }

View File

@ -26,7 +26,7 @@ Future<void> oauthLogin(Auth auth,
if (responseUri != null) { if (responseUri != null) {
final SpotifyApi spotify = final SpotifyApi spotify =
SpotifyApi.fromAuthCodeGrant(grant, responseUri); SpotifyApi.fromAuthCodeGrant(grant, responseUri);
var credentials = await spotify.getCredentials(); final credentials = await spotify.getCredentials();
if (credentials.accessToken != null) { if (credentials.accessToken != null) {
accessToken = credentials.accessToken; accessToken = credentials.accessToken;
await localStorage.setString( await localStorage.setString(
@ -56,7 +56,6 @@ Future<void> oauthLogin(Auth auth,
accessToken: accessToken, accessToken: accessToken,
refreshToken: refreshToken, refreshToken: refreshToken,
expiration: expiration, expiration: expiration,
isLoggedIn: true,
); );
} catch (e, stack) { } catch (e, stack) {
logger.e("oauthLogin", e, stack); logger.e("oauthLogin", e, stack);

View File

@ -8,7 +8,7 @@ final logger = getLogger("ServerIPC");
Future<String?> connectIpc(String authUri, String redirectUri) async { Future<String?> connectIpc(String authUri, String redirectUri) async {
try { try {
logger.i("[Launching]: $authUri"); logger.i("[Launching]: $authUri");
await launch(authUri); await launchUrl(Uri.parse(authUri));
HttpServer server = HttpServer server =
await HttpServer.bind(InternetAddress.loopbackIPv4, 4304); await HttpServer.bind(InternetAddress.loopbackIPv4, 4304);

View File

@ -3,10 +3,10 @@ abstract class LocalStorageKeys {
static String recommendationMarket = 'recommendation_market'; static String recommendationMarket = 'recommendation_market';
static String ytSearchFormate = 'youtube_search_format'; static String ytSearchFormate = 'youtube_search_format';
static String clientId = 'client_id'; static String clientId = 'clientId';
static String clientSecret = 'client_secret'; static String clientSecret = 'clientSecret';
static String accessToken = 'access_token'; static String accessToken = 'accessToken';
static String refreshToken = 'refresh_token'; static String refreshToken = 'refreshToken';
static String expiration = "expiration"; static String expiration = "expiration";
static String geniusAccessToken = "genius_access_token"; static String geniusAccessToken = "genius_access_token";

View File

@ -1,26 +1,32 @@
import 'package:flutter/cupertino.dart'; import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Auth with ChangeNotifier { import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/utils/PersistedChangeNotifier.dart';
class Auth extends PersistedChangeNotifier {
String? _clientId; String? _clientId;
String? _clientSecret; String? _clientSecret;
String? _accessToken; String? _accessToken;
String? _refreshToken; String? _refreshToken;
DateTime? _expiration; DateTime? _expiration;
bool _isLoggedIn = false; Auth() : super();
String? get clientId => _clientId; String? get clientId => _clientId;
String? get clientSecret => _clientSecret; String? get clientSecret => _clientSecret;
String? get accessToken => _accessToken; String? get accessToken => _accessToken;
String? get refreshToken => _refreshToken; String? get refreshToken => _refreshToken;
DateTime? get expiration => _expiration; DateTime? get expiration => _expiration;
bool get isLoggedIn => _isLoggedIn;
bool get isAnonymous => bool get isAnonymous =>
!_isLoggedIn && _clientId == null && _clientSecret == null; _clientId == null &&
_clientSecret == null &&
accessToken == null &&
refreshToken == null;
bool get isLoggedIn => !isAnonymous && _expiration != null;
void setAuthState({ void setAuthState({
bool? isLoggedIn,
bool safe = true, bool safe = true,
String? clientId, String? clientId,
String? clientSecret, String? clientSecret,
@ -31,7 +37,6 @@ class Auth with ChangeNotifier {
if (safe) { if (safe) {
if (clientId != null) _clientId = clientId; if (clientId != null) _clientId = clientId;
if (clientSecret != null) _clientSecret = clientSecret; if (clientSecret != null) _clientSecret = clientSecret;
if (isLoggedIn != null) _isLoggedIn = isLoggedIn;
if (refreshToken != null) _refreshToken = refreshToken; if (refreshToken != null) _refreshToken = refreshToken;
if (accessToken != null) _accessToken = accessToken; if (accessToken != null) _accessToken = accessToken;
if (expiration != null) _expiration = expiration; if (expiration != null) _expiration = expiration;
@ -43,6 +48,7 @@ class Auth with ChangeNotifier {
_expiration = expiration; _expiration = expiration;
} }
notifyListeners(); notifyListeners();
updatePersistence();
} }
logout() { logout() {
@ -51,14 +57,34 @@ class Auth with ChangeNotifier {
_accessToken = null; _accessToken = null;
_refreshToken = null; _refreshToken = null;
_expiration = null; _expiration = null;
_isLoggedIn = false;
notifyListeners(); notifyListeners();
updatePersistence();
} }
@override @override
String toString() { String toString() {
return "Auth(clientId: $clientId, clientSecret: $clientSecret, accessToken: $accessToken, refreshToken: $refreshToken, expiration: $expiration, isLoggedIn: $isLoggedIn)"; return "Auth(clientId: $clientId, clientSecret: $clientSecret, accessToken: $accessToken, refreshToken: $refreshToken, expiration: $expiration, isLoggedIn: $isLoggedIn)";
} }
@override
FutureOr<void> loadFromLocal(Map<String, dynamic> 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"]);
} }
var authProvider = ChangeNotifierProvider<Auth>((ref) => Auth()); @override
FutureOr<Map<String, dynamic>> toMap() {
return {
"clientId": _clientId,
"clientSecret": _clientSecret,
"accessToken": _accessToken,
"refreshToken": _refreshToken,
"expiration": _expiration.toString(),
};
}
}
final authProvider = ChangeNotifierProvider<Auth>((ref) => Auth());

View File

@ -1,13 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Home/Home.dart'; import 'package:spotube/components/Home/Home.dart';
import 'package:spotube/helpers/get-random-element.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/models/generated_secrets.dart';
import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Auth.dart';
var spotifyProvider = Provider<SpotifyApi>((ref) { final spotifyProvider = Provider<SpotifyApi>((ref) {
Auth authState = ref.watch(authProvider); Auth authState = ref.watch(authProvider);
final anonCred = getRandomElement(spotifySecrets); final anonCred = getRandomElement(spotifySecrets);
SpotifyApiCredentials apiCredentials = authState.isAnonymous SpotifyApiCredentials apiCredentials = authState.isAnonymous
@ -26,20 +24,13 @@ var spotifyProvider = Provider<SpotifyApi>((ref) {
return SpotifyApi( return SpotifyApi(
apiCredentials, apiCredentials,
onCredentialsRefreshed: (credentials) async { onCredentialsRefreshed: (credentials) {
SharedPreferences localStorage = await SharedPreferences.getInstance(); authState.setAuthState(
localStorage.setString( clientId: credentials.clientId,
LocalStorageKeys.refreshToken, clientSecret: credentials.clientSecret,
credentials.refreshToken!, accessToken: credentials.accessToken,
); refreshToken: credentials.refreshToken,
localStorage.setString( expiration: credentials.expiration,
LocalStorageKeys.accessToken,
credentials.accessToken!,
);
localStorage.setString(LocalStorageKeys.clientId, credentials.clientId!);
localStorage.setString(
LocalStorageKeys.clientSecret,
credentials.clientSecret!,
); );
}, },
); );

View File

@ -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<Map<String, dynamic>>({}, (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<void> loadFromLocal(Map<String, dynamic> map);
FutureOr<Map<String, dynamic>> toMap();
Future<void> 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);
}
}
}
}

View File

@ -724,11 +724,9 @@ packages:
spotify: spotify:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "../spotify-dart"
ref: HEAD relative: true
resolved-ref: e36eb5884de44d39b310d0878779a697048061bd source: path
url: "https://github.com/KRTirtho/spotify-dart.git"
source: git
version: "0.7.0" version: "0.7.0"
sqflite: sqflite:
dependency: transitive dependency: transitive

View File

@ -38,7 +38,8 @@ dependencies:
http: ^0.13.4 http: ^0.13.4
shared_preferences: ^2.0.11 shared_preferences: ^2.0.11
spotify: 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 url_launcher: ^6.0.17
youtube_explode_dart: ^1.10.8 youtube_explode_dart: ^1.10.8
infinite_scroll_pagination: ^3.1.0 infinite_scroll_pagination: ^3.1.0