From b3c27d1fca233ea079803fe49134abc528376df3 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 12 Nov 2022 13:30:21 +0600 Subject: [PATCH] feat: titlebar complete compatibility, platform specific login, library tabbar in titlebar --- lib/components/Home/Genres.dart | 2 + lib/components/Home/Shell.dart | 28 +- lib/components/Home/Sidebar.dart | 27 +- lib/components/Library/UserAlbums.dart | 6 + lib/components/Library/UserArtists.dart | 6 + lib/components/Library/UserLibrary.dart | 61 +- lib/components/Library/UserPlaylists.dart | 7 + lib/components/Login/LoginTutorial.dart | 49 +- lib/components/Login/TokenLogin.dart | 19 +- lib/components/Login/TokenLoginForms.dart | 20 +- lib/components/Login/WebViewLogin.dart | 3 +- lib/components/Lyrics/Lyrics.dart | 101 ++-- lib/components/Player/PlayerView.dart | 14 +- lib/components/Search/Search.dart | 541 +++++++++--------- lib/components/Shared/AnonymousFallback.dart | 4 +- lib/components/Shared/PageWindowTitleBar.dart | 6 + 16 files changed, 444 insertions(+), 450 deletions(-) diff --git a/lib/components/Home/Genres.dart b/lib/components/Home/Genres.dart index 558a19b0e..0b0677f08 100644 --- a/lib/components/Home/Genres.dart +++ b/lib/components/Home/Genres.dart @@ -6,6 +6,7 @@ import 'package:platform_ui/platform_ui.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Category/CategoryCard.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerCategories.dart'; +import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/Waypoint.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyRequests.dart'; @@ -42,6 +43,7 @@ class Genres extends HookConsumerWidget { ]; return PlatformScaffold( + appBar: PageWindowTitleBar(), body: ListView.builder( itemCount: categories.length, itemBuilder: (context, index) { diff --git a/lib/components/Home/Shell.dart b/lib/components/Home/Shell.dart index 7518cddc3..b9e85605f 100644 --- a/lib/components/Home/Shell.dart +++ b/lib/components/Home/Shell.dart @@ -64,40 +64,14 @@ class Shell extends HookConsumerWidget { return null; }, [backgroundColor]); - final allowedPath = - rootPaths.values.contains(GoRouter.of(context).location); - final titleBar = PageWindowTitleBar( - backgroundColor: - platform == TargetPlatform.android ? Colors.transparent : null, - ); - final preferredSize = allowedPath ? titleBar.preferredSize : Size.zero; - var appBar = kIsDesktop - ? PreferredSize( - preferredSize: preferredSize, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - height: allowedPath ? titleBar.preferredSize.height : 0, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 250), - opacity: allowedPath ? 1 : 0, - child: titleBar, - ), - ), - ) - : null; return PlatformScaffold( - appBar: platform == TargetPlatform.windows ? appBar : null, - extendBodyBehindAppBar: false, body: Sidebar( selectedIndex: index.value, onSelectedIndexChanged: (i) { index.value = i; GoRouter.of(context).go(rootPaths[index.value]!); }, - child: PlatformScaffold( - appBar: platform != TargetPlatform.windows ? appBar : null, - body: child, - ), + child: child, ), extendBody: true, bottomNavigationBar: Column( diff --git a/lib/components/Home/Sidebar.dart b/lib/components/Home/Sidebar.dart index 673fe4f95..c7557392d 100644 --- a/lib/components/Home/Sidebar.dart +++ b/lib/components/Home/Sidebar.dart @@ -133,18 +133,21 @@ class Sidebar extends HookConsumerWidget { ), ), (extended.value) - ? Row( - children: [ - brandLogo(), - const SizedBox( - width: 10, - ), - PlatformText.headline("Spotube"), - PlatformIconButton( - icon: const Icon(Icons.menu_rounded), - onPressed: toggleExtended, - ), - ], + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + brandLogo(), + const SizedBox( + width: 10, + ), + PlatformText.headline("Spotube"), + PlatformIconButton( + icon: const Icon(Icons.menu_rounded), + onPressed: toggleExtended, + ), + ], + ), ) : brandLogo(), ], diff --git a/lib/components/Library/UserAlbums.dart b/lib/components/Library/UserAlbums.dart index 2ac63cafc..30c7f4ca7 100644 --- a/lib/components/Library/UserAlbums.dart +++ b/lib/components/Library/UserAlbums.dart @@ -4,6 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotube/components/Album/AlbumCard.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart'; +import 'package:spotube/components/Shared/AnonymousFallback.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyRequests.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -13,6 +15,10 @@ class UserAlbums extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final auth = ref.watch(authProvider); + if (auth.isAnonymous) { + return const AnonymousFallback(); + } final albumsQuery = useQuery( job: currentUserAlbumsQueryJob, externalData: ref.watch(spotifyProvider), diff --git a/lib/components/Library/UserArtists.dart b/lib/components/Library/UserArtists.dart index 981027691..757193a13 100644 --- a/lib/components/Library/UserArtists.dart +++ b/lib/components/Library/UserArtists.dart @@ -5,7 +5,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/Artist/ArtistCard.dart'; +import 'package:spotube/components/Shared/AnonymousFallback.dart'; import 'package:spotube/components/Shared/Waypoint.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyRequests.dart'; @@ -14,6 +16,10 @@ class UserArtists extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final auth = ref.watch(authProvider); + if (auth.isAnonymous) { + return const AnonymousFallback(); + } final artistQuery = useInfiniteQuery( job: currentUserFollowingArtistsQueryJob, externalData: ref.watch(spotifyProvider), diff --git a/lib/components/Library/UserLibrary.dart b/lib/components/Library/UserLibrary.dart index 24718674b..98da2d1aa 100644 --- a/lib/components/Library/UserLibrary.dart +++ b/lib/components/Library/UserLibrary.dart @@ -1,47 +1,48 @@ import 'package:flutter/material.dart' hide Image; -import 'package:flutter_riverpod/flutter_riverpod.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/components/Library/UserAlbums.dart'; import 'package:spotube/components/Library/UserArtists.dart'; import 'package:spotube/components/Library/UserDownloads.dart'; import 'package:spotube/components/Library/UserLocalTracks.dart'; import 'package:spotube/components/Library/UserPlaylists.dart'; -import 'package:spotube/components/Shared/AnonymousFallback.dart'; +import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; -class UserLibrary extends ConsumerWidget { +class UserLibrary extends HookConsumerWidget { const UserLibrary({Key? key}) : super(key: key); @override Widget build(BuildContext context, ref) { - return DefaultTabController( - length: 5, - child: SafeArea( - child: PlatformTabView( + final index = useState(0); + + final body = [ + const UserPlaylists(), + const UserLocalTracks(), + const UserDownloads(), + const UserArtists(), + const UserAlbums(), + ][index.value]; + + return PlatformScaffold( + appBar: PageWindowTitleBar( + titleWidth: 415, + centerTitle: true, + center: PlatformTabBar( androidIsScrollable: true, - placement: PlatformProperty.all(PlatformTabbarPlacement.top), - body: { - PlatformTab( - label: "Playlist", - icon: Container(), - ): const AnonymousFallback(child: UserPlaylists()), - PlatformTab( - label: "Downloads", - icon: Container(), - ): const UserDownloads(), - PlatformTab( - label: "Local", - icon: Container(), - ): const UserLocalTracks(), - PlatformTab( - label: "Artists", - icon: Container(), - ): const AnonymousFallback(child: UserArtists()), - PlatformTab( - label: "Album", - icon: Container(), - ): const AnonymousFallback(child: UserAlbums()), - }, + selectedIndex: index.value, + onSelectedIndexChanged: (value) => index.value = value, + isNavigational: + PlatformProperty.byPlatformGroup(mobile: false, desktop: true), + tabs: [ + PlatformTab(label: 'Playlists', icon: const SizedBox.shrink()), + PlatformTab(label: 'Tracks', icon: const SizedBox.shrink()), + PlatformTab(label: 'Downloads', icon: const SizedBox.shrink()), + PlatformTab(label: 'Artists', icon: const SizedBox.shrink()), + PlatformTab(label: 'Albums', icon: const SizedBox.shrink()), + ], ), ), + body: body, ); } } diff --git a/lib/components/Library/UserPlaylists.dart b/lib/components/Library/UserPlaylists.dart index ecfb639b7..8fdfcefa0 100644 --- a/lib/components/Library/UserPlaylists.dart +++ b/lib/components/Library/UserPlaylists.dart @@ -6,6 +6,8 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart'; import 'package:spotube/components/Playlist/PlaylistCard.dart'; import 'package:spotube/components/Playlist/PlaylistCreateDialog.dart'; +import 'package:spotube/components/Shared/AnonymousFallback.dart'; +import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyRequests.dart'; @@ -14,6 +16,11 @@ class UserPlaylists extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final auth = ref.watch(authProvider); + if (auth.isAnonymous) { + return const AnonymousFallback(); + } + final playlistsQuery = useQuery( job: currentUserPlaylistsQueryJob, externalData: ref.watch(spotifyProvider), diff --git a/lib/components/Login/LoginTutorial.dart b/lib/components/Login/LoginTutorial.dart index 6d15578ec..4a0ec3a3d 100644 --- a/lib/components/Login/LoginTutorial.dart +++ b/lib/components/Login/LoginTutorial.dart @@ -14,46 +14,56 @@ class LoginTutorial extends ConsumerWidget { @override Widget build(BuildContext context, ref) { final auth = ref.watch(authProvider); + final key = GlobalKey>(); - return Scaffold( + return PlatformScaffold( appBar: PageWindowTitleBar( leading: PlatformTextButton( - child: const Text("Exit"), + child: const PlatformText("Exit"), onPressed: () { Navigator.of(context).pop(); }, ), ), body: IntroductionScreen( - next: const Text("Next"), - back: const Text("Previous"), + key: key, + overrideBack: PlatformFilledButton( + isSecondary: true, + child: const Center(child: PlatformText("Previous")), + onPressed: () { + (key.currentState as IntroductionScreenState).previous(); + }, + ), + overrideNext: PlatformFilledButton( + child: const Center(child: PlatformText("Next")), + onPressed: () { + (key.currentState as IntroductionScreenState).next(); + }, + ), showBackButton: true, - overrideDone: PlatformTextButton( + overrideDone: PlatformFilledButton( onPressed: auth.isLoggedIn ? () { ServiceUtils.navigate(context, "/"); } : null, - child: const Text("Done"), + child: const Center(child: PlatformText("Done")), ), pages: [ PageViewModel( title: "Step 1", image: Image.asset("assets/tutorial/step-1.png"), bodyWidget: Wrap( - children: [ - Text( + children: const [ + PlatformText( "First, Go to ", - style: Theme.of(context).textTheme.bodyText1, ), Hyperlink( "accounts.spotify.com ", "https://accounts.spotify.com", - style: Theme.of(context).textTheme.bodyText1!, ), - Text( + PlatformText( "and Login/Sign up if you're not logged in", - style: Theme.of(context).textTheme.bodyText1, ), ], ), @@ -61,10 +71,9 @@ class LoginTutorial extends ConsumerWidget { PageViewModel( title: "Step 2", image: Image.asset("assets/tutorial/step-2.png"), - bodyWidget: Text( + bodyWidget: const PlatformText( "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection", textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyText1, ), ), PageViewModel( @@ -72,10 +81,9 @@ class LoginTutorial extends ConsumerWidget { image: Image.asset( "assets/tutorial/step-3.png", ), - bodyWidget: Text( + bodyWidget: const PlatformText( "Copy the values of \"sp_dc\" and \"sp_key\" Cookies", textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyText1, ), ), if (auth.isLoggedIn) @@ -92,13 +100,12 @@ class LoginTutorial extends ConsumerWidget { PageViewModel( title: "Step 5", bodyWidget: Column( - children: [ - Text( + children: const [ + PlatformText( "Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields", - style: Theme.of(context).textTheme.bodyText1, ), - const SizedBox(height: 10), - const TokenLoginForm(), + SizedBox(height: 10), + TokenLoginForm(), ], ), ), diff --git a/lib/components/Login/TokenLogin.dart b/lib/components/Login/TokenLogin.dart index 8528d3d01..681818f53 100644 --- a/lib/components/Login/TokenLogin.dart +++ b/lib/components/Login/TokenLogin.dart @@ -15,12 +15,17 @@ class TokenLogin extends HookConsumerWidget { final textTheme = Theme.of(context).textTheme; return SafeArea( - child: Scaffold( - appBar: PageWindowTitleBar(leading: PlatformBackButton()), + child: PlatformScaffold( + appBar: PageWindowTitleBar(leading: const PlatformBackButton()), body: SingleChildScrollView( child: Center( child: Container( - margin: const EdgeInsets.symmetric(horizontal: 10), + margin: const EdgeInsets.all(10), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: PlatformTheme.of(context).secondaryBackgroundColor, + borderRadius: BorderRadius.circular(10), + ), child: Column( children: [ Image.asset( @@ -28,11 +33,11 @@ class TokenLogin extends HookConsumerWidget { width: MediaQuery.of(context).size.width * (breakpoint <= Breakpoints.md ? .5 : .3), ), - Text("Add your spotify credentials to get started", + PlatformText("Add your spotify credentials to get started", style: breakpoint <= Breakpoints.md ? textTheme.headline5 : textTheme.headline4), - Text( + PlatformText( "Don't worry, any of your credentials won't be collected or shared with anyone", style: Theme.of(context).textTheme.caption, ), @@ -45,9 +50,9 @@ class TokenLogin extends HookConsumerWidget { alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ - const Text("Don't know how to do this?"), + const PlatformText("Don't know how to do this?"), PlatformTextButton( - child: const Text( + child: const PlatformText( "Follow along the Step by Step guide", ), onPressed: () => GoRouter.of(context).push( diff --git a/lib/components/Login/TokenLoginForms.dart b/lib/components/Login/TokenLoginForms.dart index 76ee5a5c3..85f7c77d7 100644 --- a/lib/components/Login/TokenLoginForms.dart +++ b/lib/components/Login/TokenLoginForms.dart @@ -25,21 +25,17 @@ class TokenLoginForm extends HookConsumerWidget { ), child: Column( children: [ - TextField( + PlatformTextField( controller: directCodeController, - decoration: const InputDecoration( - hintText: "Spotify \"sp_dc\" Cookie", - label: Text("sp_dc Cookie"), - ), + placeholder: "Spotify \"sp_dc\" Cookie", + label: "sp_dc Cookie", keyboardType: TextInputType.visiblePassword, ), const SizedBox(height: 10), - TextField( + PlatformTextField( controller: keyCodeController, - decoration: const InputDecoration( - hintText: "Spotify \"sp_key\" Cookie", - label: Text("sp_key Cookie"), - ), + placeholder: "Spotify \"sp_key\" Cookie", + label: "sp_key Cookie", keyboardType: TextInputType.visiblePassword, ), const SizedBox(height: 20), @@ -49,7 +45,7 @@ class TokenLoginForm extends HookConsumerWidget { directCodeController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text("Please fill in all fields"), + content: PlatformText("Please fill in all fields"), behavior: SnackBarBehavior.floating, ), ); @@ -68,7 +64,7 @@ class TokenLoginForm extends HookConsumerWidget { onDone?.call(); } }, - child: const Text("Submit"), + child: const PlatformText("Submit"), ) ], ), diff --git a/lib/components/Login/WebViewLogin.dart b/lib/components/Login/WebViewLogin.dart index 4ab840161..32f29a35e 100644 --- a/lib/components/Login/WebViewLogin.dart +++ b/lib/components/Login/WebViewLogin.dart @@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; 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.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -23,7 +24,7 @@ class WebViewLogin extends HookConsumerWidget { ); } - return Scaffold( + return PlatformScaffold( body: SafeArea( child: InAppWebView( initialOptions: InAppWebViewGroupOptions( diff --git a/lib/components/Lyrics/Lyrics.dart b/lib/components/Lyrics/Lyrics.dart index 225e13be0..99b1509a0 100644 --- a/lib/components/Lyrics/Lyrics.dart +++ b/lib/components/Lyrics/Lyrics.dart @@ -3,38 +3,20 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:palette_generator/palette_generator.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotube/components/Lyrics/GeniusLyrics.dart'; import 'package:spotube/components/Lyrics/SyncedLyrics.dart'; +import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/UniversalImage.dart'; import 'package:spotube/hooks/useCustomStatusBarColor.dart'; import 'package:spotube/hooks/usePaletteColor.dart'; import 'package:spotube/provider/Playback.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class Lyrics extends HookConsumerWidget { const Lyrics({Key? key}) : super(key: key); - Widget buildContainer(Widget child, String albumArt, PaletteColor palette) { - return Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - image: DecorationImage( - image: UniversalImage.imageProvider(albumArt), - fit: BoxFit.cover, - ), - ), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: Container( - color: palette.color.withOpacity(.7), - child: child, - ), - ), - ); - } - @override Widget build(BuildContext context, ref) { Playback playback = ref.watch(playbackProvider); @@ -47,6 +29,7 @@ class Lyrics extends HookConsumerWidget { [playback.track?.album?.images], ); final palette = usePaletteColor(albumArt, ref); + final index = useState(0); useCustomStatusBarColor( palette.color, @@ -54,52 +37,42 @@ class Lyrics extends HookConsumerWidget { noSetBGColor: true, ); - return SafeArea( - child: PlatformTabView( - body: { - PlatformTab( - label: "Synced Lyrics", - icon: Container(), - ): buildContainer(SyncedLyrics(palette: palette), albumArt, palette), - PlatformTab( - label: "Lyrics (genius.com)", - icon: Container(), - ): buildContainer(GeniusLyrics(palette: palette), albumArt, palette), - }, - ), - ); + final body = [ + SyncedLyrics(palette: palette), + GeniusLyrics(palette: palette), + ][index.value]; - return SafeArea( - child: Scaffold( - extendBodyBehindAppBar: true, - appBar: const TabBar( - isScrollable: true, - tabs: [ - Tab(text: "Synced Lyrics"), - Tab(text: "Lyrics (genius.com)"), - ], - ), - body: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - image: DecorationImage( - image: UniversalImage.imageProvider(albumArt), - fit: BoxFit.cover, - ), - ), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: Container( - color: palette.color.withOpacity(.7), - child: SafeArea( - child: TabBarView( - children: [ - SyncedLyrics(palette: palette), - GeniusLyrics(palette: palette), - ], - ), + return PlatformScaffold( + extendBodyBehindAppBar: true, + appBar: !kIsMacOS + ? PageWindowTitleBar( + backgroundColor: Colors.transparent, + toolbarOpacity: 0, + center: PlatformTabBar( + isNavigational: + PlatformProperty.only(linux: true, other: false), + selectedIndex: index.value, + onSelectedIndexChanged: (value) => index.value = value, + tabs: [ + PlatformTab(label: "Synced", icon: const SizedBox.shrink()), + PlatformTab(label: "Genius", icon: const SizedBox.shrink()), + ], ), - ), + ) + : null, + body: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + image: DecorationImage( + image: UniversalImage.imageProvider(albumArt), + fit: BoxFit.cover, + ), + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: palette.color.withOpacity(.7), + child: SafeArea(child: body), ), ), ), diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index 0878a2967..624296eab 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -58,7 +58,14 @@ class PlayerView extends HookConsumerWidget { noSetBGColor: true, ); - return Scaffold( + return PlatformScaffold( + appBar: PageWindowTitleBar( + backgroundColor: Colors.transparent, + foregroundColor: paletteColor.titleTextColor, + toolbarOpacity: 0, + automaticallyImplyLeading: true, + ), + extendBodyBehindAppBar: true, body: Container( decoration: BoxDecoration( image: DecorationImage( @@ -74,11 +81,6 @@ class PlayerView extends HookConsumerWidget { child: SafeArea( child: Column( children: [ - PageWindowTitleBar( - leading: const PlatformBackButton(), - backgroundColor: Colors.transparent, - foregroundColor: paletteColor.titleTextColor, - ), Padding( padding: const EdgeInsets.all(10), child: Column( diff --git a/lib/components/Search/Search.dart b/lib/components/Search/Search.dart index aad3dfc01..81be822b6 100644 --- a/lib/components/Search/Search.dart +++ b/lib/components/Search/Search.dart @@ -10,6 +10,7 @@ import 'package:spotube/components/Artist/ArtistCard.dart'; import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart'; import 'package:spotube/components/Playlist/PlaylistCard.dart'; import 'package:spotube/components/Shared/AnonymousFallback.dart'; +import 'package:spotube/components/Shared/PageWindowTitleBar.dart'; import 'package:spotube/components/Shared/TrackTile.dart'; import 'package:spotube/components/Shared/Waypoint.dart'; import 'package:spotube/hooks/useBreakpoints.dart'; @@ -18,6 +19,7 @@ import 'package:spotube/provider/Auth.dart'; import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyRequests.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/primitive_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:tuple/tuple.dart'; @@ -57,10 +59,6 @@ class Search extends HookConsumerWidget { job: searchQueryJob(SearchType.artist.key), externalData: getVariables()); - if (auth.isAnonymous) { - return const AnonymousFallback(); - } - void onSearch() { for (final query in [ searchTrack, @@ -75,281 +73,288 @@ class Search extends HookConsumerWidget { } } - return SafeArea( - child: Material( - color: PlatformTheme.of(context).scaffoldBackgroundColor, - textStyle: PlatformTheme.of(context).textTheme!.body!, - child: Column( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, - ), - child: PlatformTextField( - onChanged: (value) { - ref.read(searchTermStateProvider.notifier).state = value; - }, - prefixIcon: Icons.search_rounded, - placeholder: "Search...", - onSubmitted: (value) { - onSearch(); - }, - ), - ), - HookBuilder( - builder: (context) { - Playback playback = ref.watch(playbackProvider); - List albums = []; - List artists = []; - List tracks = []; - List playlists = []; - final pages = [ - ...searchTrack.pages, - ...searchAlbum.pages, - ...searchPlaylist.pages, - ...searchArtist.pages, - ].expand((page) => page ?? []).toList(); - for (MapEntry page in pages.asMap().entries) { - for (var item in page.value.items ?? []) { - if (item is AlbumSimple) { - albums.add(item); - } else if (item is PlaylistSimple) { - playlists.add(item); - } else if (item is Artist) { - artists.add(item); - } else if (item is Track) { - tracks.add(item); + return PlatformScaffold( + appBar: !kIsMacOS ? PageWindowTitleBar() : null, + body: auth.isAnonymous + ? const AnonymousFallback() + : Column( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + child: PlatformTextField( + onChanged: (value) { + ref.read(searchTermStateProvider.notifier).state = value; + }, + prefixIcon: Icons.search_rounded, + placeholder: "Search...", + onSubmitted: (value) { + onSearch(); + }, + ), + ), + HookBuilder( + builder: (context) { + Playback playback = ref.watch(playbackProvider); + List albums = []; + List artists = []; + List tracks = []; + List playlists = []; + final pages = [ + ...searchTrack.pages, + ...searchAlbum.pages, + ...searchPlaylist.pages, + ...searchArtist.pages, + ].expand((page) => page ?? []).toList(); + for (MapEntry page in pages.asMap().entries) { + for (var item in page.value.items ?? []) { + if (item is AlbumSimple) { + albums.add(item); + } else if (item is PlaylistSimple) { + playlists.add(item); + } else if (item is Artist) { + artists.add(item); + } else if (item is Track) { + tracks.add(item); + } + } } - } - } - return Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 20, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (tracks.isNotEmpty) PlatformText.headline("Songs"), - if (searchTrack.isLoading && - !searchTrack.isFetchingNextPage) - const PlatformCircularProgressIndicator() - else if (searchTrack.hasError) - PlatformText( - searchTrack.error?[searchTrack.pageParams.last]) - else - ...tracks.asMap().entries.map((track) { - String duration = - "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; - return TrackTile( - playback, - track: track, - duration: duration, - isActive: playback.track?.id == track.value.id, - onTrackPlayButtonPressed: (currentTrack) async { - var isPlaylistPlaying = - playback.playlist?.id != null && - playback.playlist?.id == - currentTrack.id; - if (!isPlaylistPlaying) { - playback.playPlaylist( - CurrentPlaylist( - tracks: [currentTrack], - id: currentTrack.id!, - name: currentTrack.name!, - thumbnail: TypeConversionUtils - .image_X_UrlString( - currentTrack.album?.images, - placeholder: - ImagePlaceholder.albumArt, - ), - ), - ); - } else if (isPlaylistPlaying && - currentTrack.id != null && - currentTrack.id != playback.track?.id) { - playback.play(currentTrack); - } - }, - ); - }), - if (searchTrack.hasNextPage && tracks.isNotEmpty) - Center( - child: PlatformTextButton( - onPressed: searchTrack.isFetchingNextPage - ? null - : () => searchTrack.fetchNextPage(), - child: searchTrack.isFetchingNextPage - ? const PlatformCircularProgressIndicator() - : const PlatformText("Load more"), - ), - ), - if (playlists.isNotEmpty) - PlatformText.headline("Playlists"), - const SizedBox(height: 10), - if (searchPlaylist.isLoading && - !searchPlaylist.isFetchingNextPage) - const PlatformCircularProgressIndicator() - else if (searchPlaylist.hasError) - PlatformText(searchPlaylist - .error?[searchPlaylist.pageParams.last]) - else - ScrollConfiguration( - behavior: - ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - scrollbarOrientation: - breakpoint > Breakpoints.md - ? ScrollbarOrientation.bottom - : ScrollbarOrientation.top, - controller: playlistController, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: playlistController, - child: Row( - children: [ - ...playlists.mapIndexed( - (i, playlist) { - if (i == playlists.length - 1 && - searchPlaylist.hasNextPage) { - return Waypoint( - onEnter: () { - searchPlaylist.fetchNextPage(); - }, - child: - const ShimmerPlaybuttonCard( - count: 1), - ); - } - return PlaylistCard(playlist); - }, - ), - ], + return Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 20, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tracks.isNotEmpty) + PlatformText.headline("Songs"), + if (searchTrack.isLoading && + !searchTrack.isFetchingNextPage) + const PlatformCircularProgressIndicator() + else if (searchTrack.hasError) + PlatformText(searchTrack + .error?[searchTrack.pageParams.last]) + else + ...tracks.asMap().entries.map((track) { + String duration = + "${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}"; + return TrackTile( + playback, + track: track, + duration: duration, + isActive: + playback.track?.id == track.value.id, + onTrackPlayButtonPressed: + (currentTrack) async { + var isPlaylistPlaying = + playback.playlist?.id != null && + playback.playlist?.id == + currentTrack.id; + if (!isPlaylistPlaying) { + playback.playPlaylist( + CurrentPlaylist( + tracks: [currentTrack], + id: currentTrack.id!, + name: currentTrack.name!, + thumbnail: TypeConversionUtils + .image_X_UrlString( + currentTrack.album?.images, + placeholder: + ImagePlaceholder.albumArt, + ), + ), + ); + } else if (isPlaylistPlaying && + currentTrack.id != null && + currentTrack.id != + playback.track?.id) { + playback.play(currentTrack); + } + }, + ); + }), + if (searchTrack.hasNextPage && tracks.isNotEmpty) + Center( + child: PlatformTextButton( + onPressed: searchTrack.isFetchingNextPage + ? null + : () => searchTrack.fetchNextPage(), + child: searchTrack.isFetchingNextPage + ? const PlatformCircularProgressIndicator() + : const PlatformText("Load more"), ), ), - ), - ), - const SizedBox(height: 20), - if (artists.isNotEmpty) - PlatformText.headline("Artists"), - const SizedBox(height: 10), - if (searchArtist.isLoading && - !searchArtist.isFetchingNextPage) - const PlatformCircularProgressIndicator() - else if (searchArtist.hasError) - PlatformText(searchArtist - .error?[searchArtist.pageParams.last]) - else - ScrollConfiguration( - behavior: - ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - controller: artistController, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: artistController, - child: Row( - children: [ - ...artists.mapIndexed( - (i, artist) { - if (i == artists.length - 1 && - searchArtist.hasNextPage) { - return Waypoint( - onEnter: () { - searchArtist.fetchNextPage(); - }, - child: - const ShimmerPlaybuttonCard( - count: 1), - ); - } - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 15), - child: ArtistCard(artist), - ); - }, + if (playlists.isNotEmpty) + PlatformText.headline("Playlists"), + const SizedBox(height: 10), + if (searchPlaylist.isLoading && + !searchPlaylist.isFetchingNextPage) + const PlatformCircularProgressIndicator() + else if (searchPlaylist.hasError) + PlatformText(searchPlaylist + .error?[searchPlaylist.pageParams.last]) + else + ScrollConfiguration( + behavior: + ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + scrollbarOrientation: + breakpoint > Breakpoints.md + ? ScrollbarOrientation.bottom + : ScrollbarOrientation.top, + controller: playlistController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: playlistController, + child: Row( + children: [ + ...playlists.mapIndexed( + (i, playlist) { + if (i == playlists.length - 1 && + searchPlaylist.hasNextPage) { + return Waypoint( + onEnter: () { + searchPlaylist + .fetchNextPage(); + }, + child: + const ShimmerPlaybuttonCard( + count: 1), + ); + } + return PlaylistCard(playlist); + }, + ), + ], ), - ], + ), ), ), - ), - ), - const SizedBox(height: 20), - if (albums.isNotEmpty) - PlatformText( - "Albums", - style: Theme.of(context).textTheme.headline5, - ), - const SizedBox(height: 10), - if (searchAlbum.isLoading && - !searchAlbum.isFetchingNextPage) - const PlatformCircularProgressIndicator() - else if (searchAlbum.hasError) - PlatformText( - searchAlbum.error?[searchAlbum.pageParams.last]) - else - ScrollConfiguration( - behavior: - ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: Scrollbar( - controller: albumController, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: albumController, - child: Row( - children: [ - ...albums.mapIndexed((i, album) { - if (i == albums.length - 1 && - searchAlbum.hasNextPage) { - return Waypoint( - onEnter: () { - searchAlbum.fetchNextPage(); + const SizedBox(height: 20), + if (artists.isNotEmpty) + PlatformText.headline("Artists"), + const SizedBox(height: 10), + if (searchArtist.isLoading && + !searchArtist.isFetchingNextPage) + const PlatformCircularProgressIndicator() + else if (searchArtist.hasError) + PlatformText(searchArtist + .error?[searchArtist.pageParams.last]) + else + ScrollConfiguration( + behavior: + ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + controller: artistController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: artistController, + child: Row( + children: [ + ...artists.mapIndexed( + (i, artist) { + if (i == artists.length - 1 && + searchArtist.hasNextPage) { + return Waypoint( + onEnter: () { + searchArtist + .fetchNextPage(); + }, + child: + const ShimmerPlaybuttonCard( + count: 1), + ); + } + return Container( + margin: + const EdgeInsets.symmetric( + horizontal: 15), + child: ArtistCard(artist), + ); }, - child: const ShimmerPlaybuttonCard( - count: 1), - ); - } - return AlbumCard( - TypeConversionUtils - .simpleAlbum_X_Album( - album, ), - ); - }), - ], + ], + ), + ), ), ), - ), - ), - ], + const SizedBox(height: 20), + if (albums.isNotEmpty) + PlatformText( + "Albums", + style: Theme.of(context).textTheme.headline5, + ), + const SizedBox(height: 10), + if (searchAlbum.isLoading && + !searchAlbum.isFetchingNextPage) + const PlatformCircularProgressIndicator() + else if (searchAlbum.hasError) + PlatformText(searchAlbum + .error?[searchAlbum.pageParams.last]) + else + ScrollConfiguration( + behavior: + ScrollConfiguration.of(context).copyWith( + dragDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }, + ), + child: Scrollbar( + controller: albumController, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + controller: albumController, + child: Row( + children: [ + ...albums.mapIndexed((i, album) { + if (i == albums.length - 1 && + searchAlbum.hasNextPage) { + return Waypoint( + onEnter: () { + searchAlbum.fetchNextPage(); + }, + child: + const ShimmerPlaybuttonCard( + count: 1), + ); + } + return AlbumCard( + TypeConversionUtils + .simpleAlbum_X_Album( + album, + ), + ); + }), + ], + ), + ), + ), + ), + ], + ), + ), ), - ), - ), - ); - }, - ) - ], - ), - ), + ); + }, + ) + ], + ), ); } } diff --git a/lib/components/Shared/AnonymousFallback.dart b/lib/components/Shared/AnonymousFallback.dart index 252d11068..85aa9712d 100644 --- a/lib/components/Shared/AnonymousFallback.dart +++ b/lib/components/Shared/AnonymousFallback.dart @@ -20,10 +20,10 @@ class AnonymousFallback extends ConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text("You're not logged in"), + const PlatformText("You're not logged in"), const SizedBox(height: 10), PlatformFilledButton( - child: const Text("Login with Spotify"), + child: const PlatformText("Login with Spotify"), onPressed: () => ServiceUtils.navigate(context, "/settings"), ) ], diff --git a/lib/components/Shared/PageWindowTitleBar.dart b/lib/components/Shared/PageWindowTitleBar.dart index 88e857b43..582351262 100644 --- a/lib/components/Shared/PageWindowTitleBar.dart +++ b/lib/components/Shared/PageWindowTitleBar.dart @@ -1,3 +1,4 @@ +import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:platform_ui/platform_ui.dart'; import 'package:spotube/utils/platform.dart'; @@ -26,4 +27,9 @@ class PageWindowTitleBar extends PlatformAppBar { ], title: center, ); + + @override + Widget build(BuildContext context) { + return MoveWindow(child: super.build(context)); + } }