mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 12:52:13 +02:00
fix(lyrics): hide controls after five seconds only during playback
This commit is contained in:
@@ -316,6 +316,7 @@ class NowPlayingScreen extends ConsumerStatefulWidget {
|
||||
class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
final PageController _pageController = PageController();
|
||||
ProviderSubscription<AsyncValue<MediaItem?>>? _mediaItemSub;
|
||||
ProviderSubscription<bool>? _lyricsPlayingSub;
|
||||
String? _loadedSource;
|
||||
String? _loadedResolvedSource;
|
||||
String? _loadedMetadataPath;
|
||||
@@ -359,6 +360,15 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
inspectUnresolvedContentUri: _currentPage == 1,
|
||||
),
|
||||
);
|
||||
_lyricsPlayingSub = ref.listenManual<bool>(playbackPlayingProvider, (
|
||||
previous,
|
||||
playing,
|
||||
) {
|
||||
if (!playing && _lyricsControlsHidden) {
|
||||
setState(() => _lyricsControlsHidden = false);
|
||||
}
|
||||
_scheduleLyricsControlsHide();
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_loadMetadataForItem(ref.read(currentMediaItemProvider).value);
|
||||
@@ -380,6 +390,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
void dispose() {
|
||||
_lyricsIdleTimer?.cancel();
|
||||
_mediaItemSub?.close();
|
||||
_lyricsPlayingSub?.close();
|
||||
_pageController.dispose();
|
||||
_artworkColorsChanged.dispose();
|
||||
super.dispose();
|
||||
@@ -1039,6 +1050,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
context.isMornye &&
|
||||
!_landscape &&
|
||||
_currentPage == 1 &&
|
||||
ref.read(playbackPlayingProvider) &&
|
||||
!MediaQuery.accessibleNavigationOf(context);
|
||||
|
||||
void _scheduleLyricsControlsHide() {
|
||||
@@ -1050,7 +1062,7 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
_lyricsPointers.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
_lyricsIdleTimer = Timer(const Duration(seconds: 3), () {
|
||||
_lyricsIdleTimer = Timer(const Duration(seconds: 5), () {
|
||||
if (!mounted || !_canAutoHideLyricsControls) return;
|
||||
if (ModalRoute.of(context)?.isCurrent == false) {
|
||||
_scheduleLyricsControlsHide();
|
||||
@@ -1394,6 +1406,8 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
|
||||
if (landscape) {
|
||||
return MornyeLandscapePlayer(
|
||||
page: _currentPage,
|
||||
isPlaying: ref.watch(playbackPlayingProvider),
|
||||
controlsHeldOpen: _lyricsOptionsOpen,
|
||||
onPageChanged: _setMornyePage,
|
||||
artwork: stage(artworkOnly: true),
|
||||
header: _trackHeader(mediaItem, colorScheme, compact: true),
|
||||
|
||||
@@ -17,8 +17,10 @@ class MornyeLandscapePlayer extends StatefulWidget {
|
||||
required this.controls,
|
||||
required this.volume,
|
||||
required this.page,
|
||||
required this.isPlaying,
|
||||
required this.onPageChanged,
|
||||
this.lyricsOptions,
|
||||
this.controlsHeldOpen = false,
|
||||
});
|
||||
|
||||
final Widget artwork;
|
||||
@@ -28,6 +30,8 @@ class MornyeLandscapePlayer extends StatefulWidget {
|
||||
final Widget controls;
|
||||
final Widget volume;
|
||||
final int page;
|
||||
final bool isPlaying;
|
||||
final bool controlsHeldOpen;
|
||||
final ValueChanged<int> onPageChanged;
|
||||
final Widget? lyricsOptions;
|
||||
|
||||
@@ -54,21 +58,26 @@ class _MornyeLandscapePlayerState extends State<MornyeLandscapePlayer> {
|
||||
@override
|
||||
void didUpdateWidget(MornyeLandscapePlayer oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.page != widget.page) {
|
||||
if (oldWidget.page != widget.page ||
|
||||
oldWidget.isPlaying != widget.isPlaying) {
|
||||
_actionsVisible = true;
|
||||
_scheduleHide();
|
||||
} else if (oldWidget.controlsHeldOpen != widget.controlsHeldOpen) {
|
||||
_scheduleHide();
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleHide() {
|
||||
_hideTimer?.cancel();
|
||||
if (widget.page != 1 ||
|
||||
!widget.isPlaying ||
|
||||
widget.controlsHeldOpen ||
|
||||
!_actionsVisible ||
|
||||
_audioOutputOpen ||
|
||||
MediaQuery.accessibleNavigationOf(context)) {
|
||||
return;
|
||||
}
|
||||
_hideTimer = Timer(const Duration(seconds: 3), () {
|
||||
_hideTimer = Timer(const Duration(seconds: 5), () {
|
||||
if (!mounted) return;
|
||||
if (ModalRoute.of(context)?.isCurrent == false) {
|
||||
_scheduleHide();
|
||||
|
||||
@@ -446,12 +446,13 @@ void main() {
|
||||
|
||||
for (final reducedMotion in [false, true]) {
|
||||
testWidgets(
|
||||
'lyrics hide controls after three idle seconds and restore on tap (reduced motion: $reducedMotion)',
|
||||
'lyrics hide controls after five idle seconds and restore on tap (reduced motion: $reducedMotion)',
|
||||
(tester) async {
|
||||
await pumpNowPlaying(
|
||||
tester,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
size: const Size(393, 852),
|
||||
playback: PlaybackState(playing: true),
|
||||
wrapPlayer: (player) => Builder(
|
||||
builder: (context) => MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
@@ -467,12 +468,12 @@ void main() {
|
||||
final queueButton = find.byIcon(CupertinoIcons.list_bullet);
|
||||
final play = find.widgetWithIcon(
|
||||
MornyePlaybackButton,
|
||||
CupertinoIcons.play_fill,
|
||||
CupertinoIcons.pause_fill,
|
||||
);
|
||||
final volume = find.byType(MornyeVolumeControl);
|
||||
await tester.tap(lyricsButton);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 2900));
|
||||
await tester.pump(const Duration(milliseconds: 4900));
|
||||
expect(play.hitTestable(), findsOneWidget);
|
||||
final list = find.byType(ListView);
|
||||
final originalHeight = tester.getSize(list).height;
|
||||
@@ -505,7 +506,7 @@ void main() {
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
expect(volume.hitTestable(), findsOneWidget);
|
||||
await touch.up();
|
||||
await tester.pump(const Duration(milliseconds: 2900));
|
||||
await tester.pump(const Duration(milliseconds: 4900));
|
||||
expect(volume.hitTestable(), findsOneWidget);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -514,18 +515,77 @@ void main() {
|
||||
await reveal();
|
||||
await tester.tap(queueButton.hitTestable());
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 4));
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(play.hitTestable(), findsOneWidget);
|
||||
expect(queueButton.hitTestable(), findsOneWidget);
|
||||
await tester.tap(queueButton.hitTestable());
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 4));
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(play.hitTestable(), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (final landscape in [false, true]) {
|
||||
testWidgets(
|
||||
'paused lyrics keep controls visible and resume gets five seconds (landscape: $landscape)',
|
||||
(tester) async {
|
||||
final playback = StreamController<PlaybackState>();
|
||||
addTearDown(playback.close);
|
||||
await pumpNowPlaying(
|
||||
tester,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
size: landscape ? const Size(852, 393) : const Size(393, 852),
|
||||
playbackEvents: playback.stream,
|
||||
);
|
||||
playback.add(PlaybackState(playing: false));
|
||||
mediaItems.add(item('many'));
|
||||
await tester.pumpAndSettle();
|
||||
final lyricsButton = find.byIcon(CupertinoIcons.quote_bubble);
|
||||
await tester.tap(lyricsButton);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
|
||||
playback.add(PlaybackState(playing: true));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 4900));
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await tester.pumpAndSettle();
|
||||
expect(lyricsButton.hitTestable(), findsNothing);
|
||||
|
||||
playback.add(PlaybackState(playing: false));
|
||||
await tester.pumpAndSettle();
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
|
||||
// Pausing during the countdown must cancel the pending hide too.
|
||||
playback.add(PlaybackState(playing: true));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 3));
|
||||
playback.add(PlaybackState(playing: false));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
|
||||
playback.add(PlaybackState(playing: true));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 4900));
|
||||
expect(lyricsButton.hitTestable(), findsOneWidget);
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
await tester.pumpAndSettle();
|
||||
expect(lyricsButton.hitTestable(), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'accessible navigation keeps lyric controls available while idle',
|
||||
(tester) async {
|
||||
@@ -533,6 +593,7 @@ void main() {
|
||||
tester,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
size: const Size(393, 852),
|
||||
playback: PlaybackState(playing: true),
|
||||
wrapPlayer: (player) => Builder(
|
||||
builder: (context) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(accessibleNavigation: true),
|
||||
@@ -544,7 +605,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byIcon(CupertinoIcons.quote_bubble));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(find.byType(MornyeVolumeControl).hitTestable(), findsOneWidget);
|
||||
expect(
|
||||
find.byIcon(CupertinoIcons.quote_bubble).hitTestable(),
|
||||
@@ -1185,6 +1246,7 @@ void main() {
|
||||
tester,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
size: const Size(393, 852),
|
||||
playback: PlaybackState(playing: true),
|
||||
);
|
||||
mediaItems.add(item('first'));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -1217,7 +1279,7 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
expect(
|
||||
find.byIcon(CupertinoIcons.play_fill).hitTestable(),
|
||||
find.byIcon(CupertinoIcons.pause_fill).hitTestable(),
|
||||
findsOneWidget,
|
||||
);
|
||||
final volumeSlider = find.descendant(
|
||||
@@ -1243,7 +1305,7 @@ void main() {
|
||||
tester.getRect(lyric).top,
|
||||
greaterThan(tester.getRect(header).bottom),
|
||||
);
|
||||
await tester.pump(const Duration(seconds: 3));
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byIcon(CupertinoIcons.quote_bubble).hitTestable(),
|
||||
@@ -1838,6 +1900,7 @@ void main() {
|
||||
tester,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
size: landscape ? const Size(844, 390) : const Size(390, 844),
|
||||
playback: PlaybackState(playing: true),
|
||||
wrapPlayer: (player) => Builder(
|
||||
builder: (context) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(disableAnimations: true),
|
||||
@@ -1852,7 +1915,7 @@ void main() {
|
||||
final options = find.byKey(const ValueKey('lyrics-language-options'));
|
||||
await tester.tap(options);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump(const Duration(seconds: 4));
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(find.text('Hide Pronunciation').hitTestable(), findsOneWidget);
|
||||
await tester.tap(find.text('Hide Pronunciation'));
|
||||
await tester.pump();
|
||||
@@ -1860,7 +1923,7 @@ void main() {
|
||||
expect(find.text('Pronunciation'), findsNothing);
|
||||
expect(options.hitTestable(), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(seconds: 3));
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pumpAndSettle();
|
||||
expect(options.hitTestable(), findsNothing);
|
||||
if (landscape) {
|
||||
|
||||
Reference in New Issue
Block a user