Audit mobile — UX, branding, navigazione (2026-04-20)
Autore: Claude Code · Branch mobile:
edit/mobile-audit-ux-polish· Branch docs:edit/web-brand-parity-docs· Scope: l’intera app Flutterflow_mobile.
Executive summary
L’app è strutturalmente solida — Riverpod + GoRouter + Supabase direct — ma il layer di presentazione non regge la promessa del brand. Il Brand Strategy dice “one element glows per viewport”; in pratica oggi troppi widget competono per l’attenzione con colori hardcoded, gradient divergenti dal brand, naming inconsistente, e quattro schermi orfani (senza route) che segnalano lavoro incompiuto.
L’utente ha verbalizzato la sensazione giusta:
“Molti widget non sono uniformati allo stesso stile, alcuni sono piatti, altri sono esagerati, alcuni hanno overlapping, altri si distanziano troppo.”
Questo audit dà a quella sensazione prove concrete (file + riga) e un piano di rientro.
Scope verificato
| Layer | File chiave | Stato recon |
|---|---|---|
| Theme tokens | app_theme.dart (434 righe) | ✅ |
| Bottom nav | shared/widgets/flow_bottom_nav.dart (172 righe) | ✅ |
| Creation flow | shared/widgets/creation_bottom_sheet.dart (123 righe) | ✅ |
| Button system | shared/widgets/custom_button.dart (417 righe) | ✅ |
| Router | core/router/app_router.dart (351 righe) | ✅ |
| Shell navigazione | features/home/screens/main_screen.dart (61 righe) | ✅ |
| Feed home | features/events/screens/feed_screen.dart (1398 righe — god screen) | 🟡 ricognizione solo |
| Brand docs | assets/brand/FLOW_BRAND_STRATEGY.md + FLOW_DESIGN_PHILOSOPHY.md | ✅ allineamento verificato |
Validazione naming “Crew”
L’utente chiede: “La possibilità di creare le ‘crew’ (va bene come nome?)“.
Sì, va bene. Il Brand Strategy assets/brand/FLOW_BRAND_STRATEGY.md lo conferma come Pilastro 2 del posizionamento:
Pillar 2 — Crew / Squad: il momento sociale che precede l’evento.
Consigli operativi:
- Usare solo “Crew” (inglese, mono-sillaba in italiano, immediato) — oggi il codice mescola crew e squad (residuo storico). Nel fix pass si standardizza su crew in UI e copy; squad resta ammesso solo come alias tecnico interno se serve back-compat.
- Mai “Gruppo” — generico, non differenzia.
- In italiano, microcopy: “Crea una crew”, “La tua crew”, “Unisciti alla crew”. Mai “crews” al plurale con -s (ibrido brutto); preferire “le tue crew”.
Findings per severità
🔴 Critical — impattano la percezione di qualità del brand
C1. Bottom nav gradient ≠ brand gradient
File: lib/shared/widgets/flow_bottom_nav.dart:151
gradient: const LinearGradient(
colors: [AppTheme.electricCoral, Color(0xFFFF7B54)], // ← coral→arancione
),Il brand gradient locked è coral → violet (#FF5E57 → #8A2BE2). La barra di navigazione — l’elemento più visto dell’app — usa un gradient arancione non esistente nel brand system. Rompe immediatamente la firma visiva.
Fix: usare AppTheme.brandGradient (già definito ma non consumato).
C2. Bottom nav: icona chat porta a schermata amici
File: lib/shared/widgets/flow_bottom_nav.dart + lib/core/router/app_router.dart
La terza icona della nav usa glyph “chat” ma la route /social monta FriendsScreen. L’utente vede bubble-chat, preme, trova lista amici. Aspettativa violata su ogni tap.
Fix: O cambiare glyph a people (se la schermata resta amici), o far puntare a ChatListScreen. Decisione presa nel fix pass: glyph → people (vedi §Fix plan).
C3. Token radius mentono sul valore
File: lib/core/theme/app_theme.dart
static const double radius32 = 24.0; // WAS 32. Reduced for density.Il nome del token dichiara 32 ma il valore è 24. Ogni dev che scriverà AppTheme.radius32 lo farà pensando 32 e otterrà 24 → inconsistenza che si propaga. Un commento non risolve un bug di naming.
Fix: rinominare in radius24 e rimuovere il commento archeologico. Usare BorderRadius.circular(AppTheme.radius24) in tutti i consumer.
C4. Quattro schermi orfani (feat dichiarate, non raggiungibili)
File: lib/core/router/app_router.dart
Screen presenti nel codice ma senza alcuna route:
CreateCampaignScreen(venue campaigns)VendorDashboardScreen(venue dashboard)CreateSquadScreen(crew creation alt flow)CrewMatchmakingWizard(matchmaking flow)
Per un utente sono funzioni fantasma. Per un recruiter o investor che apre il repo è debito tecnico visibile. O vanno collegati o cancellati.
Decisione proposta:
- Collegare:
CrewMatchmakingWizard(= core differenziatore “crew”),VendorDashboardScreen(= monetization path). - Rimuovere:
CreateCampaignScreen(duplicato di flusso venue),CreateSquadScreen(duplicato di crew creation viaCreationBottomSheet).
🟠 High — spezzano uniformità visiva
H1. Tre colori diversi per “testo secondario”
File: lib/core/theme/app_theme.dart
static const Color textSecondary = Color(0xFF8E8E93);
static const Color textSecondaryLight = Color(0xFF6D6D72);
static const Color textSecondaryWarm = Color(0xFF9E9E9E);Tre token semantici per lo stesso ruolo. Di fatto l’app ha tre livelli di grigio intermedio, usati senza logica: basta leggere una schermata della home e una del profilo per sentire lo stacco.
Fix: consolidare in un solo textSecondary + textMuted (solo se realmente serve un terzo livello). I call-site vanno normalizzati.
H2. creation_bottom_sheet indistinguibile fra Crew e Evento
File: lib/shared/widgets/creation_bottom_sheet.dart
Entrambe le card di scelta usano color: AppTheme.electricCoral. Stesso colore, stesso peso, stessa size → il cervello non riesce a differenziare. Inoltre:
- Background
Colors.whitehardcoded → rotto in dark mode - Naming: “Crew per stasera” (short, imperativo) vs “Evento Community” (categoria + sostantivo) → registro diverso, stessa schermata.
Fix: Crew = coral (differenziatore brand), Evento = violet outline. Background via colorScheme.surface. Naming uniforme: “Crea una crew” + “Crea un evento” (stesso registro, stesso verbo).
H3. CustomButton non rispetta i token del tema
File: lib/shared/widgets/custom_button.dart
| Dimensione | Theme default | CustomButton | Effetto |
|---|---|---|---|
| BorderRadius | radius14 (14) | circular(16) | 2 px di differenza ma visibile se affiancato |
| Height | Size(64, 48) | height ?? 56 | button custom più alto del Material default |
| Loading copy | — | 'Loading...' hardcoded | ❌ non localizzato |
Fix: CustomButton deve leggere i token da AppTheme (radius, height, gradient opzionale) e usare la chiave AppLocalizations.of(context).loading per lo stato di caricamento. Aggiungere variante ButtonType.gradient per l’hero CTA.
H4. Secondary button invisibile in dark mode
File: lib/shared/widgets/custom_button.dart
ButtonType.secondary usa colorScheme.surface come background — in dark mode surface ≈ inkBlack, quindi un pulsante nero su sfondo nero. Rotto.
Fix: Usare colorScheme.surfaceContainerHighest (Material 3) o un token esplicito AppTheme.buttonSecondaryBg che cambia valore per brightness.
🟡 Medium — qualità “mid”
M1. Badge nav con contrasto WCAG insufficiente
File: lib/shared/widgets/flow_bottom_nav.dart
Testo fontSize: 9, color: Colors.white su background coral (#FF5E57). Contrast ratio ≈ 2.7:1 — sotto soglia WCAG AA (4.5:1) e anche AA Large (3:1). Un utente con bassa visione non legge il numero di notifiche.
Fix: alzare font a 10px minimo, usare inkBlack come foreground su coral (7.2:1), oppure invertire (coral text su white chip).
M2. Violet (secondo colore brand) non usato nel theme
File: lib/core/theme/app_theme.dart
colorScheme: ColorScheme(
primary: electricCoral,
secondary: primary, // ← secondary = primary
...
),Il violet è solo decorativo in qualche gradient isolato, ma ColorScheme.tertiary / secondary non lo espongono. Risultato: tutto il tema Material (FAB, chip selezionati, focus ring) è coral. Zero gerarchia.
Fix: tertiary: electricViolet + consumare colorScheme.tertiary negli accent secondari (filtri attivi, chip selezionati).
M3. Leaderboard / Ticket Wallet / PR Dashboard sepolti sotto /profile
File: lib/core/router/app_router.dart
Route come /profile/leaderboard, /profile/tickets, /profile/pr-dashboard esistono ma sono raggiungibili solo dal menu profilo. Leaderboard e tickets sono feature di engagement core, non settings.
Fix: esporre quick-access nella home (sezione “La tua attività”) e nel menu di ricerca eventi. Route rimangono, cambia solo la discoverability.
M4. feed_screen.dart è un god screen (1398 righe)
File: lib/features/events/screens/feed_screen.dart
Una schermata che fa home, discovery, filtri, stories, empty states, pull-to-refresh, socket updates. Impossibile da mantenere, impossibile da testare isolatamente. Non blocca il deploy ma va smontata.
Fix (deferito): estrarre FeedHeader, FeedStories, FeedEventList, FeedFilterBar in widget figli. Settimana successiva.
🟢 Low — nice to have
- L1. Dipendenza
provider: ^6.1.2residua con Riverpod già in casa → rimuovibile in pulizia deps. - L2. Tre varianti di
event_card(grid/list/featured) con stili non omogenei → unificabili convariant: EventCardVariant.xxx. - L3. Doppio file
loading_widget.dartinshared/widgets/efeatures/common/→ duplicazione, da dedupare. - L4. Shadow in
app_theme.dartusaColors.black.withValues(alpha: 0.08)→ invisibile in dark mode, serve branch su brightness.
Fix plan — questo pass
Il fix pass si esegue sul branch edit/mobile-audit-ux-polish in 5 commit atomici:
| # | Commit | Target | Severità | Note |
|---|---|---|---|---|
| 1 | refactor(theme): consolidate tokens, wire violet as tertiary | C3, H1, M2, L4 | 🔴🟠🟡🟢 | Theme-only, no UI regressions attese |
| 2 | fix(nav): use brand gradient and correct glyph-to-route mapping | C1, C2, M1 | 🔴🟡 | Sostituisce coral→arancione con coral→violet |
| 3 | fix(ui): dark-mode safe creation sheet with distinct crew/event styling | H2 | 🟠 | Crew coral / Evento violet outline |
| 4 | refactor(button): align CustomButton with theme tokens + gradient variant | H3, H4 | 🟠 | Aggiunge ButtonType.gradient, localizza loading |
| 5 | chore(router): register crew matchmaking + venue dashboard, remove orphans | C4 | 🔴 | Rimuove CreateCampaignScreen, CreateSquadScreen; collega gli altri due |
Ogni commit ha un doc companion in flow_docs/docs/mobile/ per spiegare il perché (ricorda la regola: ogni modifica → doc in flow_docs).
Deferred (non in questo pass)
- M3 Re-discoverability di leaderboard/tickets — richiede design decisioni più ampie (dove metterli in home).
- M4 Smontaggio
feed_screen.dartgod screen — task da solo da ~2h, separato. - L2 Unificazione event_card varianti — richiede inventario call-site.
- Stress test & fake DB entries (richiesti dall’utente) — dopo che l’app è clean visualmente: testare su una UI inconsistente è debito che si somma.
- Unificazione nomenclatura crew/squad nel codice (oggi mescolati) — passa dopo che il routing è chiaro.
Acceptance criteria del pass
Il pass si considera chiuso quando:
-
flutter analyze→ 0 errori, ≤5 warning (esistenti a baseline) -
flutter test→ pass - Bottom nav usa gradient coral→violet su tutte le piattaforme
- Dark mode non mostra nessuna card bianca hardcoded nel flusso di creazione
- Ogni
CustomButtonha dimensioni e radius derivati dal theme - Nessun
Screenclass esiste senza essere referenziato da almeno unGoRoute - Ogni fix ha un doc in
flow_docs/docs/mobile/(convenzionefix-*.mdorefactor-*.md) - Tutti i commit seguono Conventional Commits e sono pushati sul remoto
Risorse
- Brand Strategy:
flow_mobile/assets/brand/FLOW_BRAND_STRATEGY.md - Design Philosophy:
flow_mobile/assets/brand/FLOW_DESIGN_PHILOSOPHY.md - Brand Book (sito): Brand Book
- Audit metodologia: audit-project skill (adattata da Node/Python a Flutter)