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 Flutter flow_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

LayerFile chiaveStato recon
Theme tokensapp_theme.dart (434 righe)
Bottom navshared/widgets/flow_bottom_nav.dart (172 righe)
Creation flowshared/widgets/creation_bottom_sheet.dart (123 righe)
Button systemshared/widgets/custom_button.dart (417 righe)
Routercore/router/app_router.dart (351 righe)
Shell navigazionefeatures/home/screens/main_screen.dart (61 righe)
Feed homefeatures/events/screens/feed_screen.dart (1398 righe — god screen)🟡 ricognizione solo
Brand docsassets/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 via CreationBottomSheet).

🟠 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.white hardcoded → 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

DimensioneTheme defaultCustomButtonEffetto
BorderRadiusradius14 (14)circular(16)2 px di differenza ma visibile se affiancato
HeightSize(64, 48)height ?? 56button 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 surfaceinkBlack, 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.2 residua con Riverpod già in casa → rimuovibile in pulizia deps.
  • L2. Tre varianti di event_card (grid/list/featured) con stili non omogenei → unificabili con variant: EventCardVariant.xxx.
  • L3. Doppio file loading_widget.dart in shared/widgets/ e features/common/ → duplicazione, da dedupare.
  • L4. Shadow in app_theme.dart usa Colors.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:

#CommitTargetSeveritàNote
1refactor(theme): consolidate tokens, wire violet as tertiaryC3, H1, M2, L4🔴🟠🟡🟢Theme-only, no UI regressions attese
2fix(nav): use brand gradient and correct glyph-to-route mappingC1, C2, M1🔴🟡Sostituisce coral→arancione con coral→violet
3fix(ui): dark-mode safe creation sheet with distinct crew/event stylingH2🟠Crew coral / Evento violet outline
4refactor(button): align CustomButton with theme tokens + gradient variantH3, H4🟠Aggiunge ButtonType.gradient, localizza loading
5chore(router): register crew matchmaking + venue dashboard, remove orphansC4🔴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.dart god 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 CustomButton ha dimensioni e radius derivati dal theme
  • Nessun Screen class esiste senza essere referenziato da almeno un GoRoute
  • Ogni fix ha un doc in flow_docs/docs/mobile/ (convenzione fix-*.md o refactor-*.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)