|
@@ -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,10 @@ 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;
|
|
|
|
|
|
// Total timer duration in seconds (for progress calculation)
|
|
// Total timer duration in seconds (for progress calculation)
|
|
int _totalDurationSeconds = 0;
|
|
int _totalDurationSeconds = 0;
|
|
@@ -60,6 +64,7 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
void dispose() {
|
|
void dispose() {
|
|
_audioManager.dispose();
|
|
_audioManager.dispose();
|
|
_backgroundTimerService.dispose();
|
|
_backgroundTimerService.dispose();
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
if (ScreenManager.isWakeLockEnabled) {
|
|
ScreenManager.disableWakeLock();
|
|
ScreenManager.disableWakeLock();
|
|
}
|
|
}
|
|
@@ -116,6 +121,62 @@ 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
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// Wait until the next 10-minute mark and then start the timer
|
|
|
|
+ void _startTimerWithAlignment(int totalSeconds) {
|
|
|
|
+ // 取消任何现有的对齐定时器
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+
|
|
|
|
+ // 计算下一个整10分钟的时间点
|
|
|
|
+ final alignmentTime = _calculateNextAlignmentTime();
|
|
|
|
+ _alignmentTargetTime = alignmentTime;
|
|
|
|
+
|
|
|
|
+ // 设置状态为等待
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.waiting;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 计算等待时间(毫秒)
|
|
|
|
+ final waitDuration = alignmentTime.difference(DateTime.now());
|
|
|
|
+
|
|
|
|
+ // 创建定时器等待到指定时间
|
|
|
|
+ _alignmentTimer = Timer(waitDuration, () {
|
|
|
|
+ // 时间到了,启动实际的倒计时
|
|
|
|
+ setState(() {
|
|
|
|
+ timerState = TimerState.running;
|
|
|
|
+ _remainingSeconds = totalSeconds;
|
|
|
|
+ _totalDurationSeconds = totalSeconds;
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ _backgroundTimerService.startTimer(totalSeconds);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
/// 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 +193,54 @@ 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();
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+ } else {
|
|
|
|
+ _backgroundTimerService.pauseTimer();
|
|
|
|
+ }
|
|
|
|
+
|
|
setState(() {
|
|
setState(() {
|
|
timerState = TimerState.pause;
|
|
timerState = TimerState.pause;
|
|
});
|
|
});
|
|
@@ -168,6 +251,9 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
_backgroundTimerService.cancelTimer();
|
|
_backgroundTimerService.cancelTimer();
|
|
_audioManager.stopSound();
|
|
_audioManager.stopSound();
|
|
_audioManager.stopVibration();
|
|
_audioManager.stopVibration();
|
|
|
|
+ _alignmentTimer?.cancel();
|
|
|
|
+ _alignmentTargetTime = null;
|
|
|
|
+
|
|
setState(() {
|
|
setState(() {
|
|
timerState = TimerState.prepare;
|
|
timerState = TimerState.prepare;
|
|
});
|
|
});
|
|
@@ -201,6 +287,22 @@ 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 +412,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 +531,101 @@ class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
|
|
),
|
|
),
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ // 构建等待整点对齐的视图
|
|
|
|
+ Widget _buildWaitingForAlignmentView() {
|
|
|
|
+ final nextAlignmentTime = _alignmentTargetTime;
|
|
|
|
+
|
|
|
|
+ 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: null, // 显示不确定进度
|
|
|
|
+ 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}) {
|