floating mini player works flawlessly

Custom bg-color for floating player for each title track album art
go_true routing integrated
floating player now disappears if not on home
This commit is contained in:
Kingkor Roy Tirtho 2022-03-10 15:11:02 +06:00
parent d608fa7d02
commit aaf74b46d4
19 changed files with 211 additions and 98 deletions

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumView.dart'; import 'package:spotube/components/Album/AlbumView.dart';
@ -32,12 +33,7 @@ class AlbumCard extends HookConsumerWidget {
description: description:
"Album • ${artistsToString<ArtistSimple>(album.artists ?? [])}", "Album • ${artistsToString<ArtistSimple>(album.artists ?? [])}",
onTap: () { onTap: () {
Navigator.of(context).push(SpotubePageRoute( GoRouter.of(context).push("/album/${album.id}", extra: album);
child: AlbumView(
album,
key: Key("album-${album.id}"),
),
));
}, },
onPlaybuttonPressed: () async { onPlaybuttonPressed: () async {
SpotifyApi spotify = ref.read(spotifyProvider); SpotifyApi spotify = ref.read(spotifyProvider);

View File

@ -1,8 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Artist/ArtistProfile.dart';
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
class ArtistCard extends StatelessWidget { class ArtistCard extends StatelessWidget {
final Artist artist; final Artist artist;
@ -17,12 +16,7 @@ class ArtistCard extends StatelessWidget {
: "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6"); : "https://avatars.dicebear.com/api/open-peeps/${artist.id}.png?b=%231ed760&r=50&flip=1&translateX=3&translateY=-6");
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.of(context).push(SpotubePageRoute( GoRouter.of(context).push("/artist/${artist.id}");
child: ArtistProfile(
artist.id!,
key: Key("artist-${artist.id}"),
),
));
}, },
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: Ink( child: Ink(

View File

@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumCard.dart'; import 'package:spotube/components/Album/AlbumCard.dart';
@ -243,13 +244,10 @@ class ArtistProfile extends HookConsumerWidget {
TextButton( TextButton(
child: const Text("See All"), child: const Text("See All"),
onPressed: () { onPressed: () {
Navigator.of(context).push(SpotubePageRoute( GoRouter.of(context).push(
child: ArtistAlbumView( "/artist-album/$artistId",
artistId, extra: snapshot.data?.name ?? "KRTX",
snapshot.data?.name ?? "KRTX", );
key: Key("artist-album-$artistId"),
),
));
}, },
) )
], ],

View File

@ -1,10 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart' hide Image; 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/helpers/image-to-url-string.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
@ -30,11 +29,7 @@ class Sidebar extends HookConsumerWidget {
} }
static void goToSettings(BuildContext context) { static void goToSettings(BuildContext context) {
Navigator.of(context).push(SpotubePageRoute( GoRouter.of(context).push("/settings");
child: const Settings(
key: Key("settings"),
),
));
} }
@override @override

View File

@ -1,5 +1,6 @@
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:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Settings.dart'; import 'package:spotube/components/Settings.dart';
@ -69,11 +70,7 @@ class Lyrics extends HookConsumerWidget {
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).push(SpotubePageRoute( GoRouter.of(context).push("/settings");
child: const Settings(
key: Key("settings"),
),
));
}, },
child: const Text("Add Access Token")) child: const Text("Add Access Token"))
], ],

View File

@ -1,24 +1,23 @@
import 'dart:async'; import 'dart:async';
import 'package:cached_network_image/cached_network_image.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:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotify/spotify.dart' hide Image; import 'package:spotify/spotify.dart' hide Image;
import 'package:spotube/components/Player/PlayerOverlay.dart'; import 'package:spotube/components/Player/PlayerOverlay.dart';
import 'package:spotube/components/Player/PlayerTrackDetails.dart'; import 'package:spotube/components/Player/PlayerTrackDetails.dart';
import 'package:spotube/components/Shared/DownloadTrackButton.dart'; import 'package:spotube/components/Shared/DownloadTrackButton.dart';
import 'package:spotube/components/Player/PlayerControls.dart'; import 'package:spotube/components/Player/PlayerControls.dart';
import 'package:spotube/helpers/artists-to-clickable-artists.dart';
import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart';
import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/helpers/search-youtube.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
import 'package:spotube/hooks/usePaletteColor.dart';
import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/provider/Playback.dart'; import 'package:spotube/provider/Playback.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotube/provider/SpotifyDI.dart'; import 'package:spotube/provider/SpotifyDI.dart';
import 'package:spotube/provider/ThemeProvider.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart';
class Player extends HookConsumerWidget { class Player extends HookConsumerWidget {
@ -26,6 +25,7 @@ class Player extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
Playback playback = ref.watch(playbackProvider); Playback playback = ref.watch(playbackProvider);
final _isPlaying = useState(false); final _isPlaying = useState(false);
final _shuffled = useState(false); final _shuffled = useState(false);
final _volume = useState(0.0); final _volume = useState(0.0);
@ -200,7 +200,10 @@ class Player extends HookConsumerWidget {
} }
} }
final paletteColor = usePaletteColor(albumArt);
final controls = PlayerControls( final controls = PlayerControls(
iconColor: paletteColor.bodyTextColor,
positionStream: player.positionStream, positionStream: player.positionStream,
isPlaying: _isPlaying.value, isPlaying: _isPlaying.value,
duration: _duration.value ?? Duration.zero, duration: _duration.value ?? Duration.zero,
@ -274,6 +277,7 @@ class Player extends HookConsumerWidget {
builder: (context) => PlayerOverlay( builder: (context) => PlayerOverlay(
controls: controls, controls: controls,
albumArt: albumArt, albumArt: albumArt,
paletteColor: paletteColor,
), ),
); );
// I can't believe useEffect doesn't run Post Frame aka // I can't believe useEffect doesn't run Post Frame aka

View File

@ -21,6 +21,7 @@ class PlayerControls extends HookConsumerWidget {
final Function? onPrevious; final Function? onPrevious;
final Function? onPlay; final Function? onPlay;
final Function? onPause; final Function? onPause;
final Color? iconColor;
const PlayerControls({ const PlayerControls({
required this.positionStream, required this.positionStream,
required this.isPlaying, required this.isPlaying,
@ -33,6 +34,7 @@ class PlayerControls extends HookConsumerWidget {
this.onPrevious, this.onPrevious,
this.onPlay, this.onPlay,
this.onPause, this.onPause,
this.iconColor,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -94,6 +96,7 @@ class PlayerControls extends HookConsumerWidget {
}), }),
IconButton( IconButton(
icon: const Icon(Icons.skip_previous_rounded), icon: const Icon(Icons.skip_previous_rounded),
color: iconColor,
onPressed: () { onPressed: () {
onPrevious?.call(); onPrevious?.call();
}), }),
@ -101,11 +104,14 @@ class PlayerControls extends HookConsumerWidget {
icon: Icon( icon: Icon(
isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded, isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
), ),
color: iconColor,
onPressed: () => _playOrPause(null), onPressed: () => _playOrPause(null),
), ),
IconButton( IconButton(
icon: const Icon(Icons.skip_next_rounded), icon: const Icon(Icons.skip_next_rounded),
onPressed: () => onNext?.call()), onPressed: () => onNext?.call(),
color: iconColor,
),
if (breakpoint.isMoreThan(Breakpoints.md)) if (breakpoint.isMoreThan(Breakpoints.md))
IconButton( IconButton(
icon: const Icon(Icons.stop_rounded), icon: const Icon(Icons.stop_rounded),

View File

@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
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:go_router/go_router.dart';
import 'package:palette_generator/palette_generator.dart'; import 'package:palette_generator/palette_generator.dart';
import 'package:spotube/components/Player/PlayerTrackDetails.dart'; import 'package:spotube/components/Player/PlayerTrackDetails.dart';
import 'package:spotube/hooks/useBreakpoints.dart'; import 'package:spotube/hooks/useBreakpoints.dart';
@ -8,48 +8,57 @@ import 'package:spotube/hooks/useBreakpoints.dart';
class PlayerOverlay extends HookWidget { class PlayerOverlay extends HookWidget {
final Widget controls; final Widget controls;
final String albumArt; final String albumArt;
final PaletteColor paletteColor;
const PlayerOverlay({ const PlayerOverlay({
required this.controls, required this.controls,
required this.albumArt, required this.albumArt,
required this.paletteColor,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final breakpoint = useBreakpoints(); final breakpoint = useBreakpoints();
final isCurrentRoute = useState<bool?>(null);
useEffect(() {
WidgetsBinding.instance?.addPostFrameCallback((timer) {
final matches = GoRouter.of(context).location == "/";
if (matches != isCurrentRoute.value) {
isCurrentRoute.value = matches;
}
});
return null;
});
if (isCurrentRoute.value == false) {
return Container();
}
return Positioned( return Positioned(
right: (breakpoint.isMd ? 10 : 5), right: (breakpoint.isMd ? 10 : 5),
left: (breakpoint.isSm ? 5 : 80), left: (breakpoint.isSm ? 5 : 80),
bottom: (breakpoint.isSm ? 63 : 10), bottom: (breakpoint.isSm ? 63 : 10),
child: FutureBuilder<PaletteGenerator>( child: Container(
future: PaletteGenerator.fromImageProvider( width: MediaQuery.of(context).size.width,
CachedNetworkImageProvider( height: 50,
albumArt, decoration: BoxDecoration(
cacheKey: albumArt, color: paletteColor.color,
maxHeight: 50, borderRadius: BorderRadius.circular(5),
maxWidth: 50, ),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: PlayerTrackDetails(
albumArt: albumArt,
color: paletteColor.bodyTextColor,
),
), ),
), Expanded(child: controls),
builder: (context, snapshot) { ],
return Container( ),
width: MediaQuery.of(context).size.width, ),
height: 50,
decoration: BoxDecoration(
color: snapshot.hasData
? snapshot.data!.colors.first
: Colors.blueGrey[200],
borderRadius: BorderRadius.circular(5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
PlayerTrackDetails(albumArt: albumArt),
controls,
],
),
);
}),
); );
} }
} }

View File

@ -7,7 +7,9 @@ import 'package:spotube/provider/Playback.dart';
class PlayerTrackDetails extends HookConsumerWidget { class PlayerTrackDetails extends HookConsumerWidget {
final String? albumArt; final String? albumArt;
const PlayerTrackDetails({Key? key, this.albumArt}) : super(key: key); final Color? color;
const PlayerTrackDetails({Key? key, this.albumArt, this.color})
: super(key: key);
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
@ -36,13 +38,15 @@ class PlayerTrackDetails extends HookConsumerWidget {
), ),
if (breakpoint.isLessThanOrEqualTo(Breakpoints.md)) ...[ if (breakpoint.isLessThanOrEqualTo(Breakpoints.md)) ...[
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Flexible(
playback.currentTrack?.name ?? "Not playing", child: Text(
overflow: TextOverflow.ellipsis, playback.currentTrack?.name ?? "Not playing",
style: Theme.of(context) overflow: TextOverflow.ellipsis,
.textTheme style: Theme.of(context)
.bodyText1 .textTheme
?.copyWith(fontWeight: FontWeight.bold), .bodyText1
?.copyWith(fontWeight: FontWeight.bold, color: color),
),
), ),
], ],
// title of the currently playing track // title of the currently playing track
@ -53,10 +57,11 @@ class PlayerTrackDetails extends HookConsumerWidget {
children: [ children: [
Text( Text(
playback.currentTrack?.name ?? "Not playing", playback.currentTrack?.name ?? "Not playing",
overflow: TextOverflow.ellipsis,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
?.copyWith(fontWeight: FontWeight.bold), ?.copyWith(fontWeight: FontWeight.bold, color: color),
), ),
artistsToClickableArtists( artistsToClickableArtists(
playback.currentTrack?.artists ?? [], playback.currentTrack?.artists ?? [],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/components/Playlist/PlaylistView.dart'; import 'package:spotube/components/Playlist/PlaylistView.dart';
@ -26,13 +27,9 @@ class PlaylistCard extends HookConsumerWidget {
imageUrl: playlist.images![0].url!, imageUrl: playlist.images![0].url!,
isPlaying: isPlaylistPlaying, isPlaying: isPlaylistPlaying,
onTap: () { onTap: () {
Navigator.of(context).push( GoRouter.of(context).push(
SpotubePageRoute( "/playlist/${playlist.id}",
child: PlaylistView( extra: playlist,
playlist,
key: Key("playlist-${playlist.id}"),
),
),
); );
}, },
onPlaybuttonPressed: () async { onPlaybuttonPressed: () async {

View File

@ -1,5 +1,6 @@
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:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/components/Settings/SettingsHotkeyTile.dart'; import 'package:spotube/components/Settings/SettingsHotkeyTile.dart';
@ -57,7 +58,7 @@ class Settings extends HookConsumerWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: ElevatedButton( child: ElevatedButton(
onPressed: geniusAccessToken != null onPressed: geniusAccessToken.value != null
? () async { ? () async {
SharedPreferences localStorage = SharedPreferences localStorage =
await SharedPreferences.getInstance(); await SharedPreferences.getInstance();
@ -148,7 +149,7 @@ class Settings extends HookConsumerWidget {
await SharedPreferences.getInstance(); await SharedPreferences.getInstance();
await localStorage.clear(); await localStorage.clear();
auth.logout(); auth.logout();
Navigator.of(context).pop(); GoRouter.of(context).pop();
}, },
), ),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:spotube/components/Shared/AnchorButton.dart'; import 'package:spotube/components/Shared/AnchorButton.dart';
class LinkText<T> extends StatelessWidget { class LinkText<T> extends StatelessWidget {
@ -6,12 +7,14 @@ class LinkText<T> extends StatelessWidget {
final TextStyle style; final TextStyle style;
final TextAlign? textAlign; final TextAlign? textAlign;
final TextOverflow? overflow; final TextOverflow? overflow;
final Route<T> route; final String route;
final T? extra;
const LinkText( const LinkText(
this.text, this.text,
this.route, { this.route, {
Key? key, Key? key,
this.textAlign, this.textAlign,
this.extra,
this.overflow, this.overflow,
this.style = const TextStyle(), this.style = const TextStyle(),
}) : super(key: key); }) : super(key: key);
@ -20,8 +23,8 @@ class LinkText<T> extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnchorButton( return AnchorButton(
text, text,
onTap: () async { onTap: () {
await Navigator.of(context).push(route); GoRouter.of(context).push(route, extra: extra);
}, },
key: key, key: key,
overflow: overflow, overflow: overflow,

View File

@ -1,5 +1,6 @@
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:go_router/go_router.dart';
import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:hotkey_manager/hotkey_manager.dart';
class RecordHotKeyDialog extends HookWidget { class RecordHotKeyDialog extends HookWidget {
@ -66,7 +67,7 @@ class RecordHotKeyDialog extends HookWidget {
TextButton( TextButton(
child: const Text('Cancel'), child: const Text('Cancel'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); GoRouter.of(context).pop();
}, },
), ),
TextButton( TextButton(
@ -75,7 +76,7 @@ class RecordHotKeyDialog extends HookWidget {
? null ? null
: () { : () {
onHotKeyRecorded(_hotKey.value); onHotKeyRecorded(_hotKey.value);
Navigator.of(context).pop(); GoRouter.of(context).pop();
}, },
), ),
], ],

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class SpotubePageRoute extends PageRouteBuilder { class SpotubePageRoute extends PageRouteBuilder {
final Widget child; final Widget child;
@ -16,3 +17,20 @@ class SpotubePageRoute extends PageRouteBuilder {
); );
} }
} }
class SpotubePage extends CustomTransitionPage {
SpotubePage({
required Widget child,
}) : super(
child: child,
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}

View File

@ -180,9 +180,8 @@ class TrackTile extends HookWidget {
Expanded( Expanded(
child: LinkText( child: LinkText(
track.value.album!.name!, track.value.album!.name!,
SpotubePageRoute( "/album/${track.value.album?.id}",
child: AlbumView(track.value.album!), extra: track.value.album,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),

View File

@ -21,9 +21,7 @@ Widget artistsToClickableArtists(
(artist.key != artists.length - 1) (artist.key != artists.length - 1)
? "${artist.value.name}, " ? "${artist.value.name}, "
: artist.value.name!, : artist.value.name!,
SpotubePageRoute( "/artist/${artist.value.id}",
child: ArtistProfile(artist.value.id!),
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: textStyle, style: textStyle,
), ),

View File

@ -5,5 +5,5 @@ const uuid = Uuid();
String imageToUrlString(List<Image>? images, {int index = 0}) { String imageToUrlString(List<Image>? images, {int index = 0}) {
return images != null && images.isNotEmpty return images != null && images.isNotEmpty
? images[0].url! ? images[0].url!
: "https://avatars.dicebear.com/api/croodles-neutral/${uuid.v4()}.png"; : "https://avatars.dicebear.com/api/bottts/${uuid.v4()}.png";
} }

View File

@ -0,0 +1,32 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:palette_generator/palette_generator.dart';
PaletteColor usePaletteColor(String imageUrl) {
final paletteColor =
useState<PaletteColor>(PaletteColor(Colors.grey[300]!, 0));
final context = useContext();
useEffect(() {
PaletteGenerator.fromImageProvider(
CachedNetworkImageProvider(
imageUrl,
cacheKey: imageUrl,
maxHeight: 50,
maxWidth: 50,
),
).then((palette) {
final color = Theme.of(context).brightness == Brightness.light
? palette.lightMutedColor ?? palette.lightVibrantColor
: palette.darkMutedColor ?? palette.darkVibrantColor;
if (color != null) {
paletteColor.value = color;
}
});
return null;
}, [imageUrl]);
return paletteColor.value;
}

View File

@ -3,10 +3,18 @@ import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
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:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/Album/AlbumView.dart';
import 'package:spotube/components/Artist/ArtistAlbumView.dart';
import 'package:spotube/components/Artist/ArtistProfile.dart';
import 'package:spotube/components/Home/Home.dart'; import 'package:spotube/components/Home/Home.dart';
import 'package:spotube/components/Playlist/PlaylistView.dart';
import 'package:spotube/components/Settings.dart';
import 'package:spotube/components/Shared/SpotubePageRoute.dart';
import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/models/LocalStorageKeys.dart';
import 'package:spotube/provider/ThemeProvider.dart'; import 'package:spotube/provider/ThemeProvider.dart';
@ -25,6 +33,56 @@ void main() async {
} }
class MyApp extends HookConsumerWidget { class MyApp extends HookConsumerWidget {
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: "/",
builder: (context, state) => const Home(),
),
GoRoute(
path: "/settings",
pageBuilder: (context, state) => SpotubePage(
child: const Settings(),
),
),
GoRoute(
path: "/album/:id",
pageBuilder: (context, state) {
assert(state.extra is AlbumSimple);
return SpotubePage(child: AlbumView(state.extra as AlbumSimple));
},
),
GoRoute(
path: "/artist/:id",
pageBuilder: (context, state) {
assert(state.params["id"] != null);
return SpotubePage(child: ArtistProfile(state.params["id"]!));
},
),
GoRoute(
path: "/artist-album/:id",
pageBuilder: (context, state) {
assert(state.params["id"] != null);
assert(state.extra is String);
return SpotubePage(
child: ArtistAlbumView(
state.params["id"]!,
state.extra as String,
),
);
},
),
GoRoute(
path: "/playlist/:id",
pageBuilder: (context, state) {
assert(state.extra is PlaylistSimple);
return SpotubePage(
child: PlaylistView(state.extra as PlaylistSimple),
);
},
),
],
);
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
var themeMode = ref.watch(themeProvider); var themeMode = ref.watch(themeProvider);
@ -44,9 +102,12 @@ class MyApp extends HookConsumerWidget {
themeNotifier.state = ThemeMode.system; themeNotifier.state = ThemeMode.system;
} }
}); });
return null;
}, []); }, []);
return MaterialApp( return MaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Spotube', title: 'Spotube',
theme: ThemeData( theme: ThemeData(
@ -142,7 +203,6 @@ class MyApp extends HookConsumerWidget {
canvasColor: Colors.blueGrey[900], canvasColor: Colors.blueGrey[900],
), ),
themeMode: themeMode, themeMode: themeMode,
home: const Home(),
); );
} }
} }