Skip to content

Flutter App

Tech Stack

TechnologiaZastosowanie
Flutter / DartFramework UI
iOS (priorytet) + AndroidPlatformy docelowe
RiverpodState management
GoRouterNawigacja / routing
DioHTTP client (API)
WidgetbookStorybook dla komponentów
Flutter Secure StorageBezpieczne przechowywanie tokenów
flutter_testTesty widgetów i integracyjne

Struktura Projektu

app/
├── lib/
│   ├── main.dart                  # Entry point
│   ├── app.dart                   # MaterialApp + GoRouter
│   │
│   ├── core/                      # Rdzeń aplikacji
│   │   ├── theme/                 # Design system (kolory, typografia, spacing)
│   │   ├── router/                # Konfiguracja GoRouter
│   │   ├── network/               # Dio setup, interceptory, error handling
│   │   └── constants/             # Stałe (API URLs, konfiguracja)
│   │
│   ├── features/                  # Funkcjonalności (feature-first)
│   │   ├── auth/                  # Rejestracja, logowanie, kody
│   │   │   ├── data/              # Repository, modele, API calls
│   │   │   ├── domain/            # Logika biznesowa
│   │   │   └── presentation/      # Ekrany, widgety
│   │   │
│   │   ├── globe/                 # Globus z krajami
│   │   ├── map/                   # Mapa kraju z lokacjami
│   │   ├── location/              # Lokacja z zadaniami (Netflix-style)
│   │   ├── cosmos/                # Planety (custom worlds)
│   │   ├── spaceship/             # Statek (awatar, todo)
│   │   ├── profile/               # Profil, ustawienia
│   │   └── parent/                # Ekrany rodzica (powiązanie, kody)
│   │
│   ├── shared/                    # Współdzielone elementy
│   │   ├── widgets/               # Reużywalne widgety
│   │   ├── models/                # Współdzielone modele danych
│   │   └── providers/             # Globalne providery Riverpod
│   │
│   └── gen/                       # Generowany kod (build_runner)

├── test/                          # Testy
│   ├── unit/
│   ├── widget/
│   └── integration/

├── widgetbook/                    # Widgetbook (storybook)
│   └── lib/
│       └── main.dart

├── ios/                           # Konfiguracja iOS
├── android/                       # Konfiguracja Android
└── pubspec.yaml                   # Zależności

Architektura

Feature-first

Każda funkcjonalność ma własny katalog z podziałem na warstwy:

feature/
├── data/              # Źródła danych
│   ├── models/        # Modele danych (fromJson, toJson)
│   ├── repositories/  # Implementacje repozytoriów
│   └── sources/       # API calls (Dio)

├── domain/            # Logika biznesowa (opcjonalnie)
│   └── usecases/      # Use cases

└── presentation/      # UI
    ├── screens/        # Pełne ekrany
    ├── widgets/        # Widgety specyficzne dla feature
    └── providers/      # Riverpod providers dla feature

State Management (Riverpod)

dart
// Provider dla listy światów
final worldsProvider = FutureProvider<List<World>>((ref) {
  final repository = ref.watch(worldRepositoryProvider);
  return repository.getWorlds();
});

// Provider dla aktualnie wybranego świata
final selectedWorldProvider = StateProvider<World?>((ref) => null);

// Ekran korzystający z providera
class GlobeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final worlds = ref.watch(worldsProvider);
    return worlds.when(
      data: (data) => GlobeView(worlds: data),
      loading: () => LoadingIndicator(),
      error: (err, stack) => ErrorView(error: err),
    );
  }
}

Routing (GoRouter)

dart
final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (_, __) => GlobeScreen()),
    GoRoute(path: '/world/:id', builder: (_, state) =>
      MapScreen(worldId: state.pathParameters['id']!)),
    GoRoute(path: '/location/:id', builder: (_, state) =>
      LocationScreen(locationId: state.pathParameters['id']!)),
    GoRoute(path: '/cosmos', builder: (_, __) => CosmosScreen()),
    GoRoute(path: '/spaceship', builder: (_, __) => SpaceshipScreen()),
    GoRoute(path: '/profile', builder: (_, __) => ProfileScreen()),
  ],
);

Kluczowe Ekrany

1. Globus

Ekran główny po zalogowaniu. Uproszczona mapa 2D (ilustrowana) z pinezkami krajów do eksploracji.

  • Przesuwanie (pan) i zoom mapy
  • Piny na krajach z dostępnymi lokacjami
  • Przejście do mapy kraju po tapnięciu

Uwaga: Globus 3D planowany na przyszłą iterację.

2. Mapa Kraju

Mapa z lokacjami (camp, biblioteka, fabryka, boisko...) rozmieszczonymi na niej.

  • Piny na mapie oznaczające lokacje
  • Status lokacji (odblokowana / zablokowana / ukończona)
  • Przejście do lokacji po tapnięciu

3. Lokacja (Netflix-style)

Lista zadań w stylu Netflix — przewijalne kafelki z miniaturami.

  • Sekcje: video, quizy, podcasty, gry
  • Kafelki z miniaturami, tytułami, ikonami typu zadania
  • Filtrowanie po grupie wiekowej
  • Status zadania (nowe / w trakcie / ukończone)

4. Kosmos (Planety)

Widok planet (custom worlds) stworzonych przez rodziców / szkoły.

  • Planety otwarte (open to join)
  • Planety przypisane (assigned)
  • Przejście do mapy planety

5. Statek

Osobista strefa dziecka.

  • Pokój z awatarem (personalizacja)
  • Todo lista (zadania od rodzica + własne)
  • Odznaki i osiągnięcia

Integracja z Manager-Content

Flutter app komunikuje się z manager-content (Go server) przez endpointy /app/* (REST/JSON). Manager-content przechowuje dane i serwuje je dedykowanymi endpointami dla aplikacji mobilnej.

HTTP Client (Dio)

dart
class ApiClient {
  late final Dio _dio;

  ApiClient() {
    _dio = Dio(BaseOptions(
      baseUrl: AppConstants.managerContentUrl, // URL manager-content
      connectTimeout: Duration(seconds: 10),
      receiveTimeout: Duration(seconds: 10),
    ));
    _dio.interceptors.add(AuthInterceptor());
    _dio.interceptors.add(LogInterceptor());
  }
}

Wzorzec Repository

dart
abstract class WorldRepository {
  Future<List<World>> getWorlds();
  Future<World> getWorld(String id);
  Future<List<Location>> getLocations(String worldId);
}

class WorldRepositoryImpl implements WorldRepository {
  final ApiClient _client;

  @override
  Future<List<World>> getWorlds() async {
    final response = await _client.get('/app/worlds');
    return (response.data as List)
        .map((json) => World.fromJson(json))
        .toList();
  }
}

Design System

Flutter app ma własny design system oparty na:

  • Tokeny — kolory, typografia, spacing, border radius
  • Komponenty — przyciski, karty, inputy, nawigacja
  • Widgetbook — interaktywny katalog komponentów

Szczegóły: Design Systemy

Internacjonalizacja (i18n)

Na start: polski + angielski.

  • flutter_localizations + pliki ARB (app_pl.arb, app_en.arb)
  • Zmiana języka: ekran ustawień na statku
  • Język zapisywany w profilu użytkownika (PATCH /app/users/me)
  • Wszystkie stringi UI przez AppLocalizations.of(context)

Szczegóły: Decyzje Techniczne — i18n

Offline i Cache

Minimalne wsparcie offline na start:

  • Profil i stats — cache w SharedPreferences (odświeżane przy każdym otwarciu)
  • Quizy — mogą być wykonywane offline, sync po reconnect
  • Media (video, audio) — wymagają połączenia, brak offline cache
  • Todo lista — cache lokalny, optimistic updates

Szczegóły: Decyzje Techniczne — Offline

Push Notifications

Firebase Cloud Messaging (FCM) + APNs:

  • firebase_messaging package
  • Typy: streak reminder, nowe zadania, parent approve, level up / badge
  • Ikona dzwonka w top bar ekranu głównego (mapa)
  • Notification history: nie na start, planowane

Szczegóły: Decyzje Techniczne — Push

Platformy

iOS (priorytet)

  • Minimum iOS 16+
  • Natywne integracje: Push Notifications, Keychain, Sign in with Apple
  • Testowanie na symulatorze i fizycznych urządzeniach

Android

  • Minimum API 24 (Android 7.0)
  • Material Design 3 adaptacje
  • Google Play deployment

Lumos Islands v2 - Dokumentacja Projektowa