timer_page.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_clock/model/timer_data.dart';
  4. import 'package:flutter_clock/model/timer_settings.dart';
  5. import 'package:flutter_clock/pages/timer/timer_settings_page.dart';
  6. import 'package:flutter_clock/utils/audio_manager.dart';
  7. import 'package:flutter_clock/utils/screen_manager.dart';
  8. import 'package:flutter_clock/utils/background_timer_service.dart';
  9. /// Description: 倒计时页面
  10. /// Time : 04/06/2025 Sunday
  11. /// Author : liuyuqi.gov@msn.cn
  12. class TimerPage extends StatefulWidget {
  13. @override
  14. _TimerPageState createState() => _TimerPageState();
  15. }
  16. enum TimerState { prepare, running, pause, finish, waiting }
  17. class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
  18. // Timer duration values
  19. int _hours = 0;
  20. int _minutes = 10;
  21. int _seconds = 0;
  22. // For timer controller
  23. TimerState timerState = TimerState.prepare;
  24. int _remainingSeconds = 0;
  25. // Waiting for alignment timer
  26. Timer? _alignmentTimer;
  27. DateTime? _alignmentTargetTime;
  28. DateTime? _alignmentStartTime; // 记录开始等待的时间点
  29. Timer? _alignmentUIUpdateTimer; // For refreshing UI during waiting
  30. // Total timer duration in seconds (for progress calculation)
  31. int _totalDurationSeconds = 0;
  32. // Background services
  33. final BackgroundTimerService _backgroundTimerService =
  34. BackgroundTimerService();
  35. // Settings
  36. late TimerSettings _settings;
  37. bool _settingsLoaded = false;
  38. final AudioManager _audioManager = AudioManager();
  39. // Wheel controllers
  40. final FixedExtentScrollController _hoursController =
  41. FixedExtentScrollController(initialItem: 0);
  42. final FixedExtentScrollController _minutesController =
  43. FixedExtentScrollController(initialItem: 10);
  44. final FixedExtentScrollController _secondsController =
  45. FixedExtentScrollController(initialItem: 0);
  46. @override
  47. void initState() {
  48. super.initState();
  49. WidgetsBinding.instance.addObserver(this);
  50. _initializeBackgroundTimer();
  51. _loadSettings();
  52. _restoreTimerState();
  53. }
  54. @override
  55. void dispose() {
  56. _audioManager.dispose();
  57. _backgroundTimerService.dispose();
  58. _alignmentTimer?.cancel();
  59. _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
  60. if (ScreenManager.isWakeLockEnabled) {
  61. ScreenManager.disableWakeLock();
  62. }
  63. WidgetsBinding.instance.removeObserver(this);
  64. _hoursController.dispose();
  65. _minutesController.dispose();
  66. _secondsController.dispose();
  67. super.dispose();
  68. }
  69. @override
  70. void didChangeAppLifecycleState(AppLifecycleState state) {
  71. // No additional handling required as background service handles the state
  72. }
  73. /// Restore timer state from persistent storage
  74. Future<void> _restoreTimerState() async {
  75. final timerData = await TimerData.load();
  76. if (timerData.isRunning) {
  77. setState(() {
  78. timerState = TimerState.running;
  79. _remainingSeconds = timerData.calculateRemainingSeconds();
  80. _totalDurationSeconds = _backgroundTimerService.totalDurationSeconds;
  81. });
  82. } else if (timerData.isPaused && timerData.pausedRemaining != null) {
  83. setState(() {
  84. timerState = TimerState.pause;
  85. _remainingSeconds = timerData.pausedRemaining!;
  86. _totalDurationSeconds = _backgroundTimerService.totalDurationSeconds;
  87. });
  88. }
  89. }
  90. /// Initialize the background timer service
  91. Future<void> _initializeBackgroundTimer() async {
  92. await _backgroundTimerService.initialize(
  93. onTimerComplete: _timerCompleted,
  94. onTimerTick: (remainingSeconds) {
  95. setState(() {
  96. _remainingSeconds = remainingSeconds;
  97. if (remainingSeconds <= 0 && timerState != TimerState.finish) {
  98. timerState = TimerState.finish;
  99. }
  100. });
  101. },
  102. );
  103. }
  104. Future<void> _loadSettings() async {
  105. _settings = await TimerSettings.loadSettings();
  106. setState(() {
  107. _settingsLoaded = true;
  108. });
  109. }
  110. /// Calculate the next 10-minute alignment time
  111. DateTime _calculateNextAlignmentTime() {
  112. final now = DateTime.now();
  113. // 计算下一个整10分钟的时间点
  114. int nextMinutes = ((now.minute ~/ 10) + 1) * 10;
  115. int nextHour = now.hour;
  116. // 处理进位
  117. if (nextMinutes >= 60) {
  118. nextMinutes = 0;
  119. nextHour = (nextHour + 1) % 24;
  120. }
  121. return DateTime(
  122. now.year,
  123. now.month,
  124. now.day,
  125. nextHour,
  126. nextMinutes,
  127. 0,
  128. 0,
  129. 0
  130. );
  131. }
  132. // 计算等待进度
  133. double _calculateWaitingProgress() {
  134. if (_alignmentTargetTime == null) return 0.0;
  135. final now = DateTime.now();
  136. final targetTime = _alignmentTargetTime!;
  137. final startTime = _alignmentStartTime ?? now;
  138. // 如果还没开始等待或已经过了目标时间
  139. if (now.isAfter(targetTime)) return 1.0;
  140. if (now.isBefore(startTime)) return 0.0;
  141. // 计算总等待时间和已等待时间
  142. final totalWaitDuration = targetTime.difference(startTime).inMilliseconds;
  143. final elapsedWaitDuration = now.difference(startTime).inMilliseconds;
  144. // 计算进度比例
  145. if (totalWaitDuration <= 0) return 1.0;
  146. final progress = elapsedWaitDuration / totalWaitDuration;
  147. // 确保进度在0.0到1.0之间
  148. return progress.clamp(0.0, 1.0);
  149. }
  150. /// Wait until the next 10-minute mark and then start the timer
  151. void _startTimerWithAlignment(int totalSeconds) {
  152. // 取消任何现有的对齐定时器
  153. _alignmentTimer?.cancel();
  154. _alignmentUIUpdateTimer?.cancel();
  155. // 计算下一个整10分钟的时间点
  156. final alignmentTime = _calculateNextAlignmentTime();
  157. _alignmentTargetTime = alignmentTime;
  158. _alignmentStartTime = DateTime.now(); // 记录开始等待的时间
  159. // 设置状态为等待
  160. setState(() {
  161. timerState = TimerState.waiting;
  162. });
  163. // 计算等待时间(毫秒)
  164. final waitDuration = alignmentTime.difference(DateTime.now());
  165. // 创建定时器等待到指定时间
  166. _alignmentTimer = Timer(waitDuration, () {
  167. // 时间到了,启动实际的倒计时
  168. setState(() {
  169. timerState = TimerState.running;
  170. _remainingSeconds = totalSeconds;
  171. _totalDurationSeconds = totalSeconds;
  172. _alignmentTargetTime = null;
  173. _alignmentStartTime = null;
  174. });
  175. _backgroundTimerService.startTimer(totalSeconds);
  176. _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer when alignment is done
  177. });
  178. // 创建UI刷新定时器,每秒更新一次
  179. _alignmentUIUpdateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
  180. if (mounted) {
  181. setState(() {
  182. // 仅用于刷新UI
  183. });
  184. }
  185. });
  186. }
  187. /// Handle timer completion
  188. Future<void> _timerCompleted() async {
  189. setState(() {
  190. timerState = TimerState.finish;
  191. });
  192. // Play sound and vibrate
  193. if (_settings.vibrate) {
  194. _audioManager.triggerVibration();
  195. }
  196. _audioManager.playSound(_settings.sound, _settings.volume, _settings.loop);
  197. // If loop is enabled, restart the timer
  198. if (_settings.loop) {
  199. Future.delayed(Duration(seconds: 5), () {
  200. _audioManager.stopSound();
  201. _audioManager.stopVibration();
  202. });
  203. // 不再检查是否需要整点对齐,直接启动倒计时
  204. setState(() {
  205. timerState = TimerState.running;
  206. _remainingSeconds = _hours * 3600 + _minutes * 60 + _seconds;
  207. _totalDurationSeconds = _remainingSeconds;
  208. });
  209. _backgroundTimerService.startTimer(_remainingSeconds);
  210. }
  211. }
  212. /// Start a new timer or resume an existing paused timer
  213. void _startTimer(String status) {
  214. if (status == "resume") {
  215. setState(() {
  216. timerState = TimerState.running;
  217. });
  218. _backgroundTimerService.resumeTimer(
  219. _remainingSeconds, _totalDurationSeconds);
  220. } else if (status == "start") {
  221. // 获取界面输入的时间
  222. int _hourstmp = _hoursController.selectedItem;
  223. int _minutestmp = _minutesController.selectedItem;
  224. int _secondstmp = _secondsController.selectedItem;
  225. // 计算总秒数
  226. final totalSeconds = _hourstmp * 3600 + _minutestmp * 60 + _secondstmp;
  227. if (totalSeconds <= 0) return;
  228. // 保存选择的时间值
  229. _hours = _hourstmp;
  230. _minutes = _minutestmp;
  231. _seconds = _secondstmp;
  232. // 检查是否需要整点对齐 - 只有在初始启动时才检查
  233. if (_settings.alignToHour && _settings.loop && totalSeconds >= 600) { // 只有在循环模式、时间至少10分钟时才启用整点对齐
  234. _startTimerWithAlignment(totalSeconds);
  235. } else {
  236. setState(() {
  237. timerState = TimerState.running;
  238. _remainingSeconds = totalSeconds;
  239. _totalDurationSeconds = totalSeconds;
  240. });
  241. _backgroundTimerService.startTimer(totalSeconds);
  242. }
  243. } else if (status == "loop") {
  244. // 计算总秒数
  245. final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
  246. if (totalSeconds <= 0) return;
  247. // 直接启动倒计时,不再检查是否需要整点对齐
  248. setState(() {
  249. timerState = TimerState.running;
  250. _remainingSeconds = totalSeconds;
  251. _totalDurationSeconds = totalSeconds;
  252. });
  253. _backgroundTimerService.startTimer(totalSeconds);
  254. }
  255. }
  256. /// Pause the running timer
  257. void _pauseTimer() {
  258. if (timerState == TimerState.waiting) {
  259. // 如果在等待对齐状态,取消等待定时器
  260. _alignmentTimer?.cancel();
  261. _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
  262. _alignmentTargetTime = null;
  263. _alignmentStartTime = null;
  264. } else {
  265. _backgroundTimerService.pauseTimer();
  266. }
  267. setState(() {
  268. timerState = TimerState.pause;
  269. });
  270. }
  271. /// Reset the timer to initial state
  272. void _resetTimer() {
  273. _backgroundTimerService.cancelTimer();
  274. _audioManager.stopSound();
  275. _audioManager.stopVibration();
  276. _alignmentTimer?.cancel();
  277. _alignmentUIUpdateTimer?.cancel(); // Cancel UI update timer
  278. _alignmentTargetTime = null;
  279. _alignmentStartTime = null;
  280. setState(() {
  281. timerState = TimerState.prepare;
  282. });
  283. }
  284. String _formatTime(int seconds) {
  285. final hours = seconds ~/ 3600;
  286. final minutes = (seconds % 3600) ~/ 60;
  287. final secs = seconds % 60;
  288. return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
  289. }
  290. // 格式化剩余等待时间
  291. String _formatWaitingTime() {
  292. if (_alignmentTargetTime == null) return "00:00";
  293. final now = DateTime.now();
  294. final difference = _alignmentTargetTime!.difference(now);
  295. // 如果差异为负,说明已经过了目标时间
  296. if (difference.isNegative) return "00:00";
  297. // 计算分钟和秒数
  298. final minutes = difference.inMinutes;
  299. final seconds = difference.inSeconds % 60;
  300. return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  301. }
  302. @override
  303. Widget build(BuildContext context) {
  304. if (!_settingsLoaded) {
  305. return Center(child: CircularProgressIndicator());
  306. }
  307. if (timerState != TimerState.prepare) {
  308. return _buildCountdownView();
  309. } else {
  310. return _buildTimerSetupView();
  311. }
  312. }
  313. Widget _buildTimerSetupView() {
  314. return Scaffold(
  315. backgroundColor: Colors.white,
  316. body: Column(
  317. mainAxisAlignment: MainAxisAlignment.center,
  318. children: [
  319. Expanded(
  320. child: Row(
  321. children: [
  322. Expanded(
  323. child: _buildTimerWheel(
  324. _hoursController,
  325. List.generate(24, (index) => index),
  326. (value) {
  327. setState(() {
  328. _hours = value;
  329. });
  330. },
  331. 'H',
  332. ),
  333. ),
  334. Expanded(
  335. child: _buildTimerWheel(
  336. _minutesController,
  337. List.generate(60, (index) => index),
  338. (value) {
  339. setState(() {
  340. _minutes = value;
  341. });
  342. },
  343. 'M',
  344. ),
  345. ),
  346. Expanded(
  347. child: _buildTimerWheel(
  348. _secondsController,
  349. List.generate(60, (index) => index),
  350. (value) {
  351. setState(() {
  352. _seconds = value;
  353. });
  354. },
  355. 'S',
  356. ),
  357. ),
  358. ],
  359. ),
  360. ),
  361. SizedBox(height: 20),
  362. Padding(
  363. padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
  364. child: Row(
  365. mainAxisAlignment: MainAxisAlignment.spaceAround,
  366. children: [
  367. _buildCircleButton(
  368. Icons.sunny,
  369. Colors.grey[600]!,
  370. () {
  371. ScreenManager.toggleWakeLock();
  372. setState(() {});
  373. },
  374. isActive: ScreenManager.isWakeLockEnabled,
  375. ),
  376. _buildCircleButton(
  377. Icons.play_arrow,
  378. Colors.blue,
  379. () => _startTimer("start"),
  380. ),
  381. _buildCircleButton(
  382. Icons.settings,
  383. Colors.grey[600]!,
  384. () async {
  385. final result = await Navigator.push(
  386. context,
  387. MaterialPageRoute(
  388. builder: (context) =>
  389. TimerSettingsPage(settings: _settings)),
  390. );
  391. if (result != null) {
  392. setState(() {
  393. _settings = result;
  394. });
  395. }
  396. },
  397. ),
  398. ],
  399. ),
  400. ),
  401. ],
  402. ),
  403. );
  404. }
  405. Widget _buildCountdownView() {
  406. final totalMinutes = _remainingSeconds ~/ 60;
  407. // 如果处于等待整点对齐状态,显示不同的UI
  408. if (timerState == TimerState.waiting) {
  409. return _buildWaitingForAlignmentView();
  410. }
  411. return Scaffold(
  412. backgroundColor: Colors.white,
  413. body: Column(
  414. mainAxisAlignment: MainAxisAlignment.center,
  415. children: [
  416. Expanded(
  417. child: Center(
  418. child: Column(
  419. mainAxisAlignment: MainAxisAlignment.center,
  420. children: [
  421. Container(
  422. width: 300,
  423. height: 300,
  424. decoration: BoxDecoration(
  425. shape: BoxShape.circle,
  426. border: Border.all(
  427. color: Colors.blue.withOpacity(0.3),
  428. width: 3,
  429. ),
  430. ),
  431. child: Stack(
  432. alignment: Alignment.center,
  433. children: [
  434. // Timer progress
  435. SizedBox(
  436. width: 300,
  437. height: 300,
  438. child: CircularProgressIndicator(
  439. value: timerState == TimerState.finish
  440. ? 1
  441. : _totalDurationSeconds > 0
  442. ? _remainingSeconds / _totalDurationSeconds
  443. : 0,
  444. strokeWidth: 5,
  445. backgroundColor: Colors.grey.withOpacity(0.1),
  446. color: Colors.blue,
  447. ),
  448. ),
  449. // Time display
  450. Column(
  451. mainAxisAlignment: MainAxisAlignment.center,
  452. children: [
  453. Text(
  454. _formatTime(_remainingSeconds),
  455. style: TextStyle(
  456. fontSize: 40, fontWeight: FontWeight.bold),
  457. ),
  458. Text(
  459. 'Total ${totalMinutes} minutes',
  460. style:
  461. TextStyle(fontSize: 16, color: Colors.grey),
  462. ),
  463. ],
  464. ),
  465. // Indicator dot
  466. Positioned(
  467. bottom: 0,
  468. child: Container(
  469. width: 20,
  470. height: 20,
  471. decoration: BoxDecoration(
  472. color: Colors.blue,
  473. shape: BoxShape.circle,
  474. ),
  475. ),
  476. ),
  477. ],
  478. ),
  479. ),
  480. ],
  481. ),
  482. ),
  483. ),
  484. Padding(
  485. padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
  486. child: Row(
  487. mainAxisAlignment: MainAxisAlignment.spaceAround,
  488. children: [
  489. // 唤醒屏幕
  490. _buildCircleButton(
  491. Icons.sunny,
  492. Colors.grey[600]!,
  493. () {
  494. ScreenManager.toggleWakeLock();
  495. setState(() {});
  496. },
  497. isActive: ScreenManager.isWakeLockEnabled,
  498. ),
  499. // 暂停/开始
  500. timerState == TimerState.running
  501. ? _buildCircleButton(
  502. Icons.pause,
  503. Colors.blue,
  504. () => _pauseTimer(),
  505. )
  506. : _buildCircleButton(
  507. Icons.play_arrow,
  508. Colors.blue,
  509. () => _startTimer("resume"),
  510. ),
  511. // 重置
  512. _buildCircleButton(
  513. Icons.stop,
  514. Colors.red,
  515. () => _resetTimer(),
  516. ),
  517. ],
  518. ),
  519. ),
  520. ],
  521. ),
  522. );
  523. }
  524. // 构建等待整点对齐的视图
  525. Widget _buildWaitingForAlignmentView() {
  526. final nextAlignmentTime = _alignmentTargetTime;
  527. final waitingProgress = _calculateWaitingProgress();
  528. return Scaffold(
  529. backgroundColor: Colors.white,
  530. body: Column(
  531. mainAxisAlignment: MainAxisAlignment.center,
  532. children: [
  533. Expanded(
  534. child: Center(
  535. child: Column(
  536. mainAxisAlignment: MainAxisAlignment.center,
  537. children: [
  538. Container(
  539. width: 300,
  540. height: 300,
  541. decoration: BoxDecoration(
  542. shape: BoxShape.circle,
  543. border: Border.all(
  544. color: Colors.orange.withOpacity(0.3),
  545. width: 3,
  546. ),
  547. ),
  548. child: Stack(
  549. alignment: Alignment.center,
  550. children: [
  551. // 等待进度指示器
  552. SizedBox(
  553. width: 300,
  554. height: 300,
  555. child: CircularProgressIndicator(
  556. value: waitingProgress, // 显示确定的等待进度
  557. strokeWidth: 5,
  558. backgroundColor: Colors.grey.withOpacity(0.1),
  559. color: Colors.orange,
  560. ),
  561. ),
  562. // 等待时间显示
  563. Column(
  564. mainAxisAlignment: MainAxisAlignment.center,
  565. children: [
  566. Text(
  567. _formatWaitingTime(),
  568. style: TextStyle(
  569. fontSize: 40, fontWeight: FontWeight.bold),
  570. ),
  571. Text(
  572. '等待对齐整10分钟',
  573. style: TextStyle(fontSize: 16, color: Colors.grey),
  574. ),
  575. SizedBox(height: 10),
  576. if (nextAlignmentTime != null)
  577. Text(
  578. '将在 ${nextAlignmentTime.hour.toString().padLeft(2, '0')}:${nextAlignmentTime.minute.toString().padLeft(2, '0')} 开始',
  579. style: TextStyle(fontSize: 14, color: Colors.orange),
  580. ),
  581. ],
  582. ),
  583. ],
  584. ),
  585. ),
  586. ],
  587. ),
  588. ),
  589. ),
  590. Padding(
  591. padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
  592. child: Row(
  593. mainAxisAlignment: MainAxisAlignment.spaceAround,
  594. children: [
  595. // 唤醒屏幕
  596. _buildCircleButton(
  597. Icons.sunny,
  598. Colors.grey[600]!,
  599. () {
  600. ScreenManager.toggleWakeLock();
  601. setState(() {});
  602. },
  603. isActive: ScreenManager.isWakeLockEnabled,
  604. ),
  605. // 在等待状态只显示取消按钮
  606. _buildCircleButton(
  607. Icons.stop,
  608. Colors.red,
  609. () => _resetTimer(),
  610. ),
  611. ],
  612. ),
  613. ),
  614. ],
  615. ),
  616. );
  617. }
  618. Widget _buildCircleButton(IconData icon, Color color, VoidCallback onPressed,
  619. {bool isActive = false}) {
  620. return Container(
  621. width: 70,
  622. height: 70,
  623. decoration: BoxDecoration(
  624. shape: BoxShape.circle,
  625. color: isActive ? color : Colors.white,
  626. boxShadow: [
  627. BoxShadow(
  628. color: Colors.black.withOpacity(0.1),
  629. blurRadius: 8,
  630. offset: Offset(0, 2),
  631. ),
  632. ],
  633. ),
  634. child: IconButton(
  635. icon: Icon(icon, size: 30),
  636. color: isActive ? Colors.white : color,
  637. onPressed: onPressed,
  638. ),
  639. );
  640. }
  641. Widget _buildTimerWheel(
  642. FixedExtentScrollController controller,
  643. List<int> items,
  644. ValueChanged<int> onChanged,
  645. String unit,
  646. ) {
  647. return Container(
  648. height: 400,
  649. decoration: BoxDecoration(
  650. border: Border(
  651. top: BorderSide(color: Colors.grey.withOpacity(0.3), width: 1),
  652. bottom: BorderSide(color: Colors.grey.withOpacity(0.3), width: 1),
  653. ),
  654. ),
  655. child: Stack(
  656. children: [
  657. // Center highlight
  658. Positioned.fill(
  659. child: Center(
  660. child: Container(
  661. height: 50,
  662. decoration: BoxDecoration(
  663. color: Colors.blue.withOpacity(0.1),
  664. borderRadius: BorderRadius.circular(8),
  665. ),
  666. ),
  667. ),
  668. ),
  669. Container(
  670. alignment: Alignment.centerRight,
  671. padding: EdgeInsets.only(right: 15),
  672. child: Text(
  673. unit,
  674. style: TextStyle(
  675. fontSize: 18,
  676. fontWeight: FontWeight.bold,
  677. ),
  678. ),
  679. ),
  680. ListWheelScrollView(
  681. controller: controller,
  682. physics: FixedExtentScrollPhysics(),
  683. diameterRatio: 1.5,
  684. itemExtent: 50,
  685. children: items.map((value) {
  686. return Center(
  687. child: Text(
  688. value.toString().padLeft(2, '0'),
  689. style: TextStyle(
  690. fontSize: 30,
  691. color: Colors.black,
  692. fontWeight: FontWeight.w500,
  693. ),
  694. ),
  695. );
  696. }).toList(),
  697. onSelectedItemChanged: onChanged,
  698. ),
  699. ],
  700. ),
  701. );
  702. }
  703. }