feat(gui): add GUI V6 G1 foundation (license client/store, shell, About tab)

Socle de la refonte GUI V6 (couche présentation uniquement, aucune logique de
détection) :
- license_store: stockage licence hors dépôt (%LOCALAPPDATA%/Aivanov | XDG),
  read/write atomique/delete, ne journalise aucun token
- license_client: LicenseStatus + activate/check/local_status, session HTTP
  injectable, serveur indisponible géré sans crash, aucune clé privée
- theme: 4 thèmes + couleurs de statut licence
- app + tab_about: shell customtkinter minimal (header, bandeau licence,
  3 onglets), onglet À propos étoffé
- Pseudonymisation_Gui_V6.py: point d'entrée + --self-test (exit 0 sans fenêtre)
- requirements.txt: customtkinter==5.2.2

Tests: 20 nouveaux (store sur vrais fichiers, client sur session injectée).
Suite tests/unit: 167 passed, 0 régression. V5/moteur/managers/specs intacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 18:47:56 +02:00
parent ae3e2050c1
commit d265cd3269
11 changed files with 865 additions and 0 deletions

83
gui_v6/theme.py Normal file
View File

@@ -0,0 +1,83 @@
"""Thèmes et palette de la GUI V6.
Mappe les tokens de couleur de la maquette ``docs/ui_mockup_v6.html`` vers
customtkinter. Lot G1 : 4 thèmes de base + helper d'application. L'import de ce
module ne crée aucun widget (compatible ``--self-test`` sans display).
"""
from __future__ import annotations
from typing import Dict
# Tokens couleur par thème (bandeau statut licence, accents, surfaces).
THEMES: Dict[str, dict] = {
"clair": {
"appearance": "light",
"color_theme": "blue",
"accent": "#2563eb",
"ok": "#16a34a",
"warn": "#d97706",
"error": "#dc2626",
},
"sombre": {
"appearance": "dark",
"color_theme": "blue",
"accent": "#3b82f6",
"ok": "#22c55e",
"warn": "#f59e0b",
"error": "#ef4444",
},
"médical": {
"appearance": "light",
"color_theme": "green",
"accent": "#0d9488",
"ok": "#15803d",
"warn": "#ca8a04",
"error": "#b91c1c",
},
"contraste": {
"appearance": "dark",
"color_theme": "dark-blue",
"accent": "#60a5fa",
"ok": "#4ade80",
"warn": "#fbbf24",
"error": "#f87171",
},
}
DEFAULT_THEME = "clair"
# Couleurs du bandeau de statut licence selon l'état métier.
STATUS_COLORS = {
"active": "ok",
"grace": "warn",
"expired": "error",
"revoked": "error",
"invalid": "error",
"unavailable": "warn",
"none": "warn",
}
def theme_names() -> list[str]:
return list(THEMES.keys())
def get_theme(name: str) -> dict:
return THEMES.get(name, THEMES[DEFAULT_THEME])
def status_color(theme_name: str, status: str) -> str:
"""Couleur hex pour un statut licence, dans le thème donné."""
theme = get_theme(theme_name)
return theme[STATUS_COLORS.get(status, "warn")]
def apply_theme(name: str = DEFAULT_THEME) -> dict:
"""Applique le thème à customtkinter. Import paresseux de ctk."""
theme = get_theme(name)
import customtkinter as ctk
ctk.set_appearance_mode(theme["appearance"])
ctk.set_default_color_theme(theme["color_theme"])
return theme