mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
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:
parent
d608fa7d02
commit
aaf74b46d4
@ -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);
|
||||||
|
@ -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(
|
||||||
|
@ -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"),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -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
|
||||||
|
@ -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"))
|
||||||
],
|
],
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 ?? [],
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
|
32
lib/hooks/usePaletteColor.dart
Normal file
32
lib/hooks/usePaletteColor.dart
Normal 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;
|
||||||
|
}
|
@ -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(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user