Skip to content

Ekran — Lokacja (Netflix-style)

Przegląd

Ekran lokacji wyświetla zadania w stylu Netflix — header z obrazem i nazwą lokacji, a pod nim horyzontalnie przewijane rzędy kafelków zadań pogrupowane wg typu (video, quiz, podcast, gra, kreatywne). Obsługuje filtrowanie po grupie wiekowej i paginację kursorową (infinite scroll).

Routing (GoRouter)

dart
GoRoute(
  path: '/location/:locationId',
  builder: (context, state) => LocationScreen(
    locationId: state.pathParameters['locationId']!,
  ),
),

Riverpod Providers

dart
// Dane lokacji (header + zadania pogrupowane wg typu)
final locationDetailProvider = FutureProvider.family<LocationDetail, String>((ref, locationId) {
  return ref.watch(locationRepositoryProvider).getLocation(locationId);
});

// Filtr grupy wiekowej
final ageGroupFilterProvider = StateProvider<AgeGroup?>((ref) => null);

// Paginacja kursorem per sekcja (typ zadania)
final taskSectionProvider = StateNotifierProvider.family<TaskSectionNotifier, TaskSectionState, TaskSectionParams>(
  (ref, params) => TaskSectionNotifier(
    ref.watch(locationRepositoryProvider),
    params,
  ),
);

// Aktualnie wybrany task (do nawigacji)
final selectedTaskProvider = StateProvider<Task?>((ref) => null);

ASCII Wireframe

┌──────────────────────────────────┐
│  ← Camp Odkrywców                │
├──────────────────────────────────┤
│ ┌────────────────────────────────┤
│ │                                │
│ │  (hero image lokacji)          │
│ │                                │
│ │  Camp Odkrywców                │
│ │  🇵🇱 Polska · 11 zadań         │
│ │  Postęp: ████░░░░ 40%         │
│ │                                │
│ └────────────────────────────────┘
│                                  │
│  [Wszystkie] [6-8] [9-11] [12+]  │
│                                  │
│  🎬 Video                        │
│  ┌──────┐ ┌──────┐ ┌──────┐ ──▶ │
│  │thumb │ │thumb │ │thumb │     │
│  │      │ │      │ │      │     │
│  │Tytuł │ │Tytuł │ │Tytuł │     │
│  │🎬 50XP│ │🎬 30XP│ │🎬 40XP│     │
│  │  ✅   │ │  ○   │ │  ○   │     │
│  └──────┘ └──────┘ └──────┘     │
│                                  │
│  📝 Quizy                        │
│  ┌──────┐ ┌──────┐ ┌──────┐ ──▶ │
│  │thumb │ │thumb │ │thumb │     │
│  │      │ │      │ │      │     │
│  │Tytuł │ │Tytuł │ │Tytuł │     │
│  │📝 40XP│ │📝 35XP│ │📝 45XP│     │
│  │  ✅   │ │  ✅   │ │  ○   │     │
│  └──────┘ └──────┘ └──────┘     │
│                                  │
│  🎧 Podcasty                     │
│  ┌──────┐ ┌──────┐           ──▶ │
│  │thumb │ │thumb │               │
│  │      │ │      │               │
│  │Tytuł │ │Tytuł │               │
│  │🎧 30XP│ │🎧 25XP│               │
│  │  ○   │ │  ○   │               │
│  └──────┘ └──────┘               │
│                                  │
│  🎮 Mini-gry                     │
│  ┌──────┐                    ──▶ │
│  │thumb │                        │
│  │      │                        │
│  │Tytuł │                        │
│  │🎮 60XP│                        │
│  │  ○   │                        │
│  └──────┘                        │
│                                  │
└──────────────────────────────────┘

Kafelek zadania — szczegóły

┌──────────────────┐
│  ┌──────────────┐│
│  │              ││
│  │  thumbnail   ││
│  │              ││
│  │     🎬       ││  ← ikona typu (prawy dolny róg)
│  └──────────────┘│
│                  │
│  Dinozaury i     │
│  ich świat       │
│                  │
│  🎬 50 XP  ✅    │  ← typ, nagroda, status
│                  │
└──────────────────┘

Komponenty

KomponentOpis
LocationHeaderHero image z gradientem, nazwa lokacji, kraj, statystyki, postęp
AgeGroupChipsHorizontal chip bar: "Wszystkie", "6-8", "9-11", "12+"
TaskSectionSekcja z nagłówkiem typu + horizontal ListView zadań
TaskCardKafelek zadania: thumbnail, tytuł, ikona typu, XP, status
TaskTypeIconIkona typu zadania (video, quiz, podcast, game, creative)
CompletionBadgeZnacznik ukończenia: checkmark (done), kółko (not done), w trakcie
XpBadgeBadge z liczbą XP do zdobycia
SectionHeaderNagłówek sekcji z ikoną i nazwą typu zadań
LocationAppBarApp bar z przyciskiem back i nazwą lokacji

Logika

  1. Przy wejściu — pobranie danych lokacji (GET /app/locations/:id)
  2. Zadania pogrupowane wg typu i wyświetlone w horyzontalnych sekcjach
  3. Filtr grupy wiekowej — filtruje zadania w ramach każdej sekcji (wyświetla zadania, których tablica age_groups zawiera wybraną grupę)
  4. Infinite scroll per sekcja — po dotarciu do końca listy horyzontalnej, ładowanie kolejnych (cursor pagination)
  5. Tap na kafelek → nawigacja do /task/:taskId
  6. Pull-to-refresh odświeża całą lokację

Stany kafelka zadania

StatusWygląd
newPuste kółko, pełna opacity
in_progressKółko z fragmentem wypełnienia (np. 50%), żółta ramka
completedZielony checkmark, lekka redukcja opacity (0.8)
lockedKłódka, szary overlay, disabled tap

Ikony typów zadań

TypIkonaKolor
videoFilm/kamera#EF4444 (red)
quizZnaki zapytania#6366F1 (indigo)
podcastSłuchawki#8B5CF6 (violet)
gameGamepad#10B981 (emerald)
creativePędzel#F59E0B (amber)

Endpoint

MetodaEndpointOpis
GET/app/locations/:idDane lokacji + zadania (z paginacją)

Query params:

ParamTypDomyślnieOpis
age_groupstringFiltr grupy wiekowej (np. 4-8, 8-13, 14+). Zwraca zadania, których tablica age_groups zawiera podaną wartość
cursorstringKursor paginacji
limitint20Maks. elementów per typ

Response:

json
{
  "data": {
    "id": "loc-camp",
    "name": "Camp Odkrywców",
    "world_name": "Polska",
    "world_flag": "🇵🇱",
    "header_image_url": "https://...",
    "description": "Twoja pierwsza przygoda!",
    "progress_percent": 40,
    "task_count": 11,
    "completed_count": 4,
    "sections": [
      {
        "type": "video",
        "label": "Video",
        "tasks": [
          {
            "id": "task-v1",
            "title": "Dinozaury i ich świat",
            "type": "video",
            "thumbnail_url": "https://...",
            "xp_reward": 50,
            "status": "completed",
            "age_groups": ["6-8", "8-13"],
            "duration_minutes": 8
          },
          {
            "id": "task-v2",
            "title": "Podróż do kosmosu",
            "type": "video",
            "thumbnail_url": "https://...",
            "xp_reward": 30,
            "status": "new",
            "age_groups": ["8-13"],
            "duration_minutes": 12
          }
        ],
        "next_cursor": "eyJ0IjoidmlkZW8iLCJpZCI6MTB9",
        "has_more": true
      },
      {
        "type": "quiz",
        "label": "Quizy",
        "tasks": [
          {
            "id": "task-q1",
            "title": "Zwierzęta Polski",
            "type": "quiz",
            "thumbnail_url": "https://...",
            "xp_reward": 40,
            "status": "completed",
            "age_groups": ["6-8"],
            "question_count": 10
          }
        ],
        "next_cursor": null,
        "has_more": false
      },
      {
        "type": "podcast",
        "label": "Podcasty",
        "tasks": [],
        "next_cursor": null,
        "has_more": false
      },
      {
        "type": "game",
        "label": "Mini-gry",
        "tasks": [
          {
            "id": "task-g1",
            "title": "Labirynt wiedzy",
            "type": "game",
            "thumbnail_url": "https://...",
            "xp_reward": 60,
            "status": "new",
            "age_groups": ["6-8"]
          }
        ],
        "next_cursor": null,
        "has_more": false
      }
    ]
  }
}

Stany Ekranu

StanWidok
ŁadowanieSkeleton header + skeleton kafelki (shimmer)
DanePełny layout z sekcjami
Pusty (po filtrze)Komunikat "Brak zadań dla tej grupy wiekowej"
BłądErrorView z przyciskiem retry

Uwagi Techniczne

  • Każda sekcja to SizedBox z ListView.builder (horizontal, lazy loading)
  • Infinite scroll per sekcja: ScrollController + addListener → fetch next cursor
  • Filtrowanie po grupie wiekowej — lokalne (dane w pamięci) lub z API (query param)
  • Kafelki mają stały rozmiar 140x200 (thumbnail 140x100 + tekst)
  • Hero animation na thumbnail przy przejściu do ekranu zadania
  • Sekcje z 0 zadań są ukrywane

Lumos Islands v2 - Dokumentacja Projektowa