781 lines
24 KiB
Markdown
781 lines
24 KiB
Markdown
|
|
# План разработки 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 дней**
|
|||
|
|
|
|||
|
|
Готов к началу разработки! 🚀
|
|||
|
|
|