feat(01-01): extract models and move config files to new structure

- Move SpeedUnit enum to config/speed_unit.dart
- Move ColorTheme/ColorThemes classes to config/color_themes.dart
- Extract ProcessedGpsData model from gps_data_manager to models/
- Create OverlayMessage typed class for overlay IPC (replaces raw Maps)
- Create ThemeModel with palette rotation and state management
This commit is contained in:
Mikkeli Matlock
2026-02-09 21:23:49 +09:00
parent 2b2fc8fbd3
commit 5122be4bc1
5 changed files with 264 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';
class ColorTheme {
final Color background;
final Color speedText;
final Color speedTextSub;
final Color headingText;
final Color unitText;
final String name;
const ColorTheme({
required this.background,
required this.speedText,
required this.speedTextSub,
required this.headingText,
required this.unitText,
required this.name,
});
}
class ColorThemes {
static const List<ColorTheme> themes = [
ColorTheme(
name: 'Yuzuki',
background: Color.fromARGB(255, 114, 85, 109),
speedText: Color.fromARGB(255, 246, 224, 255),
speedTextSub: Color.fromARGB(255, 245, 217, 245),
headingText: Color.fromARGB(255, 244, 201, 255),
unitText: Color.fromARGB(255, 244, 201, 255),
),
ColorTheme(
name: 'Yuuparo',
background: Color.fromARGB(255, 85, 99, 112),
speedText: Color.fromARGB(255, 255, 171, 16),
speedTextSub: Color.fromARGB(255, 245, 204, 116),
headingText: Color.fromARGB(255, 228, 235, 238),
unitText: Color.fromARGB(255, 141, 196, 184),
),
ColorTheme(
name: 'Yuzuki Light',
background: Color.fromARGB(255, 246, 224, 255),
speedText: Color.fromARGB(255, 114, 85, 109),
speedTextSub: Color.fromARGB(255, 161, 138, 170),
headingText: Color.fromARGB(255, 140, 94, 151),
unitText: Color.fromARGB(255, 114, 85, 109),
),
ColorTheme(
name: 'Yuuparo Light',
background: Color.fromARGB(255, 228, 235, 238),
speedText: Color.fromARGB(255, 85, 99, 112),
speedTextSub: Color.fromARGB(255, 100, 100, 131),
headingText: Color.fromARGB(255, 221, 118, 0),
unitText: Color.fromARGB(255, 98, 124, 118),
),
];
static ColorTheme getTheme(int index) {
return themes[index % themes.length];
}
static int getNextThemeIndex(int currentIndex) {
return (currentIndex + 1) % themes.length;
}
}
+20
View File
@@ -0,0 +1,20 @@
enum SpeedUnit {
kmh('km/h', 1.0),
mph('mph', 0.621371),
knots('kts', 0.539957);
const SpeedUnit(this.label, this.multiplier);
final String label;
final double multiplier;
double convert(double speedMps) {
return speedMps * 3.6 * multiplier;
}
SpeedUnit get next {
final values = SpeedUnit.values;
final currentIndex = values.indexOf(this);
return values[(currentIndex + 1) % values.length];
}
}
+113
View File
@@ -0,0 +1,113 @@
/// Typed message class for overlay IPC communication
/// Replaces unvalidated Map data with type-safe serialization
class OverlayMessage {
final String action;
final String? speedText;
final String? unitText;
final String? headingText;
final double? heading;
final int? unitIndex;
final int? themeIndex;
final double? overlayWidth;
final double? overlayHeight;
const OverlayMessage({
required this.action,
this.speedText,
this.unitText,
this.headingText,
this.heading,
this.unitIndex,
this.themeIndex,
this.overlayWidth,
this.overlayHeight,
});
/// Factory constructor for display update messages
factory OverlayMessage.updateDisplay({
required String speedText,
required String unitText,
required String headingText,
required double heading,
required int unitIndex,
required int themeIndex,
double? overlayWidth,
double? overlayHeight,
}) {
return OverlayMessage(
action: 'updateDisplay',
speedText: speedText,
unitText: unitText,
headingText: headingText,
heading: heading,
unitIndex: unitIndex,
themeIndex: themeIndex,
overlayWidth: overlayWidth,
overlayHeight: overlayHeight,
);
}
/// Factory constructor for long press close signal
factory OverlayMessage.longPressClose() {
return const OverlayMessage(action: 'longPressClose');
}
/// Factory constructor for overlay closed notification
factory OverlayMessage.overlayClosed() {
return const OverlayMessage(action: 'overlayClosed');
}
/// Serialize to Map for overlay IPC
Map<String, dynamic> toMap() {
final map = <String, dynamic>{'action': action};
if (speedText != null) map['speedText'] = speedText;
if (unitText != null) map['unitText'] = unitText;
if (headingText != null) map['headingText'] = headingText;
if (heading != null) map['heading'] = heading;
if (unitIndex != null) map['unitIndex'] = unitIndex;
if (themeIndex != null) map['themeIndex'] = themeIndex;
if (overlayWidth != null) map['overlayWidth'] = overlayWidth;
if (overlayHeight != null) map['overlayHeight'] = overlayHeight;
return map;
}
/// Deserialize from Map received via overlay IPC
factory OverlayMessage.fromMap(Map<dynamic, dynamic> map) {
return OverlayMessage(
action: map['action'] as String? ?? 'unknown',
speedText: map['speedText'] as String?,
unitText: map['unitText'] as String?,
headingText: map['headingText'] as String?,
heading: (map['heading'] as num?)?.toDouble(),
unitIndex: map['unitIndex'] as int?,
themeIndex: map['themeIndex'] as int?,
overlayWidth: (map['overlayWidth'] as num?)?.toDouble(),
overlayHeight: (map['overlayHeight'] as num?)?.toDouble(),
);
}
/// Create a copy with updated fields
OverlayMessage copyWith({
String? action,
String? speedText,
String? unitText,
String? headingText,
double? heading,
int? unitIndex,
int? themeIndex,
double? overlayWidth,
double? overlayHeight,
}) {
return OverlayMessage(
action: action ?? this.action,
speedText: speedText ?? this.speedText,
unitText: unitText ?? this.unitText,
headingText: headingText ?? this.headingText,
heading: heading ?? this.heading,
unitIndex: unitIndex ?? this.unitIndex,
themeIndex: themeIndex ?? this.themeIndex,
overlayWidth: overlayWidth ?? this.overlayWidth,
overlayHeight: overlayHeight ?? this.overlayHeight,
);
}
}
+35
View File
@@ -0,0 +1,35 @@
class ProcessedGpsData {
final double speed; // Raw speed in m/s
final double heading; // Raw heading in degrees
final String displaySpeed; // Formatted speed text (with -- logic)
final String displayHeading; // Formatted heading text
final bool isSpeedValid; // True if from recent GPS
final bool isHeadingValid; // True if from recent GPS
const ProcessedGpsData({
required this.speed,
required this.heading,
required this.displaySpeed,
required this.displayHeading,
required this.isSpeedValid,
required this.isHeadingValid,
});
ProcessedGpsData copyWith({
double? speed,
double? heading,
String? displaySpeed,
String? displayHeading,
bool? isSpeedValid,
bool? isHeadingValid,
}) {
return ProcessedGpsData(
speed: speed ?? this.speed,
heading: heading ?? this.heading,
displaySpeed: displaySpeed ?? this.displaySpeed,
displayHeading: displayHeading ?? this.displayHeading,
isSpeedValid: isSpeedValid ?? this.isSpeedValid,
isHeadingValid: isHeadingValid ?? this.isHeadingValid,
);
}
}
+32
View File
@@ -0,0 +1,32 @@
import '../config/color_themes.dart';
/// Theme model that wraps ColorThemes with rotation and state management.
/// Holds all possible color palettes via delegation, exposing only the current set.
class ThemeModel {
final int _currentIndex;
const ThemeModel({int currentIndex = 0}) : _currentIndex = currentIndex;
/// Get the current active color theme
ColorTheme get current => ColorThemes.getTheme(_currentIndex);
/// Get the name of the current theme
String get currentSetName => current.name;
/// Get the current theme index
int get currentIndex => _currentIndex;
/// Rotate to the next theme (circular)
ThemeModel rotate() {
return ThemeModel(
currentIndex: ColorThemes.getNextThemeIndex(_currentIndex),
);
}
/// Create a copy with an updated theme index
ThemeModel copyWith({int? currentIndex}) {
return ThemeModel(
currentIndex: currentIndex ?? _currentIndex,
);
}
}