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
- AppTheme overrides — propagazione automatica attraverso
ThemeData - Widget condivisi nuovi — 3 widget per pattern non coperti dal tema
- Widget condivisi esistenti — aggiornamento dei 31 widget in
lib/shared/widgets/ - 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
| Token | Light | Dark |
|---|---|---|
| Scaffold background | AppTheme.warmCanvas (#F0EEE9) | Color(0xFF1A1A1A) |
| Surface / Card | Colors.white | Color(0xFF1E1E1E) |
| Input fill | AppTheme.warmCanvas | Color(0xFF2A2A2A) |
| Accento primario | AppTheme.electricCoral (#FF5E57) | stesso |
| Testo primario | AppTheme.inkBlack (#121212) | Colors.white |
| Testo secondario | AppTheme.textSecondaryWarm (→ Color(0xFF9E9E9E)) | Colors.grey[400] |
| Divider | Color(0xFFE8E5E0) | Colors.white.withValues(alpha: 0.08) |
| AppBar background | AppTheme.warmCanvas | Color(0xFF1A1A1A) |
| Bottom sheet | Colors.white | Color(0xFF1E1E1E) |
Contrasto nota:
electricCoralsuwarmCanvasè ~3.2:1 — non WCAG AA per testo piccolo. Per label ≤ 12px usareAppTheme.inkBlackcome 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| Componente | Radius |
|---|---|
| TextField / Input | 14 |
| Card standard | 20 |
| Map card compatta | 20 |
| FlowStatCard / Map preview | 16 |
| Bottom sheet (top corners) | 28 |
| Chip / pill | 999 (full) |
| Avatar piccolo | 999 (full) |
Tipografia
| Uso | Size | Weight | LetterSpacing |
|---|---|---|---|
| Titolo schermata / AppBar | 20px | w900 | -0.5 |
| Titolo sezione | 18px | w900 | -0.3 |
| Titolo card | 15–16px | w900 | -0.3 |
| Label uppercase (eyebrow) | 11px | w800 | +0.8 |
| Body | 14px | w500 | 0 |
| Caption / label secondaria | 12px | w600 | 0 |
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:
FlowListTilesostituisce iListTileraw nelle schermate Settings/Help/Social. NON sostituiscechat_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.
| Widget | Intervento |
|---|---|
vibe_filter_bar.dart | Chip → ChipTheme, rimuovere colori hardcoded |
hero_event_card.dart | Già stilato — verificare shadow coral e radius 28 |
minimal_event_card.dart | Già stilato — nessun intervento |
squad_bento_section.dart | Già stilato — nessun intervento |
feed_interruption_widgets.dart | Già stilato — nessun intervento |
chat_tile.dart | Avatar border coral, timestamp 11px grey400, unread dot coral |
crew_summary_card.dart | Card → CardTheme, titolo w900, accenti coral |
squad_detail_sheet.dart | BottomSheetTheme automatico, titolo w900, button → tema |
map_preview.dart | Border radius 16, nessun altro intervento (widget third-party) |
flow_event_image.dart | Nessun intervento |
Sezione 4 — Livelli di Intervento per Schermata
Inventario completo (~45 file)
Livello 0 — Tema sufficiente, nessun intervento manuale (10 file)
| File | Motivo |
|---|---|
forgot_password_screen.dart | Solo input + button → tema |
privacy_settings_screen.dart | Toggle + ListTile → tema |
notifications_settings_screen.dart | Toggle + ListTile → tema |
about_screen.dart | Centered layout, nessun componente interattivo |
log_viewer_screen.dart | Debug screen, non user-facing |
manage_event_types_screen.dart | Admin screen, non user-facing |
create_review_screen.dart | Star picker + button → tema |
new_chat_screen.dart | Input + ListTile → tema |
message_requests_screen.dart | AppBar + lista → tema |
main_screen.dart | Shell di navigazione, nessun contenuto UI — solo il PageView/BottomNav |
Livello 1 — Tocco leggero (19 file)
Rimuovere colori hardcoded, aggiungere FlowSectionHeader/FlowListTile,
lasciare layout invariato.
| File | Intervento |
|---|---|
settings_screen.dart | FlowListTile × N, rimuovere Card manuale, FlowSectionHeader per sezioni |
help_support_screen.dart | FlowListTile × 3, rimuovere emoji header |
notifications_screen.dart | FlowSectionHeader “Richieste” / “Tutte”, avatar border coral |
search_screen.dart | Chip → ChipTheme, rimuovere colori hardcoded |
friends_screen.dart | FlowSectionHeader per sezioni, rimuovere colori hardcoded |
chats_screen.dart | FlowSectionHeader, chat_tile già aggiornato da shared-widgets |
my_events_screen.dart | TabBar → tema, empty state icon coral |
social_list_screen.dart | TabBar → tema, FlowListTile |
chat_details_screen.dart | FlowListTile per opzioni, AppBar automatica |
create_group_screen.dart | Input → tema, button → tema |
my_crews_screen.dart | FlowSectionHeader, empty state coral |
verification_request_screen.dart | Input → tema, dropdown coral accent, button → tema |
registration_wizard_screen.dart | Rimuovere tutti i OutlineInputBorder() hardcoded → tema |
profile_setup_screen.dart | Input → tema, interest chip → ChipTheme, button → tema |
welcome_screen.dart | Typography serrata Inter, bottoni → tema |
login_screen.dart | Input → tema, button → tema |
edit_profile_screen.dart | Input → tema, avatar ring coral, button → tema |
create_squad_screen.dart | Input → tema (già rinominato Crew), chip → ChipTheme, button → tema |
create_campaign_screen.dart | Input → 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”
FlowSectionHeaderper “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 FlowSectionHeaderper 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
_MapEventCardgià 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
- Mai
Colors.blueo colori Material default — sempre token AppTheme - Mai
AppBarconelevation > 0 - Input:
filled: true+OutlineInputBorder(borderSide: BorderSide.none)+ coral al focus - Border radius: 14 input, 20 card, 28 bottom sheet — usare
AppTheme.radius14/20/28. Eccezioni permesse senza token: radius 10 per icon background inFlowListTile, radius 18/4 per chat bubble (valori specifici della forma, non generalizzabili). - Font weight titoli:
w800(subheading) ow900(titolo principale) — maiFontWeight.bold - Letter spacing titoli ≥ 16px:
-0.3o più stretto - Coral su sfondo chiaro: solo per icone, indicatori, CTA — non per testo < 13px
- Dark mode: warmCanvas →
Color(0xFF1A1A1A), white card →Color(0xFF1E1E1E), input fill →Color(0xFF2A2A2A). Sempre gestire entrambi i ramiisDark.
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
| Categoria | File | Note |
|---|---|---|
| Theme + constants | 1 | app_theme.dart |
| Nuovi widget condivisi | 3 | section header, list tile, stat card |
| Widget condivisi esistenti | ~10 | vedi sezione 3 (6 con intervento, 4 già fatti/no-op) |
| Livello 0 | 10 | nessun tocco manuale |
| Livello 1 | 19 | tocco leggero |
| Livello 2 | 15 | redesign mirato |
| Totale file modificati | ~38 file | 1 + 3 + 10 + 0(L0) + 19 + 15 — escluso L0 |