|
@@ -1,9 +1,12 @@
|
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:flutter_clock/model/timer_data.dart';
|
|
|
import 'package:flutter_clock/model/timer_settings.dart';
|
|
import 'package:flutter_clock/model/timer_settings.dart';
|
|
|
import 'package:flutter_clock/pages/timer/timer_settings_page.dart';
|
|
import 'package:flutter_clock/pages/timer/timer_settings_page.dart';
|
|
|
import 'package:flutter_clock/utils/audio_manager.dart';
|
|
import 'package:flutter_clock/utils/audio_manager.dart';
|
|
|
import 'package:flutter_clock/utils/screen_manager.dart';
|
|
import 'package:flutter_clock/utils/screen_manager.dart';
|
|
|
|
|
+import 'package:flutter_clock/utils/background_timer_service.dart';
|
|
|
|
|
+import 'package:flutter_clock/utils/notification_manager.dart';
|
|
|
|
|
|
|
|
/// Description: 倒计时页面
|
|
/// Description: 倒计时页面
|
|
|
/// Time : 04/06/2025 Sunday
|
|
/// Time : 04/06/2025 Sunday
|
|
@@ -22,10 +25,16 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
int _seconds = 0;
|
|
int _seconds = 0;
|
|
|
|
|
|
|
|
// For timer controller
|
|
// For timer controller
|
|
|
- Timer? _timer;
|
|
|
|
|
TimerState timerState = TimerState.prepare;
|
|
TimerState timerState = TimerState.prepare;
|
|
|
int _remainingSeconds = 0;
|
|
int _remainingSeconds = 0;
|
|
|
|
|
|
|
|
|
|
+ // Total timer duration in seconds (for progress calculation)
|
|
|
|
|
+ int _totalDurationSeconds = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Background services
|
|
|
|
|
+ final BackgroundTimerService _backgroundTimerService = BackgroundTimerService();
|
|
|
|
|
+ final NotificationManager _notificationManager = NotificationManager();
|
|
|
|
|
+
|
|
|
// Settings
|
|
// Settings
|
|
|
late TimerSettings _settings;
|
|
late TimerSettings _settings;
|
|
|
bool _settingsLoaded = false;
|
|
bool _settingsLoaded = false;
|
|
@@ -43,13 +52,15 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
void initState() {
|
|
void initState() {
|
|
|
super.initState();
|
|
super.initState();
|
|
|
WidgetsBinding.instance.addObserver(this);
|
|
WidgetsBinding.instance.addObserver(this);
|
|
|
|
|
+ _initializeBackgroundTimer();
|
|
|
_loadSettings();
|
|
_loadSettings();
|
|
|
|
|
+ _restoreTimerState();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void dispose() {
|
|
void dispose() {
|
|
|
- _timer?.cancel();
|
|
|
|
|
_audioManager.dispose();
|
|
_audioManager.dispose();
|
|
|
|
|
+ _backgroundTimerService.dispose();
|
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
|
ScreenManager.disableWakeLock();
|
|
ScreenManager.disableWakeLock();
|
|
|
}
|
|
}
|
|
@@ -62,11 +73,42 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
|
- if (state == AppLifecycleState.resumed &&
|
|
|
|
|
- timerState == TimerState.running) {
|
|
|
|
|
- _syncTimer();
|
|
|
|
|
|
|
+ // No additional handling required as background service handles the state
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Restore timer state from persistent storage
|
|
|
|
|
+ Future<void> _restoreTimerState() async {
|
|
|
|
|
+ final timerData = await TimerData.load();
|
|
|
|
|
+
|
|
|
|
|
+ if (timerData.isRunning) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ timerState = TimerState.running;
|
|
|
|
|
+ _remainingSeconds = timerData.calculateRemainingSeconds();
|
|
|
|
|
+ _totalDurationSeconds = _backgroundTimerService.totalDurationSeconds;
|
|
|
|
|
+ });
|
|
|
|
|
+ } else if (timerData.isPaused && timerData.pausedRemaining != null) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ timerState = TimerState.pause;
|
|
|
|
|
+ _remainingSeconds = timerData.pausedRemaining!;
|
|
|
|
|
+ _totalDurationSeconds = _backgroundTimerService.totalDurationSeconds;
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /// Initialize the background timer service
|
|
|
|
|
+ Future<void> _initializeBackgroundTimer() async {
|
|
|
|
|
+ await _backgroundTimerService.initialize(
|
|
|
|
|
+ onTimerComplete: _timerCompleted,
|
|
|
|
|
+ onTimerTick: (remainingSeconds) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _remainingSeconds = remainingSeconds;
|
|
|
|
|
+ if (remainingSeconds <= 0 && timerState != TimerState.finish) {
|
|
|
|
|
+ timerState = TimerState.finish;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
Future<void> _loadSettings() async {
|
|
Future<void> _loadSettings() async {
|
|
|
_settings = await TimerSettings.loadSettings();
|
|
_settings = await TimerSettings.loadSettings();
|
|
@@ -75,12 +117,13 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// resumed 暂停, 恢复
|
|
|
|
|
|
|
+ /// Start a new timer or resume an existing paused timer
|
|
|
void _startTimer(bool resumed) {
|
|
void _startTimer(bool resumed) {
|
|
|
if (resumed) {
|
|
if (resumed) {
|
|
|
setState(() {
|
|
setState(() {
|
|
|
timerState = TimerState.running;
|
|
timerState = TimerState.running;
|
|
|
});
|
|
});
|
|
|
|
|
+ _backgroundTimerService.resumeTimer(_remainingSeconds, _totalDurationSeconds);
|
|
|
} else {
|
|
} else {
|
|
|
final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
|
|
final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
|
|
|
if (totalSeconds <= 0) return;
|
|
if (totalSeconds <= 0) return;
|
|
@@ -88,52 +131,41 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
setState(() {
|
|
setState(() {
|
|
|
timerState = TimerState.running;
|
|
timerState = TimerState.running;
|
|
|
_remainingSeconds = totalSeconds;
|
|
_remainingSeconds = totalSeconds;
|
|
|
|
|
+ _totalDurationSeconds = totalSeconds;
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ _backgroundTimerService.startTimer(totalSeconds);
|
|
|
}
|
|
}
|
|
|
- _timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
|
|
|
|
- setState(() {
|
|
|
|
|
- if (_remainingSeconds > 0) {
|
|
|
|
|
- _remainingSeconds--;
|
|
|
|
|
- } else {
|
|
|
|
|
- timerState = TimerState.finish;
|
|
|
|
|
- _timerCompleted();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// Pause the running timer
|
|
|
void _pauseTimer() {
|
|
void _pauseTimer() {
|
|
|
- _timer?.cancel();
|
|
|
|
|
|
|
+ _backgroundTimerService.pauseTimer();
|
|
|
setState(() {
|
|
setState(() {
|
|
|
timerState = TimerState.pause;
|
|
timerState = TimerState.pause;
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// Reset the timer to initial state
|
|
|
void _resetTimer() {
|
|
void _resetTimer() {
|
|
|
- _timer?.cancel();
|
|
|
|
|
|
|
+ _backgroundTimerService.cancelTimer();
|
|
|
_audioManager.stopSound();
|
|
_audioManager.stopSound();
|
|
|
_audioManager.stopVibration();
|
|
_audioManager.stopVibration();
|
|
|
|
|
+ _notificationManager.stopVibration();
|
|
|
setState(() {
|
|
setState(() {
|
|
|
timerState = TimerState.prepare;
|
|
timerState = TimerState.prepare;
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- void _syncTimer() {
|
|
|
|
|
- // Recalculate elapsed time if app was in background
|
|
|
|
|
- // final elapsedSeconds = _hours * 3600 + _minutes * 60 + _seconds -
|
|
|
|
|
- // _remainingSeconds;
|
|
|
|
|
- // setState(() {
|
|
|
|
|
- // _remainingSeconds = elapsedSeconds;
|
|
|
|
|
- // });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ /// Handle timer completion
|
|
|
Future<void> _timerCompleted() async {
|
|
Future<void> _timerCompleted() async {
|
|
|
- _timer?.cancel();
|
|
|
|
|
- timerState = TimerState.finish;
|
|
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ timerState = TimerState.finish;
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// Play sound and vibrate
|
|
// Play sound and vibrate
|
|
|
if (_settings.vibrate) {
|
|
if (_settings.vibrate) {
|
|
|
- _audioManager.triggerVibration();
|
|
|
|
|
|
|
+ _notificationManager.notifyTimerCompletion();
|
|
|
}
|
|
}
|
|
|
_audioManager.playSound(_settings.sound, _settings.volume, _settings.loop);
|
|
_audioManager.playSound(_settings.sound, _settings.volume, _settings.loop);
|
|
|
|
|
|
|
@@ -141,7 +173,7 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
if (_settings.loop) {
|
|
if (_settings.loop) {
|
|
|
Future.delayed(Duration(seconds: 5), () {
|
|
Future.delayed(Duration(seconds: 5), () {
|
|
|
_audioManager.stopSound();
|
|
_audioManager.stopSound();
|
|
|
- _audioManager.stopVibration();
|
|
|
|
|
|
|
+ _notificationManager.stopVibration();
|
|
|
});
|
|
});
|
|
|
_startTimer(false);
|
|
_startTimer(false);
|
|
|
}
|
|
}
|
|
@@ -292,8 +324,9 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
child: CircularProgressIndicator(
|
|
child: CircularProgressIndicator(
|
|
|
value: timerState == TimerState.finish
|
|
value: timerState == TimerState.finish
|
|
|
? 1
|
|
? 1
|
|
|
- : _remainingSeconds /
|
|
|
|
|
- (_hours * 3600 + _minutes * 60 + _seconds),
|
|
|
|
|
|
|
+ : _totalDurationSeconds > 0
|
|
|
|
|
+ ? _remainingSeconds / _totalDurationSeconds
|
|
|
|
|
+ : 0,
|
|
|
strokeWidth: 5,
|
|
strokeWidth: 5,
|
|
|
backgroundColor: Colors.grey.withOpacity(0.1),
|
|
backgroundColor: Colors.grey.withOpacity(0.1),
|
|
|
color: Colors.blue,
|
|
color: Colors.blue,
|