timer_page.dart 12 KB

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