mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
anonymous (guest) login support added
build pipeline update to support anon login not logged in guards added
This commit is contained in:
parent
a06d891a04
commit
6f6c00d76d
6
.github/workflows/flutter-build.yml
vendored
6
.github/workflows/flutter-build.yml
vendored
@ -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
|
||||
|
@ -5,17 +5,22 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
void main(List<String> 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<String> secrets = $decodedSecret;");
|
||||
.writeAsString(
|
||||
"final List<String> lyricsSecrets = $decodedSecret;\nfinal List<Map<String, dynamic>> spotifySecrets = $decodedSpotifySecrete;",
|
||||
);
|
||||
}
|
||||
|
@ -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<Category> 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<Category> 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(
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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"),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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);
|
||||
|
@ -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<bool>(
|
||||
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<bool>(
|
||||
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!);
|
||||
}
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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: [
|
||||
|
@ -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<String?>(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),
|
||||
|
10
lib/components/Shared/AnonymousFallback.dart
Normal file
10
lib/components/Shared/AnonymousFallback.dart
Normal 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"));
|
||||
}
|
||||
}
|
@ -54,7 +54,9 @@ Future<List?> 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";
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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<SpotifyApi>((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(
|
||||
|
Loading…
Reference in New Issue
Block a user