clock_block.dart 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:intl/intl.dart';
  4. import '../../../core/theme/app_colors.dart';
  5. /// Large, quiet clock. It sleeps until the next minute instead of ticking
  6. /// every second.
  7. class ClockBlock extends StatefulWidget {
  8. const ClockBlock({
  9. super.key,
  10. required this.compact,
  11. this.temperatureText,
  12. this.temperatureAlert = false,
  13. });
  14. final bool compact;
  15. final String? temperatureText;
  16. final bool temperatureAlert;
  17. @override
  18. State<ClockBlock> createState() => _ClockBlockState();
  19. }
  20. class _ClockBlockState extends State<ClockBlock> {
  21. late DateTime _now = DateTime.now();
  22. Timer? _timer;
  23. @override
  24. void initState() {
  25. super.initState();
  26. _scheduleTick();
  27. }
  28. void _scheduleTick() {
  29. _timer?.cancel();
  30. final now = DateTime.now();
  31. final next = DateTime(
  32. now.year,
  33. now.month,
  34. now.day,
  35. now.hour,
  36. now.minute,
  37. ).add(const Duration(minutes: 1));
  38. _timer = Timer(next.difference(now) + const Duration(milliseconds: 80), () {
  39. if (!mounted) return;
  40. setState(() => _now = DateTime.now());
  41. _scheduleTick();
  42. });
  43. }
  44. @override
  45. void dispose() {
  46. _timer?.cancel();
  47. super.dispose();
  48. }
  49. @override
  50. Widget build(BuildContext context) {
  51. final theme = Theme.of(context);
  52. final colors = context.colors;
  53. final locale = Localizations.localeOf(context).toLanguageTag();
  54. final use24Hour = MediaQuery.alwaysUse24HourFormatOf(context);
  55. final time = (use24Hour ? DateFormat.Hm(locale) : DateFormat.jm(locale))
  56. .format(_now);
  57. final date = DateFormat('EEEE, d MMMM', locale).format(_now);
  58. return Column(
  59. mainAxisSize: MainAxisSize.min,
  60. crossAxisAlignment: CrossAxisAlignment.start,
  61. children: [
  62. Text(
  63. time,
  64. maxLines: 1,
  65. style: theme.textTheme.displayLarge?.copyWith(
  66. fontSize: widget.compact ? 56 : 72,
  67. fontFeatures: const [FontFeature.tabularFigures()],
  68. height: 0.95,
  69. ),
  70. ),
  71. const SizedBox(height: 10),
  72. Row(
  73. mainAxisSize: MainAxisSize.min,
  74. children: [
  75. Container(
  76. width: 6,
  77. height: 6,
  78. decoration: BoxDecoration(
  79. color: colors.accent,
  80. shape: BoxShape.circle,
  81. ),
  82. ),
  83. const SizedBox(width: 10),
  84. Text(
  85. date,
  86. maxLines: 1,
  87. overflow: TextOverflow.ellipsis,
  88. style: theme.textTheme.titleMedium?.copyWith(
  89. color: colors.inkMuted,
  90. fontWeight: FontWeight.w500,
  91. fontSize: widget.compact ? 14 : 16,
  92. ),
  93. ),
  94. ],
  95. ),
  96. if (widget.temperatureText != null) ...[
  97. const SizedBox(height: 7),
  98. AnimatedSwitcher(
  99. duration: const Duration(milliseconds: 220),
  100. child: Text(
  101. widget.temperatureText!,
  102. key: ValueKey(widget.temperatureText),
  103. maxLines: 1,
  104. overflow: TextOverflow.ellipsis,
  105. style: theme.textTheme.titleSmall?.copyWith(
  106. color: widget.temperatureAlert ? colors.danger : colors.ink,
  107. fontWeight: FontWeight.w600,
  108. fontSize: widget.compact ? 13 : 14,
  109. ),
  110. ),
  111. ),
  112. ],
  113. ],
  114. );
  115. }
  116. }