diff --git a/lib/components/Home.dart b/lib/components/Home.dart index 2a8950db..cdaf7f32 100644 --- a/lib/components/Home.dart +++ b/lib/components/Home.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart' hide Page; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:oauth2/oauth2.dart' show AuthorizationException; import 'package:spotify/spotify.dart' hide Image; import 'package:spotube/components/CategoryCard.dart'; import 'package:spotube/components/Login.dart'; @@ -12,6 +13,7 @@ import 'package:spotube/components/PageWindowTitleBar.dart'; import 'package:spotube/components/Player.dart' as player; import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/UserLibrary.dart'; +import 'package:spotube/helpers/oauth-login.dart'; import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/sideBarTiles.dart'; import 'package:spotube/provider/Auth.dart'; @@ -42,18 +44,20 @@ class _HomeState extends State { void initState() { super.initState(); WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async { + SharedPreferences localStorage = await SharedPreferences.getInstance(); + String? clientId = localStorage.getString(LocalStorageKeys.clientId); + String? clientSecret = + localStorage.getString(LocalStorageKeys.clientSecret); + String? accessToken = + localStorage.getString(LocalStorageKeys.accessToken); + String? refreshToken = + localStorage.getString(LocalStorageKeys.refreshToken); + String? expirationStr = + localStorage.getString(LocalStorageKeys.expiration); + DateTime? expiration = + expirationStr != null ? DateTime.parse(expirationStr) : null; try { Auth authProvider = context.read(); - SharedPreferences localStorage = await SharedPreferences.getInstance(); - var clientId = localStorage.getString(LocalStorageKeys.clientId); - var clientSecret = - localStorage.getString(LocalStorageKeys.clientSecret); - var accessToken = localStorage.getString(LocalStorageKeys.accessToken); - var refreshToken = - localStorage.getString(LocalStorageKeys.refreshToken); - var expirationStr = localStorage.getString(LocalStorageKeys.expiration); - var expiration = - expirationStr != null ? DateTime.parse(expirationStr) : null; if (clientId != null && clientSecret != null) { SpotifyApi spotifyApi = SpotifyApi( @@ -103,8 +107,17 @@ class _HomeState extends State { _pagingController.error = e; } }); - } catch (e) { - print("[login state error]: $e"); + } on AuthorizationException catch (e) { + if (clientId != null && clientSecret != null) { + oauthLogin( + context, + clientId: clientId, + clientSecret: clientSecret, + ); + } + } catch (e, stack) { + print("[Home.initState]: $e"); + print(stack); } }); } diff --git a/lib/components/Login.dart b/lib/components/Login.dart index 75c9ae44..d4b9add9 100644 --- a/lib/components/Login.dart +++ b/lib/components/Login.dart @@ -1,16 +1,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:spotify/spotify.dart' hide Image; -import 'package:spotube/components/Home.dart'; import 'package:spotube/components/PageWindowTitleBar.dart'; -import 'package:spotube/helpers/server_ipc.dart'; -import 'package:spotube/models/LocalStorageKeys.dart'; +import 'package:spotube/helpers/oauth-login.dart'; import 'package:spotube/provider/Auth.dart'; -const redirectUri = "http://localhost:4304/auth/spotify/callback"; - class Login extends StatefulWidget { + const Login({Key? key}) : super(key: key); + @override _LoginState createState() => _LoginState(); } @@ -19,9 +15,6 @@ class _LoginState extends State { String clientId = ""; String clientSecret = ""; bool _fieldError = false; - String? accessToken; - String? refreshToken; - DateTime? expiration; handleLogin(Auth authState) async { try { @@ -30,48 +23,7 @@ class _LoginState extends State { _fieldError = true; }); } - final credentials = SpotifyApiCredentials(clientId, clientSecret); - final grant = SpotifyApi.authorizationCodeGrant(credentials); - - final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri), - scopes: spotifyScopes); - - final responseUri = await connectIpc(authUri.toString(), redirectUri); - SharedPreferences localStorage = await SharedPreferences.getInstance(); - if (responseUri != null) { - final SpotifyApi spotify = - SpotifyApi.fromAuthCodeGrant(grant, responseUri); - var credentials = await spotify.getCredentials(); - if (credentials.accessToken != null) { - accessToken = credentials.accessToken; - await localStorage.setString( - LocalStorageKeys.accessToken, credentials.accessToken!); - } - if (credentials.refreshToken != null) { - refreshToken = credentials.refreshToken; - await localStorage.setString( - LocalStorageKeys.refreshToken, credentials.refreshToken!); - } - if (credentials.expiration != null) { - expiration = credentials.expiration; - await localStorage.setString(LocalStorageKeys.expiration, - credentials.expiration?.toString() ?? ""); - } - } - - await localStorage.setString(LocalStorageKeys.clientId, clientId); - await localStorage.setString( - LocalStorageKeys.clientSecret, - clientSecret, - ); - authState.setAuthState( - clientId: clientId, - clientSecret: clientSecret, - accessToken: accessToken, - refreshToken: refreshToken, - expiration: expiration, - isLoggedIn: true, - ); + await oauthLogin(context, clientId: clientId, clientSecret: clientSecret); } catch (e) { print("[Login.handleLogin] $e"); } diff --git a/lib/components/Player.dart b/lib/components/Player.dart index 0c5caf8a..9aec9fbb 100644 --- a/lib/components/Player.dart +++ b/lib/components/Player.dart @@ -14,6 +14,7 @@ import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:spotube/provider/SpotifyDI.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; class Player extends StatefulWidget { const Player({Key? key}) : super(key: key); @@ -32,6 +33,8 @@ class _PlayerState extends State with WidgetsBindingObserver { double _volume = 0; + late YoutubeExplode youtube; + late List _hotKeys; @override @@ -39,6 +42,7 @@ class _PlayerState extends State with WidgetsBindingObserver { try { super.initState(); player = AudioPlayer(); + youtube = YoutubeExplode(); _hotKeys = [ GlobalKeyActions( HotKey(KeyCode.space, scope: HotKeyScope.inapp), @@ -134,6 +138,7 @@ class _PlayerState extends State with WidgetsBindingObserver { void dispose() { WidgetsBinding.instance?.removeObserver(this); player.dispose(); + youtube.close(); Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e.hotKey))); super.dispose(); } @@ -200,7 +205,7 @@ class _PlayerState extends State with WidgetsBindingObserver { }); }); } - var ytTrack = await toYoutubeTrack(currentTrack); + var ytTrack = await toYoutubeTrack(youtube, currentTrack); if (playback.setTrackUriById(currentTrack.id!, ytTrack.uri!)) { await player .setAudioSource(AudioSource.uri(Uri.parse(ytTrack.uri!))) diff --git a/lib/helpers/oauth-login.dart b/lib/helpers/oauth-login.dart new file mode 100644 index 00000000..e9bdbeb7 --- /dev/null +++ b/lib/helpers/oauth-login.dart @@ -0,0 +1,66 @@ +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/Home.dart'; +import 'package:spotube/helpers/server_ipc.dart'; +import 'package:spotube/models/LocalStorageKeys.dart'; +import 'package:spotube/provider/Auth.dart'; + +const redirectUri = "http://localhost:4304/auth/spotify/callback"; + +Future oauthLogin(BuildContext context, + {required String clientId, required String clientSecret}) async { + try { + String? accessToken; + String? refreshToken; + DateTime? expiration; + final credentials = SpotifyApiCredentials(clientId, clientSecret); + final grant = SpotifyApi.authorizationCodeGrant(credentials); + + final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri), + scopes: spotifyScopes); + + final responseUri = await connectIpc(authUri.toString(), redirectUri); + SharedPreferences localStorage = await SharedPreferences.getInstance(); + if (responseUri != null) { + final SpotifyApi spotify = + SpotifyApi.fromAuthCodeGrant(grant, responseUri); + var credentials = await spotify.getCredentials(); + if (credentials.accessToken != null) { + accessToken = credentials.accessToken; + await localStorage.setString( + LocalStorageKeys.accessToken, credentials.accessToken!); + } + if (credentials.refreshToken != null) { + refreshToken = credentials.refreshToken; + await localStorage.setString( + LocalStorageKeys.refreshToken, credentials.refreshToken!); + } + if (credentials.expiration != null) { + expiration = credentials.expiration; + await localStorage.setString(LocalStorageKeys.expiration, + credentials.expiration?.toString() ?? ""); + } + } + + await localStorage.setString(LocalStorageKeys.clientId, clientId); + await localStorage.setString( + LocalStorageKeys.clientSecret, + clientSecret, + ); + + Provider.of(context, listen: false).setAuthState( + clientId: clientId, + clientSecret: clientSecret, + accessToken: accessToken, + refreshToken: refreshToken, + expiration: expiration, + isLoggedIn: true, + ); + } catch (e, stack) { + print("[oauthLogin()] $e"); + print(stack); + rethrow; + } +} diff --git a/lib/helpers/search-youtube.dart b/lib/helpers/search-youtube.dart index 74f4351d..007b1dbd 100644 --- a/lib/helpers/search-youtube.dart +++ b/lib/helpers/search-youtube.dart @@ -1,8 +1,7 @@ import 'package:spotify/spotify.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; -YoutubeExplode youtube = YoutubeExplode(); -Future toYoutubeTrack(Track track) async { +Future toYoutubeTrack(YoutubeExplode youtube, Track track) async { var artistsName = track.artists?.map((ar) => ar.name).toList() ?? []; String queryString = "${artistsName.first} - ${track.name}${artistsName.length > 1 ? " feat. ${artistsName.sublist(1).join(" ")}" : ""}"; @@ -10,8 +9,11 @@ Future toYoutubeTrack(Track track) async { SearchList videos = await youtube.search.getVideos(queryString); List