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:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user