From d4f43de9332286491686686b57a06a83f1117488 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Thu, 6 Aug 2026 15:35:22 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20add=20UI=20redesign=20design=20spec=20(?= =?UTF-8?q?teal=20arctique,=20moderne=20&=20=C3=A9pur=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec for complete visual overhaul of MyIce KMP app: - Teal arctique color scheme (full Material 3 slots, light/dark) - Typography scale, shape tokens, spacing tokens (Dimens) - 4 screens redesigned (Login, Schedule, EventDetail + navigation) - New components: SkeletonCard, EmptyState, ErrorState, FilterChips - EventCard rework: color border, type badge, leading icons - Sticky date headers in schedule list - French accents corrected, hardcoded colors removed - Branded as 'MyIce' (dropped tech codename 'K') Refs: brainstorming session 2026-08-06 --- .../specs/2026-08-06-ui-redesign-design.md | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-06-ui-redesign-design.md diff --git a/docs/superpowers/specs/2026-08-06-ui-redesign-design.md b/docs/superpowers/specs/2026-08-06-ui-redesign-design.md new file mode 100644 index 0000000..a2ec84f --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-ui-redesign-design.md @@ -0,0 +1,277 @@ +# MyIce UI Redesign — Design Spec + +**Date:** 2026-08-06 +**Status:** Approved (pending review) +**Scope:** Refonte visuelle complète — thème, 4 écrans, composants + +## Contexte + +L'app MyIce (KMP, Compose Multiplatform) est fonctionnellement complète mais visuellement squelettique: thème à 4 slots Material 3, zéro icônerie, listes plates, couleurs hardcodées, accents français manquants, pas de regroupement par date, pas de typographie dédiée. + +Ce spec décrit une refonte visuelle complète dans une direction **moderne & épurée** avec un accent **teal arctique**. + +## Direction visuelle + +- **Style:** Moderne & épuré — beaucoup d'air, surfaces propres, typo hiérarchisée, accents subtils +- **Accent:** Teal arctique (`#0D9488` light / `#2DD4BF` dark) — évoque la glace et le hockey +- **Philosophie:** Le chrome disparaît, les données sont les vedettes + +## 1. Thème & Design Tokens + +### 1.1 Palette (Material 3 complet) + +**Light scheme:** + +| Slot | Valeur | Note | +|------|--------|------| +| primary | `#0D9488` (teal-600) | | +| onPrimary | `#FFFFFF` | | +| primaryContainer | `#99F6E4` (teal-100) | | +| onPrimaryContainer | `#134E4A` (teal-900) | | +| secondary | `#475569` (slate-600) | | +| secondaryContainer | `#E2E8F0` (slate-200) | | +| onSecondaryContainer | `#1E293B` | | +| tertiary | `#0EA5E9` (sky-500) | états interactifs secondaires | +| tertiaryContainer | `#E0F2FE` | | +| error | `#DC2626` | | +| onError | `#FFFFFF` | | +| errorContainer | `#FEE2E2` | | +| onErrorContainer | `#7F1D1D` | | +| background | `#FAFAFA` (warm white) | | +| onBackground | `#1C1917` | | +| surface | `#FFFFFF` | | +| onSurface | `#1C1917` (warm dark) | | +| surfaceVariant | `#F1F5F9` (slate-100) | | +| onSurfaceVariant | `#475569` | | +| outline | `#CBD5E1` (slate-300) | | + +**Dark scheme:** + +| Slot | Valeur | Note | +|------|--------|------| +| primary | `#2DD4BF` (teal-400) | | +| onPrimary | `#003833` | | +| primaryContainer | `#0D9488` | | +| onPrimaryContainer | `#99F6E4` | | +| secondary | `#94A3B8` (slate-400) | | +| secondaryContainer | `#334155` (slate-700) | | +| tertiary | `#0EA5E9` | | +| background | `#0F172A` (slate-900) | | +| onBackground | `#F1F5F9` | | +| surface | `#1E293B` (slate-800) | | +| onSurface | `#F1F5F9` | | +| surfaceVariant | `#334155` | | +| onSurfaceVariant | `#94A3B8` | | +| outline | `#475569` | | +| error | `#F87171` | | +| onError | `#7F1D1D` | | +| errorContainer | `#7F1D1D` | | +| onErrorContainer | `#FECACA` | | + +### 1.2 Typographie + +Échelle système (pas de custom font — trop complexe en KMP), mais poids et tailles définis explicitement via `Typography()`: + +| Style | Taille | Poids | Usage | +|-------|--------|-------|-------| +| headlineLarge | 28sp | Bold | App name (login) | +| titleLarge | 22sp | SemiBold | Screen titles (TopAppBar) | +| titleMedium | 16sp | SemiBold | Card titles | +| bodyLarge | 15sp | Regular | Main content | +| bodyMedium | 14sp | Regular | Secondary info | +| bodySmall | 13sp | Regular | Metadata, labels | +| labelMedium | 12sp | Medium | Chips, badges, section headers | + +### 1.3 Shapes + +| Nom | Rayon | Usage | +|-----|-------|-------| +| small | 8dp | Chips, badges | +| medium | 12dp | Cards, inputs | +| large | 16dp | Sheets, big cards | + +### 1.4 Spacing tokens (`Dimens` object) + +| Token | Valeur | +|-------|--------| +| xs | 4dp | +| sm | 8dp | +| md | 12dp | +| lg | 16dp | +| xl | 24dp | +| xxl | 32dp | + +### 1.5 Elevation + +| Niveau | Valeur | Usage | +|--------|--------|-------| +| level0 | 0dp | Flat surfaces | +| level1 | 1dp | Cards | +| level2 | 3dp | App bar, dialogs | + +## 2. Écrans + +### 2.1 LoginScreen + +Layout centré vertical: +- Icône `SportsHockey` (Material) 48dp dans un cercle `primaryContainer` 80dp, en haut +- `"MyIce"` en `headlineLarge` (le "K" de "MyIceK" était un codename tech — supprimé) +- Sous-titre `"Votre calendrier hockey"` en `titleMedium` `onSurfaceVariant` +- Spacer `xxl` (32dp) +- `Button` plein, 48dp de haut, icône `Login` leading: `"Se connecter avec Infomaniak"` +- Snackbar erreur en `errorContainer`/`onErrorContainer` +- Fond `background`, contenu centré verticalement (pas collé au haut) + +### 2.2 ScheduleScreen + +**TopAppBar:** +- Titre `"Calendrier"` (au lieu de "MyIceK") +- Actions: icône `Refresh` + icône `Logout` uniquement (Material icons, pas de texte) +- L'email utilisateur est supprimé de la barre (trop encombrant sur mobile). Il n'est pas relocalisé — un menu compte futur est hors scope. + +**Filtres:** +- `SegmentedButton` (singleChoice, 3 segments) sur une ligne: `"Tous"` / `"Matchs"` / `"Entraînements"` (remplace le `FilterDropdown` pour le type) +- `AssistChip` cliquables pour Compte / Age / Sous-groupe (affichent la valeur sélectionnée, `"Tous"` par défaut). Utilisent `account.label` au lieu de `account.name` pour le dropdown Compte. +- `AssistChip` "Effacer" avec icône `Close` apparaît si au moins un filtre est actif +- Section filtres dans une `Card` `surfaceVariant`, `medium` shape, padding `lg` + +**Liste d'événements:** +- `LazyColumn` avec **sticky headers** par jour: `"Samedi 15 mars"` en `labelMedium` uppercase `onSurfaceVariant`, fond `surface`, divider subtil en dessous +- `EventCard` repensé (voir Section 3) +- Indicateur `"Dernière mise à jour: ..."` en bas de liste, `bodySmall` `onSurfaceVariant` (utilise `lastUpdated` du state — actuellement non affiché) +- `PullToRefreshBox` conservé (Material 3 par défaut) + +**États partagés:** +- Loading: 3-4 `SkeletonCard` animés (voir Section 3) +- Error: `ErrorState` avec icône `CloudOff`, message, bouton `"Réessayer"` +- Empty: `EmptyState` avec icône `CalendarClear`, `"Aucun événement disponible"` + +### 2.3 EventDetailScreen + +**TopAppBar:** +- Navigation icon: `ArrowBack` (icône, pas `TextButton("Retour")`) +- Titre: titre de l'événement tronqué +- `PlatformBackHandler` conservé + +**Section infos (carte unique, `medium` shape, elevation 1dp):** +- Chaque ligne: icône leading 18dp `onSurfaceVariant` + label `bodySmall` uppercase `onSurfaceVariant` + valeur `bodyLarge` `onSurface` +- Lignes: `Event` → type, `Place` → lieu, `CalendarMonth` → date, `Schedule` → heure +- Plus de `"Type: Match"` interpolé — label et valeur visuellement distincts + +**Convocation:** +- En-tête `"Joueurs (N)"` en `titleMedium` SemiBold + `BadgedBox` avec count +- **Joueurs:** `ListItem` Material 3 — numéro dans un cercle `primaryContainer` 40dp (avatar), nom `bodyLarge`, position + `"(née 2008)"` en `bodySmall` dessous. Triés par numéro. +- Si aucun joueur: carte `errorContainer` avec icône `Warning` + `"Aucun joueur ni personnel convoqué"` +- **Personnel:** `ListItem` avec icône `Person` leading, nom `bodyLarge`, role en `AssistChip` `secondaryContainer` à droite +- En-tête `"Personnel"` en `titleMedium` SemiBold + +**États partagés:** +- Loading: `LoadingIndicator` conservé +- Error: `ErrorState` avec icône `ErrorOutline`, message, bouton `"Réessayer"` + +### 2.4 Navigation & transitions + +- Navigation manuelle conservée (sealed class + `mutableStateOf` — pas de lib externe) +- **`AnimatedContent`** ajouté dans `App.kt` pour transitions fondues entre écrans (`tween(300ms)`) +- `PlatformBackHandler` conservé (known limitation: no-op sur desktop/iOS) + +## 3. Composants + +### 3.1 EventCard (repensé) + +Carte `medium` shape (12dp), elevation 1dp, cliquable: +- **Barre couleur** 4dp à gauche via `Modifier.border` sur le `Card` entier (pas un `Row`) — couleur `event.color` (hex parsé), fallback `primary` +- **Badge type** en haut-droite: `AssistChip` `"Match"` (couleur teal/primary) ou `"Entraînement"` (couleur secondary/slate) +- **Titre:** pour un match (`eventType == "Jeu"`): `"Match vs {opponent}"`. Pour un entraînement: `"Entraînement"`. Suppression de la ligne `"Adversaire:"` pour les entraînements. +- **Lignes info** avec icônes leading 18dp `onSurfaceVariant`: + - `Place` 18dp + lieu en `bodyMedium` + - `Schedule` 18dp + heure condensée (`formatEventTimeRange`: `"18h00 – 19h30"`) +- **Âge + nom** (`"{agegroup} - {name}"`) en `titleMedium` SemiBold +- La date est dans le header de section (pas dans la card) + +### 3.2 SkeletonCard + +Carte placeholder grise aux mêmes proportions que `EventCard`: +- Fond `surfaceVariant`, `alpha(0.3f)` +- Animation shimmer: translation horizontale d'un dégradé clair (`InfiniteTransition` + `rememberInfiniteTransition`) +- 4 lignes fictives (titre + 2 lignes info + badge) +- Réutilisé pour le loading state du Schedule + +### 3.3 EmptyState / ErrorState (composables partagés) + +``` +EmptyState(icon: ImageVector, title: String, message: String) +ErrorState(icon: ImageVector, title: String, message: String, onRetry: () -> Unit) +``` + +- Centrés verticalement dans un `Column` (`Arrangement.Center`, `HorizontalAlignment.CenterHorizontally`) +- Icône 48dp `onSurfaceVariant` +- Titre `titleMedium` `onSurface` +- Message `bodyMedium` `onSurfaceVariant` +- `ErrorState` ajoute un `Button` `"Réessayer"` avec `onRetry` +- Réutilisés sur ScheduleScreen et EventDetailScreen + +### 3.4 Wrappers filtres + +- `SegmentedButton` (singleChoice) pour le type: 3 options `"Tous"/"Matchs"/"Entraînements"`, mappe vers `TypeFilter.ALL/GAMES/PRACTICES` +- `AssistChip` wrapper pour Compte/Age/Sous-groupe: affiche label + valeur sélectionnée, clic ouvre un `DropdownMenu` +- `AssistChip` "Effacer" avec icône `Close`: visible si au moins un filtre non-défaut est actif, reset tous les filtres + +## 4. Corrections transversales + +### 4.1 Accents français + +Toutes les strings UI sont corrigées: +- "Rafraichir" → "Rafraîchir" +- "Deconnexion" → "Déconnexion" +- "Reessayer" → "Réessayer" +- "Aucun evenement" → "Aucun événement" +- "Entrainements" → "Entraînements" +- "convoque" → "convoqué" +- "donnees" → "données" + +### 4.2 Couleurs hardcodées supprimées + +- `Color.Gray` (ScheduleScreen) → `onSurfaceVariant` +- `Color(0xFFFFC107)` (EventDetailScreen warning) → `errorContainer`/`onErrorContainer` +- `Color(0xFF000000 or value)` (EventCard color mask) → `event.color` direct ou `primary` fallback via une util helper `parseHexColor` + +### 4.3 Branding + +- "MyIceK" → "MyIce" partout (TopAppBar, LoginScreen, titre fenêtre desktop) +- `account.label` utilisé au lieu de `account.name` dans le dropdown Compte + +### 4.4 Strings + +Toutes les strings restent hardcodées inline (pas de localization infrastructure — hors scope). Pas de `object Strings` non plus: ajouter une couche d'indirection sans localization réelle est du YAGNI. Les accents français (4.1) sont corrigés directement dans les strings inline. + +## 5. Hors scope + +- Migration vers Navigation Compose library +- `viewModel()` au lieu de `remember{}` (known limitation — perte de state rotation Android) +- Localization infrastructure (string resources) +- Custom fonts (trop complexe en KMP) +- Tests UI (Composition Testing) — le projet n'a pas de framework de test UI configuré +- iOS `.xcodeproj` creation +- Desktop window icon / menu bar + +## 6. Fichiers impactés + +| Fichier | Changement | +|---------|------------| +| `ui/theme/Theme.kt` | Réécriture complète: palette, typo, shapes, elevation, `Dimens` | +| `ui/theme/Color.kt` | Nouveau — définitions de couleur teal/slate | +| `ui/theme/Dimens.kt` | Nouveau — spacing tokens | +| `ui/screens/LoginScreen.kt` | Refonte: icône, branding, layout | +| `ui/screens/ScheduleScreen.kt` | Refonte: TopAppBar, chips filtres, sticky headers, états partagés, `lastUpdated` | +| `ui/screens/EventDetailScreen.kt` | Refonte: icône back, section infos stylisée, ListItems pour joueurs/staff | +| `ui/components/EventCard.kt` | Refonte: border couleur, badge type, icônes leading, logique match/entraînement | +| `ui/components/SkeletonCard.kt` | Nouveau — skeleton loading | +| `ui/components/EmptyState.kt` | Nouveau — état vide partagé | +| `ui/components/ErrorState.kt` | Nouveau — état erreur partagé | +| `ui/components/FilterChips.kt` | Nouveau — SegmentedButton + AssistChips (remplace FilterDropdown) | +| `ui/components/FilterDropdown.kt` | Supprimé — remplacé par FilterChips | +| `ui/components/LoadingIndicator.kt` | Conservé (utilisé par EventDetail) | +| `App.kt` | `AnimatedContent` pour transitions, branding "MyIce" | +| `util/EventDateTimeFormatter.kt` | Ajout `formatEventTimeRange` condensé (existant conservé) | +| `desktopMain/Main.kt` | Titre fenêtre "MyIce" |