123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- import 'dart:async';
- import 'dart:isolate';
- import 'dart:ui';
- import 'package:flutter_clock/model/timer_data.dart';
- /// A service to handle background timer operations
- class BackgroundTimerService {
- static const String _isolateName = 'timer_isolate';
-
- // Isolate for background processing
- Isolate? _isolate;
- ReceivePort? _receivePort;
-
- // Callback when timer completes
- Function? _onTimerComplete;
- // Callback for timer tick
- Function(int)? _onTimerTick;
-
- bool _isRunning = false;
- int _totalDurationSeconds = 0;
-
- /// Initialize the background service
- Future<void> initialize({
- Function? onTimerComplete,
- Function(int)? onTimerTick,
- }) async {
- _onTimerComplete = onTimerComplete;
- _onTimerTick = onTimerTick;
-
- // Register port for isolate communication
- _receivePort = ReceivePort();
- IsolateNameServer.registerPortWithName(
- _receivePort!.sendPort,
- _isolateName,
- );
-
- _receivePort!.listen(_handleIsolateMessage);
-
- // Check if there's a running timer from the previous session
- await _checkForExistingTimer();
- }
-
- /// Check if there's an existing timer running from a previous session
- Future<void> _checkForExistingTimer() async {
- final timerData = await TimerData.load();
-
- if (timerData.isRunning) {
- final remainingSeconds = timerData.calculateRemainingSeconds();
-
- if (remainingSeconds > 0) {
- // Timer is still running
- _startBackgroundTimer(remainingSeconds, _calculateTotalDuration(timerData));
-
- // Notify UI of the current state
- if (_onTimerTick != null) {
- _onTimerTick!(remainingSeconds);
- }
- } else {
- // Timer has completed while app was closed
- _completeTimer();
- }
- } else if (timerData.isPaused && timerData.pausedRemaining != null) {
- // Timer is paused, restore state but don't start it
- _totalDurationSeconds = _calculateTotalDuration(timerData);
-
- // Notify UI of the current state
- if (_onTimerTick != null && timerData.pausedRemaining != null) {
- _onTimerTick!(timerData.pausedRemaining!);
- }
- }
- }
-
- /// Calculate total duration from timer data
- int _calculateTotalDuration(TimerData timerData) {
- if (timerData.startTime != null && timerData.endTime != null) {
- return ((timerData.endTime! - timerData.startTime!) / 1000).round();
- }
- return 0;
- }
-
- /// Start a new timer for the given duration
- Future<void> startTimer(int durationSeconds) async {
- if (_isRunning) {
- await cancelTimer();
- }
-
- _totalDurationSeconds = durationSeconds;
-
- final now = DateTime.now().millisecondsSinceEpoch;
- final endTime = now + durationSeconds * 1000;
-
- // Save timer state
- final timerData = TimerData(
- startTime: now,
- endTime: endTime,
- isRunning: true,
- isPaused: false,
- );
- await timerData.save();
-
- _startBackgroundTimer(durationSeconds, durationSeconds);
- }
-
- /// Resume a paused timer
- Future<void> resumeTimer(int remainingSeconds, int totalDurationSeconds) async {
- _totalDurationSeconds = totalDurationSeconds;
-
- final now = DateTime.now().millisecondsSinceEpoch;
- final endTime = now + remainingSeconds * 1000;
-
- // Save timer state
- final timerData = TimerData(
- startTime: now - (totalDurationSeconds - remainingSeconds) * 1000,
- endTime: endTime,
- isRunning: true,
- isPaused: false,
- );
- await timerData.save();
-
- _startBackgroundTimer(remainingSeconds, totalDurationSeconds);
- }
-
- /// Start background timer processing
- Future<void> _startBackgroundTimer(int remainingSeconds, int totalDurationSeconds) async {
- if (_isolate != null) {
- _isolate!.kill(priority: Isolate.immediate);
- _isolate = null;
- }
-
- _isRunning = true;
-
- // Create a new isolate for background processing
- _isolate = await Isolate.spawn(
- _isolateEntryPoint,
- {
- 'remainingSeconds': remainingSeconds,
- 'totalDurationSeconds': totalDurationSeconds,
- 'sendPort': _receivePort!.sendPort,
- },
- );
- }
-
- /// Cancel the current timer
- Future<void> cancelTimer() async {
- if (_isolate != null) {
- _isolate!.kill(priority: Isolate.immediate);
- _isolate = null;
- }
-
- _isRunning = false;
-
- // Clear timer state
- final timerData = TimerData(
- isRunning: false,
- isPaused: false,
- );
- await timerData.save();
- }
-
- /// Pause the current timer
- Future<void> pauseTimer() async {
- if (!_isRunning) return;
-
- // Get current timer state
- final timerData = await TimerData.load();
- final remainingSeconds = timerData.calculateRemainingSeconds();
-
- if (_isolate != null) {
- _isolate!.kill(priority: Isolate.immediate);
- _isolate = null;
- }
-
- // Save paused state
- final updatedTimerData = TimerData(
- startTime: timerData.startTime,
- endTime: timerData.endTime,
- pausedRemaining: remainingSeconds,
- isRunning: false,
- isPaused: true,
- );
- await updatedTimerData.save();
-
- _isRunning = false;
- }
-
- /// Handle messages from the timer isolate
- void _handleIsolateMessage(dynamic message) {
- if (message is Map) {
- if (message.containsKey('tick')) {
- final remainingSeconds = message['tick'] as int;
- if (_onTimerTick != null) {
- _onTimerTick!(remainingSeconds);
- }
- } else if (message.containsKey('completed')) {
- _completeTimer();
- }
- }
- }
-
- /// Complete the timer and trigger necessary callbacks
- Future<void> _completeTimer() async {
- _isRunning = false;
-
- // Clear timer state
- final timerData = TimerData(
- isRunning: false,
- isPaused: false,
- );
- await timerData.save();
-
- if (_onTimerComplete != null) {
- _onTimerComplete!();
- }
- }
-
- /// Entry point for the timer isolate
- static void _isolateEntryPoint(Map<String, dynamic> params) {
- final remainingSeconds = params['remainingSeconds'] as int;
- final sendPort = params['sendPort'] as SendPort;
-
- int currentSeconds = remainingSeconds;
- Timer.periodic(Duration(seconds: 1), (timer) {
- currentSeconds--;
- sendPort.send({'tick': currentSeconds});
-
- if (currentSeconds <= 0) {
- timer.cancel();
- sendPort.send({'completed': true});
- }
- });
- }
-
- /// Cleanup resources
- void dispose() {
- if (_isolate != null) {
- _isolate!.kill(priority: Isolate.immediate);
- _isolate = null;
- }
-
- if (_receivePort != null) {
- _receivePort!.close();
- _receivePort = null;
- }
-
- IsolateNameServer.removePortNameMapping(_isolateName);
- }
-
- /// Get the current state of the timer
- bool get isRunning => _isRunning;
-
- /// Get the total duration in seconds
- int get totalDurationSeconds => _totalDurationSeconds;
- }
|