mnemo_cards/mnemo_cards_web_v2/PLAN.md

781 lines
24 KiB
Markdown
Raw Normal View History

2025-11-10 23:55:41 +00:00
# План разработки 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 дней**
Готов к началу разработки! 🚀