Add sleep timer feature
Timer stops playback (pause) after 5/10/15/30/60 min. Wall-clock deadline is self-correcting against OS throttling; 1Hz tick drives the countdown UI. Cancelled in stop() so no timer resurrects a paused player after teardown. - sleep_timer.dart: isolated time logic, injectable clock for testing - audio_handler.dart: sleepTimer field + cancel() in stop() - now_playing_screen.dart: AppBar button + bottom sheet - 10 unit tests via mocked clock Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
78f4b55e68
commit
51c2229148
@@ -21,6 +21,9 @@ class NowPlayingScreen extends StatelessWidget {
|
||||
icon: const Icon(Icons.keyboard_arrow_down),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
actions: [
|
||||
_SleepTimerButton(handler: handler),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: StreamBuilder<MediaItem?>(
|
||||
@@ -197,3 +200,75 @@ class _Controls extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SleepTimerButton extends StatelessWidget {
|
||||
const _SleepTimerButton({required this.handler});
|
||||
final MeloAudioHandler handler;
|
||||
|
||||
void _showSleepOptions(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (ctx) => Container(
|
||||
color: Theme.of(ctx).scaffoldBackgroundColor,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Sleep-Timer',
|
||||
style: Theme.of(ctx).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
...const [5, 10, 15, 30, 60].map(
|
||||
(minutes) => ListTile(
|
||||
title: Text('$minutes Minuten'),
|
||||
onTap: () {
|
||||
handler.sleepTimer.start(Duration(minutes: minutes));
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder<Duration?>(
|
||||
valueListenable: handler.sleepTimer.remaining,
|
||||
builder: (_, remaining, _) => remaining == null
|
||||
? const SizedBox.shrink()
|
||||
: ListTile(
|
||||
title: const Text('Timer beenden'),
|
||||
leading: const Icon(Icons.close),
|
||||
onTap: () {
|
||||
handler.sleepTimer.cancel();
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<Duration?>(
|
||||
valueListenable: handler.sleepTimer.remaining,
|
||||
builder: (context, remaining, _) {
|
||||
if (remaining == null) {
|
||||
return IconButton(
|
||||
tooltip: 'Sleep-Timer',
|
||||
icon: const Icon(Icons.bedtime_outlined),
|
||||
onPressed: () => _showSleepOptions(context),
|
||||
);
|
||||
}
|
||||
final minutes = remaining.inMinutes;
|
||||
final seconds = (remaining.inSeconds % 60).toString().padLeft(2, '0');
|
||||
return IconButton(
|
||||
tooltip: 'Sleep-Timer: noch $minutes:$seconds',
|
||||
icon: Icon(Icons.bedtime, color: MeloTheme.red),
|
||||
onPressed: () => _showSleepOptions(context),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user