multiple updates

- colour theme implemented. ThemeService based global switching for future light detection triggers
- test flipflop service for various fun
- navigator widget class with state switching
This commit is contained in:
Mikkeli Matlock
2026-01-26 00:20:52 +09:00
parent b1e23fdd10
commit c7edc30b79
18 changed files with 489 additions and 67 deletions

128
scripts/generate_theme.py Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Theme generator for Smart Serow Flutter UI.
Reads navigator from config, loads corresponding theme JSON,
and generates app_colors.dart with the colour palette.
Fallback chain: {navigator}.json -> default.json -> hardcoded defaults
"""
import json
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent.resolve()
PROJECT_ROOT = SCRIPT_DIR.parent
UI_DIR = PROJECT_ROOT / "pi" / "ui"
THEMES_DIR = PROJECT_ROOT / "extra" / "themes"
CONFIG_FILE = UI_DIR / "config.json"
OUTPUT_FILE = UI_DIR / "lib" / "theme" / "app_colors.dart"
# Hardcoded fallback if no theme files exist at all
HARDCODED_THEME = {
"dark": {
"background": "#000000",
"foreground": "#FFFFFF",
"highlight": "#FF5555",
"subdued": "#808080",
},
"bright": {
"background": "#F0F0F0",
"foreground": "#1A1A1A",
"highlight": "#CC0000",
"subdued": "#606060",
},
}
def hex_to_flutter(hex_color: str) -> str:
"""Convert #RRGGBB to 0xFFRRGGBB format."""
hex_clean = hex_color.lstrip("#").upper()
return f"0xFF{hex_clean}"
def load_config() -> dict:
"""Load UI config, return empty dict if missing."""
if CONFIG_FILE.exists():
with open(CONFIG_FILE) as f:
return json.load(f)
return {}
def load_theme(navigator: str) -> dict:
"""Load theme for navigator with fallback chain."""
# Try navigator-specific theme
nav_theme = THEMES_DIR / f"{navigator}.json"
if nav_theme.exists():
print(f" Using theme: {nav_theme.name}")
with open(nav_theme) as f:
return json.load(f)
# Try default theme
default_theme = THEMES_DIR / "default.json"
if default_theme.exists():
print(f" Fallback to: default.json")
with open(default_theme) as f:
return json.load(f)
# Last resort: hardcoded
print(f" Using hardcoded defaults")
return HARDCODED_THEME
def generate_dart(theme: dict) -> str:
"""Generate Dart source from theme dict."""
dark = theme["dark"]
bright = theme["bright"]
return f'''import 'package:flutter/material.dart';
/// Auto-generated from theme config. Do not edit manually.
/// Run scripts/generate_theme.py to regenerate.
class AppColors {{
AppColors._();
// Dark theme (low ambient light)
static const darkBackground = Color({hex_to_flutter(dark["background"])});
static const darkForeground = Color({hex_to_flutter(dark["foreground"])});
static const darkHighlight = Color({hex_to_flutter(dark["highlight"])});
static const darkSubdued = Color({hex_to_flutter(dark["subdued"])});
// Bright theme (high ambient light)
static const brightBackground = Color({hex_to_flutter(bright["background"])});
static const brightForeground = Color({hex_to_flutter(bright["foreground"])});
static const brightHighlight = Color({hex_to_flutter(bright["highlight"])});
static const brightSubdued = Color({hex_to_flutter(bright["subdued"])});
}}
'''
def main():
print("=== Theme Generation ===")
# Get navigator from config
config = load_config()
navigator = config.get("navigator", "default")
print(f" Navigator: {navigator}")
# Load theme
theme = load_theme(navigator)
# Generate Dart
dart_source = generate_dart(theme)
# Ensure output directory exists
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
# Write output
with open(OUTPUT_FILE, "w") as f:
f.write(dart_source)
print(f" Generated: {OUTPUT_FILE.relative_to(PROJECT_ROOT)}")
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)