From 226be37f8558d8fd2243e64170eae4d02019e4ef Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:31:29 +0200 Subject: [PATCH] fix(music): mark only the active sleep timer preset selected All timed presets shared one 'timed' bool, so arming any of 15/30/60 minutes checkmarked all three. The service now retains the armed duration (sleepTimerDuration) and the menu marks the matching preset. Co-Authored-By: Claude Fable 5 --- lib/screens/music/now_playing_screen.dart | 2 +- lib/services/music/music_playback_service.dart | 7 +++++++ .../music/music_playback_service_impl.dart | 8 ++++++++ .../music/music_playback_service_test.dart | 17 +++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/screens/music/now_playing_screen.dart b/lib/screens/music/now_playing_screen.dart index 5eba0458..0a96f314 100644 --- a/lib/screens/music/now_playing_screen.dart +++ b/lib/screens/music/now_playing_screen.dart @@ -209,7 +209,7 @@ class _NowPlayingScreenState extends State value: '$minutes', icon: Symbols.timer_rounded, label: t.music.sleepTimerMinutes(n: minutes), - selected: timed, + selected: timed && service.sleepTimerDuration == Duration(minutes: minutes), ), AppMenuItem( value: 'end_of_track', diff --git a/lib/services/music/music_playback_service.dart b/lib/services/music/music_playback_service.dart index 0d6fc1e6..b6eaf23e 100644 --- a/lib/services/music/music_playback_service.dart +++ b/lib/services/music/music_playback_service.dart @@ -117,6 +117,10 @@ abstract class MusicPlaybackService extends ChangeNotifier { /// inactive. DateTime? get sleepTimerEndsAt; + /// The duration the timed sleep timer was armed with (for marking the + /// chosen preset); null in end-of-track mode or when inactive. + Duration? get sleepTimerDuration; + /// Whether the sleep timer pauses at the end of the current track instead /// of after a fixed duration. bool get sleepTimerEndOfTrack; @@ -233,6 +237,9 @@ class StubMusicPlaybackService extends MusicPlaybackService { @override DateTime? get sleepTimerEndsAt => null; + @override + Duration? get sleepTimerDuration => null; + @override bool get sleepTimerEndOfTrack => false; diff --git a/lib/services/music/music_playback_service_impl.dart b/lib/services/music/music_playback_service_impl.dart index fb1573ce..50a7261a 100644 --- a/lib/services/music/music_playback_service_impl.dart +++ b/lib/services/music/music_playback_service_impl.dart @@ -129,6 +129,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO Timer? _sleepTimer; DateTime? _sleepTimerEndsAt; + Duration? _sleepTimerDuration; bool _sleepTimerEndOfTrack = false; final StreamController _positionController = StreamController.broadcast(); @@ -186,6 +187,9 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO @override DateTime? get sleepTimerEndsAt => _sleepTimerEndsAt; + @override + Duration? get sleepTimerDuration => _sleepTimerDuration; + @override bool get sleepTimerEndOfTrack => _sleepTimerEndOfTrack; @@ -932,10 +936,12 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO _sleepTimer?.cancel(); _sleepTimer = null; _sleepTimerEndsAt = null; + _sleepTimerDuration = null; final hadEndOfTrack = _sleepTimerEndOfTrack; _sleepTimerEndOfTrack = endOfTrack; if (!endOfTrack && duration != null) { _sleepTimerEndsAt = DateTime.now().add(duration); + _sleepTimerDuration = duration; _sleepTimer = Timer(duration, _onSleepTimerFired); } // End-of-track mode suppresses gapless arming (and leaving it restores @@ -949,6 +955,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO void _onSleepTimerFired() { _sleepTimer = null; _sleepTimerEndsAt = null; + _sleepTimerDuration = null; unawaited(pause()); notifyListeners(); } @@ -957,6 +964,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO _sleepTimer?.cancel(); _sleepTimer = null; _sleepTimerEndsAt = null; + _sleepTimerDuration = null; _sleepTimerEndOfTrack = false; } diff --git a/test/services/music/music_playback_service_test.dart b/test/services/music/music_playback_service_test.dart index 0dae1ce0..11b0d090 100644 --- a/test/services/music/music_playback_service_test.dart +++ b/test/services/music/music_playback_service_test.dart @@ -929,6 +929,23 @@ void main() { }); }); + test('timed sleep timer exposes its armed duration for the preset menu', () async { + await h.playTracks([t1]); + + h.service.setSleepTimer(const Duration(minutes: 30)); + expect(h.service.sleepTimerActive, isTrue); + expect(h.service.sleepTimerDuration, const Duration(minutes: 30)); + expect(h.service.sleepTimerEndOfTrack, isFalse); + + h.service.setSleepTimer(null, endOfTrack: true); + expect(h.service.sleepTimerDuration, isNull); + expect(h.service.sleepTimerActive, isTrue); + + h.service.setSleepTimer(null); + expect(h.service.sleepTimerActive, isFalse); + expect(h.service.sleepTimerDuration, isNull); + }); + test('end-of-track sleep timer suppresses arming and pauses at completion', () async { await h.playTracks([t1, t2]); expect(h.player.armed?.uri, _urlFor(t2));