mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
refactor(authentication): immutable authentication state
This commit is contained in:
parent
0751f5e317
commit
a5b7e5faf0
@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
|
||||
class TokenLoginForm extends HookConsumerWidget {
|
||||
final void Function()? onDone;
|
||||
@ -14,7 +13,8 @@ class TokenLoginForm extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
Auth authState = ref.watch(authProvider);
|
||||
final authenticationNotifier =
|
||||
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||
final directCodeController = useTextEditingController();
|
||||
final keyCodeController = useTextEditingController();
|
||||
final mounted = useIsMounted();
|
||||
@ -53,12 +53,9 @@ class TokenLoginForm extends HookConsumerWidget {
|
||||
}
|
||||
final cookieHeader =
|
||||
"sp_dc=${directCodeController.text}; sp_key=${keyCodeController.text}";
|
||||
final body = await ServiceUtils.getAccessToken(cookieHeader);
|
||||
|
||||
authState.setAuthState(
|
||||
accessToken: body.accessToken,
|
||||
authCookie: cookieHeader,
|
||||
expiration: body.expiration,
|
||||
authenticationNotifier.setCredentials(
|
||||
await AuthenticationCredentials.fromCookie(cookieHeader),
|
||||
);
|
||||
if (mounted()) {
|
||||
onDone?.call();
|
||||
|
@ -11,7 +11,7 @@ import 'package:spotube/components/shared/playbutton_card.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
|
||||
@ -23,7 +23,7 @@ class UserAlbums extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
final albumsQuery = useQuery(
|
||||
job: Queries.album.ofMine,
|
||||
externalData: ref.watch(spotifyProvider),
|
||||
@ -55,7 +55,7 @@ class UserAlbums extends HookConsumerWidget {
|
||||
[];
|
||||
}, [albumsQuery.data, searchText.value]);
|
||||
|
||||
if (auth.isAnonymous) {
|
||||
if (auth == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
if (albumsQuery.isLoading || !albumsQuery.hasData) {
|
||||
|
@ -10,7 +10,7 @@ import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
import 'package:spotube/components/artist/artist_card.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
@ -20,7 +20,7 @@ class UserArtists extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
final artistQuery = useInfiniteQuery(
|
||||
job: Queries.artist.followedByMe,
|
||||
@ -51,7 +51,7 @@ class UserArtists extends HookConsumerWidget {
|
||||
.toList();
|
||||
}, [artistQuery.pages, searchText.value]);
|
||||
|
||||
if (auth.isAnonymous) {
|
||||
if (auth == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
@ -33,7 +33,7 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
final viewType = MediaQuery.of(context).size.width < 480
|
||||
? PlaybuttonCardViewType.list
|
||||
: PlaybuttonCardViewType.square;
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
final playlistsQuery = useQuery(
|
||||
job: Queries.playlist.ofMine,
|
||||
@ -76,7 +76,7 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
[playlistsQuery.data, searchText.value],
|
||||
);
|
||||
|
||||
if (auth.isAnonymous) {
|
||||
if (auth == null) {
|
||||
return const AnonymousFallback();
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import 'package:spotube/components/player/sibling_tracks_sheet.dart';
|
||||
import 'package:spotube/components/shared/heart_button.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/downloader_provider.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
@ -36,7 +36,7 @@ class PlayerActions extends HookConsumerWidget {
|
||||
final isInQueue = downloader.inQueue
|
||||
.any((element) => element.id == playlist?.activeTrack.id);
|
||||
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
final isDownloaded = useMemoized(() {
|
||||
return localTracks.any(
|
||||
@ -124,7 +124,7 @@ class PlayerActions extends HookConsumerWidget {
|
||||
? () => downloader.addToQueue(playlist!.activeTrack)
|
||||
: null,
|
||||
),
|
||||
if (playlist?.activeTrack != null && !isLocalTrack && auth.isLoggedIn)
|
||||
if (playlist?.activeTrack != null && !isLocalTrack && auth != null)
|
||||
TrackHeartButton(track: playlist!.activeTrack),
|
||||
...(extraActions ?? [])
|
||||
],
|
||||
|
@ -10,7 +10,7 @@ import 'package:spotube/collections/side_bar_tiles.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/downloader_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
|
||||
@ -210,10 +210,10 @@ class SidebarFooter extends HookConsumerWidget {
|
||||
|
||||
// TODO: Remove below code after fl-query ^0.4.0
|
||||
/// Temporary fix before fl-query 0.4.0
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
useEffect(() {
|
||||
if (auth.isLoggedIn && me.hasError) {
|
||||
if (auth != null && me.hasError) {
|
||||
me.setExternalData(spotify);
|
||||
me.refetch();
|
||||
}
|
||||
@ -227,7 +227,7 @@ class SidebarFooter extends HookConsumerWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (auth.isLoggedIn && data == null)
|
||||
if (auth != null && data == null)
|
||||
const Center(
|
||||
child: PlatformCircularProgressIndicator(),
|
||||
)
|
||||
|
@ -19,7 +19,8 @@ Future<bool> showPromptDialog({
|
||||
primaryActions: [
|
||||
if (platform == TargetPlatform.iOS)
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
isDefaultAction: false,
|
||||
isDestructiveAction: true,
|
||||
child: PlatformText(okText),
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
)
|
||||
@ -32,7 +33,7 @@ Future<bool> showPromptDialog({
|
||||
secondaryActions: [
|
||||
if (platform == TargetPlatform.iOS)
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: false,
|
||||
isDefaultAction: true,
|
||||
child: PlatformText(cancelText),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class AnonymousFallback extends ConsumerWidget {
|
||||
@ -13,7 +13,7 @@ class AnonymousFallback extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final isLoggedIn = ref.watch(authProvider.select((s) => s.isLoggedIn));
|
||||
final isLoggedIn = ref.watch(AuthenticationNotifier.provider) != null;
|
||||
|
||||
if (isLoggedIn && child != null) return child!;
|
||||
return Center(
|
||||
|
@ -7,7 +7,7 @@ import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/hooks/use_palette_color.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/services/mutations/mutations.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
@ -32,9 +32,9 @@ class HeartButton extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
if (!auth.isLoggedIn) return Container();
|
||||
if (auth == null) return Container();
|
||||
|
||||
return PlatformIconButton(
|
||||
tooltip: tooltip,
|
||||
|
@ -12,12 +12,12 @@ import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/components/shared/track_table/tracks_table_view.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/hooks/use_custom_status_bar_color.dart';
|
||||
import 'package:spotube/hooks/use_palette_color.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
@ -64,7 +64,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
final color = usePaletteGenerator(
|
||||
context,
|
||||
titleImage,
|
||||
@ -79,7 +79,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
||||
),
|
||||
onPressed: onShare,
|
||||
),
|
||||
if (heartBtn != null && auth.isLoggedIn) heartBtn!,
|
||||
if (heartBtn != null && auth != null) heartBtn!,
|
||||
PlatformIconButton(
|
||||
tooltip: "Shuffle",
|
||||
icon: Icon(
|
||||
@ -194,7 +194,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
||||
child: searchbar,
|
||||
);
|
||||
});
|
||||
Overlay.of(context)?.insert(entry!);
|
||||
Overlay.of(context).insert(entry!);
|
||||
}
|
||||
});
|
||||
return () => entry?.remove();
|
||||
|
@ -16,7 +16,7 @@ import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/components/root/sidebar.dart';
|
||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
@ -72,7 +72,7 @@ class TrackTile extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
|
||||
@ -362,13 +362,13 @@ class TrackTile extends HookConsumerWidget {
|
||||
toggler.item2.mutate(Tuple2(spotify, toggler.item1));
|
||||
},
|
||||
),
|
||||
if (auth.isLoggedIn)
|
||||
if (auth != null)
|
||||
Action(
|
||||
icon: const Icon(SpotubeIcons.playlistAdd),
|
||||
text: const PlatformText("Add To playlist"),
|
||||
onPressed: actionAddToPlaylist,
|
||||
),
|
||||
if (userPlaylist && auth.isLoggedIn)
|
||||
if (userPlaylist && auth != null)
|
||||
Action(
|
||||
icon: (removeTrack.isLoading || !removeTrack.hasData) &&
|
||||
removingTrack.value == track.value.uri
|
||||
|
@ -16,7 +16,7 @@ import 'package:spotube/components/artist/artist_card.dart';
|
||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
@ -56,7 +56,7 @@ class ArtistPage extends HookConsumerWidget {
|
||||
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
return SafeArea(
|
||||
child: PlatformScaffold(
|
||||
@ -163,7 +163,7 @@ class ArtistPage extends HookConsumerWidget {
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (auth.isLoggedIn)
|
||||
if (auth != null)
|
||||
HookBuilder(
|
||||
builder: (context) {
|
||||
final isFollowingQuery = useQuery(
|
||||
|
@ -7,7 +7,7 @@ import 'package:spotube/collections/assets.gen.dart';
|
||||
import 'package:spotube/components/desktop_login/login_form.dart';
|
||||
import 'package:spotube/components/shared/links/hyper_link.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class LoginTutorial extends ConsumerWidget {
|
||||
@ -15,7 +15,9 @@ class LoginTutorial extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
ref.watch(AuthenticationNotifier.provider);
|
||||
final authenticationNotifier =
|
||||
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||
final key = GlobalKey<State<IntroductionScreen>>();
|
||||
|
||||
final pageDecoration = PageDecoration(
|
||||
@ -51,7 +53,7 @@ class LoginTutorial extends ConsumerWidget {
|
||||
),
|
||||
showBackButton: true,
|
||||
overrideDone: PlatformFilledButton(
|
||||
onPressed: auth.isLoggedIn
|
||||
onPressed: authenticationNotifier.isLoggedIn
|
||||
? () {
|
||||
ServiceUtils.navigate(context, "/");
|
||||
}
|
||||
@ -96,7 +98,7 @@ class LoginTutorial extends ConsumerWidget {
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
if (auth.isLoggedIn)
|
||||
if (authenticationNotifier.isLoggedIn)
|
||||
PageViewModel(
|
||||
decoration: pageDecoration.copyWith(
|
||||
bodyAlignment: Alignment.center,
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/components/genre/category_card.dart';
|
||||
import 'package:spotube/components/shared/compact_search.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
@ -36,11 +36,11 @@ class GenrePage extends HookConsumerWidget {
|
||||
|
||||
final isMounted = useIsMounted();
|
||||
|
||||
/// Temporary fix before fl-query 0.4.0
|
||||
final auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
/// Temporary fix before fl-query 0.4.0
|
||||
useEffect(() {
|
||||
if (auth.isLoggedIn && categoriesQuery.hasError) {
|
||||
if (auth != null && categoriesQuery.hasError) {
|
||||
categoriesQuery.setExternalData({
|
||||
"spotify": spotify,
|
||||
"recommendationMarket": recommendationMarket,
|
||||
|
@ -4,9 +4,8 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class WebViewLogin extends HookConsumerWidget {
|
||||
const WebViewLogin({Key? key}) : super(key: key);
|
||||
@ -14,7 +13,8 @@ class WebViewLogin extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final mounted = useIsMounted();
|
||||
final auth = ref.watch(authProvider);
|
||||
final authenticationNotifier =
|
||||
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||
|
||||
if (kIsDesktop) {
|
||||
const Scaffold(
|
||||
@ -62,11 +62,8 @@ class WebViewLogin extends HookConsumerWidget {
|
||||
return previousValue;
|
||||
});
|
||||
|
||||
final body = await ServiceUtils.getAccessToken(cookieHeader);
|
||||
auth.setAuthState(
|
||||
accessToken: body.accessToken,
|
||||
authCookie: cookieHeader,
|
||||
expiration: body.expiration,
|
||||
authenticationNotifier.setCredentials(
|
||||
await AuthenticationCredentials.fromCookie(cookieHeader),
|
||||
);
|
||||
if (mounted()) {
|
||||
// ignore: use_build_context_synchronously
|
||||
|
@ -16,7 +16,7 @@ import 'package:spotube/components/shared/waypoint.dart';
|
||||
import 'package:spotube/components/artist/artist_card.dart';
|
||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
@ -34,7 +34,9 @@ class SearchPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
ref.watch(AuthenticationNotifier.provider);
|
||||
final authenticationNotifier =
|
||||
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final albumController = useScrollController();
|
||||
final playlistController = useScrollController();
|
||||
@ -83,7 +85,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
child: PlatformScaffold(
|
||||
appBar: kIsDesktop && !kIsMacOS ? PageWindowTitleBar() : null,
|
||||
body: auth.isAnonymous
|
||||
body: !authenticationNotifier.isLoggedIn
|
||||
? const AnonymousFallback()
|
||||
: Column(
|
||||
children: [
|
||||
|
@ -11,7 +11,7 @@ import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/main.dart';
|
||||
import 'package:spotube/collections/spotify_markets.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
@ -21,7 +21,7 @@ class SettingsPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final UserPreferences preferences = ref.watch(userPreferencesProvider);
|
||||
final Auth auth = ref.watch(authProvider);
|
||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||
|
||||
final pickColorScheme = useCallback((ColorSchemeType schemeType) {
|
||||
return () => showPlatformAlertDialog(context, builder: (context) {
|
||||
@ -59,7 +59,7 @@ class SettingsPage extends HookConsumerWidget {
|
||||
.headline
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (auth.isAnonymous)
|
||||
if (auth == null)
|
||||
AdaptiveListTile(
|
||||
leading: Icon(
|
||||
SpotubeIcons.login,
|
||||
@ -93,10 +93,9 @@ class SettingsPage extends HookConsumerWidget {
|
||||
child: PlatformText(
|
||||
"Connect with Spotify".toUpperCase()),
|
||||
),
|
||||
),
|
||||
if (auth.isLoggedIn)
|
||||
)
|
||||
else
|
||||
Builder(builder: (context) {
|
||||
Auth auth = ref.watch(authProvider);
|
||||
return PlatformListTile(
|
||||
leading: const Icon(SpotubeIcons.logout),
|
||||
title: SizedBox(
|
||||
@ -119,7 +118,10 @@ class SettingsPage extends HookConsumerWidget {
|
||||
MaterialStateProperty.all(Colors.white),
|
||||
),
|
||||
onPressed: () async {
|
||||
auth.logout();
|
||||
ref
|
||||
.read(
|
||||
AuthenticationNotifier.provider.notifier)
|
||||
.logout();
|
||||
GoRouter.of(context).pop();
|
||||
},
|
||||
child: const PlatformText("Logout"),
|
||||
|
@ -1,124 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/utils/persisted_change_notifier.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class Auth extends PersistedChangeNotifier {
|
||||
String? _accessToken;
|
||||
DateTime? _expiration;
|
||||
String? _authCookie;
|
||||
|
||||
Timer? _refresher;
|
||||
|
||||
Auth() : super() {
|
||||
_refresher = _createRefresher();
|
||||
}
|
||||
|
||||
String? get accessToken => _accessToken;
|
||||
DateTime? get expiration => _expiration;
|
||||
String? get authCookie => _authCookie;
|
||||
|
||||
bool get isAnonymous => accessToken == null && authCookie == null;
|
||||
|
||||
bool get isLoggedIn => !isAnonymous && _expiration != null;
|
||||
bool get isExpired =>
|
||||
_expiration != null && _expiration!.isBefore(DateTime.now());
|
||||
|
||||
Duration get expiresIn =>
|
||||
_expiration?.difference(DateTime.now()) ?? Duration.zero;
|
||||
|
||||
Future<void> refresh() async {
|
||||
final data = await ServiceUtils.getAccessToken(authCookie!);
|
||||
_accessToken = data.accessToken;
|
||||
_expiration = data.expiration;
|
||||
_restartRefresher();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Timer? _createRefresher() {
|
||||
if (expiration == null || authCookie == null) {
|
||||
return null;
|
||||
}
|
||||
if (isExpired) {
|
||||
refresh();
|
||||
}
|
||||
_refresher?.cancel();
|
||||
return Timer(expiresIn - const Duration(minutes: 5), refresh);
|
||||
}
|
||||
|
||||
void _restartRefresher() {
|
||||
_refresher = _createRefresher();
|
||||
}
|
||||
|
||||
void setAuthState({
|
||||
bool safe = true,
|
||||
String? accessToken,
|
||||
DateTime? expiration,
|
||||
String? authCookie,
|
||||
}) {
|
||||
if (safe) {
|
||||
if (accessToken != null) _accessToken = accessToken;
|
||||
if (expiration != null) {
|
||||
_expiration = expiration;
|
||||
_restartRefresher();
|
||||
}
|
||||
if (authCookie != null) _authCookie = authCookie;
|
||||
} else {
|
||||
_accessToken = accessToken;
|
||||
_expiration = expiration;
|
||||
_authCookie = authCookie;
|
||||
|
||||
_restartRefresher();
|
||||
}
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void logout() {
|
||||
_accessToken = null;
|
||||
_expiration = null;
|
||||
_authCookie = null;
|
||||
_refresher?.cancel();
|
||||
_refresher = null;
|
||||
if (kIsMobile) {
|
||||
WebStorageManager.instance().android.deleteAllData();
|
||||
CookieManager.instance().deleteAllCookies();
|
||||
}
|
||||
notifyListeners();
|
||||
updatePersistence(clearNullEntries: true);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "Auth(accessToken: $accessToken, expiration: $expiration, isLoggedIn: $isLoggedIn, isAnonymous: $isAnonymous, authCookie: $authCookie)";
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
||||
_accessToken = map["accessToken"];
|
||||
_expiration = map["expiration"] != null
|
||||
? DateTime.tryParse(map["expiration"])
|
||||
: _expiration;
|
||||
_authCookie = map["authCookie"];
|
||||
if (isExpired) {
|
||||
final data = await ServiceUtils.getAccessToken(authCookie!);
|
||||
_accessToken = data.accessToken;
|
||||
_expiration = data.expiration;
|
||||
}
|
||||
_restartRefresher();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<Map<String, dynamic>> toMap() {
|
||||
return {
|
||||
"accessToken": _accessToken,
|
||||
"expiration": _expiration.toString(),
|
||||
"authCookie": _authCookie,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
final authProvider = ChangeNotifierProvider<Auth>((ref) => Auth());
|
133
lib/provider/authentication_provider.dart
Normal file
133
lib/provider/authentication_provider.dart
Normal file
@ -0,0 +1,133 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class AuthenticationCredentials {
|
||||
String cookie;
|
||||
String accessToken;
|
||||
DateTime expiration;
|
||||
|
||||
bool get isExpired => DateTime.now().isAfter(expiration);
|
||||
|
||||
AuthenticationCredentials({
|
||||
required this.cookie,
|
||||
required this.accessToken,
|
||||
required this.expiration,
|
||||
});
|
||||
|
||||
static Future<AuthenticationCredentials> fromCookie(String cookie) async {
|
||||
final Map body = await get(
|
||||
Uri.parse(
|
||||
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
|
||||
),
|
||||
headers: {
|
||||
"Cookie": cookie,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
|
||||
},
|
||||
).then((res) => jsonDecode(res.body));
|
||||
|
||||
return AuthenticationCredentials(
|
||||
cookie: cookie,
|
||||
accessToken: body['accessToken'],
|
||||
expiration: DateTime.fromMillisecondsSinceEpoch(
|
||||
body['accessTokenExpirationTimestampMs'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
factory AuthenticationCredentials.fromJson(Map<String, dynamic> json) {
|
||||
return AuthenticationCredentials(
|
||||
cookie: json['cookie'] as String,
|
||||
accessToken: json['accessToken'] as String,
|
||||
expiration: DateTime.parse(json['expiration'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'cookie': cookie,
|
||||
'accessToken': accessToken,
|
||||
'expiration': expiration.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
AuthenticationCredentials copyWith({
|
||||
String? cookie,
|
||||
String? accessToken,
|
||||
DateTime? expiration,
|
||||
}) {
|
||||
return AuthenticationCredentials(
|
||||
cookie: cookie ?? this.cookie,
|
||||
accessToken: accessToken ?? this.accessToken,
|
||||
expiration: expiration ?? this.expiration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationNotifier
|
||||
extends PersistedStateNotifier<AuthenticationCredentials?> {
|
||||
static final provider =
|
||||
StateNotifierProvider<AuthenticationNotifier, AuthenticationCredentials?>(
|
||||
(ref) => AuthenticationNotifier(),
|
||||
);
|
||||
|
||||
bool get isLoggedIn => state != null;
|
||||
|
||||
AuthenticationNotifier() : super(null, "authentication");
|
||||
|
||||
Timer? _refreshTimer;
|
||||
|
||||
@override
|
||||
FutureOr<void> onInit() async {
|
||||
super.onInit();
|
||||
if (isLoggedIn && state!.isExpired) {
|
||||
await refreshCredentials();
|
||||
}
|
||||
|
||||
addListener((state) {
|
||||
_refreshTimer?.cancel();
|
||||
if (isLoggedIn && !state!.isExpired) {
|
||||
_refreshTimer = Timer(
|
||||
state.expiration.difference(DateTime.now()),
|
||||
() => refreshCredentials(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setCredentials(AuthenticationCredentials credentials) {
|
||||
state = credentials;
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
state = null;
|
||||
if (kIsMobile) {
|
||||
WebStorageManager.instance().android.deleteAllData();
|
||||
CookieManager.instance().deleteAllCookies();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshCredentials() async {
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = await AuthenticationCredentials.fromCookie(state!.cookie);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<AuthenticationCredentials?> fromJson(Map<String, dynamic> json) {
|
||||
return AuthenticationCredentials.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return state?.toJson() ?? {};
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/generated_secrets.dart';
|
||||
import 'package:spotube/provider/auth_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
|
||||
final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||
Auth authState = ref.watch(authProvider);
|
||||
final authState = ref.watch(AuthenticationNotifier.provider);
|
||||
final anonCred = PrimitiveUtils.getRandomElement(spotifySecrets);
|
||||
|
||||
if (authState.isAnonymous) {
|
||||
if (authState == null) {
|
||||
return SpotifyApi(
|
||||
SpotifyApiCredentials(
|
||||
anonCred["clientId"],
|
||||
@ -17,5 +17,5 @@ final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||
);
|
||||
}
|
||||
|
||||
return SpotifyApi.withAccessToken(authState.accessToken!);
|
||||
return SpotifyApi.withAccessToken(authState.accessToken);
|
||||
});
|
||||
|
@ -6,8 +6,13 @@ import 'package:hive/hive.dart';
|
||||
abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
||||
final String cacheKey;
|
||||
|
||||
PersistedStateNotifier(super.state, this.cacheKey) {
|
||||
_load();
|
||||
FutureOr<void> onInit() {}
|
||||
|
||||
PersistedStateNotifier(
|
||||
super.state,
|
||||
this.cacheKey,
|
||||
) {
|
||||
_load().then((_) => onInit());
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
|
@ -8,7 +8,6 @@ import 'package:spotube/components/library/user_local_tracks.dart';
|
||||
import 'package:spotube/models/logger.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:spotube/models/lyrics.dart';
|
||||
import 'package:spotube/models/spotify_spotube_credentials.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
import 'package:spotube/models/generated_secrets.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
@ -246,23 +245,6 @@ abstract class ServiceUtils {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
static Future<SpotifySpotubeCredentials> getAccessToken(
|
||||
String cookieHeader) async {
|
||||
final res = await http.get(
|
||||
Uri.parse(
|
||||
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
|
||||
),
|
||||
headers: {
|
||||
"Cookie": cookieHeader,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
|
||||
},
|
||||
);
|
||||
return SpotifySpotubeCredentials.fromJson(
|
||||
jsonDecode(res.body),
|
||||
);
|
||||
}
|
||||
|
||||
static void navigate(BuildContext context, String location, {Object? extra}) {
|
||||
GoRouter.of(context).push(location, extra: extra);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user