8 Commits

Author SHA1 Message Date
Artem
6f839aea24 refactor(router): улучшить обработку ошибок при создании роутера
refactor(app): изменить AppRoot на StatefulWidget и переместить инициализацию роутера
2025-12-30 12:18:23 +07:00
Artem
617563fb6c refactor(app): перенести роутер ближе к месту инициализации в AppRoot 2025-12-30 12:13:52 +07:00
Artem
75c0ac3285 Merge branch 'main' into feat/перенести-роутер-ближе-к-месту-инициализации 2025-12-30 12:03:18 +07:00
Artem Luzin m
4260c7cc65 fix(app): remove unused parameter from _AppInternal constructor 2025-11-24 17:40:31 +07:00
Artem Luzin m
2595692107 Merge branches 'feat/перенести-роутер-ближе-к-месту-инициализации' and 'feat/перенести-роутер-ближе-к-месту-инициализации' of https://github.com/smmarty/friflex_starter into feat/перенести-роутер-ближе-к-месту-инициализации 2025-11-24 16:38:15 +07:00
Artem Luzin m
c86b4cc0bc feat(app): add mockRouter to _AppInternal for improved testing 2025-11-24 16:38:11 +07:00
Artem Luzin
d46c829959 Update lib/app/app.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 16:23:42 +07:00
Artem Luzin m
84e5f5e869 feat/перенести роутер в место инициализации 2025-11-24 16:18:36 +07:00
16 changed files with 70 additions and 223 deletions

Binary file not shown.

View File

@@ -1,32 +0,0 @@
---
name: flutter_dev
description: Скилл для разработки Flutter-приложений по стандартам компании Friflex. Используйте этот скилл при написании кода, создании новых фич, проведении ревью или настройке архитектуры проекта. Включает правила именования, структуру слоев (data/domain/presentation) и стандарты Git.
---
# Flutter Dev Skill (Friflex Standards)
Этот скилл содержит набор правил и инструкций для разработки Flutter-приложений. Основная цель — соблюдение единого стиля кода, архитектурных подходов и процессов разработки.
## Основные принципы
1. **Архитектура**: Проект делится на слои: `data`, `domain` и `presentation`.
2. **Именование**: Интерфейсы всегда начинаются с префикса `I`. Экраны имеют постфикс `Screen`.
3. **Документация**: Весь публичный API должен быть покрыт документацией `///`.
4. **Git**: Коммиты и PR на русском языке по стандарту Conventional Commits.
## Справочники (References)
Для получения детальной информации по конкретным областям обращайтесь к следующим файлам:
- [Правила именования и стиль кода](references/codestyle.md) — именование классов, методов, переменных и структура файлов.
- [Структура проекта и слои](references/project_structure.md) — детальное описание папок и взаимодействия между уровнями архитектуры.
- [Работа с Git и ветками](references/gitflow.md) — типы коммитов, именование веток и процессы релизов.
- [Документирование кода](references/documentation.md) — стандарты `///`, использование шаблонов и правила для TODO.
- [Стандарты проекта](references/project_standards.md) — управление сгенерированными файлами, `pubspec.lock` и сборка.
## Когда использовать этот скилл
- При создании новых классов или файлов (проверка именования).
- При реализации новой feature (выбор структуры папок).
- Перед созданием Pull Request (проверка соответствия стандартам).
- При возникновении вопросов по архитектурному взаимодействию слоев.

View File

@@ -1,34 +0,0 @@
# Правила именования и стиль кода
Мы придерживаемся рекомендаций **Effective Dart** и внутренних правил компании.
## Именование
### Интерфейсы
- Начинаются с заглавной буквы **I**.
- Пример: `IAuthRepository`, `IUserRepository`.
### Классы и файлы
- **Классы**: `UpperCamelCase`. Приватные — с префиксом `_`. Должны содержать тип в конце (например, `UserEntity`).
- **Файлы**: `snake_case`. Структура: `[раздел]_[тип].dart`. Пример: `user_details_screen.dart`.
### Репозитории
- Основная реализация — без постфикса (`AuthRepository`).
- Альтернативные реализации — с постфиксами: `Network`, `Local`, `Mock`.
### Виджеты
- **Экраны**: Постфикс `Screen` (`ShopListScreen`).
- **Контент экрана**: Постфикс `View` (`ShopListView`).
- **Глобальные виджеты**: Префикс `App` (`AppButton`).
- В названии **не должно** быть слова `widget`.
## Методы и переменные
- **Методы**: Начинаются с глагола (`fetch`, `put`, `update`, `delete`). Не должны содержать `And/Or`.
- **Переменные/Константы**: `lowerCamelCase`.
## Структура класса (порядок элементов)
1. Конструкторы (default, named, factory).
2. Static элементы (methods, const fields).
3. Инстанс-поля (final, потом обычные; public, потом private).
4. Геттеры/Сеттеры.
5. Методы (overridden, public, protected, private).

View File

@@ -1,29 +0,0 @@
# Документирование кода
## Документация (///)
- Оформляется с использованием `///` над объектом.
- Обязательна для всех классов, конструкторов, полей, методов и фабрик.
- Должна быть краткой, емкой и указывать на назначение.
### Шаблоны
- **Классы**: Используйте `{@template name}` и `{@endtemplate}`.
- **Конструкторы**: Если один — `{@macro name}`.
- **Параметры**: Используйте ссылки в квадратных скобках `[paramName]`.
### Пример метода
```dart
/// Метод для расчета температуры.
/// Принимает:
/// - [grad] - параметр для расчета.
/// Возвращает температуру в градусах. Null при ошибке.
int? calcTemperature({required int grad}) { ... }
```
## Комментарии (//)
- Используются только там, где код не очевиден.
- Не должны повторять то, что и так понятно из имен переменных или структуры.
## TODO
- Формат определяется линтером.
- Указывать имя разработчика в контексте.
- Указывать ссылку на задачу в скобках, если она известна.

View File

@@ -1,26 +0,0 @@
# Работа с Git и ветками
## Pull Requests
- Язык описания — **Русский**.
- Описание должно содержать суть изменений, ссылку на задачу и список deprecated-кода.
## Коммиты (Conventional Commits)
Типы:
- `feat`: Новая функциональность.
- `fix`: Исправление ошибок.
- `refactor`: Рефакторинг без смены логики.
- `docs`: Документация.
- `chore`: Инструменты, зависимости (`pubspec.yaml`).
- `test`, `build`, `ci`.
## Именование веток
Формат: `тип/PRIME-номер_описание`
- `feat/PRIME-123_auth`
- `fix/PRIME-456_typo`
## Процесс Feature-разработки
1. Создаем ветку от `main`.
2. Вносим изменения, делаем коммиты.
3. PR с названием по правилам (например, `feat(auth): PRIME-17 Добавить вход`).
4. После Review — **squash commit** в `main`.
5. Удаление ветки.

View File

@@ -1,26 +0,0 @@
# Стандарты проекта
## Управление файлами
### Сгенерированные файлы (*.g.dart, *.freezed.dart)
- **Хранить в репозитории**.
- Это обеспечивает работоспособность `main` ветки сразу после чекаута без долгого ожидания генерации.
- Нужно контролировать конфликты при слиянии и периодически актуализировать.
### pubspec.lock
- **Хранить** для приложений (applications).
- **Не хранить** для пакетов (packages).
- По умолчанию хранить GMS версию как базовую.
## Сборка и запуск
- Используйте анализатор `friflex_lint_rules`.
- Перед созданием PR обязательно:
1. Форматирование кода (`dart format`).
2. Проверка анализатором на отсутствие ошибок.
## Технологический стек
- Роутинг: `go_router`.
- State Manager: `flutter_bloc`.
- DI: Ручная реализация через `InheritedWidget`.
- API: `dio`.
- Ресурсы: `flutter_gen`.

View File

@@ -1,33 +0,0 @@
# Структура проекта
Проект строится на основе фич и слоев.
## Общая иерархия
- `/assets` — графические ресурсы.
- `/lib` — основной код.
- `/app` — глобальные настройки, интерфейсы и реализации.
- `/di` — конфигурация зависимостей.
- `/routing` — описание путей.
- `/features` — функциональные модули приложения.
- `/gen` — сгенерированный код.
## Структура Feature-папки
Каждая фича делится на три слоя:
1. **Data** (Поставщик данных):
- `/dto` — модели данных для API.
- `/repository` — реализация интерфейсов репозиториев.
2. **Domain** (Бизнес-логика):
- `/entity` — чистые модели для использования в UI.
- `/repository`**интерфейсы** репозиториев.
- `/state` — управление состоянием (BLoC).
- `/service` — реализации бизнес-сервисов.
3. **Presentation** (Представление):
- `/screens` — виджеты экранов (`*Screen`).
- `/components` — переиспользуемые компоненты внутри фичи.
## Правила взаимодействия
- **Data** → доступ к **Entity** (для маппинга), не знает про UI.
- **Domain** → не знает про **Data** (работает через интерфейсы) и **Presentation**.
- **Presentation** → работает через **Domain**, не знает про **Data**.
- Объекты внутри фичи инкапсулированы. Глобальные объекты выносятся в `/app`.

View File

@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f" revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: web - platform: android
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
# User provided section # User provided section

View File

@@ -6,6 +6,7 @@ import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/l10n/gen/app_localizations.dart'; import 'package:friflex_starter/l10n/gen/app_localizations.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:friflex_starter/router/app_router.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template app} /// {@template app}
@@ -13,21 +14,39 @@ import 'package:go_router/go_router.dart';
/// ///
/// Отвечает за: /// Отвечает за:
/// - Настройку провайдеров для темы и локализации /// - Настройку провайдеров для темы и локализации
/// - Инициализацию роутера приложения
/// {@endtemplate} /// {@endtemplate}
class AppRoot extends StatelessWidget { class AppRoot extends StatefulWidget {
/// {@macro app_root} /// {@macro app_root}
const AppRoot({required this.diContainer, required this.router, super.key}); const AppRoot({required this.diContainer, super.key});
/// Контейнер зависимостей /// Контейнер зависимостей
final DiContainer diContainer; final DiContainer diContainer;
@override
State<AppRoot> createState() => _AppRootState();
}
class _AppRootState extends State<AppRoot> {
/// Роутер приложения /// Роутер приложения
final GoRouter router; late final GoRouter router;
@override
void initState() {
super.initState();
router = AppRouter.createRouter(widget.diContainer.debugService);
}
@override
void dispose() {
router.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppProviders( return AppProviders(
diContainer: diContainer, diContainer: widget.diContainer,
child: LocalizationConsumer( child: LocalizationConsumer(
builder: (localizationContext) { builder: (localizationContext) {
return ThemeConsumer( return ThemeConsumer(

View File

@@ -37,7 +37,6 @@
- Статический метод `show` безопасно не откроет модалку, если `updateEntity == null` - Статический метод `show` безопасно не откроет модалку, если `updateEntity == null`
Пример показа модального окна: Пример показа модального окна:
```dart ```dart
await SoftUpdateModal.show( await SoftUpdateModal.show(
context, context,
@@ -55,9 +54,10 @@ await SoftUpdateModal.show(
- `UpdateRoutes.buildRoutes()` — регистрирует экран hard-обновления по пути `/update` - `UpdateRoutes.buildRoutes()` — регистрирует экран hard-обновления по пути `/update`
## Структура модуля ## Структура модуля
```md ```
features/update/ features/update/
├── data/ ├── data/
│ └── repository/ │ └── repository/

View File

@@ -26,7 +26,7 @@ final class UpdateMockRepository implements IUpdateRepository {
@override @override
Future<UpdateEntity> checkForUpdates({ Future<UpdateEntity> checkForUpdates({
required String versionApp, required String versionCode,
required String platform, required String platform,
}) async { }) async {
// Имитация задержки для асинхронной операции // Имитация задержки для асинхронной операции

View File

@@ -14,7 +14,7 @@ final class UpdateRepository implements IUpdateRepository {
@override @override
Future<UpdateEntity> checkForUpdates({ Future<UpdateEntity> checkForUpdates({
required String versionApp, required String versionCode,
required String platform, required String platform,
}) { }) {
// TODO: Реализовать реальную логику проверки обновлений // TODO: Реализовать реальную логику проверки обновлений

View File

@@ -6,11 +6,11 @@ import 'package:friflex_starter/features/update/domain/entity/update_entity.dart
/// {@endtemplate} /// {@endtemplate}
abstract interface class IUpdateRepository with DiBaseRepo { abstract interface class IUpdateRepository with DiBaseRepo {
/// Проверяет наличие обновлений /// Проверяет наличие обновлений
/// [versionApp] - текущий версия приложения /// [versionCode] - текущий код версии приложения
/// [platform] - платформа (например, 'android' или 'ios') /// [platform] - платформа (например, 'android' или 'ios')
/// Возвращает [UpdateEntity] с информацией об обновлении /// Возвращает [UpdateEntity] с информацией об обновлении
Future<UpdateEntity> checkForUpdates({ Future<UpdateEntity> checkForUpdates({
required String versionApp, required String versionCode,
required String platform, required String platform,
}); });
} }

View File

@@ -16,17 +16,17 @@ class UpdateCubit extends Cubit<UpdateState> {
final IUpdateRepository _updatesRepository; final IUpdateRepository _updatesRepository;
/// Метод для проверки доступности обновлений /// Метод для проверки доступности обновлений
/// [versionApp] - текущая версия приложения /// [versionCode] - текущий код версии приложения
/// [platform] - платформа (например, 'android' или 'ios') /// [platform] - платформа (например, 'android' или 'ios')
Future<void> checkForUpdates({ Future<void> checkForUpdates({
required String versionApp, required String versionCode,
required String platform, required String platform,
}) async { }) async {
if (state is UpdateLoadingState) return; if (state is UpdateLoadingState) return;
emit(const UpdateLoadingState()); emit(const UpdateLoadingState());
try { try {
final updateInfo = await _updatesRepository.checkForUpdates( final updateInfo = await _updatesRepository.checkForUpdates(
versionApp: versionApp, versionCode: versionCode,
platform: platform, platform: platform,
); );
emit(UpdateSuccessState(updateInfo)); emit(UpdateSuccessState(updateInfo));

View File

@@ -23,7 +23,20 @@ class AppRouter {
/// Метод для создания экземпляра GoRouter /// Метод для создания экземпляра GoRouter
static GoRouter createRouter(IDebugService debugService) { static GoRouter createRouter(IDebugService debugService) {
return GoRouter( try {
return _init(debugService);
} on Object catch (error, stackTrace) {
debugService.logError(
'Ошибка при создании роутера',
error: error,
stackTrace: stackTrace,
);
throw StateError('Не удалось создать роутер: $error');
}
}
/// Внутренний метод для инициализации роутера
static GoRouter _init(IDebugService debugService) => GoRouter(
navigatorKey: rootNavigatorKey, navigatorKey: rootNavigatorKey,
initialLocation: initialLocation, initialLocation: initialLocation,
observers: [debugService.routeObserver], observers: [debugService.routeObserver],
@@ -41,5 +54,4 @@ class AppRouter {
UpdateRoutes.buildRoutes(), UpdateRoutes.buildRoutes(),
], ],
); );
}
} }

View File

@@ -10,7 +10,6 @@ import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/debug/debug_service.dart'; import 'package:friflex_starter/features/debug/debug_service.dart';
import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart';
import 'package:friflex_starter/features/error/error_screen.dart'; import 'package:friflex_starter/features/error/error_screen.dart';
import 'package:friflex_starter/router/app_router.dart';
import 'package:friflex_starter/runner/timer_runner.dart'; import 'package:friflex_starter/runner/timer_runner.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -33,7 +32,7 @@ class AppRunner {
/// Тип окружения сборки приложения¬ /// Тип окружения сборки приложения¬
final AppEnv env; final AppEnv env;
/// Сервис отладки /// Контейнер зависимостей приложения
late IDebugService _debugService; late IDebugService _debugService;
/// Роутер приложения /// Роутер приложения
@@ -57,9 +56,6 @@ class AppRunner {
// Инициализация приложения // Инициализация приложения
await _initApp(); await _initApp();
// Инициализация роутера
router = AppRouter.createRouter(_debugService);
final diContainer = await _initDependencies( final diContainer = await _initDependencies(
debugService: _debugService, debugService: _debugService,
env: env, env: env,
@@ -67,7 +63,7 @@ class AppRunner {
); );
// Инициализация метода обработки ошибок // Инициализация метода обработки ошибок
_initErrorHandlers(_debugService); _initErrorHandlers(_debugService);
runApp(AppRoot(diContainer: diContainer, router: router)); runApp(AppRoot(diContainer: diContainer));
await _onAppLoaded(); await _onAppLoaded();
} on Object catch (e, stackTrace) { } on Object catch (e, stackTrace) {
await _onAppLoaded(); await _onAppLoaded();