timer_page.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_clock/model/timer_settings.dart';
  4. import 'package:flutter_clock/pages/timer_settings_page.dart';
  5. import 'package:flutter_clock/utils/audio_manager.dart';
  6. import 'package:flutter_clock/utils/screen_manager.dart';
  7. class TimerPage extends StatefulWidget {
  8. @override
  9. _TimerPageState createState() => _TimerPageState();
  10. }
  11. class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
  12. // Timer duration values
  13. int _hours = 0;
  14. int _minutes = 10;
  15. int _seconds = 0;
  16. // For timer controller
  17. Timer? _timer;
  18. bool _isRunning = false;
  19. bool _isCompleted = false;
  20. int _remainingSeconds = 0;
  21. // Settings
  22. late TimerSettings _settings;
  23. bool _settingsLoaded = false;
  24. final AudioManager _audioManager = AudioManager();
  25. // Wheel controllers
  26. final FixedExtentScrollController _hoursController =
  27. FixedExtentScrollController(initialItem: 0);
  28. final FixedExtentScrollController _minutesController =
  29. FixedExtentScrollController(initialItem: 10);
  30. final FixedExtentScrollController _secondsController =
  31. FixedExtentScrollController(initialItem: 0);
  32. @override
  33. void initState() {
  34. super.initState();
  35. WidgetsBinding.instance.addObserver(this);
  36. _loadSettings();
  37. }
  38. @override
  39. void dispose() {
  40. _timer?.cancel();
  41. _audioManager.dispose();
  42. if (ScreenManager.isWakeLockEnabled) {
  43. ScreenManager.disableWakeLock();
  44. }
  45. WidgetsBinding.instance.removeObserver(this);
  46. _hoursController.dispose();
  47. _minutesController.dispose();
  48. _secondsController.dispose();
  49. super.dispose();
  50. }
  51. @override
  52. void didChangeAppLifecycleState(AppLifecycleState state) {
  53. if (state == AppLifecycleState.resumed && _isRunning) {
  54. _syncTimer();
  55. }
  56. }
  57. Future<void> _loadSettings() async {
  58. _settings = await TimerSettings.loadSettings();
  59. setState(() {
  60. _settingsLoaded = true;
  61. });
  62. }
  63. void _startTimer() {
  64. final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
  65. if (totalSeconds <= 0) return;
  66. setState(() {
  67. _isRunning = true;
  68. _isCompleted = false;
  69. _remainingSeconds = totalSeconds;
  70. });
  71. _timer = Timer.periodic(Duration(seconds: 1), (timer) {
  72. setState(() {
  73. if (_remainingSeconds > 0) {
  74. _remainingSeconds--;
  75. } else {
  76. _isCompleted = true;
  77. _timerCompleted();
  78. }
  79. });
  80. });
  81. }
  82. void _pauseTimer() {
  83. _timer?.cancel();
  84. setState(() {
  85. _isRunning = false;
  86. });
  87. }
  88. void _resetTimer() {
  89. _timer?.cancel();
  90. _audioManager.stopSound();
  91. _audioManager.stopVibration();
  92. setState(() {
  93. _isRunning = false;
  94. _isCompleted = false;
  95. });
  96. }
  97. void _syncTimer() {
  98. // Recalculate elapsed time if app was in background
  99. }
  100. Future<void> _timerCompleted() async {
  101. _timer?.cancel();
  102. _isRunning = false;
  103. // Play sound and vibrate
  104. if (_settings.vibrate) {
  105. _audioManager.triggerVibration();
  106. }
  107. _audioManager.playSound(_settings.sound, _settings.volume, _settings.loop);
  108. // If loop is enabled, restart the timer
  109. if (_settings.loop) {
  110. Future.delayed(Duration(seconds: 5), () {
  111. _audioManager.stopSound();
  112. _audioManager.stopVibration();
  113. _startTimer();
  114. });
  115. }
  116. }
  117. String _formatTime(int seconds) {
  118. final hours = seconds ~/ 3600;
  119. final minutes = (seconds % 3600) ~/ 60;
  120. final secs = seconds % 60;
  121. return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
  122. }
  123. @override
  124. Widget build(BuildContext context) {
  125. if (!_settingsLoaded) {
  126. return Center(child: CircularProgressIndicator());
  127. }
  128. if (_isRunning || _isCompleted) {
  129. return _buildCountdownView();
  130. } else {
  131. return _buildTimerSetupView();
  132. }
  133. }
  134. Widget _buildTimerSetupView() {
  135. return Scaffold(
  136. backgroundColor: Colors.white,
  137. body: Column(
  138. mainAxisAlignment: MainAxisAlignment.center,
  139. children: [
  140. Expanded(
  141. child: Row(
  142. children: [
  143. Expanded(
  144. child: _buildTimerWheel(
  145. _hoursController,
  146. List.generate(24, (index) => index),
  147. (value) {
  148. setState(() {
  149. _hours = value;
  150. });
  151. },
  152. 'H',
  153. ),
  154. ),
  155. Expanded(
  156. child: _buildTimerWheel(
  157. _minutesController,
  158. List.generate(60, (index) => index),
  159. (value) {
  160. setState(() {
  161. _minutes = value;
  162. });
  163. },
  164. 'M',
  165. ),
  166. ),
  167. Expanded(
  168. child: _buildTimerWheel(
  169. _secondsController,
  170. List.generate(60, (index) => index),
  171. (value) {
  172. setState(() {
  173. _seconds = value;
  174. });
  175. },
  176. 'S',
  177. ),
  178. ),
  179. ],
  180. ),
  181. ),
  182. SizedBox(height: 20),
  183. Padding(
  184. padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
  185. child: Row(
  186. mainAxisAlignment: MainAxisAlignment.spaceAround,
  187. children: [
  188. _buildCircleButton(
  189. Icons.phone_android,
  190. Colors.grey[600]!,
  191. () {
  192. ScreenManager.toggleWakeLock();
  193. setState(() {});
  194. },
  195. isActive: ScreenManager.isWakeLockEnabled,
  196. ),
  197. _buildCircleButton(
  198. Icons.play_arrow,
  199. Colors.blue,
  200. () => _startTimer(),
  201. ),
  202. _buildCircleButton(
  203. Icons.settings,
  204. Colors.grey[600]!,
  205. () async {
  206. final result = await Navigator.push(
  207. context,
  208. MaterialPageRoute(
  209. builder: (context) =>
  210. TimerSettingsPage(settings: _settings)),
  211. );
  212. if (result != null) {
  213. setState(() {
  214. _settings = result;
  215. });
  216. }
  217. },
  218. ),
  219. ],
  220. ),
  221. ),
  222. ],
  223. ),
  224. );
  225. }
  226. Widget _buildCountdownView() {
  227. final totalMinutes = _remainingSeconds ~/ 60;
  228. return Scaffold(
  229. backgroundColor: Colors.white,
  230. body: Column(
  231. mainAxisAlignment: MainAxisAlignment.center,
  232. children: [
  233. Expanded(
  234. child: Center(
  235. child: Column(
  236. mainAxisAlignment: MainAxisAlignment.center,
  237. children: [
  238. Container(
  239. width: 300,
  240. height: 300,
  241. decoration: BoxDecoration(
  242. shape: BoxShape.circle,
  243. border: Border.all(
  244. color: Colors.blue.withOpacity(0.3),
  245. width: 1,
  246. ),
  247. ),
  248. child: Stack(
  249. alignment: Alignment.center,
  250. children: [
  251. // Timer progress
  252. SizedBox(
  253. width: 300,
  254. height: 300,
  255. child: CircularProgressIndicator(
  256. value: _isCompleted
  257. ? 1
  258. : _remainingSeconds /
  259. (_hours * 3600 + _minutes * 60 + _seconds),
  260. strokeWidth: 1,
  261. backgroundColor: Colors.grey.withOpacity(0.1),
  262. color: Colors.blue,
  263. ),
  264. ),
  265. // Time display
  266. Column(
  267. mainAxisAlignment: MainAxisAlignment.center,
  268. children: [
  269. Text(
  270. _formatTime(_remainingSeconds),
  271. style: TextStyle(
  272. fontSize: 40, fontWeight: FontWeight.bold),
  273. ),
  274. Text(
  275. 'Total ${totalMinutes} minutes',
  276. style:
  277. TextStyle(fontSize: 16, color: Colors.grey),
  278. ),
  279. ],
  280. ),
  281. // Indicator dot
  282. Positioned(
  283. bottom: 0,
  284. child: Container(
  285. width: 10,
  286. height: 10,
  287. decoration: BoxDecoration(
  288. color: Colors.blue,
  289. shape: BoxShape.circle,
  290. ),
  291. ),
  292. ),
  293. ],
  294. ),
  295. ),
  296. ],
  297. ),
  298. ),
  299. ),
  300. Padding(
  301. padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
  302. child: Row(
  303. mainAxisAlignment: MainAxisAlignment.spaceAround,
  304. children: [
  305. _buildCircleButton(
  306. Icons.phone_android,
  307. Colors.grey[600]!,
  308. () {
  309. ScreenManager.toggleWakeLock();
  310. setState(() {});
  311. },
  312. isActive: ScreenManager.isWakeLockEnabled,
  313. ),
  314. _isRunning
  315. ? _buildCircleButton(
  316. Icons.pause,
  317. Colors.blue,
  318. () => _pauseTimer(),
  319. )
  320. : _buildCircleButton(
  321. Icons.play_arrow,
  322. Colors.blue,
  323. () => _startTimer(),
  324. ),
  325. _buildCircleButton(
  326. Icons.stop,
  327. Colors.red,
  328. () => _resetTimer(),
  329. ),
  330. ],
  331. ),
  332. ),
  333. ],
  334. ),
  335. );
  336. }
  337. Widget _buildCircleButton(IconData icon, Color color, VoidCallback onPressed,
  338. {bool isActive = false}) {
  339. return Container(
  340. width: 70,
  341. height: 70,
  342. decoration: BoxDecoration(
  343. shape: BoxShape.circle,
  344. color: isActive ? color : Colors.white,
  345. boxShadow: [
  346. BoxShadow(
  347. color: Colors.black.withOpacity(0.1),
  348. blurRadius: 8,
  349. offset: Offset(0, 2),
  350. ),
  351. ],
  352. ),
  353. child: IconButton(
  354. icon: Icon(icon, size: 30),
  355. color: isActive ? Colors.white : color,
  356. onPressed: onPressed,
  357. ),
  358. );
  359. }
  360. Widget _buildTimerWheel(
  361. FixedExtentScrollController controller,
  362. List<int> items,
  363. ValueChanged<int> onChanged,
  364. String unit,
  365. ) {
  366. return Column(
  367. children: [
  368. Expanded(
  369. child: Container(
  370. decoration: BoxDecoration(
  371. border: Border(
  372. top: BorderSide(color: Colors.grey.withOpacity(0.3), width: 1),
  373. bottom:
  374. BorderSide(color: Colors.grey.withOpacity(0.3), width: 1),
  375. ),
  376. ),
  377. child: Stack(
  378. children: [
  379. // Center highlight
  380. Positioned.fill(
  381. child: Center(
  382. child: Container(
  383. height: 50,
  384. decoration: BoxDecoration(
  385. color: Colors.blue.withOpacity(0.1),
  386. borderRadius: BorderRadius.circular(8),
  387. ),
  388. ),
  389. ),
  390. ),
  391. ListWheelScrollView(
  392. controller: controller,
  393. physics: FixedExtentScrollPhysics(),
  394. diameterRatio: 1.5,
  395. itemExtent: 50,
  396. children: items.map((value) {
  397. return Center(
  398. child: Text(
  399. value.toString().padLeft(2, '0'),
  400. style: TextStyle(
  401. fontSize: 30,
  402. color: Colors.black,
  403. fontWeight: FontWeight.w500,
  404. ),
  405. ),
  406. );
  407. }).toList(),
  408. onSelectedItemChanged: onChanged,
  409. ),
  410. ],
  411. ),
  412. ),
  413. ),
  414. SizedBox(height: 8),
  415. Text(
  416. unit,
  417. style: TextStyle(
  418. fontSize: 18,
  419. fontWeight: FontWeight.bold,
  420. ),
  421. ),
  422. ],
  423. );
  424. }
  425. }