mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: titlebar complete compatibility, platform specific login, library tabbar in titlebar
This commit is contained in:
parent
e659e3c56f
commit
b3c27d1fca
@ -6,6 +6,7 @@ import 'package:platform_ui/platform_ui.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Category/CategoryCard.dart';
|
import 'package:spotube/components/Category/CategoryCard.dart';
|
||||||
import 'package:spotube/components/LoaderShimmers/ShimmerCategories.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/components/Shared/Waypoint.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
@ -42,6 +43,7 @@ class Genres extends HookConsumerWidget {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return PlatformScaffold(
|
return PlatformScaffold(
|
||||||
|
appBar: PageWindowTitleBar(),
|
||||||
body: ListView.builder(
|
body: ListView.builder(
|
||||||
itemCount: categories.length,
|
itemCount: categories.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
|
@ -64,40 +64,14 @@ class Shell extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [backgroundColor]);
|
}, [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(
|
return PlatformScaffold(
|
||||||
appBar: platform == TargetPlatform.windows ? appBar : null,
|
|
||||||
extendBodyBehindAppBar: false,
|
|
||||||
body: Sidebar(
|
body: Sidebar(
|
||||||
selectedIndex: index.value,
|
selectedIndex: index.value,
|
||||||
onSelectedIndexChanged: (i) {
|
onSelectedIndexChanged: (i) {
|
||||||
index.value = i;
|
index.value = i;
|
||||||
GoRouter.of(context).go(rootPaths[index.value]!);
|
GoRouter.of(context).go(rootPaths[index.value]!);
|
||||||
},
|
},
|
||||||
child: PlatformScaffold(
|
child: child,
|
||||||
appBar: platform != TargetPlatform.windows ? appBar : null,
|
|
||||||
body: child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
bottomNavigationBar: Column(
|
bottomNavigationBar: Column(
|
||||||
|
@ -133,18 +133,21 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
(extended.value)
|
(extended.value)
|
||||||
? Row(
|
? Padding(
|
||||||
children: [
|
padding: const EdgeInsets.all(8.0),
|
||||||
brandLogo(),
|
child: Row(
|
||||||
const SizedBox(
|
children: [
|
||||||
width: 10,
|
brandLogo(),
|
||||||
),
|
const SizedBox(
|
||||||
PlatformText.headline("Spotube"),
|
width: 10,
|
||||||
PlatformIconButton(
|
),
|
||||||
icon: const Icon(Icons.menu_rounded),
|
PlatformText.headline("Spotube"),
|
||||||
onPressed: toggleExtended,
|
PlatformIconButton(
|
||||||
),
|
icon: const Icon(Icons.menu_rounded),
|
||||||
],
|
onPressed: toggleExtended,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: brandLogo(),
|
: brandLogo(),
|
||||||
],
|
],
|
||||||
|
@ -4,6 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/components/Album/AlbumCard.dart';
|
import 'package:spotube/components/Album/AlbumCard.dart';
|
||||||
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.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/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
@ -13,6 +15,10 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final auth = ref.watch(authProvider);
|
||||||
|
if (auth.isAnonymous) {
|
||||||
|
return const AnonymousFallback();
|
||||||
|
}
|
||||||
final albumsQuery = useQuery(
|
final albumsQuery = useQuery(
|
||||||
job: currentUserAlbumsQueryJob,
|
job: currentUserAlbumsQueryJob,
|
||||||
externalData: ref.watch(spotifyProvider),
|
externalData: ref.watch(spotifyProvider),
|
||||||
|
@ -5,7 +5,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/Artist/ArtistCard.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/components/Shared/Waypoint.dart';
|
||||||
|
import 'package:spotube/provider/Auth.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
|
||||||
@ -14,6 +16,10 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final auth = ref.watch(authProvider);
|
||||||
|
if (auth.isAnonymous) {
|
||||||
|
return const AnonymousFallback();
|
||||||
|
}
|
||||||
final artistQuery = useInfiniteQuery(
|
final artistQuery = useInfiniteQuery(
|
||||||
job: currentUserFollowingArtistsQueryJob,
|
job: currentUserFollowingArtistsQueryJob,
|
||||||
externalData: ref.watch(spotifyProvider),
|
externalData: ref.watch(spotifyProvider),
|
||||||
|
@ -1,47 +1,48 @@
|
|||||||
import 'package:flutter/material.dart' hide Image;
|
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:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/components/Library/UserAlbums.dart';
|
import 'package:spotube/components/Library/UserAlbums.dart';
|
||||||
import 'package:spotube/components/Library/UserArtists.dart';
|
import 'package:spotube/components/Library/UserArtists.dart';
|
||||||
import 'package:spotube/components/Library/UserDownloads.dart';
|
import 'package:spotube/components/Library/UserDownloads.dart';
|
||||||
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
||||||
import 'package:spotube/components/Library/UserPlaylists.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);
|
const UserLibrary({Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
return DefaultTabController(
|
final index = useState(0);
|
||||||
length: 5,
|
|
||||||
child: SafeArea(
|
final body = [
|
||||||
child: PlatformTabView(
|
const UserPlaylists(),
|
||||||
|
const UserLocalTracks(),
|
||||||
|
const UserDownloads(),
|
||||||
|
const UserArtists(),
|
||||||
|
const UserAlbums(),
|
||||||
|
][index.value];
|
||||||
|
|
||||||
|
return PlatformScaffold(
|
||||||
|
appBar: PageWindowTitleBar(
|
||||||
|
titleWidth: 415,
|
||||||
|
centerTitle: true,
|
||||||
|
center: PlatformTabBar(
|
||||||
androidIsScrollable: true,
|
androidIsScrollable: true,
|
||||||
placement: PlatformProperty.all(PlatformTabbarPlacement.top),
|
selectedIndex: index.value,
|
||||||
body: {
|
onSelectedIndexChanged: (value) => index.value = value,
|
||||||
PlatformTab(
|
isNavigational:
|
||||||
label: "Playlist",
|
PlatformProperty.byPlatformGroup(mobile: false, desktop: true),
|
||||||
icon: Container(),
|
tabs: [
|
||||||
): const AnonymousFallback(child: UserPlaylists()),
|
PlatformTab(label: 'Playlists', icon: const SizedBox.shrink()),
|
||||||
PlatformTab(
|
PlatformTab(label: 'Tracks', icon: const SizedBox.shrink()),
|
||||||
label: "Downloads",
|
PlatformTab(label: 'Downloads', icon: const SizedBox.shrink()),
|
||||||
icon: Container(),
|
PlatformTab(label: 'Artists', icon: const SizedBox.shrink()),
|
||||||
): const UserDownloads(),
|
PlatformTab(label: 'Albums', icon: const SizedBox.shrink()),
|
||||||
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()),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
body: body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistCreateDialog.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/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
|
||||||
@ -14,6 +16,11 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final auth = ref.watch(authProvider);
|
||||||
|
if (auth.isAnonymous) {
|
||||||
|
return const AnonymousFallback();
|
||||||
|
}
|
||||||
|
|
||||||
final playlistsQuery = useQuery(
|
final playlistsQuery = useQuery(
|
||||||
job: currentUserPlaylistsQueryJob,
|
job: currentUserPlaylistsQueryJob,
|
||||||
externalData: ref.watch(spotifyProvider),
|
externalData: ref.watch(spotifyProvider),
|
||||||
|
@ -14,46 +14,56 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final auth = ref.watch(authProvider);
|
final auth = ref.watch(authProvider);
|
||||||
|
final key = GlobalKey<State<IntroductionScreen>>();
|
||||||
|
|
||||||
return Scaffold(
|
return PlatformScaffold(
|
||||||
appBar: PageWindowTitleBar(
|
appBar: PageWindowTitleBar(
|
||||||
leading: PlatformTextButton(
|
leading: PlatformTextButton(
|
||||||
child: const Text("Exit"),
|
child: const PlatformText("Exit"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: IntroductionScreen(
|
body: IntroductionScreen(
|
||||||
next: const Text("Next"),
|
key: key,
|
||||||
back: const Text("Previous"),
|
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,
|
showBackButton: true,
|
||||||
overrideDone: PlatformTextButton(
|
overrideDone: PlatformFilledButton(
|
||||||
onPressed: auth.isLoggedIn
|
onPressed: auth.isLoggedIn
|
||||||
? () {
|
? () {
|
||||||
ServiceUtils.navigate(context, "/");
|
ServiceUtils.navigate(context, "/");
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: const Text("Done"),
|
child: const Center(child: PlatformText("Done")),
|
||||||
),
|
),
|
||||||
pages: [
|
pages: [
|
||||||
PageViewModel(
|
PageViewModel(
|
||||||
title: "Step 1",
|
title: "Step 1",
|
||||||
image: Image.asset("assets/tutorial/step-1.png"),
|
image: Image.asset("assets/tutorial/step-1.png"),
|
||||||
bodyWidget: Wrap(
|
bodyWidget: Wrap(
|
||||||
children: [
|
children: const [
|
||||||
Text(
|
PlatformText(
|
||||||
"First, Go to ",
|
"First, Go to ",
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
|
||||||
),
|
),
|
||||||
Hyperlink(
|
Hyperlink(
|
||||||
"accounts.spotify.com ",
|
"accounts.spotify.com ",
|
||||||
"https://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",
|
"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(
|
PageViewModel(
|
||||||
title: "Step 2",
|
title: "Step 2",
|
||||||
image: Image.asset("assets/tutorial/step-2.png"),
|
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",
|
"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,
|
textAlign: TextAlign.left,
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PageViewModel(
|
PageViewModel(
|
||||||
@ -72,10 +81,9 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
image: Image.asset(
|
image: Image.asset(
|
||||||
"assets/tutorial/step-3.png",
|
"assets/tutorial/step-3.png",
|
||||||
),
|
),
|
||||||
bodyWidget: Text(
|
bodyWidget: const PlatformText(
|
||||||
"Copy the values of \"sp_dc\" and \"sp_key\" Cookies",
|
"Copy the values of \"sp_dc\" and \"sp_key\" Cookies",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (auth.isLoggedIn)
|
if (auth.isLoggedIn)
|
||||||
@ -92,13 +100,12 @@ class LoginTutorial extends ConsumerWidget {
|
|||||||
PageViewModel(
|
PageViewModel(
|
||||||
title: "Step 5",
|
title: "Step 5",
|
||||||
bodyWidget: Column(
|
bodyWidget: Column(
|
||||||
children: [
|
children: const [
|
||||||
Text(
|
PlatformText(
|
||||||
"Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields",
|
"Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields",
|
||||||
style: Theme.of(context).textTheme.bodyText1,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
const TokenLoginForm(),
|
TokenLoginForm(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -15,12 +15,17 @@ class TokenLogin extends HookConsumerWidget {
|
|||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: PlatformScaffold(
|
||||||
appBar: PageWindowTitleBar(leading: PlatformBackButton()),
|
appBar: PageWindowTitleBar(leading: const PlatformBackButton()),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
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(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Image.asset(
|
||||||
@ -28,11 +33,11 @@ class TokenLogin extends HookConsumerWidget {
|
|||||||
width: MediaQuery.of(context).size.width *
|
width: MediaQuery.of(context).size.width *
|
||||||
(breakpoint <= Breakpoints.md ? .5 : .3),
|
(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
|
style: breakpoint <= Breakpoints.md
|
||||||
? textTheme.headline5
|
? textTheme.headline5
|
||||||
: textTheme.headline4),
|
: textTheme.headline4),
|
||||||
Text(
|
PlatformText(
|
||||||
"Don't worry, any of your credentials won't be collected or shared with anyone",
|
"Don't worry, any of your credentials won't be collected or shared with anyone",
|
||||||
style: Theme.of(context).textTheme.caption,
|
style: Theme.of(context).textTheme.caption,
|
||||||
),
|
),
|
||||||
@ -45,9 +50,9 @@ class TokenLogin extends HookConsumerWidget {
|
|||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Text("Don't know how to do this?"),
|
const PlatformText("Don't know how to do this?"),
|
||||||
PlatformTextButton(
|
PlatformTextButton(
|
||||||
child: const Text(
|
child: const PlatformText(
|
||||||
"Follow along the Step by Step guide",
|
"Follow along the Step by Step guide",
|
||||||
),
|
),
|
||||||
onPressed: () => GoRouter.of(context).push(
|
onPressed: () => GoRouter.of(context).push(
|
||||||
|
@ -25,21 +25,17 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
PlatformTextField(
|
||||||
controller: directCodeController,
|
controller: directCodeController,
|
||||||
decoration: const InputDecoration(
|
placeholder: "Spotify \"sp_dc\" Cookie",
|
||||||
hintText: "Spotify \"sp_dc\" Cookie",
|
label: "sp_dc Cookie",
|
||||||
label: Text("sp_dc Cookie"),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
PlatformTextField(
|
||||||
controller: keyCodeController,
|
controller: keyCodeController,
|
||||||
decoration: const InputDecoration(
|
placeholder: "Spotify \"sp_key\" Cookie",
|
||||||
hintText: "Spotify \"sp_key\" Cookie",
|
label: "sp_key Cookie",
|
||||||
label: Text("sp_key Cookie"),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
@ -49,7 +45,7 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
directCodeController.text.isEmpty) {
|
directCodeController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text("Please fill in all fields"),
|
content: PlatformText("Please fill in all fields"),
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -68,7 +64,7 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
onDone?.call();
|
onDone?.call();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text("Submit"),
|
child: const PlatformText("Submit"),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
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:spotube/provider/Auth.dart';
|
import 'package:spotube/provider/Auth.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
@ -23,7 +24,7 @@ class WebViewLogin extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return PlatformScaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: InAppWebView(
|
child: InAppWebView(
|
||||||
initialOptions: InAppWebViewGroupOptions(
|
initialOptions: InAppWebViewGroupOptions(
|
||||||
|
@ -3,38 +3,20 @@ import 'dart:ui';
|
|||||||
import 'package:flutter/material.dart';
|
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:palette_generator/palette_generator.dart';
|
|
||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/components/Lyrics/GeniusLyrics.dart';
|
import 'package:spotube/components/Lyrics/GeniusLyrics.dart';
|
||||||
import 'package:spotube/components/Lyrics/SyncedLyrics.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/components/Shared/UniversalImage.dart';
|
||||||
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
||||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||||
import 'package:spotube/provider/Playback.dart';
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class Lyrics extends HookConsumerWidget {
|
class Lyrics extends HookConsumerWidget {
|
||||||
const Lyrics({Key? key}) : super(key: key);
|
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
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
@ -47,6 +29,7 @@ class Lyrics extends HookConsumerWidget {
|
|||||||
[playback.track?.album?.images],
|
[playback.track?.album?.images],
|
||||||
);
|
);
|
||||||
final palette = usePaletteColor(albumArt, ref);
|
final palette = usePaletteColor(albumArt, ref);
|
||||||
|
final index = useState(0);
|
||||||
|
|
||||||
useCustomStatusBarColor(
|
useCustomStatusBarColor(
|
||||||
palette.color,
|
palette.color,
|
||||||
@ -54,52 +37,42 @@ class Lyrics extends HookConsumerWidget {
|
|||||||
noSetBGColor: true,
|
noSetBGColor: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
return SafeArea(
|
final body = [
|
||||||
child: PlatformTabView(
|
SyncedLyrics(palette: palette),
|
||||||
body: {
|
GeniusLyrics(palette: palette),
|
||||||
PlatformTab(
|
][index.value];
|
||||||
label: "Synced Lyrics",
|
|
||||||
icon: Container(),
|
|
||||||
): buildContainer(SyncedLyrics(palette: palette), albumArt, palette),
|
|
||||||
PlatformTab(
|
|
||||||
label: "Lyrics (genius.com)",
|
|
||||||
icon: Container(),
|
|
||||||
): buildContainer(GeniusLyrics(palette: palette), albumArt, palette),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return SafeArea(
|
return PlatformScaffold(
|
||||||
child: Scaffold(
|
extendBodyBehindAppBar: true,
|
||||||
extendBodyBehindAppBar: true,
|
appBar: !kIsMacOS
|
||||||
appBar: const TabBar(
|
? PageWindowTitleBar(
|
||||||
isScrollable: true,
|
backgroundColor: Colors.transparent,
|
||||||
tabs: [
|
toolbarOpacity: 0,
|
||||||
Tab(text: "Synced Lyrics"),
|
center: PlatformTabBar(
|
||||||
Tab(text: "Lyrics (genius.com)"),
|
isNavigational:
|
||||||
],
|
PlatformProperty.only(linux: true, other: false),
|
||||||
),
|
selectedIndex: index.value,
|
||||||
body: Container(
|
onSelectedIndexChanged: (value) => index.value = value,
|
||||||
clipBehavior: Clip.hardEdge,
|
tabs: [
|
||||||
decoration: BoxDecoration(
|
PlatformTab(label: "Synced", icon: const SizedBox.shrink()),
|
||||||
image: DecorationImage(
|
PlatformTab(label: "Genius", icon: const SizedBox.shrink()),
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
: 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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -58,7 +58,14 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
noSetBGColor: true,
|
noSetBGColor: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return PlatformScaffold(
|
||||||
|
appBar: PageWindowTitleBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
foregroundColor: paletteColor.titleTextColor,
|
||||||
|
toolbarOpacity: 0,
|
||||||
|
automaticallyImplyLeading: true,
|
||||||
|
),
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
@ -74,11 +81,6 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
PageWindowTitleBar(
|
|
||||||
leading: const PlatformBackButton(),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
foregroundColor: paletteColor.titleTextColor,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -10,6 +10,7 @@ import 'package:spotube/components/Artist/ArtistCard.dart';
|
|||||||
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
import 'package:spotube/components/LoaderShimmers/ShimmerPlaybuttonCard.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
import 'package:spotube/components/Playlist/PlaylistCard.dart';
|
||||||
import 'package:spotube/components/Shared/AnonymousFallback.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/TrackTile.dart';
|
||||||
import 'package:spotube/components/Shared/Waypoint.dart';
|
import 'package:spotube/components/Shared/Waypoint.dart';
|
||||||
import 'package:spotube/hooks/useBreakpoints.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/Playback.dart';
|
||||||
import 'package:spotube/provider/SpotifyDI.dart';
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.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';
|
||||||
@ -57,10 +59,6 @@ class Search extends HookConsumerWidget {
|
|||||||
job: searchQueryJob(SearchType.artist.key),
|
job: searchQueryJob(SearchType.artist.key),
|
||||||
externalData: getVariables());
|
externalData: getVariables());
|
||||||
|
|
||||||
if (auth.isAnonymous) {
|
|
||||||
return const AnonymousFallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSearch() {
|
void onSearch() {
|
||||||
for (final query in [
|
for (final query in [
|
||||||
searchTrack,
|
searchTrack,
|
||||||
@ -75,281 +73,288 @@ class Search extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return PlatformScaffold(
|
||||||
child: Material(
|
appBar: !kIsMacOS ? PageWindowTitleBar() : null,
|
||||||
color: PlatformTheme.of(context).scaffoldBackgroundColor,
|
body: auth.isAnonymous
|
||||||
textStyle: PlatformTheme.of(context).textTheme!.body!,
|
? const AnonymousFallback()
|
||||||
child: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 20,
|
horizontal: 20,
|
||||||
vertical: 10,
|
vertical: 10,
|
||||||
),
|
),
|
||||||
child: PlatformTextField(
|
child: PlatformTextField(
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
ref.read(searchTermStateProvider.notifier).state = value;
|
ref.read(searchTermStateProvider.notifier).state = value;
|
||||||
},
|
},
|
||||||
prefixIcon: Icons.search_rounded,
|
prefixIcon: Icons.search_rounded,
|
||||||
placeholder: "Search...",
|
placeholder: "Search...",
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
onSearch();
|
onSearch();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
Playback playback = ref.watch(playbackProvider);
|
Playback playback = ref.watch(playbackProvider);
|
||||||
List<AlbumSimple> albums = [];
|
List<AlbumSimple> albums = [];
|
||||||
List<Artist> artists = [];
|
List<Artist> artists = [];
|
||||||
List<Track> tracks = [];
|
List<Track> tracks = [];
|
||||||
List<PlaylistSimple> playlists = [];
|
List<PlaylistSimple> playlists = [];
|
||||||
final pages = [
|
final pages = [
|
||||||
...searchTrack.pages,
|
...searchTrack.pages,
|
||||||
...searchAlbum.pages,
|
...searchAlbum.pages,
|
||||||
...searchPlaylist.pages,
|
...searchPlaylist.pages,
|
||||||
...searchArtist.pages,
|
...searchArtist.pages,
|
||||||
].expand<Page>((page) => page ?? []).toList();
|
].expand<Page>((page) => page ?? []).toList();
|
||||||
for (MapEntry<int, Page> page in pages.asMap().entries) {
|
for (MapEntry<int, Page> page in pages.asMap().entries) {
|
||||||
for (var item in page.value.items ?? []) {
|
for (var item in page.value.items ?? []) {
|
||||||
if (item is AlbumSimple) {
|
if (item is AlbumSimple) {
|
||||||
albums.add(item);
|
albums.add(item);
|
||||||
} else if (item is PlaylistSimple) {
|
} else if (item is PlaylistSimple) {
|
||||||
playlists.add(item);
|
playlists.add(item);
|
||||||
} else if (item is Artist) {
|
} else if (item is Artist) {
|
||||||
artists.add(item);
|
artists.add(item);
|
||||||
} else if (item is Track) {
|
} else if (item is Track) {
|
||||||
tracks.add(item);
|
tracks.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return Expanded(
|
||||||
}
|
child: SingleChildScrollView(
|
||||||
return Expanded(
|
child: Padding(
|
||||||
child: SingleChildScrollView(
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Padding(
|
vertical: 8,
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 20,
|
||||||
vertical: 8,
|
),
|
||||||
horizontal: 20,
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (tracks.isNotEmpty)
|
||||||
children: [
|
PlatformText.headline("Songs"),
|
||||||
if (tracks.isNotEmpty) PlatformText.headline("Songs"),
|
if (searchTrack.isLoading &&
|
||||||
if (searchTrack.isLoading &&
|
!searchTrack.isFetchingNextPage)
|
||||||
!searchTrack.isFetchingNextPage)
|
const PlatformCircularProgressIndicator()
|
||||||
const PlatformCircularProgressIndicator()
|
else if (searchTrack.hasError)
|
||||||
else if (searchTrack.hasError)
|
PlatformText(searchTrack
|
||||||
PlatformText(
|
.error?[searchTrack.pageParams.last])
|
||||||
searchTrack.error?[searchTrack.pageParams.last])
|
else
|
||||||
else
|
...tracks.asMap().entries.map((track) {
|
||||||
...tracks.asMap().entries.map((track) {
|
String duration =
|
||||||
String duration =
|
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||||
"${track.value.duration?.inMinutes.remainder(60)}:${PrimitiveUtils.zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
return TrackTile(
|
||||||
return TrackTile(
|
playback,
|
||||||
playback,
|
track: track,
|
||||||
track: track,
|
duration: duration,
|
||||||
duration: duration,
|
isActive:
|
||||||
isActive: playback.track?.id == track.value.id,
|
playback.track?.id == track.value.id,
|
||||||
onTrackPlayButtonPressed: (currentTrack) async {
|
onTrackPlayButtonPressed:
|
||||||
var isPlaylistPlaying =
|
(currentTrack) async {
|
||||||
playback.playlist?.id != null &&
|
var isPlaylistPlaying =
|
||||||
playback.playlist?.id ==
|
playback.playlist?.id != null &&
|
||||||
currentTrack.id;
|
playback.playlist?.id ==
|
||||||
if (!isPlaylistPlaying) {
|
currentTrack.id;
|
||||||
playback.playPlaylist(
|
if (!isPlaylistPlaying) {
|
||||||
CurrentPlaylist(
|
playback.playPlaylist(
|
||||||
tracks: [currentTrack],
|
CurrentPlaylist(
|
||||||
id: currentTrack.id!,
|
tracks: [currentTrack],
|
||||||
name: currentTrack.name!,
|
id: currentTrack.id!,
|
||||||
thumbnail: TypeConversionUtils
|
name: currentTrack.name!,
|
||||||
.image_X_UrlString(
|
thumbnail: TypeConversionUtils
|
||||||
currentTrack.album?.images,
|
.image_X_UrlString(
|
||||||
placeholder:
|
currentTrack.album?.images,
|
||||||
ImagePlaceholder.albumArt,
|
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
} 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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
)
|
||||||
},
|
],
|
||||||
)
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ class AnonymousFallback extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Text("You're not logged in"),
|
const PlatformText("You're not logged in"),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
PlatformFilledButton(
|
PlatformFilledButton(
|
||||||
child: const Text("Login with Spotify"),
|
child: const PlatformText("Login with Spotify"),
|
||||||
onPressed: () => ServiceUtils.navigate(context, "/settings"),
|
onPressed: () => ServiceUtils.navigate(context, "/settings"),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
@ -26,4 +27,9 @@ class PageWindowTitleBar extends PlatformAppBar {
|
|||||||
],
|
],
|
||||||
title: center,
|
title: center,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MoveWindow(child: super.build(context));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user