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 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 _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 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 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 _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 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 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 _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 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; }