Feed Widgets — Design System
Direzione: Light Foundation + Cinematic Pockets
Basata sulla ricerca estetica su Airbnb (2025), Eventbrite “The Path”, e Duolingo “Juicy System”. Tutti e tre usano una base chiara con accenti scuri/colorati strategici — lo stesso principio di Flow.
Filosofia
Il feed di Flow alterna tre “toni” visivi:
| Tono | Widget | Effetto |
|---|---|---|
| Cinematic | HeroEventCard | Scuro, pieno schermo, foto + gradiente |
| Elevated | MinimalEventCard (standard) | Bianco puro, shadow Airbnb |
| Energy Aura | SquadBentoSection | Full-bleed con il colore della vibe |
| Dark compact | SocialInviteCard | Ink black, compatto, CTA coral |
| Editorial | VibeQuoteWidget | Testo flottante senza container |
Il ritmo del feed deve mai mettere due pocket scuri consecutivi.
Palette
| Token | Valore | Uso |
|---|---|---|
AppTheme.warmCanvas | #F0EEE9 | Scaffold background (tema chiaro) |
AppTheme.inkBlack | #121212 | Pocket scuri, card dark |
AppTheme.electricCoral | #FF5E57 | Accento primario, date, CTA |
AppTheme.pureWhite | #FFFFFF | Card bianche (MinimalEventCard) |
Lo scaffold del tema chiaro usa warmCanvas (leggermente caldo) invece del bianco puro.
Questo evita l’effetto “blank page” e fa risaltare le card bianche con shadow leggere.
HeroEventCard
Non modificare — è il riferimento cinematico del feed.
- Background scuro con gradiente to-bottom
- Testo bianco, badge coral
SCELTO PER TE 🔥 - Shadow coral (
electricCoral.withValues(alpha: 0.25)) borderRadius: 28, height:400
MinimalEventCard (standard)
Airbnb elevation style.
┌────────────────────────────┐
│ [immagine 16:9] [€ chip]│
├────────────────────────────┤
│ VEN 21 MAR · 22:00 │ ← coral, uppercase, 11px w800
│ Titolo Evento Bold │ ← 18px w900 letterSpacing -0.3
│ 📍 Location [68 ci vanno] │
└────────────────────────────┘
- Background:
Colors.white(light) /Color(0xFF1E1E1E)(dark) - Shadow:
0 6px 20px rgba(0,0,0,0.07)+0 2px 4px rgba(0,0,0,0.03) borderRadius: 24(non 32 — più moderno, meno rotondo)- Data in
electricCoral, nessun box calendario - “N ci vanno” pill:
Color(0xFFF4F4F5)background - Urgency labels:
ORA,OGGI,DOMANI,TRA N GIORNI, oGIO 21 MAR
CrewBentoSection — Energy Aura
Nota terminologica: “Squad” è il termine interno nel codice (file, provider, modelli). Tutta la UI visibile all’utente usa “Crew”.
Ogni crew card usa il colore della vibe come sfondo full-bleed con gradiente verso il basso scuro.
Modello dati
Il modello Squad ha un campo name (mappa alla colonna title nel DB).
Se title è vuoto, Squad.fromJson usa vibe_tag come fallback.
name: (json['title'] as String? ?? '').isNotEmpty
? json['title'] as String
: json['vibe_tag'] as String,Colori vibe
Le vibe conosciute hanno colori fissi (vedi _vibeColors in squad_bento_section.dart).
Per vibe sconosciute, viene usato un hash DJB2 deterministico sulla stringa vibe che
seleziona dalla _fallbackPalette di 10 energy colors. Il risultato è consistente
tra sessioni (a differenza di String.hashCode che Dart randomizza).
// Esempio: "Reggaeton" → hash → sempre lo stesso colore
Color _colorForVibe(String vibe) =>
_vibeColors[vibe] ?? _fallbackPalette[_djb2(vibe) % _fallbackPalette.length];Layout card
┌────────────────────┐
│ ✨ (emoji bianca) │ ← 22px, ShaderMask BlendMode.srcIn
│ Nome Crew │ ← 15px w900 white, maxLines:1
│ [ vibe tag ] │ ← pill translucente white/18%
│ ████░░ 3/5 │ ← progress bar white
│ 3/5 nel Flow │ ← white70
│ │
│ ⏱ 4h rimaste │ ← white55
│ [ UNISCITI ] │ ← chip bianca, testo vibeColor
└────────────────────┘
- Card:
width: 168, padding:14pxtutti i lati,SizedBox(height: 232)per la sezione - Badge “TUA” in top-right per la propria crew
- Action button: bianca con testo vibeColor (
UNISCITI), o translucente perAL COMPLETO/LA MIA CREW - Cerchi decorativi
white.withValues(alpha: 0.10)per profondità - Shadow:
vibeColor.withValues(alpha: 0.35)— ListView ha bottom padding 20 per non tagliare il glow - Emoji bianca via
ShaderMask(blendMode: BlendMode.srcIn)— non usareColorFilter.matrix(i valori offset sono in spazio [0, 255], emoji su Android ignorano il matrix filter)
Stringhe UI → Crew
| Vecchio (codice) | Visibile all’utente |
|---|---|
LA MIA SQUAD | LA MIA CREW |
Lasciare la Squad? | Lasciare la Crew? |
Lancia la tua Squad | Lancia la tua Crew |
Squad in corso | Crew in corso |
SocialInviteCard
Dark compact pocket — rompe il ritmo tra card bianche senza essere imponente come HeroEventCard.
┌──────────────────────────────────┐
│ [🔴][🟣] Il Flow è meglio │
│ insieme │ [Invita]
│ +100 Karma per amico │
└──────────────────────────────────┘
- Background:
AppTheme.inkBlack - Avatar circles sovrapposti con gradient coral + violet
- CTA button coral con shadow coral
- Layout Row (non Column) — compatto, max 80px di altezza
VibeQuoteWidget
Editorial float — non ha container, “galleggia” sullo scaffold.
VIBRA DEL GIORNO ← coral, 11px uppercase tracking
"La musica è l'unica..." ← 22px w900 inkBlack, letterSpacing -0.5
— Flow ← grey500, 13px
- Nessun
ContaineroBoxDecoration - Padding:
horizontal: 24, vertical: 8 - Non uppercase (a differenza del vecchio widget) — più editoriale
Ritmo consigliato del feed
HeroEventCard ← cinema pocket (scuro)
MinimalEventCard × 2 ← elevated white
SquadBentoSection ← energy aura (colorato)
SocialInviteCard ← dark compact
VibeQuoteWidget ← editorial float
MinimalEventCard × 2 ← elevated white
...
Regola: non mettere mai HeroEventCard e SocialInviteCard consecutivi.
Mappa — eventi sopra la bottom nav
RadarMapScreen viene usata in modalità embedded: true dentro il PageView del feed.
Il Scaffold principale ha extendBody: true, il che fa sì che il body si estenda
dietro la bottom navigation bar. I widget Positioned in fondo alla mappa devono
compensare questa sovrapposizione:
final bottomInset = widget.embedded
? kBottomNavigationBarHeight + MediaQuery.of(context).padding.bottom
: MediaQuery.of(context).padding.bottom;
// Carousel eventi: bottom: bottomInset + 24
// Pulsanti zoom/centro: bottom: bottomInset + 260Regola: non usare valori
bottomfissi nella mappa quandoembedded: true. Usare semprebottomInsetcalcolato come sopra.
File coinvolti
| File | Cosa cambia |
|---|---|
lib/core/theme/app_theme.dart | warmCanvas (#F0EEE9), scaffold light = warmCanvas |
lib/shared/widgets/minimal_event_card.dart | _buildStandardCard redesign Airbnb-style |
lib/shared/widgets/squad_bento_section.dart | _SquadCard Energy Aura, crew name+vibe pill, DJB2 hash, ShaderMask emoji |
lib/shared/widgets/feed_interruption_widgets.dart | SocialInviteCard dark, VibeQuoteWidget editorial |
lib/shared/models/squad_model.dart | Campo name da colonna title DB |
lib/features/events/screens/radar_map_screen.dart | bottomInset per extendBody:true |
lib/features/feed/screens/feed_screen.dart | ScrollController feed, refresh button completo |
lib/features/social/screens/create_squad_screen.dart | UI Crew rename |
lib/shared/widgets/squad_detail_sheet.dart | UI Crew rename |