mnemo_cards/mnemo_cards_web_v2/PLAN.md
2025-11-11 02:55:41 +03:00

24 KiB
Raw Blame History

План разработки mnemo_cards_web_v2

📋 Описание проекта

Flutter web приложение для изучения языков с использованием yx_scope и yx_state для управления зависимостями и состоянием.

Основные функции:

  • 📚 Изучение языков через карточки и темы
  • 🎮 Мини-игры для запоминания
  • 👤 Профиль пользователя со статистикой
  • 🔐 Авторизация через Google и Telegram
  • 👻 Гостевой режим (без авторизации)

🏗️ Архитектура (yx_scope)

Иерархия скоупов:

AppScope (корневой, всегда существует)
  ├── AuthModule (модуль авторизации)
  ├── RouterModule (модуль навигации)
  ├── AnalyticsModule (модуль аналитики)
  └── UserScope (дочерний скоуп, создается при входе)
       ├── PacksModule (модуль тем/карточек)
       ├── GamesModule (модуль игр)
       └── ProfileModule (модуль профиля)

Детальное описание скоупов:

AppScope

Жизненный цикл: весь запуск приложения

Зависимости:

  • Dio - HTTP клиент
  • GoRouter - роутинг приложения
  • FirebaseApp - Firebase инстанс
  • FirebaseAnalytics - аналитика
  • SharedPreferences - локальное хранилище
  • UserScopeHolder - холдер для UserScope
  • AuthService - сервис авторизации (работает с Firebase Auth, Google Sign-In, Telegram)
  • RemoteConfigService - Remote Config
  • ThemeStateManager - управление темой (yx_state)

Интерфейс:

abstract class AppScope implements Scope {
  GoRouter get router;
  FirebaseAnalytics get analytics;
  AuthService get authService;
  UserScopeHolder get userScopeHolder;
  ThemeStateManager get themeManager;
  SharedPreferences get sharedPreferences;
}

Модули:

  • AuthModule - Google/Telegram авторизация
  • RouterModule - настройка роутинга
  • AnalyticsModule - Firebase Analytics, Crashlytics
  • StorageModule - SharedPreferences, SecureStorage

UserScope

Жизненный цикл: от входа пользователя до выхода (или с начала для гостя)

Зависимости:

  • UserStateManager - состояние пользователя (yx_state)
  • HttpRepositoryV2 - API запросы с токеном пользователя
  • PackManager - управление темами/карточками
  • GamesManager - управление играми
  • FavoriteCardsManager - избранные карточки
  • TestStateManager - состояние тестов
  • StatisticsService - статистика пользователя

Интерфейс:

abstract class UserScope implements Scope {
  UserStateManager get userStateManager;
  PackManager get packManager;
  GamesManager get gamesManager;
  StatisticsService get statisticsService;
}

// Интерфейс для родителя (AppScope должен его реализовать)
abstract class UserScopeParent implements Scope {
  GoRouter get router;
  FirebaseAnalytics get analytics;
  AuthService get authService;
  SharedPreferences get sharedPreferences;
}

Модули:

  • PacksModule - работа с темами и карточками
  • GamesModule - загрузка и запуск игр
  • ProfileModule - статистика, настройки профиля

Типы пользователей:

  • Гость - UserScope создается без авторизации, UserDto = null
  • Авторизованный - UserScope с UserDto после логина

🎨 UI Структура (3 вкладки)

1. Темы (HomePage)

  • Список доступных тем (CardPackDto)
  • Карточки тем с превью
  • Переход к просмотру карточек темы
  • Фильтры и поиск

2. Игры (GamesPage)

  • Список доступных игр (GameDto)
  • Кнопки запуска игр
  • Интеграция с WebView играми
  • Прогресс по играм

3. Профиль (ProfilePage)

  • Статистика изучения
  • Кнопка входа/выхода
  • Настройки (тема, звук, etc)
  • Промокоды и подписка

📦 State Management (yx_state)

State Managers:

1. ThemeStateManager (в AppScope)

class ThemeState {
  final ThemeMode mode;
  const ThemeState(this.mode);
}

class ThemeStateManager extends StateManager<ThemeState> {
  ThemeStateManager(SharedPreferences prefs) 
      : super(ThemeState(_loadFromPrefs(prefs)));
  
  void toggleTheme() => handle((emit) async {
    final newMode = state.mode == ThemeMode.light 
        ? ThemeMode.dark 
        : ThemeMode.light;
    emit(ThemeState(newMode));
    await _saveToPrefs(newMode);
  });
}

2. UserStateManager (в UserScope)

@freezed
class UserState with _$UserState {
  const factory UserState.guest() = _Guest;
  const factory UserState.authenticated({
    required UserDto user,
  }) = _Authenticated;
  const factory UserState.loading() = _Loading;
}

class UserStateManager extends StateManager<UserState> {
  UserStateManager() : super(const UserState.guest());
  
  void setUser(UserDto user) => handle((emit) async {
    emit(UserState.authenticated(user: user));
  });
  
  void logout() => handle((emit) async {
    emit(const UserState.guest());
  });
}

3. PacksStateManager (в UserScope)

@freezed
class PacksState with _$PacksState {
  const factory PacksState.loading() = _Loading;
  const factory PacksState.loaded(List<CardPackDto> packs) = _Loaded;
  const factory PacksState.error(String message) = _Error;
}

class PacksStateManager extends StateManager<PacksState> {
  final HttpRepositoryV2 _repository;
  
  PacksStateManager(this._repository) 
      : super(const PacksState.loading());
  
  Future<void> loadPacks() => handle((emit) async {
    emit(const PacksState.loading());
    try {
      final packs = await _repository.getPacks();
      emit(PacksState.loaded(packs));
    } catch (e) {
      emit(PacksState.error(e.toString()));
    }
  });
}

4. GamesStateManager (в UserScope)

@freezed
class GamesState with _$GamesState {
  const factory GamesState.loading() = _Loading;
  const factory GamesState.loaded(List<GameDto> games) = _Loaded;
  const factory GamesState.error(String message) = _Error;
}

🔐 Авторизация

Процесс авторизации:

Гостевой режим:

// При запуске приложения
void main() async {
  final appScopeHolder = AppScopeHolder();
  await appScopeHolder.create();
  
  // Создаем UserScope для гостя сразу
  final appScope = appScopeHolder.scope!;
  await appScope.userScopeHolder.create();
  
  runApp(App(appScopeHolder: appScopeHolder));
}

Google авторизация:

class AuthService {
  final GoogleSignIn _googleSignIn;
  final HttpRepositoryV2 _repository;
  
  Future<UserDto> loginWithGoogle() async {
    final account = await _googleSignIn.signIn();
    final auth = await account.authentication;
    
    // Отправляем токен на backend
    final (user, token) = await _repository.createOrGetUser(
      auth.idToken!,
      ExternalIdType.google,
      account.email,
      account.displayName,
    );
    
    return user;
  }
}

Telegram авторизация:

class AuthService {
  Future<UserDto> loginWithTelegram(TelegramWebAppData data) async {
    final (user, token) = await _repository.createOrGetUser(
      data.user.id.toString(),
      ExternalIdType.telegram,
      'no-email-tg',
      data.user.username,
    );
    
    return user;
  }
}

Переключение между гостем и авторизованным:

// В AuthPage после успешной авторизации
final user = await authService.loginWithGoogle();
userScopeHolder.scope!.userStateManager.setUser(user);

// При выходе
await userStateManager.logout();
// UserScope НЕ удаляется, просто переходит в guest режим

🚦 Навигация (go_router)

Структура роутов:

final router = GoRouter(
  initialLocation: '/home',
  routes: [
    ShellRoute(
      builder: (context, state, child) => MainShell(child: child),
      routes: [
        GoRoute(
          path: '/home',
          builder: (context, state) => const HomePage(),
        ),
        GoRoute(
          path: '/games',
          builder: (context, state) => const GamesPage(),
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfilePage(),
        ),
      ],
    ),
    GoRoute(
      path: '/auth',
      builder: (context, state) => const AuthPage(),
    ),
    GoRoute(
      path: '/pack/:id',
      builder: (context, state) => PackDetailsPage(
        packId: state.pathParameters['id']!,
      ),
    ),
    GoRoute(
      path: '/test/:packId',
      builder: (context, state) => TestPage(
        packId: state.pathParameters['packId']!,
      ),
    ),
  ],
);

MainShell - Bottom Navigation:

class MainShell extends StatelessWidget {
  final Widget child;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: child,
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Темы'),
          BottomNavigationBarItem(icon: Icon(Icons.games), label: 'Игры'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
        ],
        onTap: (index) {
          switch (index) {
            case 0: context.go('/home');
            case 1: context.go('/games');
            case 2: context.go('/profile');
          }
        },
      ),
    );
  }
}

📚 Зависимости (pubspec.yaml)

Обновленный pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  # YX Framework
  yx_scope: ^1.1.2
  yx_scope_flutter: ^1.1.2
  yx_state: ^1.0.0
  yx_state_flutter: ^1.0.0

  # Общие пакеты проекта
  mnemo_cards_common:
    path: ../mnemo_cards_common
  mnemo_cards_frontend_common:
    path: ../mnemo_cards_frontend_common

  # Роутинг
  go_router: ^14.2.0

  # HTTP
  dio: ^5.3.3

  # State Management helpers
  rxdart: ^0.28.0
  
  # Firebase
  firebase_core: ^3.3.0
  firebase_auth: ^5.3.1
  firebase_analytics: ^11.2.1
  firebase_crashlytics: ^4.0.4
  firebase_remote_config: ^5.4.7

  # Авторизация
  google_sign_in: ^6.2.1
  # telegram_web_app: ^0.3.1 (если нужно)

  # Code Generation
  freezed_annotation: ^2.4.1
  json_annotation: ^4.7.0

  # Storage
  shared_preferences: ^2.2.3
  flutter_secure_storage: ^9.2.2

  # UI
  flutter_screenutil: ^5.9.0
  shimmer: ^3.0.0
  auto_size_text: ^3.0.0
  fl_chart: ^0.68.0

  # Utils
  universal_image: ^1.0.10
  url_launcher: ^6.2.6
  package_info_plus: ^8.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter

  # Code Generation
  build_runner: ^2.4.13
  freezed: ^2.4.5
  json_serializable: ^6.8.0

  # Linting
  flutter_lints: ^6.0.0
  yx_scope_linter: ^1.1.0
  custom_lint: ^0.5.3

📁 Структура проекта

lib/
├── main.dart                          # Точка входа
├── app.dart                           # Главный виджет приложения
│
├── di/                                # Dependency Injection (yx_scope)
│   ├── app_scope/
│   │   ├── app_scope_container.dart   # Контейнер AppScope
│   │   ├── app_scope_holder.dart      # Холдер AppScope
│   │   ├── app_scope.dart             # Интерфейс AppScope
│   │   └── modules/
│   │       ├── auth_module.dart       # Модуль авторизации
│   │       ├── router_module.dart     # Модуль роутинга
│   │       ├── analytics_module.dart  # Модуль аналитики
│   │       └── storage_module.dart    # Модуль хранилища
│   │
│   └── user_scope/
│       ├── user_scope_container.dart  # Контейнер UserScope
│       ├── user_scope_holder.dart     # Холдер UserScope
│       ├── user_scope.dart            # Интерфейс UserScope
│       └── modules/
│           ├── packs_module.dart      # Модуль тем/карточек
│           ├── games_module.dart      # Модуль игр
│           └── profile_module.dart    # Модуль профиля
│
├── domain/                            # Бизнес-логика
│   ├── models/                        # Модели (из mnemo_cards_common)
│   ├── services/
│   │   ├── auth_service.dart         # Сервис авторизации
│   │   ├── http_repository_v2.dart   # HTTP клиент (Bearer OAuth2)
│   │   ├── pack_manager.dart         # Менеджер тем
│   │   ├── games_manager.dart        # Менеджер игр
│   │   └── statistics_service.dart   # Сервис статистики
│   │
│   └── state/                         # State Managers (yx_state)
│       ├── theme_state_manager.dart
│       ├── user_state_manager.dart
│       ├── packs_state_manager.dart
│       └── games_state_manager.dart
│
├── presentation/                      # UI слой
│   ├── router/
│   │   └── app_router.dart           # Конфигурация go_router
│   │
│   ├── pages/
│   │   ├── home/
│   │   │   ├── home_page.dart        # Страница "Темы"
│   │   │   └── widgets/
│   │   │
│   │   ├── games/
│   │   │   ├── games_page.dart       # Страница "Игры"
│   │   │   └── widgets/
│   │   │
│   │   ├── profile/
│   │   │   ├── profile_page.dart     # Страница "Профиль"
│   │   │   └── widgets/
│   │   │
│   │   ├── auth/
│   │   │   └── auth_page.dart        # Страница авторизации
│   │   │
│   │   ├── pack_details/
│   │   │   └── pack_details_page.dart # Детали темы
│   │   │
│   │   └── test/
│   │       └── test_page.dart        # Страница теста
│   │
│   ├── widgets/                       # Общие виджеты
│   │   ├── app_bar.dart
│   │   ├── bottom_nav_bar.dart
│   │   ├── pack_card.dart
│   │   ├── game_card.dart
│   │   └── statistics_chart.dart
│   │
│   └── theme/
│       └── app_theme.dart            # Темы приложения
│
└── utils/                             # Утилиты
    ├── logger.dart
    ├── extensions.dart
    └── constants.dart

🔄 Жизненный цикл приложения

1. Запуск приложения:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Инициализация Firebase
  await Firebase.initializeApp();
  
  // Создание AppScope
  final appScopeHolder = AppScopeHolder();
  await appScopeHolder.create();
  
  // Создание UserScope для гостя
  final appScope = appScopeHolder.scope!;
  await appScope.userScopeHolder.create();
  
  runApp(App(appScopeHolder: appScopeHolder));
}

2. Структура App виджета:

class App extends StatelessWidget {
  final AppScopeHolder appScopeHolder;
  
  const App({required this.appScopeHolder, super.key});

  @override
  Widget build(BuildContext context) {
    return ScopeProvider<AppScopeContainer>(
      holder: appScopeHolder,
      child: ScopeBuilder<AppScopeContainer>.withPlaceholder(
        builder: (context, appScope) {
          // Вложенный ScopeProvider для UserScope
          return ScopeProvider<UserScopeContainer>(
            holder: appScope.userScopeHolder,
            child: ScopeBuilder<UserScopeContainer>.withPlaceholder(
              builder: (context, userScope) {
                return StateManagerBuilder<ThemeState>(
                  stateManager: appScope.themeManager,
                  builder: (context, themeState) {
                    return MaterialApp.router(
                      routerConfig: appScope.router,
                      theme: AppTheme.light,
                      darkTheme: AppTheme.dark,
                      themeMode: themeState.mode,
                    );
                  },
                );
              },
              placeholder: const Center(
                child: CircularProgressIndicator(),
              ),
            ),
          );
        },
        placeholder: const Center(
          child: CircularProgressIndicator(),
        ),
      ),
    );
  }
}

3. Авторизация:

// В AuthPage
class AuthPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopeBuilder<AppScopeContainer>(
      builder: (context, appScope) {
        return ScopeBuilder<UserScopeContainer>(
          builder: (context, userScope) {
            return Column(
              children: [
                ElevatedButton(
                  onPressed: () async {
                    // Логин через Google
                    final user = await appScope.authService
                        .loginWithGoogle();
                    
                    // Обновляем состояние пользователя
                    userScope.userStateManager.setUser(user);
                    
                    // Роутер автоматически перенаправит на home
                    context.go('/home');
                  },
                  child: Text('Войти через Google'),
                ),
                ElevatedButton(
                  onPressed: () async {
                    // Логин через Telegram
                    final user = await appScope.authService
                        .loginWithTelegram();
                    userScope.userStateManager.setUser(user);
                    context.go('/home');
                  },
                  child: Text('Войти через Telegram'),
                ),
                TextButton(
                  onPressed: () {
                    // Войти как гость (UserScope уже создан)
                    context.go('/home');
                  },
                  child: Text('Продолжить как гость'),
                ),
              ],
            );
          },
        );
      },
    );
  }
}

4. Использование в страницах:

// HomePage
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopeBuilder<UserScopeContainer>(
      builder: (context, userScope) {
        return StateManagerBuilder<PacksState>(
          stateManager: userScope.packsStateManager,
          builder: (context, state) {
            return state.when(
              loading: () => CircularProgressIndicator(),
              loaded: (packs) => ListView.builder(
                itemCount: packs.length,
                itemBuilder: (context, index) {
                  return PackCard(pack: packs[index]);
                },
              ),
              error: (message) => Text('Error: $message'),
            );
          },
        );
      },
    );
  }
}

🎯 Этапы разработки

Этап 1: Основа (1-2 дня)

  • Создать структуру проекта
  • Настроить pubspec.yaml с зависимостями
  • Создать AppScope (контейнер, холдер, интерфейс)
  • Создать UserScope (контейнер, холдер, интерфейс)
  • Настроить Firebase
  • Реализовать ThemeStateManager
  • Настроить go_router с базовыми роутами
  • Создать главный App виджет с ScopeProvider'ами

Этап 2: Авторизация (1-2 дня)

  • Реализовать AuthService (Google, Telegram)
  • Создать UserStateManager
  • Реализовать HttpRepositoryV2 с токенами
  • Создать AuthPage
  • Реализовать гостевой режим
  • Настроить роутинг для auth/guest

Этап 3: Темы (2-3 дня)

  • Создать PacksModule в UserScope
  • Реализовать PacksStateManager
  • Создать PackManager
  • Реализовать HomePage с списком тем
  • Создать PackDetailsPage
  • Реализовать TestPage
  • Добавить избранное

Этап 4: Игры (1-2 дня)

  • Создать GamesModule в UserScope
  • Реализовать GamesStateManager
  • Создать GamesManager
  • Реализовать GamesPage
  • Интегрировать WebView для игр

Этап 5: Профиль (1-2 дня)

  • Создать ProfileModule в UserScope
  • Реализовать StatisticsService
  • Создать ProfilePage
  • Добавить графики статистики (fl_chart)
  • Реализовать настройки
  • Добавить промокоды и подписку

Этап 6: Полировка (1-2 дня)

  • Добавить анимации и переходы
  • Оптимизировать производительность
  • Добавить обработку ошибок
  • Добавить loading states
  • Протестировать все flow'ы
  • Адаптивная верстка для разных экранов

Этап 7: Тестирование и деплой (1 день)

  • Тестирование авторизации
  • Тестирование всех страниц
  • Проверка работы с backend
  • Build для production
  • Деплой на хостинг

📝 Примечания

Преимущества yx_scope:

  • Compile-safe доступ к зависимостям
  • Четкий жизненный цикл скоупов
  • Отсутствие Service Locator паттерна
  • Простая иерархия и изоляция
  • Flutter-friendly интеграция

Преимущества yx_state:

  • Простой и понятный API
  • Встроенная обработка ошибок
  • Интеграция с Flutter виджетами
  • Поддержка rxdart transformers

Важные моменты:

  • UserScope создается сразу при запуске (для гостя)
  • UserScope НЕ удаляется при logout, только меняется состояние
  • AuthService находится в AppScope (доступен всегда)
  • HttpRepositoryV2 в UserScope получает токен из AuthService
  • Все State Managers используют freezed для типобезопасности

🔗 Ссылки на документацию


Общая оценка времени разработки: 8-14 дней

Готов к началу разработки! 🚀