anonymous (guest) login support added

build pipeline update to support anon login
not logged in guards added
This commit is contained in:
Kingkor Roy Tirtho 2022-03-19 14:34:39 +06:00
parent a06d891a04
commit 6f6c00d76d
16 changed files with 287 additions and 190 deletions

View File

@ -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 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 config --enable-linux-desktop
- run: flutter pub get - 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 clean
- run: flutter build linux - run: flutter build linux
- run: make deb - run: make deb
@ -46,7 +46,7 @@ jobs:
cache: true cache: true
- run: flutter config --enable-windows-desktop - run: flutter config --enable-windows-desktop
- run: flutter pub get - 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: flutter build windows
- run: choco install make -y - run: choco install make -y
- run: make innoinstall - run: make innoinstall
@ -67,7 +67,7 @@ jobs:
cache: true cache: true
- run: flutter config --enable-macos-desktop - run: flutter config --enable-macos-desktop
- run: flutter pub get - 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: flutter build macos
- run: du -sh build/macos/Build/Products/Release/spotube.app - run: du -sh build/macos/Build/Products/Release/spotube.app
- run: npm install -g appdmg - run: npm install -g appdmg

View File

@ -5,17 +5,22 @@ import 'package:path/path.dart' as path;
void main(List<String> args) async { void main(List<String> args) async {
if (args.isEmpty) { 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); 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( throw Exception(
"'SECRET' Environmental Variable isn't configured properly"); "'LYRICS_SECRET' and 'SPOTIFY_SECRET' Environmental Variable isn't configured properly");
} }
await File(path.join( await File(path.join(
Directory.current.path, "lib/models/generated_secrets.dart")) Directory.current.path, "lib/models/generated_secrets.dart"))
.writeAsString("final List<String> secrets = $decodedSecret;"); .writeAsString(
"final List<String> lyricsSecrets = $decodedSecret;\nfinal List<Map<String, dynamic>> spotifySecrets = $decodedSpotifySecrete;",
);
} }

View File

@ -11,18 +11,19 @@ import 'package:spotify/spotify.dart' hide Image, Player, Search;
import 'package:spotube/components/Category/CategoryCard.dart'; import 'package:spotube/components/Category/CategoryCard.dart';
import 'package:spotube/components/Home/Sidebar.dart'; import 'package:spotube/components/Home/Sidebar.dart';
import 'package:spotube/components/Home/SpotubeNavigationBar.dart'; import 'package:spotube/components/Home/SpotubeNavigationBar.dart';
import 'package:spotube/components/Login.dart';
import 'package:spotube/components/Lyrics.dart'; import 'package:spotube/components/Lyrics.dart';
import 'package:spotube/components/Search/Search.dart'; 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/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/LocalStorageKeys.dart';
import 'package:spotube/models/generated_secrets.dart';
import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -60,25 +61,13 @@ class Home extends HookConsumerWidget {
// initializing global hot keys // initializing global hot keys
useHotKeys(ref); useHotKeys(ref);
useEffect(() { final listener = useCallback((int pageKey) async {
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);
listener(pageKey) async {
final spotify = ref.read(spotifyProvider); final spotify = ref.read(spotifyProvider);
try { try {
Page<Category> categories = Page<Category> categories =
await spotify.categories.list(country: "US").getPage(15, pageKey); await spotify.categories.list(country: "US").getPage(15, pageKey);
var items = categories.items!.toList(); final items = categories.items!.toList();
if (pageKey == 0) { if (pageKey == 0) {
Category category = Category(); Category category = Category();
category.id = "user-featured-playlists"; category.id = "user-featured-playlists";
@ -96,24 +85,44 @@ class Home extends HookConsumerWidget {
print("[Home.pagingController.addPageRequestListener] $e"); print("[Home.pagingController.addPageRequestListener] $e");
print(stack); print(stack);
} }
} }, []);
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 = final DateTime? expiration =
expirationStr != null ? DateTime.parse(expirationStr) : null; expirationStr != null ? DateTime.parse(expirationStr) : null;
if (clientId != null && clientSecret != null) { final anonCred = getRandomElement(spotifySecrets);
SpotifyApi spotify = SpotifyApi( SpotifyApiCredentials apiCredentials =
SpotifyApiCredentials( clientId != null && clientSecret != null
? SpotifyApiCredentials(
clientId, clientId,
clientSecret, clientSecret,
accessToken: accessToken, accessToken: accessToken,
refreshToken: refreshToken, refreshToken: refreshToken,
expiration: expiration, expiration: expiration,
scopes: spotifyScopes, scopes: spotifyScopes,
), )
: SpotifyApiCredentials(
anonCred["clientId"],
anonCred["clientSecret"],
); );
SpotifyApi spotify = SpotifyApi(apiCredentials);
if (clientId != null && clientSecret != null) {
spotify.getCredentials().then((credentials) { spotify.getCredentials().then((credentials) {
if (credentials.accessToken?.isNotEmpty ?? false) { if (credentials.accessToken?.isNotEmpty == true) {
auth.setAuthState( auth.setAuthState(
clientId: clientId, clientId: clientId,
clientSecret: clientSecret, clientSecret: clientSecret,
@ -124,9 +133,11 @@ class Home extends HookConsumerWidget {
isLoggedIn: true, isLoggedIn: true,
); );
} }
return null;
}).then((_) {
pagingController.addPageRequestListener(listener); 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) { }).catchError((e, stack) {
if (e is AuthorizationException) { if (e is AuthorizationException) {
oauthLogin( oauthLogin(
@ -138,6 +149,9 @@ class Home extends HookConsumerWidget {
print("[Home.useEffect.spotify.getCredentials]: $e"); print("[Home.useEffect.spotify.getCredentials]: $e");
print(stack); print(stack);
}); });
} else {
pagingController.addPageRequestListener(listener);
pagingController.notifyPageRequestListeners(0);
} }
} catch (e, stack) { } catch (e, stack) {
print("[Home.initState]: $e"); print("[Home.initState]: $e");
@ -148,10 +162,6 @@ class Home extends HookConsumerWidget {
}; };
}, [localStorage]); }, [localStorage]);
if (!auth.isLoggedIn) {
return const Login();
}
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
body: Column( body: Column(

View File

@ -6,10 +6,9 @@ import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart' hide Image; import 'package:spotify/spotify.dart' hide Image;
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/models/sideBarTiles.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
import '../../models/sideBarTiles.dart';
class Sidebar extends HookConsumerWidget { class Sidebar extends HookConsumerWidget {
final int selectedIndex; final int selectedIndex;
final void Function(int) onSelectedIndexChanged; final void Function(int) onSelectedIndexChanged;
@ -98,7 +97,7 @@ class Sidebar extends HookConsumerWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
snapshot.data?.displayName ?? "User's name", snapshot.data?.displayName ?? "Guest",
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),

View File

@ -1,11 +1,16 @@
import 'package:flutter/material.dart' hide Image; 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/UserArtists.dart';
import 'package:spotube/components/Library/UserPlaylists.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); const UserLibrary({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, ref) {
final Auth auth = ref.watch(authProvider);
return Expanded( return Expanded(
child: DefaultTabController( child: DefaultTabController(
length: 3, length: 3,
@ -20,11 +25,13 @@ class UserLibrary extends StatelessWidget {
Tab(text: "Album"), Tab(text: "Album"),
], ],
), ),
body: const TabBarView(children: [ body: auth.isLoggedIn
? const TabBarView(children: [
UserPlaylists(), UserPlaylists(),
UserArtists(), UserArtists(),
Icon(Icons.ac_unit_outlined), Icon(Icons.ac_unit_outlined),
]), ])
: const AnonymousFallback(),
), ),
), ),
); );

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/components/Shared/Hyperlink.dart'; import 'package:spotube/components/Shared/Hyperlink.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/helpers/oauth-login.dart'; import 'package:spotube/helpers/oauth-login.dart';
import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
@ -14,10 +16,12 @@ class Login extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
var clientIdController = useTextEditingController(); Auth authState = ref.watch(authProvider);
var clientSecretController = useTextEditingController(); final clientIdController = useTextEditingController();
var accessTokenController = useTextEditingController(); final clientSecretController = useTextEditingController();
var fieldError = useState(false); final accessTokenController = useTextEditingController();
final fieldError = useState(false);
final breakpoint = useBreakpoints();
Future handleLogin(Auth authState) async { Future handleLogin(Auth authState) async {
try { try {
@ -29,26 +33,33 @@ class Login extends HookConsumerWidget {
ref.read(authProvider), ref.read(authProvider),
clientId: clientIdController.value.text, clientId: clientIdController.value.text,
clientSecret: clientSecretController.value.text, clientSecret: clientSecretController.value.text,
).then(
(value) => GoRouter.of(context).pop(),
); );
} catch (e) { } catch (e) {
print("[Login.handleLogin] $e"); print("[Login.handleLogin] $e");
} }
} }
Auth authState = ref.watch(authProvider); final textTheme = Theme.of(context).textTheme;
return Scaffold( return Scaffold(
appBar: const PageWindowTitleBar(), appBar: const PageWindowTitleBar(leading: BackButton()),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Center( child: Center(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 10),
child: Column( child: Column(
children: [ children: [
Image.asset( Image.asset(
"assets/spotube-logo.png", "assets/spotube-logo.png",
width: 400, width: MediaQuery.of(context).size.width *
height: 400, (breakpoint <= Breakpoints.md ? .5 : .3),
), ),
Text("Add your spotify credentials to get started", Text("Add your spotify credentials to get started",
style: Theme.of(context).textTheme.headline4), style: breakpoint <= Breakpoints.md
? textTheme.headline5
: textTheme.headline4),
const Text( const Text(
"Don't worry, any of your credentials won't be collected or shared with anyone"), "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?", const Hyperlink("How to get these client-id & client-secret?",
@ -112,6 +123,7 @@ class Login extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -4,18 +4,15 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:shared_preferences/shared_preferences.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/PlayerActions.dart';
import 'package:spotube/components/Player/PlayerOverlay.dart'; import 'package:spotube/components/Player/PlayerOverlay.dart';
import 'package:spotube/components/Player/PlayerTrackDetails.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/components/Player/PlayerControls.dart';
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotube/provider/SpotifyDI.dart';
class Player extends HookConsumerWidget { class Player extends HookConsumerWidget {
const Player({Key? key}) : super(key: key); const Player({Key? key}) : super(key: key);

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Shared/DownloadTrackButton.dart'; import 'package:spotube/components/Shared/DownloadTrackButton.dart';
import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -16,12 +17,14 @@ class PlayerActions extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final SpotifyApi spotifyApi = ref.watch(spotifyProvider); final SpotifyApi spotifyApi = ref.watch(spotifyProvider);
final Playback playback = ref.watch(playbackProvider); final Playback playback = ref.watch(playbackProvider);
final Auth auth = ref.watch(authProvider);
return Row( return Row(
mainAxisAlignment: mainAxisAlignment, mainAxisAlignment: mainAxisAlignment,
children: [ children: [
DownloadTrackButton( DownloadTrackButton(
track: playback.currentTrack, track: playback.currentTrack,
), ),
if (auth.isLoggedIn)
FutureBuilder<bool>( FutureBuilder<bool>(
future: playback.currentTrack?.id != null future: playback.currentTrack?.id != null
? spotifyApi.tracks.me.containsOne(playback.currentTrack!.id!) ? spotifyApi.tracks.me.containsOne(playback.currentTrack!.id!)
@ -38,7 +41,8 @@ class PlayerActions extends HookConsumerWidget {
), ),
onPressed: () { onPressed: () {
if (!isLiked && playback.currentTrack?.id != null) { if (!isLiked && playback.currentTrack?.id != null) {
spotifyApi.tracks.me.saveOne(playback.currentTrack!.id!); spotifyApi.tracks.me
.saveOne(playback.currentTrack!.id!);
} }
}); });
}), }),

View File

@ -2,6 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
import 'package:spotube/components/Shared/TracksTableView.dart'; import 'package:spotube/components/Shared/TracksTableView.dart';
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
@ -35,6 +36,7 @@ class PlaylistView extends ConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Playback playback = ref.watch(playbackProvider); Playback playback = ref.watch(playbackProvider);
final Auth auth = ref.watch(authProvider);
SpotifyApi spotifyApi = ref.watch(spotifyProvider); SpotifyApi spotifyApi = ref.watch(spotifyProvider);
var isPlaylistPlaying = playback.currentPlaylist?.id != null && var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
playback.currentPlaylist?.id == playlist.id; playback.currentPlaylist?.id == playlist.id;
@ -56,6 +58,7 @@ class PlaylistView extends ConsumerWidget {
// nav back // nav back
const BackButton(), const BackButton(),
// heart playlist // heart playlist
if (auth.isLoggedIn)
IconButton( IconButton(
icon: const Icon(Icons.favorite_outline_rounded), icon: const Icon(Icons.favorite_outline_rounded),
onPressed: () {}, onPressed: () {},

View File

@ -5,11 +5,13 @@ import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumCard.dart'; import 'package:spotube/components/Album/AlbumCard.dart';
import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/Artist/ArtistCard.dart';
import 'package:spotube/components/Playlist/PlaylistCard.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/components/Shared/TracksTableView.dart';
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/helpers/simple-album-to-album.dart'; import 'package:spotube/helpers/simple-album-to-album.dart';
import 'package:spotube/helpers/zero-pad-num-str.dart'; import 'package:spotube/helpers/zero-pad-num-str.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/provider/Auth.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -18,7 +20,8 @@ class Search extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { 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 controller = useTextEditingController();
var searchTerm = useState(""); var searchTerm = useState("");
final albumController = useScrollController(); final albumController = useScrollController();
@ -26,6 +29,10 @@ class Search extends HookConsumerWidget {
final artistController = useScrollController(); final artistController = useScrollController();
final breakpoint = useBreakpoints(); final breakpoint = useBreakpoints();
if (auth.isAnonymous) {
return const Expanded(child: AnonymousFallback());
}
return Expanded( return Expanded(
child: Column( child: Column(
children: [ children: [

View File

@ -18,8 +18,9 @@ class Settings extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
UserPreferences preferences = ref.watch(userPreferencesProvider); final UserPreferences preferences = ref.watch(userPreferencesProvider);
ThemeMode theme = ref.watch(themeProvider); final ThemeMode theme = ref.watch(themeProvider);
final Auth auth = ref.watch(authProvider);
var geniusAccessToken = useState<String?>(null); var geniusAccessToken = useState<String?>(null);
TextEditingController textEditingController = useTextEditingController(); TextEditingController textEditingController = useTextEditingController();
@ -138,6 +139,27 @@ class Settings extends HookConsumerWidget {
], ],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
if (auth.isAnonymous)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("Login with your Spotify"),
ElevatedButton(
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) { Builder(builder: (context) {
Auth auth = ref.watch(authProvider); Auth auth = ref.watch(authProvider);
return Row( return Row(
@ -147,7 +169,8 @@ class Settings extends HookConsumerWidget {
ElevatedButton( ElevatedButton(
child: const Text("Logout"), child: const Text("Logout"),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red), backgroundColor:
MaterialStateProperty.all(Colors.red),
), ),
onPressed: () async { onPressed: () async {
SharedPreferences localStorage = SharedPreferences localStorage =

View File

@ -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"));
}
}

View File

@ -54,7 +54,9 @@ Future<List?> searchSong(
bool authHeader = false, bool authHeader = false,
}) async { }) async {
try { try {
if (apiKey == "" || apiKey == null) apiKey = getRandomElement(secrets); if (apiKey == "" || apiKey == null) {
apiKey = getRandomElement(lyricsSecrets);
}
const searchUrl = 'https://api.genius.com/search?q='; const searchUrl = 'https://api.genius.com/search?q=';
String song = optimizeQuery ? getTitle(title, artist) : "$title $artist"; String song = optimizeQuery ? getTitle(title, artist) : "$title $artist";

View File

@ -5,6 +5,7 @@ import 'package:spotube/components/Album/AlbumView.dart';
import 'package:spotube/components/Artist/ArtistAlbumView.dart'; import 'package:spotube/components/Artist/ArtistAlbumView.dart';
import 'package:spotube/components/Artist/ArtistProfile.dart'; import 'package:spotube/components/Artist/ArtistProfile.dart';
import 'package:spotube/components/Home/Home.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/Player/PlayerView.dart';
import 'package:spotube/components/Playlist/PlaylistView.dart'; import 'package:spotube/components/Playlist/PlaylistView.dart';
import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/Settings.dart';
@ -16,6 +17,12 @@ GoRouter createGoRouter() => GoRouter(
path: "/", path: "/",
builder: (context, state) => const Home(), builder: (context, state) => const Home(),
), ),
GoRoute(
path: "/login",
pageBuilder: (context, state) => SpotubePage(
child: const Login(),
),
),
GoRoute( GoRoute(
path: "/settings", path: "/settings",
pageBuilder: (context, state) => SpotubePage( pageBuilder: (context, state) => SpotubePage(

View File

@ -16,6 +16,8 @@ class Auth with ChangeNotifier {
String? get refreshToken => _refreshToken; String? get refreshToken => _refreshToken;
DateTime? get expiration => _expiration; DateTime? get expiration => _expiration;
bool get isLoggedIn => _isLoggedIn; bool get isLoggedIn => _isLoggedIn;
bool get isAnonymous =>
!_isLoggedIn && _clientId == null && _clientSecret == null;
void setAuthState({ void setAuthState({
bool? isLoggedIn, bool? isLoggedIn,

View File

@ -2,21 +2,30 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.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/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.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) { var spotifyProvider = Provider<SpotifyApi>((ref) {
Auth authState = ref.watch(authProvider); Auth authState = ref.watch(authProvider);
final anonCred = getRandomElement(spotifySecrets);
return SpotifyApi( SpotifyApiCredentials apiCredentials = authState.isAnonymous
SpotifyApiCredentials( ? SpotifyApiCredentials(
anonCred["clientId"],
anonCred["clientSecret"],
)
: SpotifyApiCredentials(
authState.clientId, authState.clientId,
authState.clientSecret, authState.clientSecret,
accessToken: authState.accessToken, accessToken: authState.accessToken,
refreshToken: authState.refreshToken, refreshToken: authState.refreshToken,
expiration: authState.expiration, expiration: authState.expiration,
scopes: spotifyScopes, scopes: spotifyScopes,
), );
return SpotifyApi(
apiCredentials,
onCredentialsRefreshed: (credentials) async { onCredentialsRefreshed: (credentials) async {
SharedPreferences localStorage = await SharedPreferences.getInstance(); SharedPreferences localStorage = await SharedPreferences.getInstance();
localStorage.setString( localStorage.setString(