Files
gps-speedo/lib/ui/shared/speed_display.dart
T
irene 44480eb969 refactor(data): create layered architecture with core/data/ui packages
- Move constants from lib/config/ to lib/core/constants/
- Move color themes to lib/core/theme/
- Create LocationData domain model in data/models/
- Create lightweight GpsService in data/services/
- Create LocationRepository in data/repositories/ consolidating
  GpsDataManager business logic (GPS stream, staleness, precision)
- Create shared SpeedDisplay widget in ui/shared/
- Defer overlay feature — stubs reserved in ui/features/overlay/
2026-08-23 17:16:43 +09:00

95 lines
2.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/overlay_constants.dart';
import '../../../core/theme/color_themes.dart';
import '../../providers/settings_provider.dart';
/// Shared speed display widget — integral + decimal parts with
/// staleness dimming and tap-to-cycle-theme.
///
/// Dumb widget — accepts data as parameters so it works in both
/// the old Provider-based and new ViewModel-based architectures.
class SpeedDisplay extends StatelessWidget {
final double fontSize;
final double opacity;
final String speedText;
final DateTime timestamp;
const SpeedDisplay({
super.key,
required this.fontSize,
required this.opacity,
required this.speedText,
required this.timestamp,
});
@override
Widget build(BuildContext context) {
return Selector<SettingsProvider, ColorTheme>(
selector: (_, s) => s.currentTheme,
builder: (context, theme, _) {
// Staleness — UI-layer concern
final age = DateTime.now().difference(timestamp);
final showDashes = age >= OverlayConfig.STALENESS_DASH_THRESHOLD;
final isDimmed = age >= OverlayConfig.STALENESS_DIM_THRESHOLD;
final staleOpacity = isDimmed ? OverlayConfig.STALENESS_DIM_OPACITY : 1.0;
final effectiveOpacity = opacity * staleOpacity;
final displayText = showDashes ? '--' : speedText;
return GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
context.read<SettingsProvider>().cycleTheme();
},
child: _buildSpeedText(displayText, theme, fontSize, effectiveOpacity),
);
},
);
}
Widget _buildSpeedText(String speedText, ColorTheme theme, double fontSize, double opacity) {
if (speedText == '--') {
return Text(
speedText,
style: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.w300,
color: theme.speedText.withValues(alpha: opacity),
fontFamily: 'DIN1451Alt',
),
);
}
final parts = speedText.split('.');
final integralPart = parts[0];
final decimalPart = parts.length > 1 ? '.${parts[1]}' : '.0';
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
integralPart,
style: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.w300,
color: theme.speedText.withValues(alpha: opacity),
fontFamily: 'DIN1451Alt',
),
),
Text(
decimalPart,
style: TextStyle(
fontSize: fontSize * 0.5,
fontWeight: FontWeight.w300,
color: theme.speedTextSub.withValues(alpha: opacity),
fontFamily: 'DIN1451Alt',
),
),
],
);
}
}