Files
plezy/lib/services/sleep_timer_service.dart
T

153 lines
4.5 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../utils/app_logger.dart';
/// Service to manage sleep timer functionality
/// Allows setting a timer to pause/stop playback after a specified duration
class SleepTimerService extends ChangeNotifier {
static final SleepTimerService _instance = SleepTimerService._internal();
factory SleepTimerService() => _instance;
SleepTimerService._internal();
Timer? _timer;
DateTime? _endTime;
Duration? _duration;
Duration? _originalDuration;
VoidCallback? _onTimerComplete;
bool _needsRestart = false;
final StreamController<void> _completedController = StreamController<void>.broadcast();
final StreamController<void> _promptController = StreamController<void>.broadcast();
/// Emits when the sleep timer completes (not when cancelled)
Stream<void> get onCompleted => _completedController.stream;
/// Emits when the timer fires and wants to show a "still watching?" prompt
Stream<void> get onPrompt => _promptController.stream;
bool get isActive => _timer != null && _timer!.isActive;
/// The time when the timer will complete
DateTime? get endTime => _endTime;
/// The original duration of the timer
Duration? get duration => _duration;
/// The user-selected duration (unmodified by extendTimer)
Duration? get originalDuration => _originalDuration;
/// Remaining time on the timer
Duration? get remainingTime {
if (_endTime == null) return null;
final remaining = _endTime!.difference(DateTime.now());
return remaining.isNegative ? Duration.zero : remaining;
}
void startTimer(Duration duration, VoidCallback onComplete) {
cancelTimer();
_originalDuration = duration;
_duration = duration;
_endTime = DateTime.now().add(duration);
_onTimerComplete = onComplete;
appLogger.d('Sleep timer started: ${duration.inMinutes} minutes');
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
final remaining = remainingTime;
if (remaining == null || remaining.inSeconds <= 0) {
appLogger.d('Sleep timer completed - showing prompt');
_stopTimerOnly();
_promptController.add(null);
} else {
notifyListeners();
}
});
notifyListeners();
}
/// Cancel the active timer (user-initiated, clears everything)
void cancelTimer() {
if (_timer != null || _originalDuration != null) {
appLogger.d('Sleep timer cancelled');
_timer?.cancel();
_timer = null;
_endTime = null;
_duration = null;
_originalDuration = null;
_onTimerComplete = null;
notifyListeners();
}
}
void restartTimer() {
if (_originalDuration != null && _onTimerComplete != null) {
final duration = _originalDuration!;
final callback = _onTimerComplete!;
startTimer(duration, callback);
}
}
/// Execute the completion callback directly (fallback path)
void executeCompletion() {
_executeCallback();
}
/// Mark that the timer should restart when a new playback session begins
/// (e.g. user exited the player and started something new)
void markNeedsRestart() {
if (isActive || _originalDuration != null) {
_needsRestart = true;
}
}
/// Restart the timer if it was marked for restart (new playback session).
/// [onComplete] provides the new callback for the fresh session.
void restartIfNeeded(VoidCallback onComplete) {
if (_needsRestart && _originalDuration != null) {
_needsRestart = false;
startTimer(_originalDuration!, onComplete);
}
}
/// Extend the current timer by the specified duration
void extendTimer(Duration additionalTime) {
if (_endTime != null) {
_endTime = _endTime!.add(additionalTime);
_duration = _duration != null ? _duration! + additionalTime : additionalTime;
appLogger.d('Sleep timer extended by ${additionalTime.inMinutes} minutes');
notifyListeners();
}
}
/// Stop the periodic timer but preserve _originalDuration and _onTimerComplete
/// for the prompt flow (restart/completion)
void _stopTimerOnly() {
_timer?.cancel();
_timer = null;
_endTime = null;
_duration = null;
notifyListeners();
}
void _executeCallback() {
if (_onTimerComplete != null) {
try {
_onTimerComplete!();
} catch (e) {
appLogger.e('Error executing sleep timer callback', error: e);
}
}
_completedController.add(null);
}
@override
void dispose() {
_timer?.cancel();
_completedController.close();
_promptController.close();
super.dispose();
}
}