jellyfin_status_strip.dart 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:intl/intl.dart';
  4. import 'package:provider/provider.dart';
  5. import '../../../core/theme/app_colors.dart';
  6. import '../../../core/widgets/app_icon.dart';
  7. import '../../../core/widgets/pressable.dart';
  8. import '../../home/gps_speed_controller.dart';
  9. import '../../home/obd_controller.dart';
  10. import '../../home/telemetry_settings_controller.dart';
  11. import '../../home/weather_controller.dart';
  12. /// Time, speed, temperature and a Home shortcut for the Jellyfin pages.
  13. class JellyfinStatusStrip extends StatefulWidget {
  14. const JellyfinStatusStrip({super.key});
  15. @override
  16. State<JellyfinStatusStrip> createState() => _JellyfinStatusStripState();
  17. }
  18. class _JellyfinStatusStripState extends State<JellyfinStatusStrip> {
  19. late DateTime _now = DateTime.now();
  20. Timer? _timer;
  21. @override
  22. void initState() {
  23. super.initState();
  24. _scheduleTick();
  25. }
  26. void _scheduleTick() {
  27. _timer?.cancel();
  28. final now = DateTime.now();
  29. final next = DateTime(
  30. now.year,
  31. now.month,
  32. now.day,
  33. now.hour,
  34. now.minute,
  35. ).add(const Duration(minutes: 1));
  36. _timer = Timer(next.difference(now) + const Duration(milliseconds: 80), () {
  37. if (!mounted) return;
  38. setState(() => _now = DateTime.now());
  39. _scheduleTick();
  40. });
  41. }
  42. @override
  43. void dispose() {
  44. _timer?.cancel();
  45. super.dispose();
  46. }
  47. @override
  48. Widget build(BuildContext context) {
  49. final colors = context.colors;
  50. final theme = Theme.of(context);
  51. final telemetry = context.watch<TelemetrySettingsController>();
  52. final obd = context.watch<ObdController>();
  53. final gps = context.watch<GpsSpeedController>();
  54. final weather = context.watch<WeatherController>();
  55. final useObdSpeed =
  56. telemetry.speedEnabled && telemetry.speedUseObd && obd.hasTelemetry;
  57. final speed = !telemetry.speedEnabled
  58. ? null
  59. : useObdSpeed
  60. ? obd.speedKmh
  61. : gps.speedKmh;
  62. final useObdTemperature =
  63. telemetry.temperatureUseObd && obd.ambientCelsius != null;
  64. final temperature = !telemetry.temperatureEnabled
  65. ? null
  66. : useObdTemperature
  67. ? obd.ambientCelsius
  68. : (weather.hasTemperature ? weather.temperatureCelsius : null);
  69. final locale = Localizations.localeOf(context).toLanguageTag();
  70. final use24Hour = MediaQuery.alwaysUse24HourFormatOf(context);
  71. final time = (use24Hour ? DateFormat.Hm(locale) : DateFormat.jm(locale))
  72. .format(_now);
  73. return Container(
  74. key: const ValueKey('jellyfin-status-strip'),
  75. padding: const EdgeInsets.fromLTRB(16, 8, 8, 8),
  76. child: Row(
  77. mainAxisSize: MainAxisSize.min,
  78. children: [
  79. Text(
  80. time,
  81. style: theme.textTheme.labelLarge?.copyWith(
  82. color: colors.ink,
  83. fontSize: 16,
  84. fontFeatures: const [FontFeature.tabularFigures()],
  85. ),
  86. ),
  87. const SizedBox(width: 14),
  88. _StripMetric(
  89. iconAsset: 'assets/icons/gauge.svg',
  90. text: speed == null ? '-- km/h' : '${speed.round()} km/h',
  91. available: speed != null,
  92. ),
  93. const SizedBox(width: 14),
  94. _StripMetric(
  95. iconAsset: 'assets/icons/thermometer.svg',
  96. text: temperature == null ? '--°C' : '${temperature.round()}°C',
  97. available: temperature != null,
  98. ),
  99. const SizedBox(width: 10),
  100. Pressable(
  101. onTap: () =>
  102. Navigator.of(context).popUntil((route) => route.isFirst),
  103. semanticLabel: 'Go to home',
  104. pressedScale: 0.92,
  105. child: Container(
  106. width: 42,
  107. height: 42,
  108. decoration: BoxDecoration(
  109. color: colors.surfaceStrong,
  110. shape: BoxShape.circle,
  111. border: Border.all(color: colors.border),
  112. ),
  113. alignment: Alignment.center,
  114. child: AppIcon(
  115. 'assets/icons/home.svg',
  116. size: 21,
  117. color: colors.ink,
  118. ),
  119. ),
  120. ),
  121. ],
  122. ),
  123. );
  124. }
  125. }
  126. class _StripMetric extends StatelessWidget {
  127. const _StripMetric({
  128. required this.iconAsset,
  129. required this.text,
  130. required this.available,
  131. });
  132. final String iconAsset;
  133. final String text;
  134. final bool available;
  135. @override
  136. Widget build(BuildContext context) {
  137. final colors = context.colors;
  138. final theme = Theme.of(context);
  139. return Row(
  140. mainAxisSize: MainAxisSize.min,
  141. children: [
  142. AppIcon(
  143. iconAsset,
  144. size: 18,
  145. color: available ? colors.inkMuted : colors.inkFaint,
  146. ),
  147. const SizedBox(width: 6),
  148. Text(
  149. text,
  150. style: theme.textTheme.labelLarge?.copyWith(
  151. color: available ? colors.ink : colors.inkFaint,
  152. fontFeatures: const [FontFeature.tabularFigures()],
  153. ),
  154. ),
  155. ],
  156. );
  157. }
  158. }