background_timer_service.dart 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import 'dart:async';
  2. import 'dart:isolate';
  3. import 'dart:ui';
  4. import 'package:flutter_clock/model/timer_data.dart';
  5. import 'package:flutter_clock/utils/audio_manager.dart';
  6. /// A service to handle background timer operations
  7. class BackgroundTimerService {
  8. static const String _isolateName = 'timer_isolate';
  9. // Isolate for background processing
  10. Isolate? _isolate;
  11. ReceivePort? _receivePort;
  12. SendPort? _sendPort;
  13. // Callback when timer completes
  14. Function? _onTimerComplete;
  15. // Callback for timer tick
  16. Function(int)? _onTimerTick;
  17. bool _isRunning = false;
  18. int _totalDurationSeconds = 0;
  19. /// Initialize the background service
  20. Future<void> initialize({
  21. Function? onTimerComplete,
  22. Function(int)? onTimerTick,
  23. }) async {
  24. _onTimerComplete = onTimerComplete;
  25. _onTimerTick = onTimerTick;
  26. // Register port for isolate communication
  27. _receivePort = ReceivePort();
  28. IsolateNameServer.registerPortWithName(
  29. _receivePort!.sendPort,
  30. _isolateName,
  31. );
  32. _receivePort!.listen(_handleIsolateMessage);
  33. // Check if there's a running timer from the previous session
  34. await _checkForExistingTimer();
  35. }
  36. /// Check if there's an existing timer running from a previous session
  37. Future<void> _checkForExistingTimer() async {
  38. final timerData = await TimerData.load();
  39. if (timerData.isRunning) {
  40. final remainingSeconds = timerData.calculateRemainingSeconds();
  41. if (remainingSeconds > 0) {
  42. // Timer is still running
  43. _startBackgroundTimer(remainingSeconds, _calculateTotalDuration(timerData));
  44. // Notify UI of the current state
  45. if (_onTimerTick != null) {
  46. _onTimerTick!(remainingSeconds);
  47. }
  48. } else {
  49. // Timer has completed while app was closed
  50. _completeTimer();
  51. }
  52. } else if (timerData.isPaused && timerData.pausedRemaining != null) {
  53. // Timer is paused, restore state but don't start it
  54. _totalDurationSeconds = _calculateTotalDuration(timerData);
  55. // Notify UI of the current state
  56. if (_onTimerTick != null && timerData.pausedRemaining != null) {
  57. _onTimerTick!(timerData.pausedRemaining!);
  58. }
  59. }
  60. }
  61. /// Calculate total duration from timer data
  62. int _calculateTotalDuration(TimerData timerData) {
  63. if (timerData.startTime != null && timerData.endTime != null) {
  64. return ((timerData.endTime! - timerData.startTime!) / 1000).round();
  65. }
  66. return 0;
  67. }
  68. /// Start a new timer for the given duration
  69. Future<void> startTimer(int durationSeconds) async {
  70. if (_isRunning) {
  71. await cancelTimer();
  72. }
  73. _totalDurationSeconds = durationSeconds;
  74. final now = DateTime.now().millisecondsSinceEpoch;
  75. final endTime = now + durationSeconds * 1000;
  76. // Save timer state
  77. final timerData = TimerData(
  78. startTime: now,
  79. endTime: endTime,
  80. isRunning: true,
  81. isPaused: false,
  82. );
  83. await timerData.save();
  84. _startBackgroundTimer(durationSeconds, durationSeconds);
  85. }
  86. /// Resume a paused timer
  87. Future<void> resumeTimer(int remainingSeconds, int totalDurationSeconds) async {
  88. _totalDurationSeconds = totalDurationSeconds;
  89. final now = DateTime.now().millisecondsSinceEpoch;
  90. final endTime = now + remainingSeconds * 1000;
  91. // Save timer state
  92. final timerData = TimerData(
  93. startTime: now - (totalDurationSeconds - remainingSeconds) * 1000,
  94. endTime: endTime,
  95. isRunning: true,
  96. isPaused: false,
  97. );
  98. await timerData.save();
  99. _startBackgroundTimer(remainingSeconds, totalDurationSeconds);
  100. }
  101. /// Start background timer processing
  102. Future<void> _startBackgroundTimer(int remainingSeconds, int totalDurationSeconds) async {
  103. if (_isolate != null) {
  104. _isolate!.kill(priority: Isolate.immediate);
  105. _isolate = null;
  106. }
  107. _isRunning = true;
  108. // Create a new isolate for background processing
  109. _isolate = await Isolate.spawn(
  110. _isolateEntryPoint,
  111. {
  112. 'remainingSeconds': remainingSeconds,
  113. 'totalDurationSeconds': totalDurationSeconds,
  114. 'sendPort': _receivePort!.sendPort,
  115. },
  116. );
  117. }
  118. /// Cancel the current timer
  119. Future<void> cancelTimer() async {
  120. if (_isolate != null) {
  121. _isolate!.kill(priority: Isolate.immediate);
  122. _isolate = null;
  123. }
  124. _isRunning = false;
  125. // Clear timer state
  126. final timerData = TimerData(
  127. isRunning: false,
  128. isPaused: false,
  129. );
  130. await timerData.save();
  131. }
  132. /// Pause the current timer
  133. Future<void> pauseTimer() async {
  134. if (!_isRunning) return;
  135. // Get current timer state
  136. final timerData = await TimerData.load();
  137. final remainingSeconds = timerData.calculateRemainingSeconds();
  138. if (_isolate != null) {
  139. _isolate!.kill(priority: Isolate.immediate);
  140. _isolate = null;
  141. }
  142. // Save paused state
  143. final updatedTimerData = TimerData(
  144. startTime: timerData.startTime,
  145. endTime: timerData.endTime,
  146. pausedRemaining: remainingSeconds,
  147. isRunning: false,
  148. isPaused: true,
  149. );
  150. await updatedTimerData.save();
  151. _isRunning = false;
  152. }
  153. /// Handle messages from the timer isolate
  154. void _handleIsolateMessage(dynamic message) {
  155. if (message is Map) {
  156. if (message.containsKey('tick')) {
  157. final remainingSeconds = message['tick'] as int;
  158. if (_onTimerTick != null) {
  159. _onTimerTick!(remainingSeconds);
  160. }
  161. } else if (message.containsKey('completed')) {
  162. _completeTimer();
  163. }
  164. }
  165. }
  166. /// Complete the timer and trigger necessary callbacks
  167. Future<void> _completeTimer() async {
  168. _isRunning = false;
  169. // Clear timer state
  170. final timerData = TimerData(
  171. isRunning: false,
  172. isPaused: false,
  173. );
  174. await timerData.save();
  175. if (_onTimerComplete != null) {
  176. _onTimerComplete!();
  177. }
  178. }
  179. /// Entry point for the timer isolate
  180. static void _isolateEntryPoint(Map<String, dynamic> params) {
  181. final remainingSeconds = params['remainingSeconds'] as int;
  182. final sendPort = params['sendPort'] as SendPort;
  183. int currentSeconds = remainingSeconds;
  184. Timer.periodic(Duration(seconds: 1), (timer) {
  185. currentSeconds--;
  186. sendPort.send({'tick': currentSeconds});
  187. if (currentSeconds <= 0) {
  188. timer.cancel();
  189. sendPort.send({'completed': true});
  190. }
  191. });
  192. }
  193. /// Cleanup resources
  194. void dispose() {
  195. if (_isolate != null) {
  196. _isolate!.kill(priority: Isolate.immediate);
  197. _isolate = null;
  198. }
  199. if (_receivePort != null) {
  200. _receivePort!.close();
  201. _receivePort = null;
  202. }
  203. IsolateNameServer.removePortNameMapping(_isolateName);
  204. }
  205. /// Get the current state of the timer
  206. bool get isRunning => _isRunning;
  207. /// Get the total duration in seconds
  208. int get totalDurationSeconds => _totalDurationSeconds;
  209. }