Summary

Event series are the brand identity behind recurring events. “La Prosperosa”, “Pullup”, etc. are series — each venue occurrence is a separate events row linked via series_id. Searching for a series shows upcoming dates, past edition photos, and aggregated star ratings.

Files Changed

  • lib/shared/models/event_series_model.dart — new EventSeries model, plain Dart (no codegen), fromRow, copyWith, recurrenceLabel
  • lib/core/network/event_series_api_service.dart — new EventSeriesApiService singleton: searchSeries(query), getSeriesDetail(seriesId)
  • lib/features/events/screens/series_detail_screen.dart — new full detail screen
  • lib/features/events/screens/event_details_screen.dart — added _seriesInfo state, _buildSeriesBanner, parallel series fetch in _loadPastEditions
  • lib/features/search/screens/search_screen.dart_seriesResults state, series query in _performSearch, _buildSeriesTile, _seriesAvatarFallback, series section at top of “Tutto” tab
  • lib/core/router/app_router.dart — new route /series/:seriesIdSeriesDetailScreen
  • lib/shared/models/event_model.dart + .g.dartseriesId field added to Event

Architecture

event_series (DB table)
  ↑  series_id FK
events rows  ←  EventSeriesApiService.getSeriesDetail()
                  ├─ upcoming: events WHERE series_id = X AND start_date > now()
                  └─ past photos: events WHERE series_id = X AND end_date < now()

DB triggers (migration 20260428061017_event_series_and_social_cleanup):

  • trg_sync_series_rating — updates event_series.avg_rating + total_ratings on INSERT/UPDATE/DELETE of event_ratings
  • trg_sync_series_editions — updates event_series.total_editions on events INSERT/UPDATE/DELETE

Key Decisions

  • No codegen for EventSeries — model is only ever hydrated from one source (API service); codegen adds build complexity without benefit here.
  • Parallel fetch in getSeriesDetail — upcoming + past photos fetched via Future.wait, not sequential. ~2× faster on slow connections.
  • Series results first in search — when a query matches a series name exactly (e.g. “La Prosperosa”), users almost always want the series, not individual event instances. Series tiles appear at the top of the “Tutto” tab.
  • Deterministic cover gradient — series without a cover_image_url get a gradient derived from name.codeUnits.fold(% 60). Unique per series, zero randomness, no storage needed.
  • initialSeries pass-throughSeriesDetailScreen accepts a pre-loaded EventSeries? for instant first-paint from search (no blank screen), then reloads full detail in background.

User Flows

  1. From search: type “La Prosperosa” → coral series tile at top of results → tap → SeriesDetailScreen
  2. From event detail: any event with seriesId != null shows a coral “PARTE DELLA SERIE” banner → tap → SeriesDetailScreen
  3. From future wizard: event creation wizard will link an event to an existing series (not yet built — see EventApiService.createEvent TODO)

SeriesDetailScreen Sections

  1. SliverAppBar with cover image or deterministic gradient fallback
  2. Rating row: ★ X.X · N valutazioni · N edizioni chip
  3. Upcoming editions list — date stamp column (day + month + time) + venue name + address + thumbnail
  4. Past edition photos — horizontal scroll, 200×150 thumbnails
  5. About — description text (if set)

Route

/series/:seriesId    →  SeriesDetailScreen(seriesId: ...)

Accessible via context.push('/series/$id') from anywhere.