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:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:platform_ui/platform_ui.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 TokenLoginForm extends HookConsumerWidget {
|
class TokenLoginForm extends HookConsumerWidget {
|
||||||
final void Function()? onDone;
|
final void Function()? onDone;
|
||||||
@ -14,7 +13,8 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
Auth authState = ref.watch(authProvider);
|
final authenticationNotifier =
|
||||||
|
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||||
final directCodeController = useTextEditingController();
|
final directCodeController = useTextEditingController();
|
||||||
final keyCodeController = useTextEditingController();
|
final keyCodeController = useTextEditingController();
|
||||||
final mounted = useIsMounted();
|
final mounted = useIsMounted();
|
||||||
@ -53,12 +53,9 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
final cookieHeader =
|
final cookieHeader =
|
||||||
"sp_dc=${directCodeController.text}; sp_key=${keyCodeController.text}";
|
"sp_dc=${directCodeController.text}; sp_key=${keyCodeController.text}";
|
||||||
final body = await ServiceUtils.getAccessToken(cookieHeader);
|
|
||||||
|
|
||||||
authState.setAuthState(
|
authenticationNotifier.setCredentials(
|
||||||
accessToken: body.accessToken,
|
await AuthenticationCredentials.fromCookie(cookieHeader),
|
||||||
authCookie: cookieHeader,
|
|
||||||
expiration: body.expiration,
|
|
||||||
);
|
);
|
||||||
if (mounted()) {
|
if (mounted()) {
|
||||||
onDone?.call();
|
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/shimmers/shimmer_playbutton_card.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoint_value.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/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final albumsQuery = useQuery(
|
final albumsQuery = useQuery(
|
||||||
job: Queries.album.ofMine,
|
job: Queries.album.ofMine,
|
||||||
externalData: ref.watch(spotifyProvider),
|
externalData: ref.watch(spotifyProvider),
|
||||||
@ -55,7 +55,7 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
[];
|
[];
|
||||||
}, [albumsQuery.data, searchText.value]);
|
}, [albumsQuery.data, searchText.value]);
|
||||||
|
|
||||||
if (auth.isAnonymous) {
|
if (auth == null) {
|
||||||
return const AnonymousFallback();
|
return const AnonymousFallback();
|
||||||
}
|
}
|
||||||
if (albumsQuery.isLoading || !albumsQuery.hasData) {
|
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/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.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/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
@ -20,7 +20,7 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final artistQuery = useInfiniteQuery(
|
final artistQuery = useInfiniteQuery(
|
||||||
job: Queries.artist.followedByMe,
|
job: Queries.artist.followedByMe,
|
||||||
@ -51,7 +51,7 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
}, [artistQuery.pages, searchText.value]);
|
}, [artistQuery.pages, searchText.value]);
|
||||||
|
|
||||||
if (auth.isAnonymous) {
|
if (auth == null) {
|
||||||
return const AnonymousFallback();
|
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/components/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.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/spotify_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
@ -33,7 +33,7 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
final viewType = MediaQuery.of(context).size.width < 480
|
final viewType = MediaQuery.of(context).size.width < 480
|
||||||
? PlaybuttonCardViewType.list
|
? PlaybuttonCardViewType.list
|
||||||
: PlaybuttonCardViewType.square;
|
: PlaybuttonCardViewType.square;
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final playlistsQuery = useQuery(
|
final playlistsQuery = useQuery(
|
||||||
job: Queries.playlist.ofMine,
|
job: Queries.playlist.ofMine,
|
||||||
@ -76,7 +76,7 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
[playlistsQuery.data, searchText.value],
|
[playlistsQuery.data, searchText.value],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (auth.isAnonymous) {
|
if (auth == null) {
|
||||||
return const AnonymousFallback();
|
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/components/shared/heart_button.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
import 'package:spotube/models/logger.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/downloader_provider.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
@ -36,7 +36,7 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
final isInQueue = downloader.inQueue
|
final isInQueue = downloader.inQueue
|
||||||
.any((element) => element.id == playlist?.activeTrack.id);
|
.any((element) => element.id == playlist?.activeTrack.id);
|
||||||
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
final localTracks = [] /* ref.watch(localTracksProvider).value */;
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final isDownloaded = useMemoized(() {
|
final isDownloaded = useMemoized(() {
|
||||||
return localTracks.any(
|
return localTracks.any(
|
||||||
@ -124,7 +124,7 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
? () => downloader.addToQueue(playlist!.activeTrack)
|
? () => downloader.addToQueue(playlist!.activeTrack)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
if (playlist?.activeTrack != null && !isLocalTrack && auth.isLoggedIn)
|
if (playlist?.activeTrack != null && !isLocalTrack && auth != null)
|
||||||
TrackHeartButton(track: playlist!.activeTrack),
|
TrackHeartButton(track: playlist!.activeTrack),
|
||||||
...(extraActions ?? [])
|
...(extraActions ?? [])
|
||||||
],
|
],
|
||||||
|
@ -10,7 +10,7 @@ import 'package:spotube/collections/side_bar_tiles.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.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/downloader_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_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
|
// TODO: Remove below code after fl-query ^0.4.0
|
||||||
/// Temporary fix before 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(() {
|
useEffect(() {
|
||||||
if (auth.isLoggedIn && me.hasError) {
|
if (auth != null && me.hasError) {
|
||||||
me.setExternalData(spotify);
|
me.setExternalData(spotify);
|
||||||
me.refetch();
|
me.refetch();
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ class SidebarFooter extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
if (auth.isLoggedIn && data == null)
|
if (auth != null && data == null)
|
||||||
const Center(
|
const Center(
|
||||||
child: PlatformCircularProgressIndicator(),
|
child: PlatformCircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
|
@ -19,7 +19,8 @@ Future<bool> showPromptDialog({
|
|||||||
primaryActions: [
|
primaryActions: [
|
||||||
if (platform == TargetPlatform.iOS)
|
if (platform == TargetPlatform.iOS)
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
isDefaultAction: true,
|
isDefaultAction: false,
|
||||||
|
isDestructiveAction: true,
|
||||||
child: PlatformText(okText),
|
child: PlatformText(okText),
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
)
|
)
|
||||||
@ -32,7 +33,7 @@ Future<bool> showPromptDialog({
|
|||||||
secondaryActions: [
|
secondaryActions: [
|
||||||
if (platform == TargetPlatform.iOS)
|
if (platform == TargetPlatform.iOS)
|
||||||
CupertinoDialogAction(
|
CupertinoDialogAction(
|
||||||
isDefaultAction: false,
|
isDefaultAction: true,
|
||||||
child: PlatformText(cancelText),
|
child: PlatformText(cancelText),
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:platform_ui/platform_ui.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';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
class AnonymousFallback extends ConsumerWidget {
|
class AnonymousFallback extends ConsumerWidget {
|
||||||
@ -13,7 +13,7 @@ class AnonymousFallback extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
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!;
|
if (isLoggedIn && child != null) return child!;
|
||||||
return Center(
|
return Center(
|
||||||
|
@ -7,7 +7,7 @@ import 'package:platform_ui/platform_ui.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/hooks/use_palette_color.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/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/services/mutations/mutations.dart';
|
import 'package:spotube/services/mutations/mutations.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
@ -32,9 +32,9 @@ class HeartButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
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(
|
return PlatformIconButton(
|
||||||
tooltip: tooltip,
|
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/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/track_table/tracks_table_view.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_custom_status_bar_color.dart';
|
||||||
import 'package:spotube/hooks/use_palette_color.dart';
|
import 'package:spotube/hooks/use_palette_color.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
@ -64,7 +64,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final color = usePaletteGenerator(
|
final color = usePaletteGenerator(
|
||||||
context,
|
context,
|
||||||
titleImage,
|
titleImage,
|
||||||
@ -79,7 +79,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onPressed: onShare,
|
onPressed: onShare,
|
||||||
),
|
),
|
||||||
if (heartBtn != null && auth.isLoggedIn) heartBtn!,
|
if (heartBtn != null && auth != null) heartBtn!,
|
||||||
PlatformIconButton(
|
PlatformIconButton(
|
||||||
tooltip: "Shuffle",
|
tooltip: "Shuffle",
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@ -194,7 +194,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
child: searchbar,
|
child: searchbar,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Overlay.of(context)?.insert(entry!);
|
Overlay.of(context).insert(entry!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => entry?.remove();
|
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/components/root/sidebar.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/models/logger.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/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_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 spotify = ref.watch(spotifyProvider);
|
||||||
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
final playlistQueueNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
|
|
||||||
@ -362,13 +362,13 @@ class TrackTile extends HookConsumerWidget {
|
|||||||
toggler.item2.mutate(Tuple2(spotify, toggler.item1));
|
toggler.item2.mutate(Tuple2(spotify, toggler.item1));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (auth.isLoggedIn)
|
if (auth != null)
|
||||||
Action(
|
Action(
|
||||||
icon: const Icon(SpotubeIcons.playlistAdd),
|
icon: const Icon(SpotubeIcons.playlistAdd),
|
||||||
text: const PlatformText("Add To playlist"),
|
text: const PlatformText("Add To playlist"),
|
||||||
onPressed: actionAddToPlaylist,
|
onPressed: actionAddToPlaylist,
|
||||||
),
|
),
|
||||||
if (userPlaylist && auth.isLoggedIn)
|
if (userPlaylist && auth != null)
|
||||||
Action(
|
Action(
|
||||||
icon: (removeTrack.isLoading || !removeTrack.hasData) &&
|
icon: (removeTrack.isLoading || !removeTrack.hasData) &&
|
||||||
removingTrack.value == track.value.uri
|
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_breakpoint_value.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/models/logger.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/blacklist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_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 playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||||
|
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: PlatformScaffold(
|
child: PlatformScaffold(
|
||||||
@ -163,7 +163,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (auth.isLoggedIn)
|
if (auth != null)
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final isFollowingQuery = useQuery(
|
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/desktop_login/login_form.dart';
|
||||||
import 'package:spotube/components/shared/links/hyper_link.dart';
|
import 'package:spotube/components/shared/links/hyper_link.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.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';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
class LoginTutorial extends ConsumerWidget {
|
class LoginTutorial extends ConsumerWidget {
|
||||||
@ -15,7 +15,9 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
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 key = GlobalKey<State<IntroductionScreen>>();
|
||||||
|
|
||||||
final pageDecoration = PageDecoration(
|
final pageDecoration = PageDecoration(
|
||||||
@ -51,7 +53,7 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
overrideDone: PlatformFilledButton(
|
overrideDone: PlatformFilledButton(
|
||||||
onPressed: auth.isLoggedIn
|
onPressed: authenticationNotifier.isLoggedIn
|
||||||
? () {
|
? () {
|
||||||
ServiceUtils.navigate(context, "/");
|
ServiceUtils.navigate(context, "/");
|
||||||
}
|
}
|
||||||
@ -96,7 +98,7 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (auth.isLoggedIn)
|
if (authenticationNotifier.isLoggedIn)
|
||||||
PageViewModel(
|
PageViewModel(
|
||||||
decoration: pageDecoration.copyWith(
|
decoration: pageDecoration.copyWith(
|
||||||
bodyAlignment: Alignment.center,
|
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/compact_search.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.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/spotify_provider.dart';
|
||||||
|
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
@ -36,11 +36,11 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
|
|
||||||
final isMounted = useIsMounted();
|
final isMounted = useIsMounted();
|
||||||
|
|
||||||
/// Temporary fix before fl-query 0.4.0
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final auth = ref.watch(authProvider);
|
|
||||||
|
|
||||||
|
/// Temporary fix before fl-query 0.4.0
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (auth.isLoggedIn && categoriesQuery.hasError) {
|
if (auth != null && categoriesQuery.hasError) {
|
||||||
categoriesQuery.setExternalData({
|
categoriesQuery.setExternalData({
|
||||||
"spotify": spotify,
|
"spotify": spotify,
|
||||||
"recommendationMarket": recommendationMarket,
|
"recommendationMarket": recommendationMarket,
|
||||||
|
@ -4,9 +4,8 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:platform_ui/platform_ui.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/platform.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
|
||||||
|
|
||||||
class WebViewLogin extends HookConsumerWidget {
|
class WebViewLogin extends HookConsumerWidget {
|
||||||
const WebViewLogin({Key? key}) : super(key: key);
|
const WebViewLogin({Key? key}) : super(key: key);
|
||||||
@ -14,7 +13,8 @@ class WebViewLogin extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final mounted = useIsMounted();
|
final mounted = useIsMounted();
|
||||||
final auth = ref.watch(authProvider);
|
final authenticationNotifier =
|
||||||
|
ref.watch(AuthenticationNotifier.provider.notifier);
|
||||||
|
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
const Scaffold(
|
const Scaffold(
|
||||||
@ -62,11 +62,8 @@ class WebViewLogin extends HookConsumerWidget {
|
|||||||
return previousValue;
|
return previousValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
final body = await ServiceUtils.getAccessToken(cookieHeader);
|
authenticationNotifier.setCredentials(
|
||||||
auth.setAuthState(
|
await AuthenticationCredentials.fromCookie(cookieHeader),
|
||||||
accessToken: body.accessToken,
|
|
||||||
authCookie: cookieHeader,
|
|
||||||
expiration: body.expiration,
|
|
||||||
);
|
);
|
||||||
if (mounted()) {
|
if (mounted()) {
|
||||||
// ignore: use_build_context_synchronously
|
// 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/artist/artist_card.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.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/spotify_provider.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
@ -34,7 +34,9 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
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 spotify = ref.watch(spotifyProvider);
|
||||||
final albumController = useScrollController();
|
final albumController = useScrollController();
|
||||||
final playlistController = useScrollController();
|
final playlistController = useScrollController();
|
||||||
@ -83,7 +85,7 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: PlatformScaffold(
|
child: PlatformScaffold(
|
||||||
appBar: kIsDesktop && !kIsMacOS ? PageWindowTitleBar() : null,
|
appBar: kIsDesktop && !kIsMacOS ? PageWindowTitleBar() : null,
|
||||||
body: auth.isAnonymous
|
body: !authenticationNotifier.isLoggedIn
|
||||||
? const AnonymousFallback()
|
? const AnonymousFallback()
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
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/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/main.dart';
|
import 'package:spotube/main.dart';
|
||||||
import 'package:spotube/collections/spotify_markets.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:spotube/provider/user_preferences_provider.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final UserPreferences preferences = ref.watch(userPreferencesProvider);
|
final UserPreferences preferences = ref.watch(userPreferencesProvider);
|
||||||
final Auth auth = ref.watch(authProvider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
final pickColorScheme = useCallback((ColorSchemeType schemeType) {
|
final pickColorScheme = useCallback((ColorSchemeType schemeType) {
|
||||||
return () => showPlatformAlertDialog(context, builder: (context) {
|
return () => showPlatformAlertDialog(context, builder: (context) {
|
||||||
@ -59,7 +59,7 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
.headline
|
.headline
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
if (auth.isAnonymous)
|
if (auth == null)
|
||||||
AdaptiveListTile(
|
AdaptiveListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
SpotubeIcons.login,
|
SpotubeIcons.login,
|
||||||
@ -93,10 +93,9 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
child: PlatformText(
|
child: PlatformText(
|
||||||
"Connect with Spotify".toUpperCase()),
|
"Connect with Spotify".toUpperCase()),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
if (auth.isLoggedIn)
|
else
|
||||||
Builder(builder: (context) {
|
Builder(builder: (context) {
|
||||||
Auth auth = ref.watch(authProvider);
|
|
||||||
return PlatformListTile(
|
return PlatformListTile(
|
||||||
leading: const Icon(SpotubeIcons.logout),
|
leading: const Icon(SpotubeIcons.logout),
|
||||||
title: SizedBox(
|
title: SizedBox(
|
||||||
@ -119,7 +118,10 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
MaterialStateProperty.all(Colors.white),
|
MaterialStateProperty.all(Colors.white),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
auth.logout();
|
ref
|
||||||
|
.read(
|
||||||
|
AuthenticationNotifier.provider.notifier)
|
||||||
|
.logout();
|
||||||
GoRouter.of(context).pop();
|
GoRouter.of(context).pop();
|
||||||
},
|
},
|
||||||
child: const PlatformText("Logout"),
|
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:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/generated_secrets.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';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
|
|
||||||
final spotifyProvider = Provider<SpotifyApi>((ref) {
|
final spotifyProvider = Provider<SpotifyApi>((ref) {
|
||||||
Auth authState = ref.watch(authProvider);
|
final authState = ref.watch(AuthenticationNotifier.provider);
|
||||||
final anonCred = PrimitiveUtils.getRandomElement(spotifySecrets);
|
final anonCred = PrimitiveUtils.getRandomElement(spotifySecrets);
|
||||||
|
|
||||||
if (authState.isAnonymous) {
|
if (authState == null) {
|
||||||
return SpotifyApi(
|
return SpotifyApi(
|
||||||
SpotifyApiCredentials(
|
SpotifyApiCredentials(
|
||||||
anonCred["clientId"],
|
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> {
|
abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
||||||
final String cacheKey;
|
final String cacheKey;
|
||||||
|
|
||||||
PersistedStateNotifier(super.state, this.cacheKey) {
|
FutureOr<void> onInit() {}
|
||||||
_load();
|
|
||||||
|
PersistedStateNotifier(
|
||||||
|
super.state,
|
||||||
|
this.cacheKey,
|
||||||
|
) {
|
||||||
|
_load().then((_) => onInit());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _load() async {
|
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:spotube/models/logger.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:spotube/models/lyrics.dart';
|
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/spotube_track.dart';
|
||||||
import 'package:spotube/models/generated_secrets.dart';
|
import 'package:spotube/models/generated_secrets.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
@ -246,23 +245,6 @@ abstract class ServiceUtils {
|
|||||||
return subtitle;
|
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}) {
|
static void navigate(BuildContext context, String location, {Object? extra}) {
|
||||||
GoRouter.of(context).push(location, extra: extra);
|
GoRouter.of(context).push(location, extra: extra);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user