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/
This commit is contained in:
2026-08-23 17:16:43 +09:00
parent 7a58483ba2
commit 44480eb969
10 changed files with 623 additions and 0 deletions
@@ -0,0 +1,81 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/constants/speed_unit.dart';
import '../../../data/models/location_data.dart';
import '../../../data/repositories/location_repository.dart';
/// ViewModel for the speedometer UI.
///
/// Thin state manager — subscribes to [LocationRepository] and exposes
/// the current display state to the view. No data-layer logic here.
class SpeedometerViewModel extends ChangeNotifier {
final LocationRepository _repository;
final SharedPreferences _prefs;
SpeedUnit _displayUnit = SpeedUnit.kmh;
LocationData _displayData = LocationData(
speed: 0.0,
heading: -1.0,
displaySpeed: '--',
displayHeading: 'N/A',
isSpeedValid: false,
isHeadingValid: false,
timestamp: DateTime.now(),
);
LocationData get displayData => _displayData;
SpeedUnit get displayUnit => _displayUnit;
SpeedometerViewModel({
required LocationRepository repository,
required SharedPreferences prefs,
}) : _repository = repository,
_prefs = prefs;
void init() {
_repository.locationStream.listen(_onLocationData);
_loadSettings();
}
void _onLocationData(LocationData data) {
_displayData = data;
notifyListeners();
}
Future<void> _loadSettings() async {
_displayUnit = _parseSpeedUnit(_prefs.getString('speed_unit') ?? 'km/h');
notifyListeners();
}
static SpeedUnit _parseSpeedUnit(String unit) {
switch (unit) {
case 'mph':
return SpeedUnit.mph;
case 'kts':
return SpeedUnit.knots;
default:
return SpeedUnit.kmh;
}
}
double get displaySpeed {
return _displayUnit.convert(_displayData.speed);
}
String get displaySpeedText {
return _displayData.displaySpeed;
}
String get displayHeading {
return _displayData.displayHeading;
}
bool get isSpeedValid {
return _displayData.isSpeedValid;
}
bool get isHeadingValid {
return _displayData.isHeadingValid;
}
}