44480eb969
- 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/
95 lines
2.9 KiB
Dart
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',
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
} |