Global UI Restyling — Flow Mobile

Contesto

Il feed è stato ridisegnato con la direzione “Light Foundation + Cinematic Pockets” (warmCanvas scaffold, card bianche elevate, accenti coral, tipografia Inter tight). Questo documento specifica come propagare lo stesso sistema visivo a tutte le schermate dell’app usando un approccio ibrido: theme-first + widget condivisi + passaggi mirati.


Approccio: Ibrido

  1. AppTheme overrides — propagazione automatica attraverso ThemeData
  2. Widget condivisi nuovi — 3 widget per pattern non coperti dal tema
  3. Widget condivisi esistenti — aggiornamento dei 31 widget in lib/shared/widgets/
  4. Passaggio schermata per schermata in 3 livelli di intervento

Token di Design

Questi valori sono la fonte di verità. Tutti i file devono usarli — mai literal values.

Colori

TokenLightDark
Scaffold backgroundAppTheme.warmCanvas (#F0EEE9)Color(0xFF1A1A1A)
Surface / CardColors.whiteColor(0xFF1E1E1E)
Input fillAppTheme.warmCanvasColor(0xFF2A2A2A)
Accento primarioAppTheme.electricCoral (#FF5E57)stesso
Testo primarioAppTheme.inkBlack (#121212)Colors.white
Testo secondarioAppTheme.textSecondaryWarm (→ Color(0xFF9E9E9E))Colors.grey[400]
DividerColor(0xFFE8E5E0)Colors.white.withValues(alpha: 0.08)
AppBar backgroundAppTheme.warmCanvasColor(0xFF1A1A1A)
Bottom sheetColors.whiteColor(0xFF1E1E1E)

Contrasto nota: electricCoral su warmCanvas è ~3.2:1 — non WCAG AA per testo piccolo. Per label ≤ 12px usare AppTheme.inkBlack come colore testo, coral solo per icone/indicatori.

Border Radius

Aggiungere a AppTheme i seguenti nuovi valori statici:

static const double radius14 = 14.0;  // input fields (new)
// radius20 già esiste = 20.0          // card standard
static const double radius16 = 16.0;  // FlowStatCard, map preview (new)
static const double radius28 = 28.0;  // bottom sheet, hero card (new)
static const Color textSecondaryWarm = Color(0xFF9E9E9E); // secondary text light (new)
// Permessi literal: Colors.grey[400] per hintStyle — shade più chiaro senza token dedicato
ComponenteRadius
TextField / Input14
Card standard20
Map card compatta20
FlowStatCard / Map preview16
Bottom sheet (top corners)28
Chip / pill999 (full)
Avatar piccolo999 (full)

Tipografia

UsoSizeWeightLetterSpacing
Titolo schermata / AppBar20pxw900-0.5
Titolo sezione18pxw900-0.3
Titolo card15–16pxw900-0.3
Label uppercase (eyebrow)11pxw800+0.8
Body14pxw5000
Caption / label secondaria12pxw6000

Regola: FontWeight massimo per titoli = w900. Non usare FontWeight.bold (700) per heading.

Input Fields

Passaggio da UnderlineInputBorder a filled + OutlineInputBorder senza bordo visibile (bordo = BorderSide.none), con coral al focus. Questo è un breaking change rispetto all’attuale underline — verificare che ogni form visivamente regga il cambio.

InputDecorationTheme(
  filled: true,
  fillColor: warmCanvas,            // dark: Color(0xFF2A2A2A)
  border: OutlineInputBorder(
    borderRadius: BorderRadius.circular(14),
    borderSide: BorderSide.none,    // nessun bordo di default
  ),
  enabledBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(14),
    borderSide: BorderSide.none,
  ),
  focusedBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(14),
    borderSide: BorderSide(color: electricCoral, width: 1.5),
  ),
  errorBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(14),
    borderSide: BorderSide(color: Colors.red, width: 1.5),
  ),
  contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
  hintStyle: TextStyle(color: Colors.grey[400], fontWeight: FontWeight.w500),
)

Sezione 1 — AppTheme Overrides

File: lib/core/theme/app_theme.dart

Aggiungere/aggiornare i seguenti ThemeData override in entrambi i temi (light/dark):

appBarTheme: AppBarTheme(
  backgroundColor: warmCanvas,           // dark: Color(0xFF1A1A1A)
  elevation: 0,
  scrolledUnderElevation: 0,
  centerTitle: false,
  titleTextStyle: GoogleFonts.inter(     // Il progetto usa google_fonts, non asset font
    color: inkBlack,                     // dark: Colors.white
    fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: -0.5,
  ),
  iconTheme: IconThemeData(color: inkBlack),       // dark: white
  actionsIconTheme: IconThemeData(color: inkBlack), // dark: white
),
 
cardTheme: CardThemeData(
  color: Colors.white,                   // dark: Color(0xFF1E1E1E)
  elevation: 0,
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
  // Le shadow custom (Airbnb-style) si applicano via BoxDecoration
  // dove il widget ha bisogno di shadow diretta.
),
 
inputDecorationTheme: /* come da sezione Token → Input Fields */,
 
elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    backgroundColor: electricCoral,
    foregroundColor: Colors.white,
    elevation: 0,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 14),
    textStyle: TextStyle(fontSize: 15, fontWeight: FontWeight.w800),
  ),
),
 
outlinedButtonTheme: OutlinedButtonThemeData(
  style: OutlinedButton.styleFrom(
    foregroundColor: inkBlack,           // dark: Colors.white
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
    side: BorderSide(color: Color(0xFFE0DDD8)),
    padding: EdgeInsets.symmetric(horizontal: 24, vertical: 14),
    textStyle: TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
  ),
),
 
bottomSheetTheme: BottomSheetThemeData(
  backgroundColor: Colors.white,         // dark: Color(0xFF1E1E1E)
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
  ),
  elevation: 0,
  modalBackgroundColor: Colors.white,    // dark: Color(0xFF1E1E1E)
  modalElevation: 0,
),
 
chipTheme: ChipThemeData(
  backgroundColor: electricCoral.withValues(alpha: 0.10),
  labelStyle: TextStyle(
    color: electricCoral, fontSize: 12, fontWeight: FontWeight.w700,
  ),
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
  side: BorderSide.none,
  padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
),
 
dividerTheme: DividerThemeData(
  color: Color(0xFFE8E5E0),             // dark: Colors.white.withValues(alpha:0.08)
  thickness: 1,
  space: 1,
),
 
listTileTheme: ListTileThemeData(
  contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 4),
  titleTextStyle: TextStyle(
    fontSize: 15, fontWeight: FontWeight.w600, color: inkBlack, // dark: white
  ),
  subtitleTextStyle: TextStyle(fontSize: 13, color: AppTheme.textSecondaryWarm),
),
 
tabBarTheme: TabBarTheme(
  labelColor: electricCoral,
  unselectedLabelColor: Colors.grey[400],
  labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w800),
  unselectedLabelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
  indicator: UnderlineTabIndicator(
    borderSide: BorderSide(color: electricCoral, width: 2.5),
  ),
  indicatorSize: TabBarIndicatorSize.label,
),

Sezione 2 — Widget Condivisi Nuovi

FlowSectionHeader

File: lib/shared/widgets/flow_section_header.dart

LABEL UPPERCASE     ← 11px w800 inkBlack (non coral — contrasto < AA a 11px)
Titolo sezione      ← 18px w900 inkBlack letterSpacing -0.3

Props: label (String), title (String?), trailing (Widget?) Padding: EdgeInsets.fromLTRB(24, 20, 24, 12)

FlowListTile

File: lib/shared/widgets/flow_list_tile.dart

┌──────────────────────────────────────────┐
│ [■ icon coral/10% bg, radius 10]  Titolo │ → (arrow)
│                                  Sub     │
└──────────────────────────────────────────┘

Props: icon (IconData), iconColor (Color?, default coral), title (String), subtitle (String?), onTap (VoidCallback?), trailing (Widget?), destructive (bool, default false — usa rosso per icon+title)

Relazione con widget esistenti: FlowListTile sostituisce i ListTile raw nelle schermate Settings/Help/Social. NON sostituisce chat_tile.dart (layout diverso).

FlowStatCard

File: lib/shared/widgets/flow_stat_card.dart

┌───────────────┐
│ [icon/emoji]  │
│ 1.240         │  ← 22px w900 inkBlack
│ Karma         │  ← 11px grey500
└───────────────┘

Props: icon (IconData), value (String), label (String), color (Color?) Background: warmCanvas (light), Color(0xFF2A2A2A) (dark — input-fill tone, più scuro di card, intenzionale per distinguere le stat), borderRadius AppTheme.radius16


Sezione 3 — Widget Condivisi Esistenti da Aggiornare

Branch: feat/restyling-shared-widgets — da mergiare PRIMA di L1 e L2.

WidgetIntervento
vibe_filter_bar.dartChip → ChipTheme, rimuovere colori hardcoded
hero_event_card.dartGià stilato — verificare shadow coral e radius 28
minimal_event_card.dartGià stilato — nessun intervento
squad_bento_section.dartGià stilato — nessun intervento
feed_interruption_widgets.dartGià stilato — nessun intervento
chat_tile.dartAvatar border coral, timestamp 11px grey400, unread dot coral
crew_summary_card.dartCard → CardTheme, titolo w900, accenti coral
squad_detail_sheet.dartBottomSheetTheme automatico, titolo w900, button → tema
map_preview.dartBorder radius 16, nessun altro intervento (widget third-party)
flow_event_image.dartNessun intervento

Sezione 4 — Livelli di Intervento per Schermata

Inventario completo (~45 file)

Livello 0 — Tema sufficiente, nessun intervento manuale (10 file)

FileMotivo
forgot_password_screen.dartSolo input + button → tema
privacy_settings_screen.dartToggle + ListTile → tema
notifications_settings_screen.dartToggle + ListTile → tema
about_screen.dartCentered layout, nessun componente interattivo
log_viewer_screen.dartDebug screen, non user-facing
manage_event_types_screen.dartAdmin screen, non user-facing
create_review_screen.dartStar picker + button → tema
new_chat_screen.dartInput + ListTile → tema
message_requests_screen.dartAppBar + lista → tema
main_screen.dartShell di navigazione, nessun contenuto UI — solo il PageView/BottomNav

Livello 1 — Tocco leggero (19 file)

Rimuovere colori hardcoded, aggiungere FlowSectionHeader/FlowListTile, lasciare layout invariato.

FileIntervento
settings_screen.dartFlowListTile × N, rimuovere Card manuale, FlowSectionHeader per sezioni
help_support_screen.dartFlowListTile × 3, rimuovere emoji header
notifications_screen.dartFlowSectionHeader “Richieste” / “Tutte”, avatar border coral
search_screen.dartChip → ChipTheme, rimuovere colori hardcoded
friends_screen.dartFlowSectionHeader per sezioni, rimuovere colori hardcoded
chats_screen.dartFlowSectionHeader, chat_tile già aggiornato da shared-widgets
my_events_screen.dartTabBar → tema, empty state icon coral
social_list_screen.dartTabBar → tema, FlowListTile
chat_details_screen.dartFlowListTile per opzioni, AppBar automatica
create_group_screen.dartInput → tema, button → tema
my_crews_screen.dartFlowSectionHeader, empty state coral
verification_request_screen.dartInput → tema, dropdown coral accent, button → tema
registration_wizard_screen.dartRimuovere tutti i OutlineInputBorder() hardcoded → tema
profile_setup_screen.dartInput → tema, interest chip → ChipTheme, button → tema
welcome_screen.dartTypography serrata Inter, bottoni → tema
login_screen.dartInput → tema, button → tema
edit_profile_screen.dartInput → tema, avatar ring coral, button → tema
create_squad_screen.dartInput → tema (già rinominato Crew), chip → ChipTheme, button → tema
create_campaign_screen.dartInput → tema, button → tema

Livello 2 — Redesign mirato (15 file)

Layout personalizzato con i token del design system. Mantenere struttura funzionale.

event_details_screen.dart

  • Hero image full-bleed top, overlay gradiente inkBlack to-top
  • Info section su warmCanvas, titolo 22px w900, data coral eyebrow
  • CTA bottom bar sticky: coral pill “Partecipa” + outline “Salva”
  • FlowSectionHeader per “Chi partecipa”, “Dove”, “Altro”
  • Map preview radius 16
  • Attendees facepile con “+N” coral

events_screen.dart + event_discovery_screen.dart

  • AppBar automatica
  • Filtri chip → ChipTheme
  • TabBar → tema coral underline
  • Empty state: icona 48px coral/20% bg radius 16, testo inkBlack

profile_screen.dart + public_profile_screen.dart

  • Header: avatar 80px ring coral 2px, nome 22px w900, handle 14px grey
  • FlowStatCard × 3 in Row (Follower, Following, Karma)
  • TabBar coral underline: “Prossimi” / “Passati”
  • Bottoni: coral “Segui” + outline “Messaggio”

chat_screen.dart

  • AppBar: avatar 36px + nome w800 + dot online mintGreen/red
  • Bubble outgoing: coral (#FF5E57) → FF8A80 gradient, bianco, radius 18/4/18/18
  • Bubble incoming: warmCanvas, inkBlack, radius 4/18/18/18
  • Input bar: warmCanvas fill radius 24, attach inkBlack, mic inkBlack, send coral
  • Timestamp: 11px grey400

ticket_wallet_screen.dart

  • TabBar coral underline
  • Ticket card: gradiente vibe-color top → darker bottom (come crew card), testo bianco
  • QR block: bianco su sfondo card, radius 12, padding 16

vendor_dashboard_screen.dart + pr_dashboard_screen.dart

  • Header pocket inkBlack: stat principale grande (coral) + label grey
  • Grid FlowStatCard × N
  • FlowSectionHeader per sezioni

create_event_screen.dart

  • Step indicator: pallini coral (attivo) / grey (inattivo), linea fra pallini
  • Input → tema
  • Step header: FlowSectionHeader

create_crew_screen.dart

  • Step indicator coral
  • Tag selector: chip → ChipTheme

radar_map_screen.dart (solo chrome, non mappa)

  • FAB zoom/rientro: Theme.of(context).colorScheme.surface (automatico)
  • Filter chip bar → ChipTheme
  • _MapEventCard già stilato

map_explore_view.dart

  • Stessa politica: solo chrome UI, non toccare flutter_map internals

timeline_discovery_view.dart

  • Stessa politica di map_explore_view.dart: solo chrome UI (chip, header)

crew_matchmaking_wizard.dart

  • Step indicator coral (come create_event_screen.dart)
  • Card per ogni crew suggerita → CardTheme
  • CTA button → tema coral

Regole Trasversali

  1. Mai Colors.blue o colori Material default — sempre token AppTheme
  2. Mai AppBar con elevation > 0
  3. Input: filled: true + OutlineInputBorder(borderSide: BorderSide.none) + coral al focus
  4. Border radius: 14 input, 20 card, 28 bottom sheet — usare AppTheme.radius14/20/28. Eccezioni permesse senza token: radius 10 per icon background in FlowListTile, radius 18/4 per chat bubble (valori specifici della forma, non generalizzabili).
  5. Font weight titoli: w800 (subheading) o w900 (titolo principale) — mai FontWeight.bold
  6. Letter spacing titoli ≥ 16px: -0.3 o più stretto
  7. Coral su sfondo chiaro: solo per icone, indicatori, CTA — non per testo < 13px
  8. Dark mode: warmCanvas → Color(0xFF1A1A1A), white card → Color(0xFF1E1E1E), input fill → Color(0xFF2A2A2A). Sempre gestire entrambi i rami isDark.

Branch Strategy e Ordine di Merge

Sequenza obbligatoria (ogni step dipende dal precedente):

develop
  └── feat/restyling-theme          ← Step 1: AppTheme + radius14/28 + 3 nuovi widget
        └── feat/restyling-shared-widgets   ← Step 2: aggiornamento widget esistenti
              ├── feat/restyling-l1          ← Step 3: 19 schermate livello leggero
              └── feat/restyling-l2-events   ← Step 4a (parallelo con 4b/4c/4d)
              └── feat/restyling-l2-profile  ← Step 4b
              └── feat/restyling-l2-chat     ← Step 4c
              └── feat/restyling-l2-misc     ← Step 4d (dashboard, ticket, map, crew)

Steps 4a/4b/4c/4d possono essere lavorati in parallelo e mergiati in qualsiasi ordine, purché Step 3 sia già su develop.


File Totali Coinvolti

CategoriaFileNote
Theme + constants1app_theme.dart
Nuovi widget condivisi3section header, list tile, stat card
Widget condivisi esistenti~10vedi sezione 3 (6 con intervento, 4 già fatti/no-op)
Livello 010nessun tocco manuale
Livello 119tocco leggero
Livello 215redesign mirato
Totale file modificati~38 file1 + 3 + 10 + 0(L0) + 19 + 15 — escluso L0