diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index 0759893d..be450bce 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -18,7 +18,7 @@ jobs: sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make libwebkit2gtk-4.0-dev keybinder-3.0 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse - run: flutter config --enable-linux-desktop - run: flutter pub get - - run: dart bin/create-secrets.dart '${{ secrets.SECRET }}' + - run: dart bin/create-secrets.dart '${{ secrets.LYRICS_SECRET }}' '${{ secrets.SPOTIFY_SECRET }}' - run: flutter clean - run: flutter build linux - run: make deb @@ -46,7 +46,7 @@ jobs: cache: true - run: flutter config --enable-windows-desktop - run: flutter pub get - - run: dart bin/create-secrets.dart '${{ secrets.SECRET }}' + - run: dart bin/create-secrets.dart '${{ secrets.LYRICS_SECRET }}' '${{ secrets.SPOTIFY_SECRET }}' - run: flutter build windows - run: choco install make -y - run: make innoinstall @@ -67,7 +67,7 @@ jobs: cache: true - run: flutter config --enable-macos-desktop - run: flutter pub get - - run: dart bin/create-secrets.dart '${{ secrets.SECRET }}' + - run: dart bin/create-secrets.dart '${{ secrets.LYRICS_SECRET }}' '${{ secrets.SPOTIFY_SECRET }}' - run: flutter build macos - run: du -sh build/macos/Build/Products/Release/spotube.app - run: npm install -g appdmg diff --git a/bin/create-secrets.dart b/bin/create-secrets.dart index e9cc9a66..10a6317b 100644 --- a/bin/create-secrets.dart +++ b/bin/create-secrets.dart @@ -5,17 +5,22 @@ import 'package:path/path.dart' as path; void main(List args) async { if (args.isEmpty) { - throw ArgumentError("Expected an argument but none was passed"); + throw ArgumentError( + "Expected 2 arguments but passed ${args.length < 2 || args.length > 2 ? args.length : "none"}"); } - var decodedSecret = utf8.decode(base64Decode(args.first)); + final decodedSecret = utf8.decode(base64Decode(args.first)); + final decodedSpotifySecrete = utf8.decode(base64Decode(args.last)); final val = jsonDecode(decodedSecret); - if (val is! List) { + final val2 = jsonDecode(decodedSpotifySecrete); + if (val is! List || (val2 is! List && (val2 as List).first is! Map)) { throw Exception( - "'SECRET' Environmental Variable isn't configured properly"); + "'LYRICS_SECRET' and 'SPOTIFY_SECRET' Environmental Variable isn't configured properly"); } await File(path.join( Directory.current.path, "lib/models/generated_secrets.dart")) - .writeAsString("final List secrets = $decodedSecret;"); + .writeAsString( + "final List lyricsSecrets = $decodedSecret;\nfinal List> spotifySecrets = $decodedSpotifySecrete;", + ); } diff --git a/lib/components/Home/Home.dart b/lib/components/Home/Home.dart index 10fbec93..bde98304 100644 --- a/lib/components/Home/Home.dart +++ b/lib/components/Home/Home.dart @@ -11,18 +11,19 @@ import 'package:spotify/spotify.dart' hide Image, Player, Search; import 'package:spotube/components/Category/CategoryCard.dart'; import 'package:spotube/components/Home/Sidebar.dart'; import 'package:spotube/components/Home/SpotubeNavigationBar.dart'; -import 'package:spotube/components/Login.dart'; import 'package:spotube/components/Lyrics.dart'; 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/generated_secrets.dart'; import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -60,6 +61,32 @@ class Home extends HookConsumerWidget { // initializing global hot keys useHotKeys(ref); + final listener = useCallback((int pageKey) async { + final spotify = ref.read(spotifyProvider); + try { + Page categories = + await spotify.categories.list(country: "US").getPage(15, pageKey); + + final items = categories.items!.toList(); + if (pageKey == 0) { + Category category = Category(); + category.id = "user-featured-playlists"; + category.name = "Featured"; + items.insert(0, category); + } + + if (categories.isLast && categories.items != null) { + pagingController.appendLastPage(items); + } else if (categories.items != null) { + pagingController.appendPage(items, categories.nextOffset); + } + } catch (e, stack) { + pagingController.error = e; + print("[Home.pagingController.addPageRequestListener] $e"); + print(stack); + } + }, []); + useEffect(() { if (localStorage == null) return null; final String? clientId = @@ -72,48 +99,30 @@ class Home extends HookConsumerWidget { localStorage.getString(LocalStorageKeys.refreshToken); final String? expirationStr = localStorage.getString(LocalStorageKeys.expiration); - listener(pageKey) async { - final spotify = ref.read(spotifyProvider); - try { - Page categories = - await spotify.categories.list(country: "US").getPage(15, pageKey); - - var items = categories.items!.toList(); - if (pageKey == 0) { - Category category = Category(); - category.id = "user-featured-playlists"; - category.name = "Featured"; - items.insert(0, category); - } - - if (categories.isLast && categories.items != null) { - pagingController.appendLastPage(items); - } else if (categories.items != null) { - pagingController.appendPage(items, categories.nextOffset); - } - } catch (e, stack) { - pagingController.error = e; - print("[Home.pagingController.addPageRequestListener] $e"); - print(stack); - } - } 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) { - SpotifyApi spotify = SpotifyApi( - SpotifyApiCredentials( - clientId, - clientSecret, - accessToken: accessToken, - refreshToken: refreshToken, - expiration: expiration, - scopes: spotifyScopes, - ), - ); spotify.getCredentials().then((credentials) { - if (credentials.accessToken?.isNotEmpty ?? false) { + if (credentials.accessToken?.isNotEmpty == true) { auth.setAuthState( clientId: clientId, clientSecret: clientSecret, @@ -124,9 +133,11 @@ class Home extends HookConsumerWidget { isLoggedIn: true, ); } - return null; - }).then((_) { 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( @@ -138,6 +149,9 @@ class Home extends HookConsumerWidget { print("[Home.useEffect.spotify.getCredentials]: $e"); print(stack); }); + } else { + pagingController.addPageRequestListener(listener); + pagingController.notifyPageRequestListeners(0); } } catch (e, stack) { print("[Home.initState]: $e"); @@ -148,10 +162,6 @@ class Home extends HookConsumerWidget { }; }, [localStorage]); - if (!auth.isLoggedIn) { - return const Login(); - } - return SafeArea( child: Scaffold( body: Column( diff --git a/lib/components/Home/Sidebar.dart b/lib/components/Home/Sidebar.dart index da77a107..ffd5219e 100644 --- a/lib/components/Home/Sidebar.dart +++ b/lib/components/Home/Sidebar.dart @@ -6,10 +6,9 @@ import 'package:flutter/material.dart'; import 'package:spotify/spotify.dart' hide Image; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; +import 'package:spotube/models/sideBarTiles.dart'; import 'package:spotube/provider/SpotifyDI.dart'; -import '../../models/sideBarTiles.dart'; - class Sidebar extends HookConsumerWidget { final int selectedIndex; final void Function(int) onSelectedIndexChanged; @@ -98,7 +97,7 @@ class Sidebar extends HookConsumerWidget { ), const SizedBox(width: 10), Text( - snapshot.data?.displayName ?? "User's name", + snapshot.data?.displayName ?? "Guest", style: const TextStyle( fontWeight: FontWeight.bold, ), diff --git a/lib/components/Library/UserLibrary.dart b/lib/components/Library/UserLibrary.dart index 09f56a44..ae13f1b6 100644 --- a/lib/components/Library/UserLibrary.dart +++ b/lib/components/Library/UserLibrary.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart' hide Image; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/components/Library/UserArtists.dart'; import 'package:spotube/components/Library/UserPlaylists.dart'; +import 'package:spotube/components/Shared/AnonymousFallback.dart'; +import 'package:spotube/provider/Auth.dart'; -class UserLibrary extends StatelessWidget { +class UserLibrary extends ConsumerWidget { const UserLibrary({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, ref) { + final Auth auth = ref.watch(authProvider); + return Expanded( child: DefaultTabController( length: 3, @@ -20,11 +25,13 @@ class UserLibrary extends StatelessWidget { Tab(text: "Album"), ], ), - body: const TabBarView(children: [ - UserPlaylists(), - UserArtists(), - Icon(Icons.ac_unit_outlined), - ]), + body: auth.isLoggedIn + ? const TabBarView(children: [ + UserPlaylists(), + UserArtists(), + Icon(Icons.ac_unit_outlined), + ]) + : const AnonymousFallback(), ), ), ); diff --git a/lib/components/Login.dart b/lib/components/Login.dart index f5d94a1b..8ac03136 100644 --- a/lib/components/Login.dart +++ b/lib/components/Login.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/components/Shared/Hyperlink.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/helpers/oauth-login.dart'; +import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/UserPreferences.dart'; @@ -14,10 +16,12 @@ class Login extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - var clientIdController = useTextEditingController(); - var clientSecretController = useTextEditingController(); - var accessTokenController = useTextEditingController(); - var fieldError = useState(false); + Auth authState = ref.watch(authProvider); + final clientIdController = useTextEditingController(); + final clientSecretController = useTextEditingController(); + final accessTokenController = useTextEditingController(); + final fieldError = useState(false); + final breakpoint = useBreakpoints(); Future handleLogin(Auth authState) async { try { @@ -29,86 +33,94 @@ class Login extends HookConsumerWidget { ref.read(authProvider), clientId: clientIdController.value.text, clientSecret: clientSecretController.value.text, + ).then( + (value) => GoRouter.of(context).pop(), ); } catch (e) { print("[Login.handleLogin] $e"); } } - Auth authState = ref.watch(authProvider); + final textTheme = Theme.of(context).textTheme; + return Scaffold( - appBar: const PageWindowTitleBar(), + appBar: const PageWindowTitleBar(leading: BackButton()), body: SingleChildScrollView( child: Center( - child: Column( - children: [ - Image.asset( - "assets/spotube-logo.png", - width: 400, - height: 400, - ), - Text("Add your spotify credentials to get started", - style: Theme.of(context).textTheme.headline4), - const Text( - "Don't worry, any of your credentials won't be collected or shared with anyone"), - const Hyperlink("How to get these client-id & client-secret?", - "https://github.com/KRTirtho/spotube#configuration"), - const SizedBox( - height: 10, - ), - Container( - constraints: const BoxConstraints( - maxWidth: 400, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + children: [ + Image.asset( + "assets/spotube-logo.png", + width: MediaQuery.of(context).size.width * + (breakpoint <= Breakpoints.md ? .5 : .3), ), - child: Column( - children: [ - TextField( - controller: clientIdController, - decoration: const InputDecoration( - hintText: "Spotify Client ID", - label: Text("ClientID"), - ), - ), - const SizedBox(height: 10), - TextField( - decoration: const InputDecoration( - hintText: "Spotify Client Secret", - label: Text("Client Secret"), - ), - controller: clientSecretController, - ), - const SizedBox(height: 10), - const Divider(color: Colors.grey), - const SizedBox(height: 10), - TextField( - decoration: const InputDecoration( - label: Text("Genius Access Token (optional)"), - ), - controller: accessTokenController, - ), - const SizedBox( - height: 10, - ), - ElevatedButton( - onPressed: () async { - await handleLogin(authState); - UserPreferences preferences = - ref.read(userPreferencesProvider); - SharedPreferences localStorage = - await SharedPreferences.getInstance(); - preferences.setGeniusAccessToken( - accessTokenController.value.text); - await localStorage.setString( - LocalStorageKeys.geniusAccessToken, - accessTokenController.value.text); - accessTokenController.text = ""; - }, - child: const Text("Submit"), - ) - ], + Text("Add your spotify credentials to get started", + style: breakpoint <= Breakpoints.md + ? textTheme.headline5 + : textTheme.headline4), + const Text( + "Don't worry, any of your credentials won't be collected or shared with anyone"), + const Hyperlink("How to get these client-id & client-secret?", + "https://github.com/KRTirtho/spotube#configuration"), + const SizedBox( + height: 10, ), - ), - ], + Container( + constraints: const BoxConstraints( + maxWidth: 400, + ), + child: Column( + children: [ + TextField( + controller: clientIdController, + decoration: const InputDecoration( + hintText: "Spotify Client ID", + label: Text("ClientID"), + ), + ), + const SizedBox(height: 10), + TextField( + decoration: const InputDecoration( + hintText: "Spotify Client Secret", + label: Text("Client Secret"), + ), + controller: clientSecretController, + ), + const SizedBox(height: 10), + const Divider(color: Colors.grey), + const SizedBox(height: 10), + TextField( + decoration: const InputDecoration( + label: Text("Genius Access Token (optional)"), + ), + controller: accessTokenController, + ), + const SizedBox( + height: 10, + ), + ElevatedButton( + onPressed: () async { + await handleLogin(authState); + UserPreferences preferences = + ref.read(userPreferencesProvider); + SharedPreferences localStorage = + await SharedPreferences.getInstance(); + preferences.setGeniusAccessToken( + accessTokenController.value.text); + await localStorage.setString( + LocalStorageKeys.geniusAccessToken, + accessTokenController.value.text); + accessTokenController.text = ""; + }, + child: const Text("Submit"), + ) + ], + ), + ), + ], + ), ), ), ), diff --git a/lib/components/Player/Player.dart b/lib/components/Player/Player.dart index 0f2f87f2..f3cf3ba5 100644 --- a/lib/components/Player/Player.dart +++ b/lib/components/Player/Player.dart @@ -4,18 +4,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:just_audio/just_audio.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:spotify/spotify.dart' hide Image; import 'package:spotube/components/Player/PlayerActions.dart'; import 'package:spotube/components/Player/PlayerOverlay.dart'; import 'package:spotube/components/Player/PlayerTrackDetails.dart'; -import 'package:spotube/components/Shared/DownloadTrackButton.dart'; import 'package:spotube/components/Player/PlayerControls.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; -import 'package:spotube/provider/SpotifyDI.dart'; class Player extends HookConsumerWidget { const Player({Key? key}) : super(key: key); diff --git a/lib/components/Player/PlayerActions.dart b/lib/components/Player/PlayerActions.dart index ba23ae70..6cc985cb 100644 --- a/lib/components/Player/PlayerActions.dart +++ b/lib/components/Player/PlayerActions.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Shared/DownloadTrackButton.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -16,32 +17,35 @@ class PlayerActions extends HookConsumerWidget { Widget build(BuildContext context, ref) { final SpotifyApi spotifyApi = ref.watch(spotifyProvider); final Playback playback = ref.watch(playbackProvider); + final Auth auth = ref.watch(authProvider); return Row( mainAxisAlignment: mainAxisAlignment, children: [ DownloadTrackButton( track: playback.currentTrack, ), - FutureBuilder( - future: playback.currentTrack?.id != null - ? spotifyApi.tracks.me.containsOne(playback.currentTrack!.id!) - : Future.value(false), - initialData: false, - builder: (context, snapshot) { - bool isLiked = snapshot.data ?? false; - return IconButton( - icon: Icon( - !isLiked - ? Icons.favorite_outline_rounded - : Icons.favorite_rounded, - color: isLiked ? Colors.green : null, - ), - onPressed: () { - if (!isLiked && playback.currentTrack?.id != null) { - spotifyApi.tracks.me.saveOne(playback.currentTrack!.id!); - } - }); - }), + if (auth.isLoggedIn) + FutureBuilder( + future: playback.currentTrack?.id != null + ? spotifyApi.tracks.me.containsOne(playback.currentTrack!.id!) + : Future.value(false), + initialData: false, + builder: (context, snapshot) { + bool isLiked = snapshot.data ?? false; + return IconButton( + icon: Icon( + !isLiked + ? Icons.favorite_outline_rounded + : Icons.favorite_rounded, + color: isLiked ? Colors.green : null, + ), + onPressed: () { + if (!isLiked && playback.currentTrack?.id != null) { + spotifyApi.tracks.me + .saveOne(playback.currentTrack!.id!); + } + }); + }), ], ); } diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index fefced39..a6908d60 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:flutter/material.dart'; import 'package:spotify/spotify.dart'; @@ -35,6 +36,7 @@ class PlaylistView extends ConsumerWidget { @override Widget build(BuildContext context, ref) { Playback playback = ref.watch(playbackProvider); + final Auth auth = ref.watch(authProvider); SpotifyApi spotifyApi = ref.watch(spotifyProvider); var isPlaylistPlaying = playback.currentPlaylist?.id != null && playback.currentPlaylist?.id == playlist.id; @@ -56,10 +58,11 @@ class PlaylistView extends ConsumerWidget { // nav back const BackButton(), // heart playlist - IconButton( - icon: const Icon(Icons.favorite_outline_rounded), - onPressed: () {}, - ), + if (auth.isLoggedIn) + IconButton( + icon: const Icon(Icons.favorite_outline_rounded), + onPressed: () {}, + ), // play playlist IconButton( icon: Icon( diff --git a/lib/components/Search/Search.dart b/lib/components/Search/Search.dart index 8d703e08..73b5f94f 100644 --- a/lib/components/Search/Search.dart +++ b/lib/components/Search/Search.dart @@ -5,11 +5,13 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/components/Album/AlbumCard.dart'; import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/Playlist/PlaylistCard.dart'; +import 'package:spotube/components/Shared/AnonymousFallback.dart'; import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/simple-album-to-album.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -18,7 +20,8 @@ class Search extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - SpotifyApi spotify = ref.watch(spotifyProvider); + final SpotifyApi spotify = ref.watch(spotifyProvider); + final Auth auth = ref.watch(authProvider); var controller = useTextEditingController(); var searchTerm = useState(""); final albumController = useScrollController(); @@ -26,6 +29,10 @@ class Search extends HookConsumerWidget { final artistController = useScrollController(); final breakpoint = useBreakpoints(); + if (auth.isAnonymous) { + return const Expanded(child: AnonymousFallback()); + } + return Expanded( child: Column( children: [ diff --git a/lib/components/Settings.dart b/lib/components/Settings.dart index 48d4335b..3ab93e98 100644 --- a/lib/components/Settings.dart +++ b/lib/components/Settings.dart @@ -18,8 +18,9 @@ class Settings extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - UserPreferences preferences = ref.watch(userPreferencesProvider); - ThemeMode theme = ref.watch(themeProvider); + final UserPreferences preferences = ref.watch(userPreferencesProvider); + final ThemeMode theme = ref.watch(themeProvider); + final Auth auth = ref.watch(authProvider); var geniusAccessToken = useState(null); TextEditingController textEditingController = useTextEditingController(); @@ -138,28 +139,50 @@ class Settings extends HookConsumerWidget { ], ), const SizedBox(height: 10), - Builder(builder: (context) { - Auth auth = ref.watch(authProvider); - return Row( + if (auth.isAnonymous) + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Log out of this account"), + const Text("Login with your Spotify"), ElevatedButton( - child: const Text("Logout"), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.red), - ), - onPressed: () async { - SharedPreferences localStorage = - await SharedPreferences.getInstance(); - await localStorage.clear(); - auth.logout(); - GoRouter.of(context).pop(); + child: Text("Connect with Spotify".toUpperCase()), + onPressed: () { + GoRouter.of(context).push("/login"); }, - ), + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25.0), + ), + ), + ), + ) ], - ); - }), + ), + if (auth.isLoggedIn) + Builder(builder: (context) { + Auth auth = ref.watch(authProvider); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text("Log out of this account"), + ElevatedButton( + child: const Text("Logout"), + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(Colors.red), + ), + onPressed: () async { + SharedPreferences localStorage = + await SharedPreferences.getInstance(); + await localStorage.clear(); + auth.logout(); + GoRouter.of(context).pop(); + }, + ), + ], + ); + }), const SizedBox(height: 40), const Text("Spotube v1.2.0"), const SizedBox(height: 10), diff --git a/lib/components/Shared/AnonymousFallback.dart b/lib/components/Shared/AnonymousFallback.dart new file mode 100644 index 00000000..d26f2d2f --- /dev/null +++ b/lib/components/Shared/AnonymousFallback.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class AnonymousFallback extends StatelessWidget { + const AnonymousFallback({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Center(child: Text("You're not logged in")); + } +} diff --git a/lib/helpers/getLyrics.dart b/lib/helpers/getLyrics.dart index 4ab5491c..8fc4867a 100644 --- a/lib/helpers/getLyrics.dart +++ b/lib/helpers/getLyrics.dart @@ -54,7 +54,9 @@ Future searchSong( bool authHeader = false, }) async { try { - if (apiKey == "" || apiKey == null) apiKey = getRandomElement(secrets); + if (apiKey == "" || apiKey == null) { + apiKey = getRandomElement(lyricsSecrets); + } const searchUrl = 'https://api.genius.com/search?q='; String song = optimizeQuery ? getTitle(title, artist) : "$title $artist"; diff --git a/lib/models/GoRouteDeclarations.dart b/lib/models/GoRouteDeclarations.dart index 4b503928..022e109a 100644 --- a/lib/models/GoRouteDeclarations.dart +++ b/lib/models/GoRouteDeclarations.dart @@ -5,6 +5,7 @@ import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Artist/ArtistAlbumView.dart'; import 'package:spotube/components/Artist/ArtistProfile.dart'; import 'package:spotube/components/Home/Home.dart'; +import 'package:spotube/components/Login.dart'; import 'package:spotube/components/Player/PlayerView.dart'; import 'package:spotube/components/Playlist/PlaylistView.dart'; import 'package:spotube/components/Settings.dart'; @@ -16,6 +17,12 @@ GoRouter createGoRouter() => GoRouter( path: "/", builder: (context, state) => const Home(), ), + GoRoute( + path: "/login", + pageBuilder: (context, state) => SpotubePage( + child: const Login(), + ), + ), GoRoute( path: "/settings", pageBuilder: (context, state) => SpotubePage( diff --git a/lib/provider/Auth.dart b/lib/provider/Auth.dart index af18e84e..48e24567 100644 --- a/lib/provider/Auth.dart +++ b/lib/provider/Auth.dart @@ -16,6 +16,8 @@ class Auth with ChangeNotifier { String? get refreshToken => _refreshToken; DateTime? get expiration => _expiration; bool get isLoggedIn => _isLoggedIn; + bool get isAnonymous => + !_isLoggedIn && _clientId == null && _clientSecret == null; void setAuthState({ bool? isLoggedIn, diff --git a/lib/provider/SpotifyDI.dart b/lib/provider/SpotifyDI.dart index 5d4f0597..4383d3a2 100644 --- a/lib/provider/SpotifyDI.dart +++ b/lib/provider/SpotifyDI.dart @@ -2,21 +2,30 @@ 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) { Auth authState = ref.watch(authProvider); + final anonCred = getRandomElement(spotifySecrets); + SpotifyApiCredentials apiCredentials = authState.isAnonymous + ? SpotifyApiCredentials( + anonCred["clientId"], + anonCred["clientSecret"], + ) + : SpotifyApiCredentials( + authState.clientId, + authState.clientSecret, + accessToken: authState.accessToken, + refreshToken: authState.refreshToken, + expiration: authState.expiration, + scopes: spotifyScopes, + ); return SpotifyApi( - SpotifyApiCredentials( - authState.clientId, - authState.clientSecret, - accessToken: authState.accessToken, - refreshToken: authState.refreshToken, - expiration: authState.expiration, - scopes: spotifyScopes, - ), + apiCredentials, onCredentialsRefreshed: (credentials) async { SharedPreferences localStorage = await SharedPreferences.getInstance(); localStorage.setString(