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

780 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План разработки 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)
**Интерфейс:**
```dart
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` - статистика пользователя
**Интерфейс:**
```dart
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)
```dart
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)
```dart
@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)
```dart
@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)
```dart
@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;
}
```
---
## 🔐 Авторизация
### Процесс авторизации:
#### **Гостевой режим:**
```dart
// При запуске приложения
void main() async {
final appScopeHolder = AppScopeHolder();
await appScopeHolder.create();
// Создаем UserScope для гостя сразу
final appScope = appScopeHolder.scope!;
await appScope.userScopeHolder.create();
runApp(App(appScopeHolder: appScopeHolder));
}
```
#### **Google авторизация:**
```dart
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 авторизация:**
```dart
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;
}
}
```
### Переключение между гостем и авторизованным:
```dart
// В AuthPage после успешной авторизации
final user = await authService.loginWithGoogle();
userScopeHolder.scope!.userStateManager.setUser(user);
// При выходе
await userStateManager.logout();
// UserScope НЕ удаляется, просто переходит в guest режим
```
---
## 🚦 Навигация (go_router)
### Структура роутов:
```dart
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:
```dart
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:
```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. Запуск приложения:
```dart
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 виджета:
```dart
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. Авторизация:
```dart
// В 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. Использование в страницах:
```dart
// 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 дня)**
- [x] Создать структуру проекта
- [ ] Настроить 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 для типобезопасности
---
## 🔗 Ссылки на документацию
- [yx_scope](../packages/yx/city-services-pub/yx_scope/packages/yx_scope/README.md)
- [yx_scope_flutter](../packages/yx/city-services-pub/yx_scope/packages/yx_scope_flutter/README.md)
- [yx_state](../packages/yx/city-services-pub/yx_state/packages/yx_state/README.md)
- [go_router](https://pub.dev/packages/go_router)
- [freezed](https://pub.dev/packages/freezed)
---
**Общая оценка времени разработки: 8-14 дней**
Готов к началу разработки! 🚀