Fix — Creation bottom sheet, dark-mode safe + differenziazione Crew/Evento

Commit: fix(ui): dark-mode safe creation sheet with distinct crew/event styling · Branch: edit/mobile-audit-ux-polish · Audit: 2026-04-20 UX polish (finding H2)

Cosa non andava

Il CreationBottomSheet è il gate principale verso la differenziazione del prodotto: è da qui che l’utente sceglie fra crew (il nostro differenziatore) e evento (il flusso classico). La qualità di questo momento definisce la percezione del valore unico dell’app.

Cosa trovavamo invece:

  • color: Colors.white hardcoded nel BoxDecoration → in dark mode un rettangolo bianco emerge dal fondo nero, rompendo il flusso visivo.
  • Colors.grey[300], Colors.grey[400], Colors.grey[600], Colors.black sparsi per tutto il file → zero adesione al theme.
  • Crew e Evento entrambi coral → visivamente indistinguibili al primo sguardo.
  • Espressione assurda: color: color == Colors.grey[400] ? Colors.grey : Colors.black (la condizione è sempre falsa perché color è electricCoral) → rotto in dark mode, codice che mente sulla sua intenzione.
  • Naming asimmetrico: “Crew per stasera” (imperativo breve) vs “Evento Community” (noun phrase) → due registri, stessa schermata.

Cosa abbiamo cambiato

Colori — dal hardcoded al tema

ElementoPrimaDopo
Sheet backgroundColors.whitetheme.bottomSheetTheme.modalBackgroundColor ?? colors.surface
Radius topRadius.circular(32) literalRadius.circular(AppTheme.radius28)
Drag handleColors.grey[300]colors.onSurface @ 0.15 alpha
Title styleheadlineSmall + override pesotheme.textTheme.headlineLarge (canonical)
Option title colorColors.black via expr. bugatacolors.onSurface
Subtitle colorColors.grey[600]colors.onSurface @ 0.60 alpha
Chevron colorColors.grey[400]colors.onSurface @ 0.40 alpha
Card radiuscircular(20) literalAppTheme.radius20 (token)

In dark mode ora tutto il sheet appare su surfaceDark e ogni testo pesca dai contrasti del ColorScheme.

Differenziazione Crew vs Evento

CrewEvento
Accent colorAppTheme.electricCoralAppTheme.electricViolet
Priorità semanticaDifferenziatore brand (Pilastro 2)Flusso community classico
IconaIcons.groups_roundedIcons.event_rounded

Il coral segnala la priorità (Flow si vende sulla crew); il violet segnala la complementarità (l’evento è l’altro lato della stessa piattaforma). Visualmente i due riquadri ora pescano da colori brand diversi — l’utente distingue in un colpo d’occhio.

Naming — registri simmetrici

PrimaDopo
CrewCrew per staseraCrea una crew
Crew subtitleTrova subito compagni per il tuo giroTrova subito compagni per stasera
EventoEvento CommunityCrea un evento
Evento subtitleOrganizza una festa o un ritrovoOrganizza una festa o un ritrovo (invariato)

Verbo all’imperativo esplicito in entrambi i titoli. L’utente legge due azioni parallele: Crea una crew / Crea un evento. Zero ambiguità su “cos’è questo pulsante”.

Non toccato (deferito)

  • i18n: le label sono ancora hardcoded in italiano. Il piano l10n gestirà le chiavi creation.title, creation.crew.title etc. in un commit a sé (coerente con il pattern di migrazione i18n già in corso).
  • A11y focus order: due GestureDetector con HitTestBehavior.opaque senza Semantics → un lettore di schermo legge solo il testo dentro, non annuncia “bottone, premere per…“. Fix nel prossimo a11y pass.

Verifica

flutter analyze lib/shared/widgets/creation_bottom_sheet.dart
# → No issues found

Check visivo manuale:

  • Aprire il ”+” in light mode → sheet con background chiaro, Crew in coral e Evento in violet, titoli in nero
  • Aprire il ”+” in dark mode → sheet su nero, NO rettangoli bianchi, Crew e Evento ancora distinguibili
  • Tap su Crew → apre CreateCrewScreen come sheet nested con stessa radius top
  • Tap su Evento → naviga a /home/create-event

Lessons

  • Un solo colore accent = zero gerarchia. Quando tutto è “primary brand”, niente è primary. Il coral deve comunicare questo è la priorità; il violet deve comunicare questo è il complemento. Perdere questa distinzione sulla schermata più strategica del prodotto è una ferita silenziosa.
  • Naming simmetrico sopra ogni inventiva. La tentazione di essere clever (“Crew per stasera” suona caldo, evocativo) va bilanciata col bisogno di parallel structure. Se due opzioni sono siblings semantici, i loro label devono essere siblings grammaticali.
  • Condizioni sempre-false sono bug mascherati. Il color == Colors.grey[400] ? ... : ... è l’antipattern classico del copy-paste-da-stackoverflow che sopravvive perché “tanto funziona”. Non funziona: rompe il dark mode e mente sull’intenzione del codice. Vanno cacciati con grep periodici su Colors\.[a-z]+ nel layer UI.