obd_card.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import 'package:flutter/material.dart';
  2. import '../../../core/theme/app_colors.dart';
  3. import '../../../core/widgets/surface_card.dart';
  4. /// Compact live car data shown only while the ELM327 adapter is delivering
  5. /// fresh speed/throttle values.
  6. class ObdCard extends StatelessWidget {
  7. const ObdCard({
  8. super.key,
  9. required this.speedKmh,
  10. required this.throttlePercent,
  11. required this.economyL100,
  12. this.compact = false,
  13. });
  14. final double? speedKmh;
  15. final double? throttlePercent;
  16. final double? economyL100;
  17. final bool compact;
  18. @override
  19. Widget build(BuildContext context) {
  20. final colors = context.colors;
  21. final theme = Theme.of(context);
  22. final throttle = (throttlePercent ?? 0).clamp(0.0, 100.0);
  23. return SurfaceCard(
  24. padding: EdgeInsets.fromLTRB(
  25. compact ? 16 : 20,
  26. compact ? 13 : 16,
  27. compact ? 16 : 20,
  28. compact ? 13 : 16,
  29. ),
  30. radius: 24,
  31. child: Column(
  32. crossAxisAlignment: CrossAxisAlignment.start,
  33. children: [
  34. Row(
  35. children: [
  36. Container(
  37. width: 7,
  38. height: 7,
  39. decoration: BoxDecoration(
  40. color: colors.accent,
  41. shape: BoxShape.circle,
  42. ),
  43. ),
  44. const SizedBox(width: 8),
  45. Text(
  46. 'OBD-II',
  47. style: theme.textTheme.labelMedium?.copyWith(
  48. color: colors.inkMuted,
  49. letterSpacing: 0.8,
  50. ),
  51. ),
  52. const Spacer(),
  53. Text(
  54. 'LIVE',
  55. style: theme.textTheme.labelSmall?.copyWith(
  56. color: colors.accent,
  57. fontWeight: FontWeight.w700,
  58. letterSpacing: 0.8,
  59. ),
  60. ),
  61. ],
  62. ),
  63. SizedBox(height: compact ? 6 : 8),
  64. Row(
  65. crossAxisAlignment: CrossAxisAlignment.end,
  66. children: [
  67. Text(
  68. speedKmh == null ? '—' : speedKmh!.round().toString(),
  69. style: theme.textTheme.displaySmall?.copyWith(
  70. fontSize: compact ? 38 : 46,
  71. height: 0.95,
  72. fontFeatures: const [FontFeature.tabularFigures()],
  73. ),
  74. ),
  75. const SizedBox(width: 7),
  76. Padding(
  77. padding: const EdgeInsets.only(bottom: 5),
  78. child: Text(
  79. 'km/h',
  80. style: theme.textTheme.bodySmall?.copyWith(
  81. color: colors.inkMuted,
  82. ),
  83. ),
  84. ),
  85. ],
  86. ),
  87. SizedBox(height: compact ? 7 : 9),
  88. Row(
  89. children: [
  90. Text(
  91. 'Throttle',
  92. style: theme.textTheme.bodySmall?.copyWith(
  93. color: colors.inkMuted,
  94. ),
  95. ),
  96. const Spacer(),
  97. Text(
  98. '${throttle.round()}%',
  99. style: theme.textTheme.labelMedium?.copyWith(
  100. color: colors.ink,
  101. fontFeatures: const [FontFeature.tabularFigures()],
  102. ),
  103. ),
  104. ],
  105. ),
  106. const SizedBox(height: 5),
  107. ClipRRect(
  108. borderRadius: BorderRadius.circular(999),
  109. child: TweenAnimationBuilder<double>(
  110. tween: Tween(begin: 0, end: throttle / 100),
  111. duration: const Duration(milliseconds: 180),
  112. curve: Curves.easeOut,
  113. builder: (context, value, _) {
  114. return LinearProgressIndicator(
  115. value: value,
  116. minHeight: compact ? 5 : 6,
  117. backgroundColor: colors.border,
  118. valueColor: AlwaysStoppedAnimation(colors.accent),
  119. );
  120. },
  121. ),
  122. ),
  123. SizedBox(height: compact ? 7 : 9),
  124. Row(
  125. children: [
  126. Text(
  127. 'Rolling economy',
  128. style: theme.textTheme.bodySmall?.copyWith(
  129. color: colors.inkMuted,
  130. ),
  131. ),
  132. const Spacer(),
  133. Text(
  134. economyL100 == null
  135. ? '—'
  136. : '${economyL100!.toStringAsFixed(1)} L/100 km',
  137. style: theme.textTheme.labelMedium?.copyWith(
  138. color: colors.ink,
  139. fontFeatures: const [FontFeature.tabularFigures()],
  140. ),
  141. ),
  142. ],
  143. ),
  144. ],
  145. ),
  146. );
  147. }
  148. }