import 'dart:async'; 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/pages/timer/timer_settings_page.dart'; import 'package:flutter_clock/utils/audio_manager.dart'; import 'package:flutter_clock/utils/screen_manager.dart'; import 'package:flutter_clock/utils/background_timer_service.dart'; /// Description: 倒计时页面 /// Time : 04/06/2025 Sunday /// Author : liuyuqi.gov@msn.cn class TimerPage extends StatefulWidget { @override _TimerPageState createState() => _TimerPageState(); } enum TimerState { prepare, running, pause, finish, waiting } class _TimerPageState extends State with WidgetsBindingObserver { // Timer duration values int _hours = 0; int _minutes = 10; int _seconds = 0; // For timer controller TimerState timerState = TimerState.prepare; 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) int _totalDurationSeconds = 0; // Background services final BackgroundTimerService _backgroundTimerService = BackgroundTimerService(); // Settings late TimerSettings _settings; bool _settingsLoaded = false; final AudioManager _audioManager = AudioManager(); // Wheel controllers final FixedExtentScrollController _hoursController = FixedExtentScrollController(initialItem: 0); final FixedExtentScrollController _minutesController = FixedExtentScrollController(initialItem: 10); final FixedExtentScrollController _secondsController = FixedExtentScrollController(initialItem: 0); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initializeBackgroundTimer(); _loadSettings(); _restoreTimerState(); } @override void dispose() { _audioManager.dispose(); _backgroundTimerService.dispose(); _alignmentTimer?.cancel(); _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer if (ScreenManager.isWakeLockEnabled) { ScreenManager.disableWakeLock(); } WidgetsBinding.instance.removeObserver(this); _hoursController.dispose(); _minutesController.dispose(); _secondsController.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { // No additional handling required as background service handles the state } /// Restore timer state from persistent storage Future _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 _initializeBackgroundTimer() async { await _backgroundTimerService.initialize( onTimerComplete: _timerCompleted, onTimerTick: (remainingSeconds) { setState(() { _remainingSeconds = remainingSeconds; if (remainingSeconds <= 0 && timerState != TimerState.finish) { timerState = TimerState.finish; } }); }, ); } Future _loadSettings() async { _settings = await TimerSettings.loadSettings(); setState(() { _settingsLoaded = true; }); } /// 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 void _startTimer(String status) { if (status == "resume") { setState(() { timerState = TimerState.running; }); _backgroundTimerService.resumeTimer( _remainingSeconds, _totalDurationSeconds); } else if (status == "start") { // 获取界面输入的时间 int _hourstmp = _hoursController.selectedItem; int _minutestmp = _minutesController.selectedItem; int _secondstmp = _secondsController.selectedItem; // 计算总秒数 final totalSeconds = _hourstmp * 3600 + _minutestmp * 60 + _secondstmp; if (totalSeconds <= 0) return; // 保存选择的时间值 _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") { // 计算总秒数 final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds; if (totalSeconds <= 0) return; // 检查是否需要整点对齐 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 void _pauseTimer() { if (timerState == TimerState.waiting) { // 如果在等待对齐状态,取消等待定时器 _alignmentTimer?.cancel(); _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer _alignmentTargetTime = null; _alignmentStartTime = null; } else { _backgroundTimerService.pauseTimer(); } setState(() { timerState = TimerState.pause; }); } /// Reset the timer to initial state void _resetTimer() { _backgroundTimerService.cancelTimer(); _audioManager.stopSound(); _audioManager.stopVibration(); _alignmentTimer?.cancel(); _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer _alignmentTargetTime = null; _alignmentStartTime = null; setState(() { timerState = TimerState.prepare; }); } /// Handle timer completion Future _timerCompleted() async { setState(() { timerState = TimerState.finish; }); // Play sound and vibrate if (_settings.vibrate) { _audioManager.triggerVibration(); } _audioManager.playSound(_settings.sound, _settings.volume, _settings.loop); // If loop is enabled, restart the timer if (_settings.loop) { Future.delayed(Duration(seconds: 5), () { _audioManager.stopSound(); _audioManager.stopVibration(); }); _startTimer("loop"); } } String _formatTime(int seconds) { final hours = seconds ~/ 3600; final minutes = (seconds % 3600) ~/ 60; final secs = seconds % 60; 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 Widget build(BuildContext context) { if (!_settingsLoaded) { return Center(child: CircularProgressIndicator()); } if (timerState != TimerState.prepare) { return _buildCountdownView(); } else { return _buildTimerSetupView(); } } Widget _buildTimerSetupView() { return Scaffold( backgroundColor: Colors.white, body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Row( children: [ Expanded( child: _buildTimerWheel( _hoursController, List.generate(24, (index) => index), (value) { setState(() { _hours = value; }); }, 'H', ), ), Expanded( child: _buildTimerWheel( _minutesController, List.generate(60, (index) => index), (value) { setState(() { _minutes = value; }); }, 'M', ), ), Expanded( child: _buildTimerWheel( _secondsController, List.generate(60, (index) => index), (value) { setState(() { _seconds = value; }); }, 'S', ), ), ], ), ), SizedBox(height: 20), 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.play_arrow, Colors.blue, () => _startTimer("start"), ), _buildCircleButton( Icons.settings, Colors.grey[600]!, () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (context) => TimerSettingsPage(settings: _settings)), ); if (result != null) { setState(() { _settings = result; }); } }, ), ], ), ), ], ), ); } Widget _buildCountdownView() { final totalMinutes = _remainingSeconds ~/ 60; // 如果处于等待整点对齐状态,显示不同的UI if (timerState == TimerState.waiting) { return _buildWaitingForAlignmentView(); } 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.blue.withOpacity(0.3), width: 3, ), ), child: Stack( alignment: Alignment.center, children: [ // Timer progress SizedBox( width: 300, height: 300, child: CircularProgressIndicator( value: timerState == TimerState.finish ? 1 : _totalDurationSeconds > 0 ? _remainingSeconds / _totalDurationSeconds : 0, strokeWidth: 5, backgroundColor: Colors.grey.withOpacity(0.1), color: Colors.blue, ), ), // Time display Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _formatTime(_remainingSeconds), style: TextStyle( fontSize: 40, fontWeight: FontWeight.bold), ), Text( 'Total ${totalMinutes} minutes', style: TextStyle(fontSize: 16, color: Colors.grey), ), ], ), // Indicator dot Positioned( bottom: 0, child: Container( width: 20, height: 20, decoration: BoxDecoration( color: Colors.blue, shape: BoxShape.circle, ), ), ), ], ), ), ], ), ), ), 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, ), // 暂停/开始 timerState == TimerState.running ? _buildCircleButton( Icons.pause, Colors.blue, () => _pauseTimer(), ) : _buildCircleButton( Icons.play_arrow, Colors.blue, () => _startTimer("resume"), ), // 重置 _buildCircleButton( Icons.stop, Colors.red, () => _resetTimer(), ), ], ), ), ], ), ); } // 构建等待整点对齐的视图 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, {bool isActive = false}) { return Container( width: 70, height: 70, decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? color : Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: IconButton( icon: Icon(icon, size: 30), color: isActive ? Colors.white : color, onPressed: onPressed, ), ); } Widget _buildTimerWheel( FixedExtentScrollController controller, List items, ValueChanged onChanged, String unit, ) { return Container( height: 400, decoration: BoxDecoration( border: Border( top: BorderSide(color: Colors.grey.withOpacity(0.3), width: 1), bottom: BorderSide(color: Colors.grey.withOpacity(0.3), width: 1), ), ), child: Stack( children: [ // Center highlight Positioned.fill( child: Center( child: Container( height: 50, decoration: BoxDecoration( color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), ), ), ), Container( alignment: Alignment.centerRight, padding: EdgeInsets.only(right: 15), child: Text( unit, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ListWheelScrollView( controller: controller, physics: FixedExtentScrollPhysics(), diameterRatio: 1.5, itemExtent: 50, children: items.map((value) { return Center( child: Text( value.toString().padLeft(2, '0'), style: TextStyle( fontSize: 30, color: Colors.black, fontWeight: FontWeight.w500, ), ), ); }).toList(), onSelectedItemChanged: onChanged, ), ], ), ); } }