background_timer_service.dart 6.8 KB

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