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
@@ -0,0 +1,74 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SleepTimer {
|
||||
final VoidCallback onElapsed;
|
||||
final DateTime Function() now;
|
||||
|
||||
late final ValueNotifier<Duration?> remaining = ValueNotifier(null);
|
||||
Timer? _timer;
|
||||
DateTime? _deadline;
|
||||
bool _firedThisSession = false;
|
||||
|
||||
SleepTimer({
|
||||
required this.onElapsed,
|
||||
DateTime Function()? now,
|
||||
}) : now = now ?? DateTime.now;
|
||||
|
||||
void start(Duration duration) {
|
||||
if (duration <= Duration.zero) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
cancel(); // Replace any running timer
|
||||
_firedThisSession = false;
|
||||
// ponytail: Wall-Clock-Deadline, selbstkorrigierend gegen OS-Throttling.
|
||||
// Nachteil: manuelle Systemuhr-Änderung feuert früh/spät — akzeptiert.
|
||||
_deadline = now().add(duration);
|
||||
_updateRemaining();
|
||||
|
||||
// Start periodic updates (1Hz for UI)
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) => tick());
|
||||
}
|
||||
|
||||
void tick() {
|
||||
_updateRemaining();
|
||||
_checkElapsed();
|
||||
}
|
||||
|
||||
void _checkElapsed() {
|
||||
if (_deadline == null || _firedThisSession) return;
|
||||
|
||||
if (!now().isBefore(_deadline!)) {
|
||||
_firedThisSession = true;
|
||||
_deadline = null;
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
remaining.value = null;
|
||||
onElapsed();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateRemaining() {
|
||||
if (_deadline == null) {
|
||||
remaining.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
final diff = _deadline!.difference(now());
|
||||
if (diff.isNegative) {
|
||||
remaining.value = null;
|
||||
} else {
|
||||
remaining.value = diff;
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
_deadline = null;
|
||||
_firedThisSession = false;
|
||||
remaining.value = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user