mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat: merge floating player with nav bar and nav bar translucent bg
This commit is contained in:
parent
67380f6876
commit
a90261ed19
@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
|
import 'package:spotube/hooks/use_progress.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
@ -58,28 +59,24 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
HookBuilder(
|
HookBuilder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final duration =
|
final progressObj = useProgress(ref);
|
||||||
useStream(PlaylistQueueNotifier.duration).data ??
|
|
||||||
Duration.zero;
|
final progressStatic = progressObj.item1;
|
||||||
final positionSnapshot =
|
final position = progressObj.item2;
|
||||||
useStream(PlaylistQueueNotifier.position);
|
final duration = progressObj.item3;
|
||||||
final position = positionSnapshot.data ?? Duration.zero;
|
|
||||||
final totalMinutes = PrimitiveUtils.zeroPadNumStr(
|
final totalMinutes = PrimitiveUtils.zeroPadNumStr(
|
||||||
duration.inMinutes.remainder(60));
|
duration.inMinutes.remainder(60),
|
||||||
|
);
|
||||||
final totalSeconds = PrimitiveUtils.zeroPadNumStr(
|
final totalSeconds = PrimitiveUtils.zeroPadNumStr(
|
||||||
duration.inSeconds.remainder(60));
|
duration.inSeconds.remainder(60),
|
||||||
|
);
|
||||||
final currentMinutes = PrimitiveUtils.zeroPadNumStr(
|
final currentMinutes = PrimitiveUtils.zeroPadNumStr(
|
||||||
position.inMinutes.remainder(60));
|
position.inMinutes.remainder(60),
|
||||||
|
);
|
||||||
final currentSeconds = PrimitiveUtils.zeroPadNumStr(
|
final currentSeconds = PrimitiveUtils.zeroPadNumStr(
|
||||||
position.inSeconds.remainder(60));
|
position.inSeconds.remainder(60),
|
||||||
|
);
|
||||||
final sliderMax = duration.inSeconds;
|
|
||||||
final sliderValue = position.inSeconds;
|
|
||||||
|
|
||||||
final progressStatic =
|
|
||||||
(sliderMax == 0 || sliderValue > sliderMax)
|
|
||||||
? 0
|
|
||||||
: sliderValue / sliderMax;
|
|
||||||
|
|
||||||
final progress = useState<num>(
|
final progress = useState<num>(
|
||||||
useMemoized(() => progressStatic, []),
|
useMemoized(() => progressStatic, []),
|
||||||
@ -90,20 +87,6 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [progressStatic]);
|
}, [progressStatic]);
|
||||||
|
|
||||||
// this is a hack to fix duration not being updated
|
|
||||||
useEffect(() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
if (positionSnapshot.hasData &&
|
|
||||||
duration == Duration.zero) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
|
||||||
await playlistNotifier.pause();
|
|
||||||
await Future.delayed(const Duration(milliseconds: 400));
|
|
||||||
await playlistNotifier.resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}, [positionSnapshot.hasData, duration]);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Tooltip(
|
Tooltip(
|
||||||
@ -121,7 +104,7 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
onChangeEnd: (value) async {
|
onChangeEnd: (value) async {
|
||||||
await playlistNotifier.seek(
|
await playlistNotifier.seek(
|
||||||
Duration(
|
Duration(
|
||||||
seconds: (value * sliderMax).toInt(),
|
seconds: (value * duration.inSeconds).toInt(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/player/player_track_details.dart';
|
import 'package:spotube/components/player/player_track_details.dart';
|
||||||
import 'package:spotube/hooks/use_palette_color.dart';
|
|
||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
|
import 'package:spotube/hooks/use_progress.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
@ -22,7 +22,6 @@ class PlayerOverlay extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final paletteColor = usePaletteColor(albumArt, ref);
|
|
||||||
final canShow = ref.watch(
|
final canShow = ref.watch(
|
||||||
PlaylistQueueNotifier.provider.select((s) => s != null),
|
PlaylistQueueNotifier.provider.select((s) => s != null),
|
||||||
);
|
);
|
||||||
@ -31,6 +30,13 @@ class PlayerOverlay extends HookConsumerWidget {
|
|||||||
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
final playing = useStream(PlaylistQueueNotifier.playing).data ??
|
||||||
PlaylistQueueNotifier.isPlaying;
|
PlaylistQueueNotifier.isPlaying;
|
||||||
|
|
||||||
|
final textColor = Theme.of(context).colorScheme.primary;
|
||||||
|
|
||||||
|
const radius = BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10),
|
||||||
|
topRight: Radius.circular(10),
|
||||||
|
);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onVerticalDragEnd: (details) {
|
onVerticalDragEnd: (details) {
|
||||||
int sensitivity = 8;
|
int sensitivity = 8;
|
||||||
@ -40,80 +46,107 @@ class PlayerOverlay extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: radius,
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
|
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: canShow ? 50 : 0,
|
height: canShow ? 53 : 0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: paletteColor.color.withOpacity(.7),
|
color: Theme.of(context)
|
||||||
border: Border.all(
|
.colorScheme
|
||||||
color: paletteColor.titleTextColor,
|
.secondaryContainer
|
||||||
width: 2,
|
.withOpacity(.8),
|
||||||
),
|
borderRadius: radius,
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
),
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
opacity: canShow ? 1 : 0,
|
opacity: canShow ? 1 : 0,
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
HookBuilder(
|
||||||
child: MouseRegion(
|
builder: (context) {
|
||||||
cursor: SystemMouseCursors.click,
|
final progress = useProgress(ref);
|
||||||
child: GestureDetector(
|
// animated
|
||||||
onTap: () => GoRouter.of(context).push("/player"),
|
return TweenAnimationBuilder<double>(
|
||||||
child: PlayerTrackDetails(
|
duration: const Duration(milliseconds: 250),
|
||||||
albumArt: albumArt,
|
tween: Tween<double>(begin: 0, end: progress.item1),
|
||||||
color: paletteColor.bodyTextColor,
|
builder: (context, value, child) {
|
||||||
),
|
return LinearProgressIndicator(
|
||||||
),
|
value: value,
|
||||||
),
|
minHeight: 2,
|
||||||
),
|
backgroundColor: Colors.transparent,
|
||||||
Row(
|
valueColor: AlwaysStoppedAnimation(
|
||||||
children: [
|
Theme.of(context).colorScheme.primary,
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
SpotubeIcons.skipBack,
|
|
||||||
color: paletteColor.bodyTextColor,
|
|
||||||
),
|
|
||||||
onPressed: playlistNotifier.previous,
|
|
||||||
),
|
|
||||||
Consumer(
|
|
||||||
builder: (context, ref, _) {
|
|
||||||
return IconButton(
|
|
||||||
icon: playlist?.isLoading == true
|
|
||||||
? const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
playing
|
|
||||||
? SpotubeIcons.pause
|
|
||||||
: SpotubeIcons.play,
|
|
||||||
color: paletteColor.bodyTextColor,
|
|
||||||
),
|
|
||||||
onPressed: Actions.handler<PlayPauseIntent>(
|
|
||||||
context,
|
|
||||||
PlayPauseIntent(ref),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
IconButton(
|
},
|
||||||
icon: Icon(
|
),
|
||||||
SpotubeIcons.skipForward,
|
Expanded(
|
||||||
color: paletteColor.bodyTextColor,
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () =>
|
||||||
|
GoRouter.of(context).push("/player"),
|
||||||
|
child: PlayerTrackDetails(
|
||||||
|
albumArt: albumArt,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: playlistNotifier.next,
|
Row(
|
||||||
),
|
children: [
|
||||||
],
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
SpotubeIcons.skipBack,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
onPressed: playlistNotifier.previous,
|
||||||
|
),
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, _) {
|
||||||
|
return IconButton(
|
||||||
|
icon: playlist?.isLoading == true
|
||||||
|
? const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
playing
|
||||||
|
? SpotubeIcons.pause
|
||||||
|
: SpotubeIcons.play,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
onPressed: Actions.handler<PlayPauseIntent>(
|
||||||
|
context,
|
||||||
|
PlayPauseIntent(ref),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
SpotubeIcons.skipForward,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
onPressed: playlistNotifier.next,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
@ -20,38 +21,59 @@ class PlayerTrackDetails extends HookConsumerWidget {
|
|||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (albumArt != null)
|
if (playback != null)
|
||||||
Padding(
|
Container(
|
||||||
padding: const EdgeInsets.all(5.0),
|
padding: const EdgeInsets.all(6),
|
||||||
child: UniversalImage(
|
constraints: const BoxConstraints(
|
||||||
path: albumArt!,
|
maxWidth: 70,
|
||||||
height: 50,
|
maxHeight: 70,
|
||||||
width: 50,
|
),
|
||||||
placeholder: (context, url) {
|
child: ClipRRect(
|
||||||
return Assets.albumPlaceholder.image(
|
borderRadius: BorderRadius.circular(4),
|
||||||
height: 50,
|
child: UniversalImage(
|
||||||
width: 50,
|
path: albumArt ?? "",
|
||||||
);
|
placeholder: (context, url) {
|
||||||
},
|
return Assets.albumPlaceholder.image(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (breakpoint.isLessThanOrEqualTo(Breakpoints.md))
|
if (breakpoint.isLessThanOrEqualTo(Breakpoints.md))
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Column(
|
||||||
playback?.activeTrack.name ?? "Not playing",
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
overflow: TextOverflow.ellipsis,
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, color: color),
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
playback?.activeTrack.name ?? "",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
TypeConversionUtils.artists_X_String<Artist>(
|
||||||
|
playback?.activeTrack.artists ?? [],
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(color: color),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// title of the currently playing track
|
|
||||||
if (breakpoint.isMoreThan(Breakpoints.md))
|
if (breakpoint.isMoreThan(Breakpoints.md))
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
playback?.activeTrack.name ?? "Not playing",
|
playback?.activeTrack.name ?? "",
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, color: color),
|
style: TextStyle(fontWeight: FontWeight.bold, color: color),
|
||||||
),
|
),
|
||||||
|
@ -42,10 +42,7 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
if (layoutMode == LayoutMode.compact ||
|
if (layoutMode == LayoutMode.compact ||
|
||||||
(breakpoint.isLessThanOrEqualTo(Breakpoints.md) &&
|
(breakpoint.isLessThanOrEqualTo(Breakpoints.md) &&
|
||||||
layoutMode == LayoutMode.adaptive)) {
|
layoutMode == LayoutMode.adaptive)) {
|
||||||
return Padding(
|
return PlayerOverlay(albumArt: albumArt);
|
||||||
padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8, top: 0),
|
|
||||||
child: PlayerOverlay(albumArt: albumArt),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
|
import 'package:curved_navigation_bar/curved_navigation_bar.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';
|
||||||
@ -45,44 +47,52 @@ class SpotubeNavigationBar extends HookConsumerWidget {
|
|||||||
(breakpoint.isMoreThan(Breakpoints.sm) &&
|
(breakpoint.isMoreThan(Breakpoints.sm) &&
|
||||||
layoutMode == LayoutMode.adaptive)) return const SizedBox();
|
layoutMode == LayoutMode.adaptive)) return const SizedBox();
|
||||||
|
|
||||||
return CurvedNavigationBar(
|
return ClipRect(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
child: BackdropFilter(
|
||||||
buttonBackgroundColor: buttonColor,
|
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
|
||||||
color: Theme.of(context).colorScheme.background,
|
child: CurvedNavigationBar(
|
||||||
height: 50,
|
backgroundColor: Theme.of(context)
|
||||||
items: [
|
.colorScheme
|
||||||
...navbarTileList.map(
|
.secondaryContainer
|
||||||
(e) {
|
.withOpacity(0.72),
|
||||||
return MouseRegion(
|
buttonBackgroundColor: buttonColor,
|
||||||
cursor: SystemMouseCursors.click,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: Badge(
|
height: 50,
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
items: [
|
||||||
isLabelVisible: e.title == "Library" && downloadCount > 0,
|
...navbarTileList.map(
|
||||||
label: Text(
|
(e) {
|
||||||
downloadCount.toString(),
|
return MouseRegion(
|
||||||
style: const TextStyle(
|
cursor: SystemMouseCursors.click,
|
||||||
color: Colors.white,
|
child: Badge(
|
||||||
fontSize: 10,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
isLabelVisible: e.title == "Library" && downloadCount > 0,
|
||||||
|
label: Text(
|
||||||
|
downloadCount.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
e.icon,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
child: Icon(
|
},
|
||||||
e.icon,
|
),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
],
|
||||||
),
|
index: insideSelectedIndex.value,
|
||||||
),
|
onTap: (i) {
|
||||||
);
|
insideSelectedIndex.value = i;
|
||||||
|
if (navbarTileList[i].title == "Settings") {
|
||||||
|
Sidebar.goToSettings(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSelectedIndexChanged(i);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
index: insideSelectedIndex.value,
|
|
||||||
onTap: (i) {
|
|
||||||
insideSelectedIndex.value = i;
|
|
||||||
if (navbarTileList[i].title == "Settings") {
|
|
||||||
Sidebar.goToSettings(context);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSelectedIndexChanged(i);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,7 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: kIsDesktop
|
appBar: kIsDesktop
|
||||||
? PageWindowTitleBar(
|
? PageWindowTitleBar(
|
||||||
|
39
lib/hooks/use_progress.dart
Normal file
39
lib/hooks/use_progress.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
Tuple3<double, Duration, Duration> useProgress(WidgetRef ref) {
|
||||||
|
ref.watch(PlaylistQueueNotifier.provider);
|
||||||
|
|
||||||
|
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
|
||||||
|
|
||||||
|
final duration =
|
||||||
|
useStream(PlaylistQueueNotifier.duration).data ?? Duration.zero;
|
||||||
|
final positionSnapshot = useStream(PlaylistQueueNotifier.position);
|
||||||
|
|
||||||
|
final position = positionSnapshot.data ?? Duration.zero;
|
||||||
|
|
||||||
|
final sliderMax = duration.inSeconds;
|
||||||
|
final sliderValue = position.inSeconds;
|
||||||
|
|
||||||
|
// this is a hack to fix duration not being updated
|
||||||
|
useEffect(() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (positionSnapshot.hasData && duration == Duration.zero) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
|
await playlistNotifier.pause();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 400));
|
||||||
|
await playlistNotifier.resume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, [positionSnapshot.hasData, duration]);
|
||||||
|
|
||||||
|
return Tuple3(
|
||||||
|
sliderMax == 0 || sliderValue > sliderMax ? 0 : sliderValue / sliderMax,
|
||||||
|
position,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
}
|
@ -86,7 +86,6 @@ class SpotubeTrack extends Track {
|
|||||||
.collection(BackendTrack.collection)
|
.collection(BackendTrack.collection)
|
||||||
.getFirstListItem("spotify_id = '${track.id}'"),
|
.getFirstListItem("spotify_id = '${track.id}'"),
|
||||||
).catchError((e, stack) {
|
).catchError((e, stack) {
|
||||||
Catcher.reportCheckedError(e, stack);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: const PageWindowTitleBar(
|
appBar: const PageWindowTitleBar(
|
||||||
leading: BackButton(),
|
leading: BackButton(),
|
||||||
|
@ -14,6 +14,7 @@ class LibraryPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
return const SafeArea(
|
return const SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
length: 5,
|
length: 5,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
@ -62,8 +62,10 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: kIsDesktop && !kIsMacOS ? PageWindowTitleBar() : null,
|
appBar: kIsDesktop && !kIsMacOS ? const PageWindowTitleBar() : null,
|
||||||
|
extendBody: true,
|
||||||
body: !authenticationNotifier.isLoggedIn
|
body: !authenticationNotifier.isLoggedIn
|
||||||
? const AnonymousFallback()
|
? const AnonymousFallback()
|
||||||
: Column(
|
: Column(
|
||||||
|
@ -41,9 +41,10 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
}, [preferences.downloadLocation]);
|
}, [preferences.downloadLocation]);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: PageWindowTitleBar(
|
appBar: const PageWindowTitleBar(
|
||||||
title: const Text("Settings"),
|
title: Text("Settings"),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Row(
|
body: Row(
|
||||||
|
@ -190,7 +190,8 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
|
|
||||||
// skip all the activeTrack.skipSegments
|
// skip all the activeTrack.skipSegments
|
||||||
if (state?.isLoading != true &&
|
if (state?.isLoading != true &&
|
||||||
(state?.activeTrack as SpotubeTrack).skipSegments.isNotEmpty &&
|
(state?.activeTrack as SpotubeTrack?)?.skipSegments.isNotEmpty ==
|
||||||
|
true &&
|
||||||
preferences.skipSponsorSegments) {
|
preferences.skipSponsorSegments) {
|
||||||
for (final segment
|
for (final segment
|
||||||
in (state!.activeTrack as SpotubeTrack).skipSegments) {
|
in (state!.activeTrack as SpotubeTrack).skipSegments) {
|
||||||
|
@ -1393,6 +1393,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
simple_circular_progress_bar:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: simple_circular_progress_bar
|
||||||
|
sha256: e661ca942fbc617298e975b41fde19003d995de73ca6c2a1526c54d52f07151b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
skeleton_text:
|
skeleton_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -64,6 +64,7 @@ dependencies:
|
|||||||
queue: ^3.1.0+1
|
queue: ^3.1.0+1
|
||||||
scroll_to_index: ^3.0.1
|
scroll_to_index: ^3.0.1
|
||||||
shared_preferences: ^2.0.11
|
shared_preferences: ^2.0.11
|
||||||
|
simple_circular_progress_bar: ^1.0.2
|
||||||
skeleton_text: ^3.0.0
|
skeleton_text: ^3.0.0
|
||||||
spotify:
|
spotify:
|
||||||
git:
|
git:
|
||||||
|
Loading…
Reference in New Issue
Block a user