24 KiB
24 KiB
План разработки 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- холдер для UserScopeAuthService- сервис авторизации (работает с Firebase Auth, Google Sign-In, Telegram)RemoteConfigService- Remote ConfigThemeStateManager- управление темой (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, CrashlyticsStorageModule- 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 дней
Готов к началу разработки! 🚀