|
@@ -15,7 +15,7 @@ class TimerPage extends StatefulWidget {
|
|
_TimerPageState createState() => _TimerPageState();
|
|
_TimerPageState createState() => _TimerPageState();
|
|
}
|
|
}
|
|
|
|
|
|
-enum TimerState { prepare, running, pause, finish }
|
|
|
|
|
|
+enum TimerState { prepare, running, pause, finish, waiting }
|
|
|
|
|
|
class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
// Timer duration values
|
|
// Timer duration values
|
|
@@ -26,6 +26,12 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
// For timer controller
|
|
// For timer controller
|
|
TimerState timerState = TimerState.prepare;
|
|
TimerState timerState = TimerState.prepare;
|
|
int _remainingSeconds = 0;
|
|
int _remainingSeconds = 0;
|
|
|
|
+
|
|
|
|
+ // Waiting for alignment timer
|
|
|
|
+ Timer? _alignmentTimer;
|
|
|
|
+ DateTime? _alignmentTargetTime;
|
|
|
|
+ DateTime? _alignmentStartTime; // 记录开始等待的时间点
|
|
|
|
+ Timer? _alignmentUIUpdateTimer; // For refreshing UI during waiting
|
|
|
|
|
|
// Total timer duration in seconds (for progress calculation)
|
|
// Total timer duration in seconds (for progress calculation)
|
|
int _totalDurationSeconds = 0;
|
|
int _totalDurationSeconds = 0;
|
|
@@ -60,6 +66,8 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
void dispose() {
|
|
void dispose() {
|
|
_audioManager.dispose();
|
|
_audioManager.dispose();
|
|
_backgroundTimerService.dispose();
|
|
_backgroundTimerService.dispose();
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+ _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
ScreenManager.disableWakeLock();
|
|
ScreenManager.disableWakeLock();
|
|
}
|
|
}
|
|
@@ -116,6 +124,99 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// Calculate the next 10-minute alignment time
|
|
|
|
+ DateTime _calculateNextAlignmentTime() {
|
|
|
|
+ final now = DateTime.now();
|
|
|
|
+ // 计算下一个整10分钟的时间点
|
|
|
|
+ int nextMinutes = ((now.minute ~/ 10) + 1) * 10;
|
|
|
|
+ int nextHour = now.hour;
|
|
|
|
+
|
|
|
|
+ // 处理进位
|
|
|
|
+ if (nextMinutes >= 60) {
|
|
|
|
+ nextMinutes = 0;
|
|
|
|
+ nextHour = (nextHour + 1) % 24;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return DateTime(
|
|
|
|
+ now.year,
|
|
|
|
+ now.month,
|
|
|
|
+ now.day,
|
|
|
|
+ nextHour,
|
|
|
|
+ nextMinutes,
|
|
|
|
+ 0,
|
|
|
|
+ 0,
|
|
|
|
+ 0
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 计算等待进度
|
|
|
|
+ double _calculateWaitingProgress() {
|
|
|
|
+ if (_alignmentTargetTime == null) return 0.0;
|
|
|
|
+
|
|
|
|
+ final now = DateTime.now();
|
|
|
|
+ final targetTime = _alignmentTargetTime!;
|
|
|
|
+ final startTime = _alignmentStartTime ?? now;
|
|
|
|
+
|
|
|
|
+ // 如果还没开始等待或已经过了目标时间
|
|
|
|
+ if (now.isAfter(targetTime)) return 1.0;
|
|
|
|
+ if (now.isBefore(startTime)) return 0.0;
|
|
|
|
+
|
|
|
|
+ // 计算总等待时间和已等待时间
|
|
|
|
+ final totalWaitDuration = targetTime.difference(startTime).inMilliseconds;
|
|
|
|
+ final elapsedWaitDuration = now.difference(startTime).inMilliseconds;
|
|
|
|
+
|
|
|
|
+ // 计算进度比例
|
|
|
|
+ if (totalWaitDuration <= 0) return 1.0;
|
|
|
|
+ final progress = elapsedWaitDuration / totalWaitDuration;
|
|
|
|
+
|
|
|
|
+ // 确保进度在0.0到1.0之间
|
|
|
|
+ return progress.clamp(0.0, 1.0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Wait until the next 10-minute mark and then start the timer
|
|
|
|
+ void _startTimerWithAlignment(int totalSeconds) {
|
|
|
|
+ // 取消任何现有的对齐定时器
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+ _alignmentUIUpdateTimer?.cancel();
|
|
|
|
+
|
|
|
|
+ // 计算下一个整10分钟的时间点
|
|
|
|
+ final alignmentTime = _calculateNextAlignmentTime();
|
|
|
|
+ _alignmentTargetTime = alignmentTime;
|
|
|
|
+ _alignmentStartTime = DateTime.now(); // 记录开始等待的时间
|
|
|
|
+
|
|
|
|
+ // 设置状态为等待
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.waiting;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 计算等待时间(毫秒)
|
|
|
|
+ final waitDuration = alignmentTime.difference(DateTime.now());
|
|
|
|
+
|
|
|
|
+ // 创建定时器等待到指定时间
|
|
|
|
+ _alignmentTimer = Timer(waitDuration, () {
|
|
|
|
+ // 时间到了,启动实际的倒计时
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.running;
|
|
|
|
+ _remainingSeconds = totalSeconds;
|
|
|
|
+ _totalDurationSeconds = totalSeconds;
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+ _alignmentStartTime = null;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
+ _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer when alignment is done
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 创建UI刷新定时器,每秒更新一次
|
|
|
|
+ _alignmentUIUpdateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
|
|
|
+ if (mounted) {
|
|
|
|
+ setState(() {
|
|
|
|
+ // 仅用于刷新UI
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
/// Start a new timer or resume an existing paused timer
|
|
/// Start a new timer or resume an existing paused timer
|
|
void _startTimer(String status) {
|
|
void _startTimer(String status) {
|
|
if (status == "resume") {
|
|
if (status == "resume") {
|
|
@@ -132,32 +233,56 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
// 计算总秒数
|
|
// 计算总秒数
|
|
final totalSeconds = _hourstmp * 3600 + _minutestmp * 60 + _secondstmp;
|
|
final totalSeconds = _hourstmp * 3600 + _minutestmp * 60 + _secondstmp;
|
|
if (totalSeconds <= 0) return;
|
|
if (totalSeconds <= 0) return;
|
|
-
|
|
|
|
- setState(() {
|
|
|
|
- timerState = TimerState.running;
|
|
|
|
- _remainingSeconds = totalSeconds;
|
|
|
|
- _totalDurationSeconds = totalSeconds;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
|
|
+
|
|
|
|
+ // 保存选择的时间值
|
|
|
|
+ _hours = _hourstmp;
|
|
|
|
+ _minutes = _minutestmp;
|
|
|
|
+ _seconds = _secondstmp;
|
|
|
|
+
|
|
|
|
+ // 检查是否需要整点对齐
|
|
|
|
+ if (_settings.alignToHour && _settings.loop && totalSeconds >= 600) { // 只有在循环模式、时间至少10分钟时才启用整点对齐
|
|
|
|
+ _startTimerWithAlignment(totalSeconds);
|
|
|
|
+ } else {
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.running;
|
|
|
|
+ _remainingSeconds = totalSeconds;
|
|
|
|
+ _totalDurationSeconds = totalSeconds;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
+ }
|
|
} else if (status == "loop") {
|
|
} else if (status == "loop") {
|
|
// 计算总秒数
|
|
// 计算总秒数
|
|
final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
|
|
final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
|
|
if (totalSeconds <= 0) return;
|
|
if (totalSeconds <= 0) return;
|
|
-
|
|
|
|
- setState(() {
|
|
|
|
- timerState = TimerState.running;
|
|
|
|
- _remainingSeconds = totalSeconds;
|
|
|
|
- _totalDurationSeconds = totalSeconds;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
|
|
+
|
|
|
|
+ // 检查是否需要整点对齐
|
|
|
|
+ if (_settings.alignToHour && _settings.loop && totalSeconds >= 600) { // 只有在循环模式、时间至少10分钟时才启用整点对齐
|
|
|
|
+ _startTimerWithAlignment(totalSeconds);
|
|
|
|
+ } else {
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.running;
|
|
|
|
+ _remainingSeconds = totalSeconds;
|
|
|
|
+ _totalDurationSeconds = totalSeconds;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// Pause the running timer
|
|
/// Pause the running timer
|
|
void _pauseTimer() {
|
|
void _pauseTimer() {
|
|
- _backgroundTimerService.pauseTimer();
|
|
|
|
|
|
+ if (timerState == TimerState.waiting) {
|
|
|
|
+ // 如果在等待对齐状态,取消等待定时器
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+ _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+ _alignmentStartTime = null;
|
|
|
|
+ } else {
|
|
|
|
+ _backgroundTimerService.pauseTimer();
|
|
|
|
+ }
|
|
|
|
+
|
|
setState(() {
|
|
setState(() {
|
|
timerState = TimerState.pause;
|
|
timerState = TimerState.pause;
|
|
});
|
|
});
|
|
@@ -168,6 +293,11 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
_backgroundTimerService.cancelTimer();
|
|
_backgroundTimerService.cancelTimer();
|
|
_audioManager.stopSound();
|
|
_audioManager.stopSound();
|
|
_audioManager.stopVibration();
|
|
_audioManager.stopVibration();
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+ _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+ _alignmentStartTime = null;
|
|
|
|
+
|
|
setState(() {
|
|
setState(() {
|
|
timerState = TimerState.prepare;
|
|
timerState = TimerState.prepare;
|
|
});
|
|
});
|
|
@@ -201,6 +331,23 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
final secs = seconds % 60;
|
|
final secs = seconds % 60;
|
|
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
|
|
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 格式化剩余等待时间
|
|
|
|
+ String _formatWaitingTime() {
|
|
|
|
+ if (_alignmentTargetTime == null) return "00:00";
|
|
|
|
+
|
|
|
|
+ final now = DateTime.now();
|
|
|
|
+ final difference = _alignmentTargetTime!.difference(now);
|
|
|
|
+
|
|
|
|
+ // 如果差异为负,说明已经过了目标时间
|
|
|
|
+ if (difference.isNegative) return "00:00";
|
|
|
|
+
|
|
|
|
+ // 计算分钟和秒数
|
|
|
|
+ final minutes = difference.inMinutes;
|
|
|
|
+ final seconds = difference.inSeconds % 60;
|
|
|
|
+
|
|
|
|
+ return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
|
|
|
+ }
|
|
|
|
|
|
@override
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
@@ -310,6 +457,12 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
|
|
|
|
Widget _buildCountdownView() {
|
|
Widget _buildCountdownView() {
|
|
final totalMinutes = _remainingSeconds ~/ 60;
|
|
final totalMinutes = _remainingSeconds ~/ 60;
|
|
|
|
+
|
|
|
|
+ // 如果处于等待整点对齐状态,显示不同的UI
|
|
|
|
+ if (timerState == TimerState.waiting) {
|
|
|
|
+ return _buildWaitingForAlignmentView();
|
|
|
|
+ }
|
|
|
|
+
|
|
return Scaffold(
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
backgroundColor: Colors.white,
|
|
body: Column(
|
|
body: Column(
|
|
@@ -423,6 +576,102 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
),
|
|
),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 构建等待整点对齐的视图
|
|
|
|
+ Widget _buildWaitingForAlignmentView() {
|
|
|
|
+ final nextAlignmentTime = _alignmentTargetTime;
|
|
|
|
+ final waitingProgress = _calculateWaitingProgress();
|
|
|
|
+
|
|
|
|
+ return Scaffold(
|
|
|
|
+ backgroundColor: Colors.white,
|
|
|
|
+ body: Column(
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
+ children: [
|
|
|
|
+ Expanded(
|
|
|
|
+ child: Center(
|
|
|
|
+ child: Column(
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
+ children: [
|
|
|
|
+ Container(
|
|
|
|
+ width: 300,
|
|
|
|
+ height: 300,
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
+ shape: BoxShape.circle,
|
|
|
|
+ border: Border.all(
|
|
|
|
+ color: Colors.orange.withOpacity(0.3),
|
|
|
|
+ width: 3,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ child: Stack(
|
|
|
|
+ alignment: Alignment.center,
|
|
|
|
+ children: [
|
|
|
|
+ // 等待进度指示器
|
|
|
|
+ SizedBox(
|
|
|
|
+ width: 300,
|
|
|
|
+ height: 300,
|
|
|
|
+ child: CircularProgressIndicator(
|
|
|
|
+ value: waitingProgress, // 显示确定的等待进度
|
|
|
|
+ strokeWidth: 5,
|
|
|
|
+ backgroundColor: Colors.grey.withOpacity(0.1),
|
|
|
|
+ color: Colors.orange,
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ // 等待时间显示
|
|
|
|
+ Column(
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
+ children: [
|
|
|
|
+ Text(
|
|
|
|
+ _formatWaitingTime(),
|
|
|
|
+ style: TextStyle(
|
|
|
|
+ fontSize: 40, fontWeight: FontWeight.bold),
|
|
|
|
+ ),
|
|
|
|
+ Text(
|
|
|
|
+ '等待对齐整10分钟',
|
|
|
|
+ style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
|
|
+ ),
|
|
|
|
+ SizedBox(height: 10),
|
|
|
|
+ if (nextAlignmentTime != null)
|
|
|
|
+ Text(
|
|
|
|
+ '将在 ${nextAlignmentTime.hour.toString().padLeft(2, '0')}:${nextAlignmentTime.minute.toString().padLeft(2, '0')} 开始',
|
|
|
|
+ style: TextStyle(fontSize: 14, color: Colors.orange),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ Padding(
|
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
|
|
|
|
+ child: Row(
|
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
|
|
+ children: [
|
|
|
|
+ // 唤醒屏幕
|
|
|
|
+ _buildCircleButton(
|
|
|
|
+ Icons.sunny,
|
|
|
|
+ Colors.grey[600]!,
|
|
|
|
+ () {
|
|
|
|
+ ScreenManager.toggleWakeLock();
|
|
|
|
+ setState(() {});
|
|
|
|
+ },
|
|
|
|
+ isActive: ScreenManager.isWakeLockEnabled,
|
|
|
|
+ ),
|
|
|
|
+ // 在等待状态只显示取消按钮
|
|
|
|
+ _buildCircleButton(
|
|
|
|
+ Icons.stop,
|
|
|
|
+ Colors.red,
|
|
|
|
+ () => _resetTimer(),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ ),
|
|
|
|
+ ],
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
|
|
Widget _buildCircleButton(IconData icon, Color color, VoidCallback onPressed,
|
|
Widget _buildCircleButton(IconData icon, Color color, VoidCallback onPressed,
|
|
{bool isActive = false}) {
|
|
{bool isActive = false}) {
|