diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index d6a9424f3..b4984c513 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -89,208 +89,215 @@ class PlayerControls extends HookConsumerWidget { iconSize: compact ? 18 : 24, ); - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - if (focusNode.canRequestFocus) { - focusNode.requestFocus(); - } - }, - child: FocusableActionDetector( - focusNode: focusNode, - shortcuts: shortcuts, - actions: actions, - child: Container( - constraints: const BoxConstraints(maxWidth: 600), - child: Column( - children: [ - if (!compact) - HookBuilder( - builder: (context) { - final ( - :bufferProgress, - :duration, - :position, - :progressStatic - ) = useProgress(ref); + return RepaintBoundary( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + if (focusNode.canRequestFocus) { + focusNode.requestFocus(); + } + }, + child: FocusableActionDetector( + focusNode: focusNode, + shortcuts: shortcuts, + actions: actions, + child: Container( + constraints: const BoxConstraints(maxWidth: 600), + child: Column( + children: [ + if (!compact) + HookBuilder( + builder: (context) { + final ( + :bufferProgress, + :duration, + :position, + :progressStatic + ) = useProgress(ref); - final totalMinutes = PrimitiveUtils.zeroPadNumStr( - duration.inMinutes.remainder(60), - ); - final totalSeconds = PrimitiveUtils.zeroPadNumStr( - duration.inSeconds.remainder(60), - ); - final currentMinutes = PrimitiveUtils.zeroPadNumStr( - position.inMinutes.remainder(60), - ); - final currentSeconds = PrimitiveUtils.zeroPadNumStr( - position.inSeconds.remainder(60), - ); + final totalMinutes = PrimitiveUtils.zeroPadNumStr( + duration.inMinutes.remainder(60), + ); + final totalSeconds = PrimitiveUtils.zeroPadNumStr( + duration.inSeconds.remainder(60), + ); + final currentMinutes = PrimitiveUtils.zeroPadNumStr( + position.inMinutes.remainder(60), + ); + final currentSeconds = PrimitiveUtils.zeroPadNumStr( + position.inSeconds.remainder(60), + ); - final progress = useState( - useMemoized(() => progressStatic, []), - ); + final progress = useState( + useMemoized(() => progressStatic, []), + ); - useEffect(() { - progress.value = progressStatic; - return null; - }, [progressStatic]); + useEffect(() { + progress.value = progressStatic; + return null; + }, [progressStatic]); - return Column( - children: [ - Tooltip( - message: context.l10n.slide_to_seek, - child: Slider( - // cannot divide by zero - // there's an edge case for value being bigger - // than total duration. Keeping it resolved - value: progress.value.toDouble(), - secondaryTrackValue: bufferProgress, - onChanged: playlist.isFetching == true || buffering - ? null - : (v) { - progress.value = v; - }, - onChangeEnd: (value) async { - await audioPlayer.seek( - Duration( - seconds: (value * duration.inSeconds).toInt(), - ), - ); - }, - activeColor: sliderColor, - secondaryActiveColor: sliderColor.withOpacity(0.2), - inactiveColor: sliderColor.withOpacity(0.15), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, + return Column( + children: [ + Tooltip( + message: context.l10n.slide_to_seek, + child: Slider( + // cannot divide by zero + // there's an edge case for value being bigger + // than total duration. Keeping it resolved + value: progress.value.toDouble(), + secondaryTrackValue: bufferProgress, + onChanged: + playlist.isFetching == true || buffering + ? null + : (v) { + progress.value = v; + }, + onChangeEnd: (value) async { + await audioPlayer.seek( + Duration( + seconds: + (value * duration.inSeconds).toInt(), + ), + ); + }, + activeColor: sliderColor, + secondaryActiveColor: + sliderColor.withOpacity(0.2), + inactiveColor: sliderColor.withOpacity(0.15), + ), ), - child: DefaultTextStyle( - style: theme.textTheme.bodySmall!.copyWith( - color: palette?.dominantColor?.bodyTextColor, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("$currentMinutes:$currentSeconds"), - Text("$totalMinutes:$totalSeconds"), - ], + child: DefaultTextStyle( + style: theme.textTheme.bodySmall!.copyWith( + color: palette?.dominantColor?.bodyTextColor, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text("$currentMinutes:$currentSeconds"), + Text("$totalMinutes:$totalSeconds"), + ], + ), ), ), - ), - ], - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - StreamBuilder( - stream: audioPlayer.shuffledStream, - builder: (context, snapshot) { - final shuffled = snapshot.data ?? false; - return IconButton( - tooltip: shuffled - ? context.l10n.unshuffle_playlist - : context.l10n.shuffle_playlist, - icon: const Icon(SpotubeIcons.shuffle), - style: shuffled ? activeButtonStyle : buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : () { - if (shuffled) { - audioPlayer.setShuffle(false); - } else { - audioPlayer.setShuffle(true); - } - }, - ); - }), - IconButton( - tooltip: context.l10n.previous_track, - icon: const Icon(SpotubeIcons.skipBack), - style: buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : playlistNotifier.previous, + ], + ); + }, ), - IconButton( - tooltip: playing - ? context.l10n.pause_playback - : context.l10n.resume_playback, - icon: playlist.isFetching == true - ? SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: accentColor?.titleTextColor ?? - theme.colorScheme.onPrimary, + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + StreamBuilder( + stream: audioPlayer.shuffledStream, + builder: (context, snapshot) { + final shuffled = snapshot.data ?? false; + return IconButton( + tooltip: shuffled + ? context.l10n.unshuffle_playlist + : context.l10n.shuffle_playlist, + icon: const Icon(SpotubeIcons.shuffle), + style: shuffled ? activeButtonStyle : buttonStyle, + onPressed: playlist.isFetching == true || buffering + ? null + : () { + if (shuffled) { + audioPlayer.setShuffle(false); + } else { + audioPlayer.setShuffle(true); + } + }, + ); + }), + IconButton( + tooltip: context.l10n.previous_track, + icon: const Icon(SpotubeIcons.skipBack), + style: buttonStyle, + onPressed: playlist.isFetching == true || buffering + ? null + : playlistNotifier.previous, + ), + IconButton( + tooltip: playing + ? context.l10n.pause_playback + : context.l10n.resume_playback, + icon: playlist.isFetching == true + ? SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: accentColor?.titleTextColor ?? + theme.colorScheme.onPrimary, + ), + ) + : Icon( + playing ? SpotubeIcons.pause : SpotubeIcons.play, ), - ) - : Icon( - playing ? SpotubeIcons.pause : SpotubeIcons.play, - ), - style: resumePauseStyle, - onPressed: playlist.isFetching == true - ? null - : Actions.handler( - context, - PlayPauseIntent(ref), - ), - ), - IconButton( - tooltip: context.l10n.next_track, - icon: const Icon(SpotubeIcons.skipForward), - style: buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : playlistNotifier.next, - ), - StreamBuilder( - stream: audioPlayer.loopModeStream, - builder: (context, snapshot) { - final loopMode = snapshot.data ?? PlaybackLoopMode.none; - return IconButton( - tooltip: loopMode == PlaybackLoopMode.one - ? context.l10n.loop_track - : loopMode == PlaybackLoopMode.all - ? context.l10n.repeat_playlist - : null, - icon: Icon( - loopMode == PlaybackLoopMode.one - ? SpotubeIcons.repeatOne - : SpotubeIcons.repeat, - ), - style: loopMode == PlaybackLoopMode.one || - loopMode == PlaybackLoopMode.all - ? activeButtonStyle - : buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : () async { - switch (await audioPlayer.loopMode) { - case PlaybackLoopMode.all: - audioPlayer - .setLoopMode(PlaybackLoopMode.one); - break; - case PlaybackLoopMode.one: - audioPlayer - .setLoopMode(PlaybackLoopMode.none); - break; - case PlaybackLoopMode.none: - audioPlayer - .setLoopMode(PlaybackLoopMode.all); - break; - } - }, - ); - }), - ], - ), - const SizedBox(height: 5) - ], + style: resumePauseStyle, + onPressed: playlist.isFetching == true + ? null + : Actions.handler( + context, + PlayPauseIntent(ref), + ), + ), + IconButton( + tooltip: context.l10n.next_track, + icon: const Icon(SpotubeIcons.skipForward), + style: buttonStyle, + onPressed: playlist.isFetching == true || buffering + ? null + : playlistNotifier.next, + ), + StreamBuilder( + stream: audioPlayer.loopModeStream, + builder: (context, snapshot) { + final loopMode = + snapshot.data ?? PlaybackLoopMode.none; + return IconButton( + tooltip: loopMode == PlaybackLoopMode.one + ? context.l10n.loop_track + : loopMode == PlaybackLoopMode.all + ? context.l10n.repeat_playlist + : null, + icon: Icon( + loopMode == PlaybackLoopMode.one + ? SpotubeIcons.repeatOne + : SpotubeIcons.repeat, + ), + style: loopMode == PlaybackLoopMode.one || + loopMode == PlaybackLoopMode.all + ? activeButtonStyle + : buttonStyle, + onPressed: playlist.isFetching == true || buffering + ? null + : () async { + switch (await audioPlayer.loopMode) { + case PlaybackLoopMode.all: + audioPlayer + .setLoopMode(PlaybackLoopMode.one); + break; + case PlaybackLoopMode.one: + audioPlayer + .setLoopMode(PlaybackLoopMode.none); + break; + case PlaybackLoopMode.none: + audioPlayer + .setLoopMode(PlaybackLoopMode.all); + break; + } + }, + ); + }), + ], + ), + const SizedBox(height: 5) + ], + ), ), ), ), diff --git a/lib/components/player/player_overlay.dart b/lib/components/player/player_overlay.dart index 7718e80f6..88ca54881 100644 --- a/lib/components/player/player_overlay.dart +++ b/lib/components/player/player_overlay.dart @@ -62,95 +62,99 @@ class PlayerOverlay extends HookConsumerWidget { child: AnimatedOpacity( duration: const Duration(milliseconds: 250), opacity: canShow ? 1 : 0, - child: Material( - type: MaterialType.transparency, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - HookBuilder( - builder: (context) { - final progress = useProgress(ref); - // animated - return TweenAnimationBuilder( - duration: const Duration(milliseconds: 250), - tween: Tween( - begin: 0, - end: progress.progressStatic, - ), - builder: (context, value, child) { - return LinearProgressIndicator( - value: value, - minHeight: 2, - backgroundColor: Colors.transparent, - valueColor: AlwaysStoppedAnimation( - theme.colorScheme.primary, - ), - ); - }, - ); - }, - ), - Expanded( - 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, + child: RepaintBoundary( + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + HookBuilder( + builder: (context) { + final progress = useProgress(ref); + // animated + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 250), + tween: Tween( + begin: 0, + end: progress.progressStatic, + ), + builder: (context, value, child) { + return LinearProgressIndicator( + value: value, + minHeight: 2, + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + theme.colorScheme.primary, + ), + ); + }, + ); + }, + ), + Expanded( + 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, + ), ), ), ), - ), - Row( - children: [ - IconButton( - icon: Icon( - SpotubeIcons.skipBack, - color: textColor, + Row( + children: [ + IconButton( + icon: Icon( + SpotubeIcons.skipBack, + color: textColor, + ), + onPressed: playlistNotifier.previous, ), - onPressed: playlistNotifier.previous, - ), - Consumer( - builder: (context, ref, _) { - return IconButton( - icon: playlist.isFetching - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(), - ) - : Icon( - playing - ? SpotubeIcons.pause - : SpotubeIcons.play, - color: textColor, - ), - onPressed: Actions.handler( - context, - PlayPauseIntent(ref), - ), - ); - }, - ), - IconButton( - icon: Icon( - SpotubeIcons.skipForward, - color: textColor, + Consumer( + builder: (context, ref, _) { + return IconButton( + icon: playlist.isFetching + ? const SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator(), + ) + : Icon( + playing + ? SpotubeIcons.pause + : SpotubeIcons.play, + color: textColor, + ), + onPressed: + Actions.handler( + context, + PlayPauseIntent(ref), + ), + ); + }, ), - onPressed: playlistNotifier.next, - ), - ], - ), - ], + IconButton( + icon: Icon( + SpotubeIcons.skipForward, + color: textColor, + ), + onPressed: playlistNotifier.next, + ), + ], + ), + ], + ), ), - ), - ], + ], + ), ), ), ),