diff --git a/lib/components/Category/CategoryCard.dart b/lib/components/Category/CategoryCard.dart index 99745883..03b7440b 100644 --- a/lib/components/Category/CategoryCard.dart +++ b/lib/components/Category/CategoryCard.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart' hide Page; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Playlist/PlaylistCard.dart'; import 'package:spotube/components/Playlist/PlaylistGenreView.dart'; import 'package:spotube/components/Shared/SpotubePageRoute.dart'; import 'package:spotube/provider/SpotifyDI.dart'; -class CategoryCard extends StatelessWidget { +class CategoryCard extends HookWidget { final Category category; final Iterable? playlists; const CategoryCard( @@ -45,9 +47,10 @@ class CategoryCard extends StatelessWidget { ], ), ), - Consumer( + HookConsumer( builder: (context, ref, child) { SpotifyApi spotifyApi = ref.watch(spotifyProvider); + final scrollController = useScrollController(); return FutureBuilder>( future: playlists == null ? (category.id != "user-featured-playlists" @@ -65,12 +68,18 @@ class CategoryCard extends StatelessWidget { child: CircularProgressIndicator.adaptive(), ); } - return Wrap( - spacing: 20, - runSpacing: 20, - children: snapshot.data! - .map((playlist) => PlaylistCard(playlist)) - .toList(), + return Scrollbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: snapshot.data! + .map((playlist) => PlaylistCard(playlist)) + .toList(), + ), + ), ); }); }, diff --git a/lib/components/Home.dart b/lib/components/Home.dart index 1d0339d5..1fc1f750 100644 --- a/lib/components/Home.dart +++ b/lib/components/Home.dart @@ -2,9 +2,9 @@ import 'dart:io'; import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart' hide Page; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:oauth2/oauth2.dart' show AuthorizationException; import 'package:spotify/spotify.dart' hide Image, Player, Search; @@ -18,6 +18,9 @@ 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/oauth-login.dart'; +import 'package:spotube/hooks/useBreakpointValue.dart'; +import 'package:spotube/hooks/usePagingController.dart'; +import 'package:spotube/hooks/useSharedPreferences.dart'; import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; @@ -32,40 +35,70 @@ List spotifyScopes = [ "playlist-read-collaborative" ]; -class Home extends ConsumerStatefulWidget { +class Home extends HookConsumerWidget { const Home({Key? key}) : super(key: key); @override - _HomeState createState() => _HomeState(); -} + Widget build(BuildContext context, ref) { + Auth auth = ref.watch(authProvider); -class _HomeState extends ConsumerState { - final PagingController _pagingController = - PagingController(firstPageKey: 0); + final pagingController = + usePagingController(firstPageKey: 0); + final int titleBarDragMaxWidth = useBreakpointValue( + md: 72, + lg: 256, + sm: 0, + xl: 0, + xxl: 0, + ); + final _selectedIndex = useState(0); + _onSelectedIndexChanged(int index) => _selectedIndex.value = index; - int _selectedIndex = 0; + final localStorage = useSharedPreferences(); - @override - void initState() { - super.initState(); - WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async { - SharedPreferences localStorage = await SharedPreferences.getInstance(); - String? clientId = localStorage.getString(LocalStorageKeys.clientId); - String? clientSecret = + useEffect(() { + if (localStorage == null) return null; + final String? clientId = + localStorage.getString(LocalStorageKeys.clientId); + final String? clientSecret = localStorage.getString(LocalStorageKeys.clientSecret); - String? accessToken = + final String? accessToken = localStorage.getString(LocalStorageKeys.accessToken); - String? refreshToken = + final String? refreshToken = localStorage.getString(LocalStorageKeys.refreshToken); - String? expirationStr = + final String? expirationStr = localStorage.getString(LocalStorageKeys.expiration); - DateTime? expiration = - expirationStr != null ? DateTime.parse(expirationStr) : null; - try { - Auth auth = ref.read(authProvider); + listener(pageKey) async { + final spotify = ref.read(spotifyProvider); + try { + Page categories = + await spotify.categories.list(country: "US").getPage(15, pageKey); + var items = categories.items!.toList(); + if (pageKey == 0) { + Category category = Category(); + category.id = "user-featured-playlists"; + category.name = "Featured"; + items.insert(0, category); + } + + if (categories.isLast && categories.items != null) { + pagingController.appendLastPage(items); + } else if (categories.items != null) { + pagingController.appendPage(items, categories.nextOffset); + } + } catch (e, stack) { + pagingController.error = e; + print("[Home.pagingController.addPageRequestListener] $e"); + print(stack); + } + } + + try { + final DateTime? expiration = + expirationStr != null ? DateTime.parse(expirationStr) : null; if (clientId != null && clientSecret != null) { - SpotifyApi spotifyApi = SpotifyApi( + SpotifyApi spotify = SpotifyApi( SpotifyApiCredentials( clientId, clientSecret, @@ -75,47 +108,27 @@ class _HomeState extends ConsumerState { scopes: spotifyScopes, ), ); - SpotifyApiCredentials credentials = await spotifyApi.getCredentials(); - if (credentials.accessToken?.isNotEmpty ?? false) { - auth.setAuthState( - clientId: clientId, - clientSecret: clientSecret, - accessToken: - credentials.accessToken, // accessToken can be new/refreshed - refreshToken: refreshToken, - expiration: credentials.expiration, - isLoggedIn: true, - ); - } + spotify.getCredentials().then((credentials) { + if (credentials.accessToken?.isNotEmpty ?? false) { + auth.setAuthState( + clientId: clientId, + clientSecret: clientSecret, + accessToken: + credentials.accessToken, // accessToken can be new/refreshed + refreshToken: refreshToken, + expiration: credentials.expiration, + isLoggedIn: true, + ); + } + return null; + }).then((_) { + pagingController.addPageRequestListener(listener); + }); } - _pagingController.addPageRequestListener((pageKey) async { - try { - SpotifyApi spotifyApi = ref.read(spotifyProvider); - Page categories = await spotifyApi.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) { - _pagingController.error = e; - } - }); } on AuthorizationException catch (_) { if (clientId != null && clientSecret != null) { oauthLogin( - ref.read(authProvider), + auth, clientId: clientId, clientSecret: clientSecret, ); @@ -124,23 +137,11 @@ class _HomeState extends ConsumerState { print("[Home.initState]: $e"); print(stack); } - }); - } + return () { + pagingController.removePageRequestListener(listener); + }; + }, [localStorage]); - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - _onSelectedIndexChanged(int index) => setState(() { - _selectedIndex = index; - }); - - @override - Widget build(BuildContext context) { - Auth auth = ref.watch(authProvider); - final width = MediaQuery.of(context).size.width; if (!auth.isLoggedIn) { return const Login(); } @@ -156,11 +157,8 @@ class _HomeState extends ConsumerState { children: [ Container( constraints: BoxConstraints( - maxWidth: width > 400 && width <= 700 - ? 72 - : width > 700 - ? 256 - : 0), + maxWidth: titleBarDragMaxWidth.toDouble(), + ), color: Theme.of(context).navigationRailTheme.backgroundColor, child: MoveWindow(), @@ -176,16 +174,16 @@ class _HomeState extends ConsumerState { child: Row( children: [ Sidebar( - selectedIndex: _selectedIndex, + selectedIndex: _selectedIndex.value, onSelectedIndexChanged: _onSelectedIndexChanged, ), // contents of the spotify - if (_selectedIndex == 0) + if (_selectedIndex.value == 0) Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: PagedListView( - pagingController: _pagingController, + pagingController: pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { return CategoryCard(item); @@ -194,16 +192,16 @@ class _HomeState extends ConsumerState { ), ), ), - if (_selectedIndex == 1) const Search(), - if (_selectedIndex == 2) const UserLibrary(), - if (_selectedIndex == 3) const Lyrics(), + if (_selectedIndex.value == 1) const Search(), + if (_selectedIndex.value == 2) const UserLibrary(), + if (_selectedIndex.value == 3) const Lyrics(), ], ), ), // player itself const Player(), SpotubeNavigationBar( - selectedIndex: _selectedIndex, + selectedIndex: _selectedIndex.value, onSelectedIndexChanged: _onSelectedIndexChanged, ), ], diff --git a/lib/components/Home/Sidebar.dart b/lib/components/Home/Sidebar.dart index f7b5be46..4cb651a4 100644 --- a/lib/components/Home/Sidebar.dart +++ b/lib/components/Home/Sidebar.dart @@ -6,6 +6,7 @@ import 'package:spotify/spotify.dart' hide Image; import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/Shared/SpotubePageRoute.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; +import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import '../../models/sideBarTiles.dart'; @@ -28,7 +29,7 @@ class Sidebar extends HookConsumerWidget { ); } - void _goToSettings(BuildContext context) { + static void goToSettings(BuildContext context) { Navigator.of(context).push(SpotubePageRoute( child: const Settings(), )); @@ -36,30 +37,35 @@ class Sidebar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final width = MediaQuery.of(context).size.width; - if (width <= 400) return Container(); + final breakpoints = useBreakpoints(); + if (breakpoints.isSm) return Container(); final extended = useState(false); final SpotifyApi spotify = ref.watch(spotifyProvider); + useEffect(() { - if (width <= 700 && extended.value) { + if (breakpoints.isMd && extended.value) { extended.value = false; - } else if (width > 700 && !extended.value) { + } else if (breakpoints.isMoreThanOrEqualTo(Breakpoints.lg) && + !extended.value) { extended.value = true; } + return null; }); return NavigationRail( destinations: sidebarTileList - .map((e) => NavigationRailDestination( - icon: Icon(e.icon), - label: Text( - e.title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), + .map( + (e) => NavigationRailDestination( + icon: Icon(e.icon), + label: Text( + e.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, ), - )) + ), + ), + ) .toList(), selectedIndex: selectedIndex, onDestinationSelected: onSelectedIndexChanged, @@ -104,11 +110,11 @@ class Sidebar extends HookConsumerWidget { ), IconButton( icon: const Icon(Icons.settings_outlined), - onPressed: () => _goToSettings(context)), + onPressed: () => goToSettings(context)), ], )) : InkWell( - onTap: () => _goToSettings(context), + onTap: () => goToSettings(context), child: CircleAvatar( backgroundImage: CachedNetworkImageProvider(avatarImg), ), diff --git a/lib/components/Home/SpotubeNavigationBar.dart b/lib/components/Home/SpotubeNavigationBar.dart index f57383ad..54d78012 100644 --- a/lib/components/Home/SpotubeNavigationBar.dart +++ b/lib/components/Home/SpotubeNavigationBar.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:spotube/components/Home/Sidebar.dart'; +import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/models/sideBarTiles.dart'; class SpotubeNavigationBar extends HookWidget { @@ -14,17 +16,21 @@ class SpotubeNavigationBar extends HookWidget { @override Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width; + final breakpoint = useBreakpoints(); - if (width > 400) return Container(); + if (breakpoint.isMoreThan(Breakpoints.sm)) return Container(); return NavigationBar( - destinations: sidebarTileList - .map( - (e) => NavigationDestination(icon: Icon(e.icon), label: e.title), - ) - .toList(), + destinations: [ + ...sidebarTileList.map( + (e) => NavigationDestination(icon: Icon(e.icon), label: e.title), + ), + const NavigationDestination( + icon: Icon(Icons.settings_rounded), + label: "Settings", + ) + ], selectedIndex: selectedIndex, - onDestinationSelected: onSelectedIndexChanged, + onDestinationSelected: (i) => Sidebar.goToSettings(context), ); } } diff --git a/lib/components/Playlist/PlaylistCard.dart b/lib/components/Playlist/PlaylistCard.dart index 2da322ee..8c2949e1 100644 --- a/lib/components/Playlist/PlaylistCard.dart +++ b/lib/components/Playlist/PlaylistCard.dart @@ -17,6 +17,7 @@ class PlaylistCard extends ConsumerWidget { bool isPlaylistPlaying = playback.currentPlaylist != null && playback.currentPlaylist!.id == playlist.id; return PlaybuttonCard( + margin: const EdgeInsets.symmetric(horizontal: 20), title: playlist.name!, imageUrl: playlist.images![0].url!, isPlaying: isPlaylistPlaying, diff --git a/lib/components/Shared/PlaybuttonCard.dart b/lib/components/Shared/PlaybuttonCard.dart index d0561b2b..4fcddd16 100644 --- a/lib/components/Shared/PlaybuttonCard.dart +++ b/lib/components/Shared/PlaybuttonCard.dart @@ -5,6 +5,7 @@ class PlaybuttonCard extends StatelessWidget { final void Function()? onTap; final void Function()? onPlaybuttonPressed; final String? description; + final EdgeInsetsGeometry? margin; final String imageUrl; final bool isPlaying; final String title; @@ -12,6 +13,7 @@ class PlaybuttonCard extends StatelessWidget { required this.imageUrl, required this.isPlaying, required this.title, + this.margin, this.description, this.onPlaybuttonPressed, this.onTap, @@ -20,85 +22,88 @@ class PlaybuttonCard extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200), - child: Ink( - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - blurRadius: 10, - offset: const Offset(0, 3), - spreadRadius: 5, - color: Theme.of(context).shadowColor) - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // thumbnail of the playlist - Stack( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(8), - child: CachedNetworkImage( - imageUrl: imageUrl, - placeholder: (context, url) => - Image.asset("assets/placeholder.png"), - ), - ), - Positioned.directional( - textDirection: TextDirection.ltr, - bottom: 10, - end: 5, - child: Builder(builder: (context) { - return ElevatedButton( - onPressed: onPlaybuttonPressed, - child: Icon( - isPlaying - ? Icons.pause_rounded - : Icons.play_arrow_rounded, - ), - style: ButtonStyle( - shape: MaterialStateProperty.all( - const CircleBorder(), - ), - padding: MaterialStateProperty.all( - const EdgeInsets.all(16), - ), - ), - ); - }), - ) - ], - ), - const SizedBox(height: 5), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 10), - child: Column( + return Container( + margin: margin, + child: InkWell( + onTap: onTap, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: Ink( + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: const Offset(0, 3), + spreadRadius: 5, + color: Theme.of(context).shadowColor) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // thumbnail of the playlist + Stack( children: [ - Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: imageUrl, + placeholder: (context, url) => + Image.asset("assets/placeholder.png"), + ), ), - if (description != null) ...[ - const SizedBox(height: 10), - Text( - description!, - style: TextStyle( - fontSize: 13, - color: Theme.of(context).textTheme.headline4?.color, - ), - ) - ] + Positioned.directional( + textDirection: TextDirection.ltr, + bottom: 10, + end: 5, + child: Builder(builder: (context) { + return ElevatedButton( + onPressed: onPlaybuttonPressed, + child: Icon( + isPlaying + ? Icons.pause_rounded + : Icons.play_arrow_rounded, + ), + style: ButtonStyle( + shape: MaterialStateProperty.all( + const CircleBorder(), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.all(16), + ), + ), + ); + }), + ) ], ), - ) - ], + const SizedBox(height: 5), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 10), + child: Column( + children: [ + Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + if (description != null) ...[ + const SizedBox(height: 10), + Text( + description!, + style: TextStyle( + fontSize: 13, + color: Theme.of(context).textTheme.headline4?.color, + ), + ) + ] + ], + ), + ) + ], + ), ), ), ), diff --git a/lib/hooks/useAsyncEffect.dart b/lib/hooks/useAsyncEffect.dart new file mode 100644 index 00000000..8af25543 --- /dev/null +++ b/lib/hooks/useAsyncEffect.dart @@ -0,0 +1,18 @@ +import 'dart:async'; + +import 'package:flutter_hooks/flutter_hooks.dart'; + +void useAsyncEffect( + FutureOr Function() effect, [ + FutureOr Function()? cleanup, + List? keys, +]) { + useEffect(() { + Future.microtask(effect); + return () { + if (cleanup != null) { + Future.microtask(cleanup); + } + }; + }, keys); +} diff --git a/lib/hooks/useBreakpointValue.dart b/lib/hooks/useBreakpointValue.dart new file mode 100644 index 00000000..58240630 --- /dev/null +++ b/lib/hooks/useBreakpointValue.dart @@ -0,0 +1,17 @@ +import 'package:spotube/hooks/useBreakpoints.dart'; + +useBreakpointValue({sm, md, lg, xl, xxl}) { + final breakpoint = useBreakpoints(); + + if (breakpoint.isSm) { + return sm; + } else if (breakpoint.isMd) { + return md; + } else if (breakpoint.isXl) { + return xl; + } else if (breakpoint.isXxl) { + return xxl; + } else { + return lg; + } +} diff --git a/lib/hooks/useBreakpoints.dart b/lib/hooks/useBreakpoints.dart new file mode 100644 index 00000000..73fb0276 --- /dev/null +++ b/lib/hooks/useBreakpoints.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class BreakpointUtils { + Breakpoints breakpoint; + List breakpointList = [ + Breakpoints.sm, + Breakpoints.md, + Breakpoints.lg, + Breakpoints.xl, + Breakpoints.xxl + ]; + BreakpointUtils(this.breakpoint); + + get isSm => breakpoint == Breakpoints.sm; + get isMd => breakpoint == Breakpoints.md; + get isLg => breakpoint == Breakpoints.lg; + get isXl => breakpoint == Breakpoints.xl; + get isXxl => breakpoint == Breakpoints.xxl; + + bool isMoreThanOrEqualTo(Breakpoints b) { + return breakpointList + .sublist(breakpointList.indexOf(b)) + .contains(breakpoint); + } + + bool isLessThanOrEqualTo(Breakpoints b) { + return breakpointList + .sublist(0, breakpointList.indexOf(b) + 1) + .contains(breakpoint); + } + + bool isMoreThan(Breakpoints b) { + return breakpointList + .sublist(breakpointList.indexOf(b) + 1) + .contains(breakpoint); + } + + bool isLessThan(Breakpoints b) { + return breakpointList + .sublist(0, breakpointList.indexOf(b)) + .contains(breakpoint); + } + + bool operator >(other) { + return isMoreThan(other); + } + + bool operator <(other) { + return isLessThan(other); + } + + bool operator >=(other) { + return isMoreThanOrEqualTo(other); + } + + bool operator <=(other) { + return isLessThanOrEqualTo(other); + } +} + +enum Breakpoints { sm, md, lg, xl, xxl } + +BreakpointUtils useBreakpoints() { + final context = useContext(); + final width = MediaQuery.of(context).size.width; + final breakpoint = useState(Breakpoints.lg); + final utils = BreakpointUtils(breakpoint.value); + + useEffect(() { + if (width >= 1920 && breakpoint.value != Breakpoints.xxl) { + breakpoint.value = Breakpoints.xxl; + } else if (width >= 1366 && + width < 1920 && + breakpoint.value != Breakpoints.xl) { + breakpoint.value = Breakpoints.xl; + } else if (width >= 768 && + width < 1366 && + breakpoint.value != Breakpoints.lg) { + breakpoint.value = Breakpoints.lg; + } else if (width >= 360 && + width < 768 && + breakpoint.value != Breakpoints.md) { + breakpoint.value = Breakpoints.md; + } else if (width >= 250 && + width < 360 && + breakpoint.value != Breakpoints.sm) { + breakpoint.value = Breakpoints.sm; + } + return null; + }, [width]); + + useEffect(() { + utils.breakpoint = breakpoint.value; + return null; + }, [breakpoint.value]); + + return utils; +} diff --git a/lib/hooks/usePagingController.dart b/lib/hooks/usePagingController.dart new file mode 100644 index 00000000..a32dfa0c --- /dev/null +++ b/lib/hooks/usePagingController.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; + +PagingController + usePagingController({ + required final PageKeyType firstPageKey, + final int? invisibleItemsThreshold, + List? keys, +}) { + return use( + _PagingControllerHook( + firstPageKey: firstPageKey, + invisibleItemsThreshold: invisibleItemsThreshold, + keys: keys, + ), + ); +} + +class _PagingControllerHook + extends Hook> { + const _PagingControllerHook({ + required this.firstPageKey, + this.invisibleItemsThreshold, + List? keys, + }) : super(keys: keys); + + final PageKeyType firstPageKey; + final int? invisibleItemsThreshold; + + @override + HookState, + Hook>> + createState() => _PagingControllerHookState(); +} + +class _PagingControllerHookState extends HookState< + PagingController, + _PagingControllerHook> { + late final controller = PagingController( + firstPageKey: hook.firstPageKey, + invisibleItemsThreshold: hook.invisibleItemsThreshold); + + @override + PagingController build(BuildContext context) => + controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'usePagingController'; +} diff --git a/lib/hooks/useSharedPreferences.dart b/lib/hooks/useSharedPreferences.dart new file mode 100644 index 00000000..922beaa6 --- /dev/null +++ b/lib/hooks/useSharedPreferences.dart @@ -0,0 +1,9 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +SharedPreferences? useSharedPreferences() { + final future = useMemoized(SharedPreferences.getInstance); + final snapshot = useFuture(future, initialData: null); + + return snapshot.data; +} diff --git a/lib/provider/Auth.dart b/lib/provider/Auth.dart index d5e69dc2..af18e84e 100644 --- a/lib/provider/Auth.dart +++ b/lib/provider/Auth.dart @@ -52,6 +52,11 @@ class Auth with ChangeNotifier { _isLoggedIn = false; notifyListeners(); } + + @override + String toString() { + return "Auth(clientId: $clientId, clientSecret: $clientSecret, accessToken: $accessToken, refreshToken: $refreshToken, expiration: $expiration, isLoggedIn: $isLoggedIn)"; + } } var authProvider = ChangeNotifierProvider((ref) => Auth()); diff --git a/lib/provider/SpotifyDI.dart b/lib/provider/SpotifyDI.dart index acfe96e2..6e03466e 100644 --- a/lib/provider/SpotifyDI.dart +++ b/lib/provider/SpotifyDI.dart @@ -7,6 +7,7 @@ import 'package:spotube/provider/Auth.dart'; var spotifyProvider = Provider((ref) { Auth authState = ref.watch(authProvider); + return SpotifyApi( SpotifyApiCredentials( authState.clientId,