28 Commits

Author SHA1 Message Date
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
Yuri Petrov
fcf5048038 chore(pubspec,app_services): обновить зависимости и настройки линтинга (#39)
Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-11-17 13:21:38 +03:00
Yuri Petrov
454f3b7929 chore(app): обновление flutter и пакетов (#38)
* chore(pubspec,di): Обновить версии SDK и исправить использование AppEnv

* chore(readme): Обновить версии Flutter и Dart, добавить новые библиотеки

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-11-17 12:49:07 +03:00
Yuri Petrov
d9c45eb57e fix(linter): улучшение правил анализа и линтинга (#37)
* fix(linter): улучшение правил анализа и линтинга, добавление исключений и ошибок

* fix(tasks): исправления по ревью

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-11-17 11:51:42 +03:00
Yuri Petrov
55de1ad8d1 github(copilot): create copilot-instructions.md 2025-10-24 10:39:56 +03:00
Yuri Petrov
42b7c34d1a fix(di): улучшить обработку ошибок при инициализации репозиториев (#34)
* fix(di): улучшить обработку ошибок при инициализации репозиториев
* fix(di): улучшить обработку ошибок при инициализации репозиториев и сервисов

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-10-23 12:00:09 +03:00
sitkat
5d93fb1713 docs(tools): Добавлена гибкость для документирования классов в RFC-documentation (#35) 2025-10-22 13:28:08 +03:00
Yuri Petrov
0351c768c4 chore(pubspec): FLUTTER-151 обновление flutter и пакетов (#33)
* chore(deps): update dependencies and SDK versions in pubspec.yaml and related files
* chore(readme): update Readme

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-10-08 15:14:02 +03:00
zl0y4951
dfe30a1762 fix(di): FLUTTER-11: Во FlutterStarter исправить инициализацию репозиториев (#32)
* fix(di): поправил множественный вызов конструктора репозитория

* docs(di): поправил документацию по подмене репозитория
2025-10-06 10:19:27 +03:00
Yuri Petrov
8710792c4b feat(update): добавить модуль управления Hard & Soft обновлений (#30)
1. Реализован интерфейс и репозитории для проверки обновлений.
2. Добавлены состояния и кубит для управления процессом обновления.
3. Созданы UI-компоненты для отображения информации об обновлениях.
4. Обновлен README.md с описанием нового модуля и его интеграции
2025-09-26 08:21:42 +03:00
Yuri Petrov
e1fb99c86f fix(assets): удалить неиспользуемые assets (#29)
Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-09-20 16:10:46 +03:00
Yuri Petrov
c295412f4d docs(readme): обновить структуру и содержание README.md, улучшить оформление (#27)
Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-09-17 12:46:45 +03:00
Yuri Petrov
03e189e46b fix(license): исправить название компании в лицензии (#28)
Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-09-17 12:46:07 +03:00
Yuri Petrov
d491a2f07f fix: FLUTTERSTARTER-3: обновить версии зависимостей и исправить типы в интерфейсе IDebugService (#26)
1. Обновил типы в IDebugService
2. Добавил отправку ошибок в блоке в Observer
2025-08-25 15:04:36 +03:00
Yuri Petrov
ac26aa4a89 feat(linter): FLUTTERSTARTER-2 добавить правило для использования только видимых параметров в документации (#24) 2025-08-08 10:59:17 +03:00
Artem Luzin
d8110c23b3 feat(app): FLUTTERSTARTER-1 Заблокировать изменение шрифта (#23)
* fix(app): добавить глобальный MediaQuery для предсказуемости изменения шрифта
2025-08-08 10:57:17 +03:00
Yuri Petrov
35bceccbf7 feat(app): добавить файл CODEOWNERS для управления правами доступа (#22)
Co-authored-by: PetrovY <y.petrov@friflex.com>
2025-07-18 13:09:39 +03:00
Yuri Petrov
d5a0602c8a docs(readme): Update README.md 2025-06-25 10:53:46 +03:00
PetrovY
63cd544184 docs(readme): Update README.md 2025-06-25 10:41:26 +03:00
Yuri Petrov
8baddfb9f9 feat(rfc): добавить рекомендации по управлению сгенерированными файлами и файлом pubspec.lock (#18)
Co-authored-by: PetrovY <y.petrov@friflex.com>
2025-06-25 10:35:03 +03:00
Yuri Petrov
80a8bfb905 feat(profile): добавить обработку события выхода из профиля и rethrow (#17)
Co-authored-by: PetrovY <y.petrov@friflex.com>
2025-06-25 10:31:37 +03:00
Artem Barkalov
c21fa95b7a chore: Добавить конфигурации запуска для IntelliJ (#15) 2025-06-23 11:01:37 +03:00
PetrovY
b54445be70 feat(app): Добавить сервис геолокации и обновить зависимости 2025-06-23 10:30:24 +03:00
PetrovY
150a85ab24 refactor(app): Обновить зависимости и улучшить документацию для сервисов 2025-06-23 10:20:29 +03:00
93 changed files with 2537 additions and 991 deletions

64
.cursorrules Normal file
View File

@@ -0,0 +1,64 @@
# Правила для Cursor AI
## Соглашение о коммитах
При генерации сообщений коммитов ВСЕГДА используй следующий формат:
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
Для Pull Request формат:
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
Где:
- `<тип>` - тип коммита (см. ниже)
- `<контекст>` - модули/компоненты, которые изменяются (можно указать несколько через запятую)
- `<короткое описание>` - краткое описание изменений на русском языке
### Типы коммитов согласно convention:
- **feat** - новая функция
- **fix** - исправление ошибок
- **refactor** - изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода)
- **build** - изменения, влияющие на систему сборки или внешние зависимости (примеры областей: android, ios, linux и так далее)
- **docs** - изменения только в документации
- **chore** - добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml)
- **test** - добавление недостающих тестов или исправление существующих тестов
- **ci** - изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI)
### Контекст (scope):
Указывай модуль или компонент, который изменяется. Можно указать несколько через запятую:
- `app` - основное приложение
- `di` - dependency injection
- `auth` - аутентификация
- `api` - API endpoints
- `db` - база данных
- `config` - конфигурация
- `i18n` - интернационализация
- `scripts` - скрипты
- `pubspec` - зависимости проекта
- `android`, `ios`, `linux` - платформы
- другие модули проекта
### Примеры правильных коммитов:
- `feat(app,di,auth): Добавить локальный репозиторий`
- `fix(api): Исправить валидацию запросов`
- `docs(i18n): Обновить руководство по генерации`
- `refactor(handler): Оптимизировать обработку запросов`
- `test(security): Добавить тесты для rate limiter`
- `chore(pubspec): Обновить зависимости`
## Язык
- Всегда отвечай на русском языке
- Коммиты пиши на русском языке
- Документацию веди на русском языке
- Язык описания PR - Русский
## Стиль кода
- Следуй Dart/Flutter conventions
- Используй осмысленные имена переменных и функций
- Добавляй комментарии к публичным функциям
- Группируй импорты (стандартные, внешние, внутренние)

63
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,63 @@
Инструкция для AI-агента (ревью Flutter-кода)
Вы — строгий ревьюер кода Flutter-приложения по принципам **Clean Architecture**.
Ваша задача — проводить ревью кода, выявлять ошибки и давать корректные рекомендации.
Bloc и Cubit должны находиться в слое domain.
Контекст (справочно, не включать в ответ):
- Архитектура: три слоя (presentation, domain, data), каждый в своей папке.
- State management: flutter_bloc.
- Навигация: go_router.
- HTTP: dio.
- Анализатор: flutter_lint_rules.
- Feature-first структура: lib/features/<feature_name>/{data,domain,presentation}.
---
## Обзор запроса на вытягивание
1. В начале ответа укажите решение: **«Принять»** или **«Отклонить»**.
2. Укажите оценку: **Оценка: X/100** (X — фактический балл).
3. Кратко и строгим тоном перечислите ключевые проблемы:
- Архитектура (слои `presentation`, `domain`, `data`).
- Принципы **DRY, KISS, SOLID**.
- Разделение ответственности (BLoC, репозитории, DTO, UI).
- Безопасность (`dio`, SSL, валидация данных).
- Кодстайл и соглашения (`flutter_lint_rules`).
- Цикломатическая сложность: избегать чрезмерно сложных функций и классов (оптимально ≤ 10).
4. При необходимости предоставьте исправленный фрагмент кода или улучшенное решение.
5. Ответ должен быть в **Markdown** и полностью на **русском языке**.
---
## Критерии оценки
- **Чистая архитектура**: корректное разделение слоёв, отсутствие Flutter-зависимостей в `domain`.
- **KISS**: минимальная сложность и читаемость решений.
- **DRY**: отсутствие дублирования логики, использование утилит/виджетов.
- **SOLID**: правильная декомпозиция классов и интерфейсы вместо жёстких связей.
- **Безопасность**: корректная работа с API, валидация данных.
- **Кодстайл**: именование, структура файлов и папок.
- **Цикломатическая сложность**: методы и классы должны быть простыми, без избыточных ветвлений.
---
## Пример ответа
**Отклонить**
**Оценка: 58/100**
- Нарушен принцип DRY: HTTP-запрос продублирован в двух репозиториях.
- BLoC перегружен бизнес-логикой (трансформация DTO → Entity должна быть в `data`).
- Domain-слой содержит зависимость от Flutter — это недопустимо.
- Отсутствует обработка ошибок и валидация данных в `dio`.
**Исправленный фрагмент:**
```dart
// Вместо дублирования запроса используем общий DataSource
class UserRemoteDataSource {
final Dio dio;
UserRemoteDataSource(this.dio);
Future<UserEntity> fetchUser(String id) async {
final response = await dio.get('/users/$id');
return UserDto.fromJson(response.data).toEntity();
}
}
```

View File

@@ -1,3 +1,5 @@
<!-- markdownlint-disable MD033 -->
<!-- markdownlint-disable MD041 -->
## Ссылка на задачу или issue (обязательно) ## Ссылка на задачу или issue (обязательно)
<!--- https://tracker.yandex.ru/XXX --> <!--- https://tracker.yandex.ru/XXX -->
@@ -5,6 +7,7 @@
<!--- Напишите здесь какую проблему решают изменения в этом запросе. --> <!--- Напишите здесь какую проблему решают изменения в этом запросе. -->
## Чек лист (обязательно) ## Чек лист (обязательно)
- [ ] Код соответствует рекомендациям и требованиям проекта. - [ ] Код соответствует рекомендациям и требованиям проекта.
- [ ] Проверил анализатор на предмет ошибок и предупреждений. - [ ] Проверил анализатор на предмет ошибок и предупреждений.
- [ ] Название ПР соответствует [требованиям проекта](../tools/rfc/RFC-documentation.md). - [ ] Название ПР соответствует [требованиям проекта](../tools/rfc/RFC-documentation.md).
@@ -12,6 +15,7 @@
- [ ] Добавлены необходимые тесты, если требуется. - [ ] Добавлены необходимые тесты, если требуется.
## Скриншоты (желательно) ## Скриншоты (желательно)
<details> <details>
<summary>Показать</summary> <summary>Показать</summary>

3
.gitignore vendored
View File

@@ -17,6 +17,9 @@ migrate_working_dir/
*.ipr *.ipr
*.iws *.iws
.idea/ .idea/
# Хранить конфигурации запуска
!.idea/runConfigurations/
!.idea/runConfigurations/**
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line

6
.idea/runConfigurations/DEV.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="DEV" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/targets/dev.dart" />
<method v="2" />
</configuration>
</component>

6
.idea/runConfigurations/PROD.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="PROD" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/targets/prod.dart" />
<method v="2" />
</configuration>
</component>

6
.idea/runConfigurations/STAGE.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="STAGE" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/targets/stage.dart" />
<method v="2" />
</configuration>
</component>

6
.idea/runConfigurations/main_dart.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method />
</configuration>
</component>

View File

@@ -14,5 +14,23 @@
"/// {@macro ${TM_FILENAME_BASE}}" "/// {@macro ${TM_FILENAME_BASE}}"
], ],
"description": "DartDoc короткая запись macro с именем файла" "description": "DartDoc короткая запись macro с именем файла"
} },
"TODO": {
"prefix": "todo",
"body": [
"// TODO($1): $2"
],
"description": "Create todo"
},
"TRYCATCH": {
"prefix": "tryc",
"body": [
"try {",
"$1",
"} on Object catch (error,stackTrace) {",
"",
"}",
],
"description": "Create trycatch"
},
} }

26
.vscode/tasks.json vendored
View File

@@ -1,24 +1,12 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{
"label": "fluttergen",
"type": "shell",
"command": "/Users/yura/.pub-cache/bin/fluttergen",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always"
},
"problemMatcher": []
},
{ {
"label": "build_runner --delete-conflicting-outputs", "label": "build_runner --delete-conflicting-outputs",
"type": "shell", "type": "shell",
"command": "dart", "command": "flutter",
"args": [ "args": [
"pub",
"run", "run",
"build_runner", "build_runner",
"build", "build",
@@ -62,6 +50,16 @@
"type": "shell", "type": "shell",
"command": "./tools/switch_service.sh aurora", "command": "./tools/switch_service.sh aurora",
"problemMatcher": [] "problemMatcher": []
},
{
"label": "flutter gen-l10n",
"type": "shell",
"command": "flutter",
"args": [
"gen-l10n"
],
"group": "build",
"problemMatcher": [],
} }
] ]
} }

0
CHANGELOG.md Normal file
View File

View File

@@ -1 +1,29 @@
Friflex LCC # Файл CODEOWNERS определяет владельцев кода для автоматического назначения ревьюеров
# GitHub Actions и CI/CD
.github/ @petrovyuri
actions/ @petrovyuri
# Конфигурация разработки и IDE
.vscode/ @petrovyuri
# Ресурсы и конфигурация приложения
assets/ @petrovyuri
env/ @petrovyuri
tools/ @petrovyuri
# Исходный код приложения
lib/ @petrovyuri
app_services/ @petrovyuri
# Конфигурационные файлы проекта
analysis_options.yaml @petrovyuri
build.yaml @petrovyuri
pubspec.yaml @petrovyuri
# Документация
CHANGELOG.md @petrovyuri
README.md @petrovyuri
# По умолчанию все остальные файлы
* @petrovyuri

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 Friflex Copyright (c) 2025 Friflex LLC
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,21 +1,14 @@
<div align="center">
# 🚀 Friflex Flutter Starter - Корпоративный шаблон # 🚀 Friflex Flutter Starter - Корпоративный шаблон
</div> ![Flutter](https://img.shields.io/badge/Flutter-3.38.1+-02569B?style=for-the-badge&logo=flutter&logoColor=white)
<div align="center"> ![Dart](https://img.shields.io/badge/Dart-3.10.0+-0175C2?style=for-the-badge&logo=dart&logoColor=white)
![Flutter](https://img.shields.io/badge/Flutter-3.32.0+-02569B?style=for-the-badge&logo=flutter&logoColor=white)
![Dart](https://img.shields.io/badge/Dart-3.8.0+-0175C2?style=for-the-badge&logo=dart&logoColor=white)
![Version](https://img.shields.io/badge/Version-v0.0.1-green?style=for-the-badge)
![License](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge) ![License](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)
**Корпоративный стартовый шаблон для разработки масштабируемых Flutter-приложений** Корпоративный стартовый шаблон для разработки масштабируемых Flutter-приложений
[📋 Документация](#-документация) • [🏗️ Архитектура](#-архитектура) • [🚀 Быстрый старт](#-быстрый-старт) • [🔧 Конфигурация](#-конфигурация) [📋 Документация](#-документация) • [🏗️ Архитектура](#-архитектура) • [🚀 Быстрый старт](#-быстрый-старт) • [🔧 Конфигурация](#-конфигурация)
</div>
--- ---
## 📖 Описание проекта ## 📖 Описание проекта
@@ -31,6 +24,7 @@
- 🌍 Поддержка интернационализации - 🌍 Поддержка интернационализации
- 🎨 UI Kit и система токенов дизайна - 🎨 UI Kit и система токенов дизайна
- 🔍 Инструменты отладки и мониторинга - 🔍 Инструменты отладки и мониторинга
- ⚡ Современный Dart 3.10+ с dot shorthands
## 🎯 Для чего нужен стартер ## 🎯 Для чего нужен стартер
@@ -97,20 +91,27 @@ features/
| Категория | Библиотека | Версия | Описание | | Категория | Библиотека | Версия | Описание |
|-----------|------------|--------|----------| |-----------|------------|--------|----------|
| 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `15.1.2` | Декларативный роутинг | | 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `17.0.0` | Декларативный роутинг |
| 🔄 **State Management** | [flutter_bloc](https://pub.dev/packages/flutter_bloc) | `9.1.1` | Управление состоянием | | 🔄 **State Management** | [flutter_bloc](https://pub.dev/packages/flutter_bloc) | `9.1.1` | Управление состоянием |
| 💉 **DI** | Custom InheritedWidget | - | Внедрение зависимостей | | 💉 **DI** | Custom InheritedWidget | - | Внедрение зависимостей |
| 🎨 **Resources** | [flutter_gen](https://pub.dev/packages/flutter_gen) | `5.10.0` | Генерация ресурсов | | 🎨 **Resources** | [flutter_gen](https://pub.dev/packages/flutter_gen) | `5.12.0` | Генерация ресурсов |
| 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.8.0+1` | HTTP клиент | | 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.9.0` | HTTP клиент |
| 🎨 **SVG** | [flutter_svg](https://pub.dev/packages/flutter_svg) | `2.2.2` | Поддержка SVG |
| 🎬 **Animation** | [lottie](https://pub.dev/packages/lottie) | `3.3.2` | Анимации Lottie |
| 🔒 **Secure Storage** | [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) | - | Защищенное хранилище | | 🔒 **Secure Storage** | [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) | - | Защищенное хранилище |
| 📊 **Logging** | [talker](https://pub.dev/packages/talker_flutter) | `4.8.0` | Логирование и отладка | | 📊 **Logging** | [talker](https://pub.dev/packages/talker_flutter) | `5.0.2` | Логирование и отладка |
| 🎨 **Theme** | [theme_tailor](https://pub.dev/packages/theme_tailor) | `3.1.1` | Генерация тем |
| ⚙️ **Environment** | [envied](https://pub.dev/packages/envied) | `1.3.1` | Управление переменными окружения |
### 🔧 Инструменты разработки ### 🔧 Инструменты разработки
- **📝 Линтинг**: корпоративные правила кода | Инструмент | Версия | Описание |
- **🏗️ Code Generation**: `build_runner` для генерации кода |-----------|--------|----------|
- **🌍 Локализация**: `flutter_localizations` + `intl` | **📝 Линтинг** | `flutter_lints: 6.0.0` | Корпоративные правила кода |
- **⚙️ Окружения**: `envied` для управления переменными | **🏗️ Code Generation** | `build_runner: 2.10.3` | Генерация кода |
| **🌍 Локализация** | `intl: 0.20.2` | Интернационализация |
| **⚙️ Environment** | `envied: 1.3.1` + `envied_generator: 1.3.1` | Управление переменными окружения |
| **🎨 Theme Generator** | `theme_tailor: 3.1.1` | Генерация тем |
## 🗂️ Структура проекта ## 🗂️ Структура проекта
@@ -162,33 +163,33 @@ enum AppEnv {
### 🛠️ Установка ### 🛠️ Установка
1. **Клонирование проекта** #### Клонирование проекта
```bash ```bash
git clone https://github.com/smmarty/friflex_starter.git git clone https://github.com/smmarty/friflex_starter.git
cd friflex_starter cd friflex_starter
``` ```
2. **Установка зависимостей** #### Установка зависимостей
```bash ```bash
flutter pub get flutter pub get
``` ```
3. **Генерация файлов** #### Генерация файлов
```bash ```bash
dart run build_runner build --delete-conflicting-outputs dart run build_runner build --delete-conflicting-outputs
flutter packages pub run flutter_gen flutter packages pub run flutter_gen
``` ```
4. **Запуск приложения** #### Запуск приложения
```bash ```bash
flutter run flutter run
``` ```
5. **Замените название пакета на ваш** #### Замените название пакета на ваш
### 🎯 Запуск с разными окружениями ### 🎯 Запуск с разными окружениями
@@ -262,6 +263,8 @@ dart run build_runner build --delete-conflicting-outputs
| 🔄 [RFC-gitflow.md](./tools/rfc/RFC-gitflow.md) | Git workflow | | 🔄 [RFC-gitflow.md](./tools/rfc/RFC-gitflow.md) | Git workflow |
| 🏗️ [RFC-projects_structure.md](./tools/rfc/RFC-projects_structure.md) | Структура проекта | | 🏗️ [RFC-projects_structure.md](./tools/rfc/RFC-projects_structure.md) | Структура проекта |
| 📝 [RFC-documentation.md](./tools/rfc/RFC-documentation.md) | Правила документирования | | 📝 [RFC-documentation.md](./tools/rfc/RFC-documentation.md) | Правила документирования |
| 🔧 [RFC-managing_generated_files.md](./tools/rfc/RFC-managing_generated_files.md) | Рекомендации по управлению сгенерированными файлами |
| 🌐 [RFC-managing_pubspec_lock.md](./tools/rfc/RFC-managing_pubspec_lock.md) | Рекомендации по управлению pubspec.lock |
## 🎯 Особенности и нюансы ## 🎯 Особенности и нюансы
@@ -286,15 +289,10 @@ dart run build_runner build --delete-conflicting-outputs
## 📄 Лицензия ## 📄 Лицензия
Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENCE). Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENSE).
Copyright © 2025 Friflex LLC. Все права защищены. Copyright © 2025 Friflex LLC. Все права защищены.
--- ---
### Разработано с любовью командой Friflex ❤️
<div align="right">
*Разработано с любовью командой Friflex ❤️*
</div>

View File

@@ -1,89 +1,135 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Включает правила из: # Персонализированные настройки анализатора и линтера.
# - package:lints/core.yaml: основные правила критических проблем # Базовый набор flutter_lints + дополнительные ужесточения.
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "android/**" - "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы)
- "assets/**" - "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером
- "build/**" - "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа
- "config/**" - "assets/**" # Статические ассеты (шрифты, изображения, json и др.)
- "core/**" - "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable
- "res/**" - "**/*.freezed.dart" # Сгенерированные модели пакетом freezed
- "ios/**" - "**/*.gen.dart" # Общий паттерн для доп. генераторов
- "**/*.g.dart" - "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие)
- "**/*.config.dart" - "**/*.config.dart" # Сгенерированные конфигурационные файлы
- "**/*.gen.dart" - "**/generated/**" # Папки с автогенерируемыми исходниками
- "**/*.freezed.dart" - "**/*.lock" # Временные/lock файлы генераторов (если создаются)
- "**/generated/*" - "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён
- "**/*.gr.dart" errors:
- "**/*.yaml" avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность)
- "app_services/aurora/**" avoid_returning_null_for_future: error # Future не должен завершаться null без явной модели
- "/app_services/aurora/**" avoid_slow_async_io: warning # Подсказка о потенциально медленных IO операциях
- "**/app_services/aurora/**" avoid_type_to_string: warning # Предупреждение против использования Type.toString для логики
- "**/*.lock.dart" cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек
errors: close_sinks: error # Обязательное закрытие Sink (ресурсное управление)
# Переопределения уровней ошибок (error/warning/info) comment_references: warning # Проверка корректности ссылок в документационных комментариях
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов avoid_void_async: error # async void нежелателен (трудно ловить ошибки)
comment_references: warning # Проверяет корректность ссылок в комментариях constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE)
always_declare_return_types: error # Требует явного указания возвращаемых типов методов unnecessary_new: warning # Снижение шума: new не нужен в современном Dart
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов
avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона
avoid_return_types_on_setters: warning # Запрещает возвращаемые типы для сеттеров
avoid_returning_null: error # Запрещает возврат null
avoid_setters_without_getters: error # Требует создания геттера при наличии сеттера
avoid_void_async: error # Запрещает использование void для асинхронных функций
constant_identifier_names: error # Проверяет правильность именования констант
unnecessary_new: warning # Запрещает избыточное использование ключевого слова new
use_decorated_box: warning # Рекомендует использовать DecoratedBox вместо Container
use_colored_box: warning # Рекомендует использовать ColoredBox вместо Container с цветом
linter: linter:
rules: rules:
# Нестандартные правила или правила с измененными значениями # === Именование и стиль ===
always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми - always_put_required_named_parameters_first # Обязательные именованные параметры первыми повышают читаемость
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения - always_use_package_imports # Единый стиль импортов через package: упрощает refactoring
avoid_catching_errors: true # Избегать перехвата ошибок типа Error - curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах - directives_ordering # Упорядочивание импортов (dart → package → relative)
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек - eol_at_end_of_file # Пустая строка в конце файла (POSIX standard)
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах - prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов - slash_for_doc_comments # Использовать /// вместо /** */ для документации
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке - sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
avoid_private_typedef_functions: true # Избегать приватных typedef-функций # === Обработка ошибок и типобезопасность ===
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов - avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
avoid_returning_this: true # Избегать возврата this - await_only_futures # await только для Future (ловит ошибки типизации)
cascade_invocations: true # Использовать каскадные вызовы - control_flow_in_finally # Запрет return/break/continue в finally блоках
deprecated_consistency: true # Поддерживать согласованность устаревших элементов - empty_catches # Предупреждение о пустых catch (скрытые баги)
do_not_use_environment: false # Разрешить использование Environment - hash_and_equals # Требует переопределять hashCode и == вместе
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки - only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты
no_runtimeType_toString: true # Не использовать runtimeType.toString() - test_types_in_equals # Проверка типа в equals для безопасности
one_member_abstracts: false # Разрешать абстрактные классы с одним методом - unrelated_type_equality_checks # Запрет сравнения несовместимых типов
only_throw_errors: true # Выбрасывать только объекты Error - use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap
parameter_assignments: true # Запрещать присваивание значений параметрам - use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace)
prefer_asserts_with_message: true # Использовать сообщения с assert - discarded_futures # Выявляет неожиданные Future без await
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам - unawaited_futures # Явно помечать намеренно не ожидаемые Future
prefer_final_in_for_each: true # Использовать final в for-each циклах
prefer_final_locals: true # Использовать final для локальных переменных # === Иммутабельность и const ===
public_member_api_docs: false # Не требовать документацию для всех публичных членов - avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования - prefer_const_constructors # Константные конструкторы для производительности
sort_constructors_first: true # Требовать размещать конструкторы первыми - prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec - prefer_const_literals_to_create_immutables # Константные литералы коллекций
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми - prefer_final_fields # final для полей где возможно
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0 - prefer_final_in_for_each # final в forEach предотвращает случайные изменения
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей - prefer_final_locals # final локальные переменные — стремимся к иммутабельности
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств - parameter_assignments # Не переназначать параметры — вводит путаницу
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости # === Flutter оптимизации ===
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false - avoid_unnecessary_containers # Убирает лишние Container виджеты
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets - sized_box_for_whitespace # SizedBox вместо Container для отступов
always_use_package_imports: true # Всегда использовать package: импорты - sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand
unawaited_futures: true # Требовать использование unawaited для неожидаемых Future - sort_child_properties_last # child/children последними в виджетах
- use_colored_box # ColoredBox для простого цвета фона
- use_decorated_box # DecoratedBox вместо контейнера без лишних виджетов
- use_key_in_widget_constructors # Ключ позволяет корректно применить дифф к дереву виджетов
# === Современный Dart (2.17+, 3.x) ===
- use_super_parameters # super параметры для краткости
- combinators_ordering # Сортировка show/hide в импортах
- implicit_call_tearoffs # Разрешить неявные tearoffs
- matching_super_parameters # Автоматическое соответствие параметрам супер-конструктора
- use_enums # Предпочитать enums вместо статических констант
# === Читаемость кода ===
- avoid_escaping_inner_quotes # Предпочтительно менять тип кавычек вместо экранирования
- avoid_field_initializers_in_const_classes # Инициализация в константных классах тяжеловесна/избыточна
- avoid_implementing_value_types # Не реализовывать value-type интерфейсы вручную (риск несовпадений)
- avoid_positional_boolean_parameters # Булевые позиционные параметры плохо читаются (использовать именованные)
- avoid_private_typedef_functions # Приватные typedef затрудняют повторное использование / читаемость
- avoid_redundant_argument_values # Удалять аргументы совпадающие с значениями по умолчанию
- avoid_returning_this # Возврат this усложняет fluent API и может скрывать ошибки
- cascade_invocations # Использовать каскады для последовательности операций над объектом
- deprecated_consistency # Единый стиль пометок @deprecated
- leading_newlines_in_multiline_strings # Многострочные строки начинают с новой строки — чище diff
- no_literal_bool_comparisons # Исключить сравнения вида flag == true
- no_runtimeType_toString # runtimeType.toString нестабилен для логики (только отладка)
- prefer_asserts_with_message # Сообщение в assert облегчает диагностику
- prefer_constructors_over_static_methods # Конструкторы лучше выражают создание экземпляра
- comment_references # Корректные ссылки в комментариях (небитые, видимые)
# === Сахар и идиомы ===
- avoid_init_to_null # Не писать = null явно (по умолчанию)
- prefer_if_null_operators # ?? оператор вместо тернарника с null
- prefer_interpolation_to_compose_strings # Интерполяция вместо конкатенации
- prefer_is_empty # .isEmpty вместо .length == 0
- prefer_is_not_empty # .isNotEmpty вместо .length > 0
- use_is_even_rather_than_modulo # isEven более явно и может быть эффективнее
- use_setters_to_change_properties # Изменение полей через сеттеры (инкапсуляция)
- use_string_buffers # StringBuffer эффективнее при конкатенации в циклах
- use_to_and_as_if_applicable # Использовать to*/as* если семантика преобразования доступна
# === Удаление избыточности ===
- avoid_return_types_on_setters # Сеттеры не должны объявлять тип возврата
- unnecessary_const # Убирает лишние const
- unnecessary_new # Убирает лишние new
- unnecessary_this # Убирает лишние this
- unnecessary_parenthesis # Лишние скобки
# === Продакшн / отладка ===
- avoid_print # Запрет print в продакшене (использовать logger)
bloc:
rules:
- avoid_flutter_imports # Логика блока должна быть платформенно независимой
- avoid_public_bloc_methods # Публичные методы могут нарушать чистоту паттерна (использовать события)
- avoid_public_fields # Публичные поля обходят инкапсуляцию; использовать состояние/события
- prefer_file_naming_conventions # Единообразие именования файлов блоков повышает навигацию

View File

@@ -1 +1 @@
# Базовые сервисы для приложения # Реализация сервисов для Аврора OC

View File

@@ -1,89 +1,135 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Включает правила из: # Персонализированные настройки анализатора и линтера.
# - package:lints/core.yaml: основные правила критических проблем # Базовый набор flutter_lints + дополнительные ужесточения.
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "android/**" - "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы)
- "assets/**" - "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером
- "build/**" - "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа
- "config/**" - "assets/**" # Статические ассеты (шрифты, изображения, json и др.)
- "core/**" - "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable
- "res/**" - "**/*.freezed.dart" # Сгенерированные модели пакетом freezed
- "ios/**" - "**/*.gen.dart" # Общий паттерн для доп. генераторов
- "**/*.g.dart" - "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие)
- "**/*.config.dart" - "**/*.config.dart" # Сгенерированные конфигурационные файлы
- "**/*.gen.dart" - "**/generated/**" # Папки с автогенерируемыми исходниками
- "**/*.freezed.dart" - "**/*.lock" # Временные/lock файлы генераторов (если создаются)
- "**/generated/*" - "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён
- "**/*.gr.dart" errors:
- "**/*.yaml" avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность)
- "app_services/aurora/**" avoid_returning_null_for_future: error # Future не должен завершаться null без явной модели
- "/app_services/aurora/**" avoid_slow_async_io: warning # Подсказка о потенциально медленных IO операциях
- "**/app_services/aurora/**" avoid_type_to_string: warning # Предупреждение против использования Type.toString для логики
- "**/*.lock.dart" cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек
errors: close_sinks: error # Обязательное закрытие Sink (ресурсное управление)
# Переопределения уровней ошибок (error/warning/info) comment_references: warning # Проверка корректности ссылок в документационных комментариях
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов avoid_void_async: error # async void нежелателен (трудно ловить ошибки)
comment_references: warning # Проверяет корректность ссылок в комментариях constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE)
always_declare_return_types: error # Требует явного указания возвращаемых типов методов unnecessary_new: warning # Снижение шума: new не нужен в современном Dart
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов
avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона
avoid_return_types_on_setters: warning # Запрещает возвращаемые типы для сеттеров
avoid_returning_null: error # Запрещает возврат null
avoid_setters_without_getters: error # Требует создания геттера при наличии сеттера
avoid_void_async: error # Запрещает использование void для асинхронных функций
constant_identifier_names: error # Проверяет правильность именования констант
unnecessary_new: warning # Запрещает избыточное использование ключевого слова new
use_decorated_box: warning # Рекомендует использовать DecoratedBox вместо Container
use_colored_box: warning # Рекомендует использовать ColoredBox вместо Container с цветом
linter: linter:
rules: rules:
# Нестандартные правила или правила с измененными значениями # === Именование и стиль ===
always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми - always_put_required_named_parameters_first # Обязательные именованные параметры первыми повышают читаемость
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения - always_use_package_imports # Единый стиль импортов через package: упрощает refactoring
avoid_catching_errors: true # Избегать перехвата ошибок типа Error - curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах - directives_ordering # Упорядочивание импортов (dart → package → relative)
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек - eol_at_end_of_file # Пустая строка в конце файла (POSIX standard)
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах - prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов - slash_for_doc_comments # Использовать /// вместо /** */ для документации
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке - sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
avoid_private_typedef_functions: true # Избегать приватных typedef-функций # === Обработка ошибок и типобезопасность ===
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов - avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
avoid_returning_this: true # Избегать возврата this - await_only_futures # await только для Future (ловит ошибки типизации)
cascade_invocations: true # Использовать каскадные вызовы - control_flow_in_finally # Запрет return/break/continue в finally блоках
deprecated_consistency: true # Поддерживать согласованность устаревших элементов - empty_catches # Предупреждение о пустых catch (скрытые баги)
do_not_use_environment: false # Разрешить использование Environment - hash_and_equals # Требует переопределять hashCode и == вместе
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки - only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты
no_runtimeType_toString: true # Не использовать runtimeType.toString() - test_types_in_equals # Проверка типа в equals для безопасности
one_member_abstracts: false # Разрешать абстрактные классы с одним методом - unrelated_type_equality_checks # Запрет сравнения несовместимых типов
only_throw_errors: true # Выбрасывать только объекты Error - use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap
parameter_assignments: true # Запрещать присваивание значений параметрам - use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace)
prefer_asserts_with_message: true # Использовать сообщения с assert - discarded_futures # Выявляет неожиданные Future без await
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам - unawaited_futures # Явно помечать намеренно не ожидаемые Future
prefer_final_in_for_each: true # Использовать final в for-each циклах
prefer_final_locals: true # Использовать final для локальных переменных # === Иммутабельность и const ===
public_member_api_docs: false # Не требовать документацию для всех публичных членов - avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования - prefer_const_constructors # Константные конструкторы для производительности
sort_constructors_first: true # Требовать размещать конструкторы первыми - prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec - prefer_const_literals_to_create_immutables # Константные литералы коллекций
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми - prefer_final_fields # final для полей где возможно
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0 - prefer_final_in_for_each # final в forEach предотвращает случайные изменения
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей - prefer_final_locals # final локальные переменные — стремимся к иммутабельности
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств - parameter_assignments # Не переназначать параметры — вводит путаницу
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости # === Flutter оптимизации ===
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false - avoid_unnecessary_containers # Убирает лишние Container виджеты
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets - sized_box_for_whitespace # SizedBox вместо Container для отступов
always_use_package_imports: true # Всегда использовать package: импорты - sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand
- sort_child_properties_last # child/children последними в виджетах
- use_colored_box # ColoredBox для простого цвета фона
- use_decorated_box # DecoratedBox вместо контейнера без лишних виджетов
- use_key_in_widget_constructors # Ключ позволяет корректно применить дифф к дереву виджетов
# === Современный Dart (2.17+, 3.x) ===
- use_super_parameters # super параметры для краткости
- combinators_ordering # Сортировка show/hide в импортах
- implicit_call_tearoffs # Разрешить неявные tearoffs
- matching_super_parameters # Автоматическое соответствие параметрам супер-конструктора
- use_enums # Предпочитать enums вместо статических констант
# === Читаемость кода ===
- avoid_escaping_inner_quotes # Предпочтительно менять тип кавычек вместо экранирования
- avoid_field_initializers_in_const_classes # Инициализация в константных классах тяжеловесна/избыточна
- avoid_implementing_value_types # Не реализовывать value-type интерфейсы вручную (риск несовпадений)
- avoid_positional_boolean_parameters # Булевые позиционные параметры плохо читаются (использовать именованные)
- avoid_private_typedef_functions # Приватные typedef затрудняют повторное использование / читаемость
- avoid_redundant_argument_values # Удалять аргументы совпадающие с значениями по умолчанию
- avoid_returning_this # Возврат this усложняет fluent API и может скрывать ошибки
- cascade_invocations # Использовать каскады для последовательности операций над объектом
- deprecated_consistency # Единый стиль пометок @deprecated
- leading_newlines_in_multiline_strings # Многострочные строки начинают с новой строки — чище diff
- no_literal_bool_comparisons # Исключить сравнения вида flag == true
- no_runtimeType_toString # runtimeType.toString нестабилен для логики (только отладка)
- prefer_asserts_with_message # Сообщение в assert облегчает диагностику
- prefer_constructors_over_static_methods # Конструкторы лучше выражают создание экземпляра
- comment_references # Корректные ссылки в комментариях (небитые, видимые)
# === Сахар и идиомы ===
- avoid_init_to_null # Не писать = null явно (по умолчанию)
- prefer_if_null_operators # ?? оператор вместо тернарника с null
- prefer_interpolation_to_compose_strings # Интерполяция вместо конкатенации
- prefer_is_empty # .isEmpty вместо .length == 0
- prefer_is_not_empty # .isNotEmpty вместо .length > 0
- use_is_even_rather_than_modulo # isEven более явно и может быть эффективнее
- use_setters_to_change_properties # Изменение полей через сеттеры (инкапсуляция)
- use_string_buffers # StringBuffer эффективнее при конкатенации в циклах
- use_to_and_as_if_applicable # Использовать to*/as* если семантика преобразования доступна
# === Удаление избыточности ===
- avoid_return_types_on_setters # Сеттеры не должны объявлять тип возврата
- unnecessary_const # Убирает лишние const
- unnecessary_new # Убирает лишние new
- unnecessary_this # Убирает лишние this
- unnecessary_parenthesis # Лишние скобки
# === Продакшн / отладка ===
- avoid_print # Запрет print в продакшене (использовать logger)
bloc:
rules:
- avoid_flutter_imports # Логика блока должна быть платформенно независимой
- avoid_public_bloc_methods # Публичные методы могут нарушать чистоту паттерна (использовать события)
- avoid_public_fields # Публичные поля обходят инкапсуляцию; использовать состояние/события
- prefer_file_naming_conventions # Единообразие именования файлов блоков повышает навигацию

View File

@@ -1,4 +1,5 @@
library; library;
export 'src/app_location_service.dart';
export 'src/app_path_provider.dart'; export 'src/app_path_provider.dart';
export 'src/app_secure_storage.dart'; export 'src/app_secure_storage.dart';

View File

@@ -0,0 +1,18 @@
import 'package:i_app_services/i_app_services.dart';
/// {@template app_location_service}
/// Реализация сервиса для работы с гео на платформе Aurora.
/// {@endtemplate}
class AppLocationService implements ILocationService {
/// {@macro app_location_service}
const AppLocationService();
/// Наименование сервиса
static const name = 'AuroraAppLocationService';
@override
Future<Object?> getCurrentPosition() {
// TODO: Реализовать получение текущей позиции в AuroraOS
throw UnimplementedError();
}
}

View File

@@ -1,5 +1,4 @@
import 'package:i_app_services/i_app_services.dart'; import 'package:i_app_services/i_app_services.dart';
import 'package:path_provider/path_provider.dart';
/// {@template app_path_provider} /// {@template app_path_provider}
/// Класс для Аврора реализации сервиса работы с путями /// Класс для Аврора реализации сервиса работы с путями
@@ -13,6 +12,7 @@ class AppPathProvider implements IPathProvider {
@override @override
Future<String> getAppDocumentsDirectoryPath() async { Future<String> getAppDocumentsDirectoryPath() async {
return (await getApplicationDocumentsDirectory()).path; // TODO: Реализовать для AuroraOS
throw UnimplementedError();
} }
} }

View File

@@ -1,5 +1,3 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_secure_storage_aurora/flutter_secure_storage_aurora.dart';
import 'package:i_app_services/i_app_services.dart'; import 'package:i_app_services/i_app_services.dart';
/// {@template app_secure_storage} /// {@template app_secure_storage}
@@ -12,7 +10,8 @@ final class AppSecureStorage implements ISecureStorage {
/// Принимает: /// Принимает:
/// - [secretKey] - ключ шифрования данных /// - [secretKey] - ключ шифрования данных
AppSecureStorage({required this.secretKey}) { AppSecureStorage({required this.secretKey}) {
FlutterSecureStorageAurora.setSecret(secretKey); // Инициализация Aurora Secure Storage с ключом шифрования
// FlutterSecureStorageAurora.setSecret(secretKey);
} }
@override @override
@@ -20,34 +19,36 @@ final class AppSecureStorage implements ISecureStorage {
static const name = 'AuroraAppSecureStorage'; static const name = 'AuroraAppSecureStorage';
/// Экземпляр хранилища данных
final _box = const FlutterSecureStorage();
@override
Future<void> clear() async {
await _box.deleteAll();
}
@override @override
Future<void> delete(String key) async { Future<void> delete(String key) async {
await _box.delete(key: key); // TODO: Реализовать удаление ключа из Aurora Secure Storage
} throw UnimplementedError();
@override
Future<bool> exists(String key) {
return _box.containsKey(key: key);
} }
@override @override
Future<String?> read(String key) async { Future<String?> read(String key) async {
return _box.read(key: key); // TODO: Реализовать чтение значения по ключу из Aurora Secure Storage
throw UnimplementedError();
} }
@override @override
Future<void> write(String key, String value) async { Future<void> write(String key, String value) async {
await _box.write(key: key, value: value); // TODO: Реализовать запись значения по ключу в Aurora Secure Storage
throw UnimplementedError();
} }
@override @override
String get nameImpl => AppSecureStorage.name; String get nameImpl => AppSecureStorage.name;
@override
Future<bool> containsKey(String key) {
// TODO: Реализовать проверку наличия ключа в Aurora Secure Storage
throw UnimplementedError();
}
@override
Future<void> deleteAll() {
// TODO: Реализовать удаление всех ключей из Aurora Secure Storage
throw UnimplementedError();
}
} }

View File

@@ -4,22 +4,12 @@ version: 0.0.1
publish_to: none publish_to: none
environment: environment:
sdk: '>=3.16.2 <4.0.0' sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# Зависимости для сервиса защищенного хранилища
flutter_secure_storage: 8.0.0
flutter_secure_storage_aurora:
git:
url: https://gitlab.com/omprussia/flutter/flutter-community-plugins/flutter_secure_storage_aurora.git
ref: aurora-0.5.3
# для работы с путями (плагин встроен в sdk flutter 3.27.1)
path_provider: 2.1.5
# Обязательные интерфейсы # Обязательные интерфейсы
i_app_services: i_app_services:
path: ../../i_app_services path: ../../i_app_services

View File

@@ -1,89 +1,135 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Включает правила из: # Персонализированные настройки анализатора и линтера.
# - package:lints/core.yaml: основные правила критических проблем # Базовый набор flutter_lints + дополнительные ужесточения.
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "android/**" - "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы)
- "assets/**" - "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером
- "build/**" - "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа
- "config/**" - "assets/**" # Статические ассеты (шрифты, изображения, json и др.)
- "core/**" - "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable
- "res/**" - "**/*.freezed.dart" # Сгенерированные модели пакетом freezed
- "ios/**" - "**/*.gen.dart" # Общий паттерн для доп. генераторов
- "**/*.g.dart" - "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие)
- "**/*.config.dart" - "**/*.config.dart" # Сгенерированные конфигурационные файлы
- "**/*.gen.dart" - "**/generated/**" # Папки с автогенерируемыми исходниками
- "**/*.freezed.dart" - "**/*.lock" # Временные/lock файлы генераторов (если создаются)
- "**/generated/*" - "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён
- "**/*.gr.dart" errors:
- "**/*.yaml" avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность)
- "app_services/aurora/**" avoid_returning_null_for_future: error # Future не должен завершаться null без явной модели
- "/app_services/aurora/**" avoid_slow_async_io: warning # Подсказка о потенциально медленных IO операциях
- "**/app_services/aurora/**" avoid_type_to_string: warning # Предупреждение против использования Type.toString для логики
- "**/*.lock.dart" cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек
errors: close_sinks: error # Обязательное закрытие Sink (ресурсное управление)
# Переопределения уровней ошибок (error/warning/info) comment_references: warning # Проверка корректности ссылок в документационных комментариях
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов avoid_void_async: error # async void нежелателен (трудно ловить ошибки)
comment_references: warning # Проверяет корректность ссылок в комментариях constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE)
always_declare_return_types: error # Требует явного указания возвращаемых типов методов unnecessary_new: warning # Снижение шума: new не нужен в современном Dart
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов
avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона
avoid_return_types_on_setters: warning # Запрещает возвращаемые типы для сеттеров
avoid_returning_null: error # Запрещает возврат null
avoid_setters_without_getters: error # Требует создания геттера при наличии сеттера
avoid_void_async: error # Запрещает использование void для асинхронных функций
constant_identifier_names: error # Проверяет правильность именования констант
unnecessary_new: warning # Запрещает избыточное использование ключевого слова new
use_decorated_box: warning # Рекомендует использовать DecoratedBox вместо Container
use_colored_box: warning # Рекомендует использовать ColoredBox вместо Container с цветом
linter: linter:
rules: rules:
# Нестандартные правила или правила с измененными значениями # === Именование и стиль ===
always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми - always_put_required_named_parameters_first # Обязательные именованные параметры первыми повышают читаемость
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения - always_use_package_imports # Единый стиль импортов через package: упрощает refactoring
avoid_catching_errors: true # Избегать перехвата ошибок типа Error - curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах - directives_ordering # Упорядочивание импортов (dart → package → relative)
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек - eol_at_end_of_file # Пустая строка в конце файла (POSIX standard)
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах - prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов - slash_for_doc_comments # Использовать /// вместо /** */ для документации
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке - sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
avoid_private_typedef_functions: true # Избегать приватных typedef-функций # === Обработка ошибок и типобезопасность ===
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов - avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
avoid_returning_this: true # Избегать возврата this - await_only_futures # await только для Future (ловит ошибки типизации)
cascade_invocations: true # Использовать каскадные вызовы - control_flow_in_finally # Запрет return/break/continue в finally блоках
deprecated_consistency: true # Поддерживать согласованность устаревших элементов - empty_catches # Предупреждение о пустых catch (скрытые баги)
do_not_use_environment: false # Разрешить использование Environment - hash_and_equals # Требует переопределять hashCode и == вместе
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки - only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты
no_runtimeType_toString: true # Не использовать runtimeType.toString() - test_types_in_equals # Проверка типа в equals для безопасности
one_member_abstracts: false # Разрешать абстрактные классы с одним методом - unrelated_type_equality_checks # Запрет сравнения несовместимых типов
only_throw_errors: true # Выбрасывать только объекты Error - use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap
parameter_assignments: true # Запрещать присваивание значений параметрам - use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace)
prefer_asserts_with_message: true # Использовать сообщения с assert - discarded_futures # Выявляет неожиданные Future без await
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам - unawaited_futures # Явно помечать намеренно не ожидаемые Future
prefer_final_in_for_each: true # Использовать final в for-each циклах
prefer_final_locals: true # Использовать final для локальных переменных # === Иммутабельность и const ===
public_member_api_docs: false # Не требовать документацию для всех публичных членов - avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования - prefer_const_constructors # Константные конструкторы для производительности
sort_constructors_first: true # Требовать размещать конструкторы первыми - prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec - prefer_const_literals_to_create_immutables # Константные литералы коллекций
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми - prefer_final_fields # final для полей где возможно
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0 - prefer_final_in_for_each # final в forEach предотвращает случайные изменения
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей - prefer_final_locals # final локальные переменные — стремимся к иммутабельности
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств - parameter_assignments # Не переназначать параметры — вводит путаницу
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости # === Flutter оптимизации ===
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false - avoid_unnecessary_containers # Убирает лишние Container виджеты
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets - sized_box_for_whitespace # SizedBox вместо Container для отступов
always_use_package_imports: true # Всегда использовать package: импорты - sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand
- sort_child_properties_last # child/children последними в виджетах
- use_colored_box # ColoredBox для простого цвета фона
- use_decorated_box # DecoratedBox вместо контейнера без лишних виджетов
- use_key_in_widget_constructors # Ключ позволяет корректно применить дифф к дереву виджетов
# === Современный Dart (2.17+, 3.x) ===
- use_super_parameters # super параметры для краткости
- combinators_ordering # Сортировка show/hide в импортах
- implicit_call_tearoffs # Разрешить неявные tearoffs
- matching_super_parameters # Автоматическое соответствие параметрам супер-конструктора
- use_enums # Предпочитать enums вместо статических констант
# === Читаемость кода ===
- avoid_escaping_inner_quotes # Предпочтительно менять тип кавычек вместо экранирования
- avoid_field_initializers_in_const_classes # Инициализация в константных классах тяжеловесна/избыточна
- avoid_implementing_value_types # Не реализовывать value-type интерфейсы вручную (риск несовпадений)
- avoid_positional_boolean_parameters # Булевые позиционные параметры плохо читаются (использовать именованные)
- avoid_private_typedef_functions # Приватные typedef затрудняют повторное использование / читаемость
- avoid_redundant_argument_values # Удалять аргументы совпадающие с значениями по умолчанию
- avoid_returning_this # Возврат this усложняет fluent API и может скрывать ошибки
- cascade_invocations # Использовать каскады для последовательности операций над объектом
- deprecated_consistency # Единый стиль пометок @deprecated
- leading_newlines_in_multiline_strings # Многострочные строки начинают с новой строки — чище diff
- no_literal_bool_comparisons # Исключить сравнения вида flag == true
- no_runtimeType_toString # runtimeType.toString нестабилен для логики (только отладка)
- prefer_asserts_with_message # Сообщение в assert облегчает диагностику
- prefer_constructors_over_static_methods # Конструкторы лучше выражают создание экземпляра
- comment_references # Корректные ссылки в комментариях (небитые, видимые)
# === Сахар и идиомы ===
- avoid_init_to_null # Не писать = null явно (по умолчанию)
- prefer_if_null_operators # ?? оператор вместо тернарника с null
- prefer_interpolation_to_compose_strings # Интерполяция вместо конкатенации
- prefer_is_empty # .isEmpty вместо .length == 0
- prefer_is_not_empty # .isNotEmpty вместо .length > 0
- use_is_even_rather_than_modulo # isEven более явно и может быть эффективнее
- use_setters_to_change_properties # Изменение полей через сеттеры (инкапсуляция)
- use_string_buffers # StringBuffer эффективнее при конкатенации в циклах
- use_to_and_as_if_applicable # Использовать to*/as* если семантика преобразования доступна
# === Удаление избыточности ===
- avoid_return_types_on_setters # Сеттеры не должны объявлять тип возврата
- unnecessary_const # Убирает лишние const
- unnecessary_new # Убирает лишние new
- unnecessary_this # Убирает лишние this
- unnecessary_parenthesis # Лишние скобки
# === Продакшн / отладка ===
- avoid_print # Запрет print в продакшене (использовать logger)
bloc:
rules:
- avoid_flutter_imports # Логика блока должна быть платформенно независимой
- avoid_public_bloc_methods # Публичные методы могут нарушать чистоту паттерна (использовать события)
- avoid_public_fields # Публичные поля обходят инкапсуляцию; использовать состояние/события
- prefer_file_naming_conventions # Единообразие именования файлов блоков повышает навигацию

View File

@@ -1,4 +1,5 @@
library; library;
export 'src/app_location_service.dart';
export 'src/app_path_provider.dart'; export 'src/app_path_provider.dart';
export 'src/app_secure_storage.dart'; export 'src/app_secure_storage.dart';

View File

@@ -0,0 +1,50 @@
import 'package:geolocator/geolocator.dart';
import 'package:i_app_services/i_app_services.dart';
/// {@template app_location_service}
/// Реализация сервиса для работы с гео в базовой реализацией Android/OS.
/// {@endtemplate}
class AppLocationService implements ILocationService {
/// {@macro app_location_service}
const AppLocationService();
/// Наименование сервиса
static const name = 'BaseAppLocationService';
@override
Future<Position> getCurrentPosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Службы геолокации не включены, не продолжаем
// обращаться к позиции и запрашиваем у пользователей
// приложения включить службы геолокации.
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Разрешения отклонены, в следующий раз можно попробовать
// запросить разрешения снова (здесь также возвращается
// shouldShowRequestPermissionRationale Android.
// Согласно рекомендациям Android ваше приложение
// должно показать пояснительный интерфейс сейчас.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Разрешения отклонены навсегда, обрабатываем соответствующим образом.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.',
);
}
// Когда мы доходим сюда, разрешения предоставлены и мы можем
// продолжить обращение к позиции устройства.
return await Geolocator.getCurrentPosition();
}
}

View File

@@ -4,7 +4,7 @@ version: 0.0.1
publish_to: none publish_to: none
environment: environment:
sdk: ^3.8.0 sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
flutter: flutter:
@@ -14,11 +14,14 @@ dependencies:
flutter_secure_storage: 9.2.4 flutter_secure_storage: 9.2.4
# Зависимости для сервиса незащищенного хранилища # Зависимости для сервиса незащищенного хранилища
shared_preferences: 2.3.5 shared_preferences: 2.5.3
# для работы с путями в хранилища # для работы с путями в хранилища
path_provider: 2.1.5 path_provider: 2.1.5
# Работа с геолокацией
geolocator: 14.0.2
# Обязательные интерфейсы # Обязательные интерфейсы
i_app_services: i_app_services:
path: ../../i_app_services path: ../../i_app_services

View File

@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/

View File

@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "7482962148e8d758338d8a28f589f317e1e42ba4"
channel: "stable"
project_type: package

View File

@@ -0,0 +1 @@
# Реализация сервисов для HarmonyOS

View File

@@ -0,0 +1,135 @@
include: package:flutter_lints/flutter.yaml
# Персонализированные настройки анализатора и линтера.
# Базовый набор flutter_lints + дополнительные ужесточения.
analyzer:
exclude:
- "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы)
- "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером
- "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа
- "assets/**" # Статические ассеты (шрифты, изображения, json и др.)
- "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable
- "**/*.freezed.dart" # Сгенерированные модели пакетом freezed
- "**/*.gen.dart" # Общий паттерн для доп. генераторов
- "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие)
- "**/*.config.dart" # Сгенерированные конфигурационные файлы
- "**/generated/**" # Папки с автогенерируемыми исходниками
- "**/*.lock" # Временные/lock файлы генераторов (если создаются)
- "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён
errors:
avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность)
avoid_returning_null_for_future: error # Future не должен завершаться null без явной модели
avoid_slow_async_io: warning # Подсказка о потенциально медленных IO операциях
avoid_type_to_string: warning # Предупреждение против использования Type.toString для логики
cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек
close_sinks: error # Обязательное закрытие Sink (ресурсное управление)
comment_references: warning # Проверка корректности ссылок в документационных комментариях
always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость
avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false
avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата
avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null
avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние
avoid_void_async: error # async void нежелателен (трудно ловить ошибки)
constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE)
unnecessary_new: warning # Снижение шума: new не нужен в современном Dart
use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов
use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона
linter:
rules:
# === Именование и стиль ===
- always_put_required_named_parameters_first # Обязательные именованные параметры первыми повышают читаемость
- always_use_package_imports # Единый стиль импортов через package: упрощает refactoring
- curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности
- directives_ordering # Упорядочивание импортов (dart → package → relative)
- eol_at_end_of_file # Пустая строка в конце файла (POSIX standard)
- prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
- slash_for_doc_comments # Использовать /// вместо /** */ для документации
- sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
# === Обработка ошибок и типобезопасность ===
- avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
- await_only_futures # await только для Future (ловит ошибки типизации)
- control_flow_in_finally # Запрет return/break/continue в finally блоках
- empty_catches # Предупреждение о пустых catch (скрытые баги)
- hash_and_equals # Требует переопределять hashCode и == вместе
- only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты
- test_types_in_equals # Проверка типа в equals для безопасности
- unrelated_type_equality_checks # Запрет сравнения несовместимых типов
- use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap
- use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace)
- discarded_futures # Выявляет неожиданные Future без await
- unawaited_futures # Явно помечать намеренно не ожидаемые Future
# === Иммутабельность и const ===
- avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях
- prefer_const_constructors # Константные конструкторы для производительности
- prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах
- prefer_const_literals_to_create_immutables # Константные литералы коллекций
- prefer_final_fields # final для полей где возможно
- prefer_final_in_for_each # final в forEach предотвращает случайные изменения
- prefer_final_locals # final локальные переменные — стремимся к иммутабельности
- parameter_assignments # Не переназначать параметры — вводит путаницу
# === Flutter оптимизации ===
- avoid_unnecessary_containers # Убирает лишние Container виджеты
- sized_box_for_whitespace # SizedBox вместо Container для отступов
- sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand
- sort_child_properties_last # child/children последними в виджетах
- use_colored_box # ColoredBox для простого цвета фона
- use_decorated_box # DecoratedBox вместо контейнера без лишних виджетов
- use_key_in_widget_constructors # Ключ позволяет корректно применить дифф к дереву виджетов
# === Современный Dart (2.17+, 3.x) ===
- use_super_parameters # super параметры для краткости
- combinators_ordering # Сортировка show/hide в импортах
- implicit_call_tearoffs # Разрешить неявные tearoffs
- matching_super_parameters # Автоматическое соответствие параметрам супер-конструктора
- use_enums # Предпочитать enums вместо статических констант
# === Читаемость кода ===
- avoid_escaping_inner_quotes # Предпочтительно менять тип кавычек вместо экранирования
- avoid_field_initializers_in_const_classes # Инициализация в константных классах тяжеловесна/избыточна
- avoid_implementing_value_types # Не реализовывать value-type интерфейсы вручную (риск несовпадений)
- avoid_positional_boolean_parameters # Булевые позиционные параметры плохо читаются (использовать именованные)
- avoid_private_typedef_functions # Приватные typedef затрудняют повторное использование / читаемость
- avoid_redundant_argument_values # Удалять аргументы совпадающие с значениями по умолчанию
- avoid_returning_this # Возврат this усложняет fluent API и может скрывать ошибки
- cascade_invocations # Использовать каскады для последовательности операций над объектом
- deprecated_consistency # Единый стиль пометок @deprecated
- leading_newlines_in_multiline_strings # Многострочные строки начинают с новой строки — чище diff
- no_literal_bool_comparisons # Исключить сравнения вида flag == true
- no_runtimeType_toString # runtimeType.toString нестабилен для логики (только отладка)
- prefer_asserts_with_message # Сообщение в assert облегчает диагностику
- prefer_constructors_over_static_methods # Конструкторы лучше выражают создание экземпляра
- comment_references # Корректные ссылки в комментариях (небитые, видимые)
# === Сахар и идиомы ===
- avoid_init_to_null # Не писать = null явно (по умолчанию)
- prefer_if_null_operators # ?? оператор вместо тернарника с null
- prefer_interpolation_to_compose_strings # Интерполяция вместо конкатенации
- prefer_is_empty # .isEmpty вместо .length == 0
- prefer_is_not_empty # .isNotEmpty вместо .length > 0
- use_is_even_rather_than_modulo # isEven более явно и может быть эффективнее
- use_setters_to_change_properties # Изменение полей через сеттеры (инкапсуляция)
- use_string_buffers # StringBuffer эффективнее при конкатенации в циклах
- use_to_and_as_if_applicable # Использовать to*/as* если семантика преобразования доступна
# === Удаление избыточности ===
- avoid_return_types_on_setters # Сеттеры не должны объявлять тип возврата
- unnecessary_const # Убирает лишние const
- unnecessary_new # Убирает лишние new
- unnecessary_this # Убирает лишние this
- unnecessary_parenthesis # Лишние скобки
# === Продакшн / отладка ===
- avoid_print # Запрет print в продакшене (использовать logger)
bloc:
rules:
- avoid_flutter_imports # Логика блока должна быть платформенно независимой
- avoid_public_bloc_methods # Публичные методы могут нарушать чистоту паттерна (использовать события)
- avoid_public_fields # Публичные поля обходят инкапсуляцию; использовать состояние/события
- prefer_file_naming_conventions # Единообразие именования файлов блоков повышает навигацию

View File

@@ -0,0 +1,5 @@
library;
export 'src/app_location_service.dart';
export 'src/app_path_provider.dart';
export 'src/app_secure_storage.dart';

View File

@@ -0,0 +1,18 @@
import 'package:i_app_services/i_app_services.dart';
/// {@template app_location_service}
/// Реализация сервиса для работы с гео на HarmonyOS.
/// {@endtemplate}
class AppLocationService implements ILocationService {
/// {@macro app_location_service}
const AppLocationService();
/// Наименование сервиса
static const name = 'HarmonyAppLocationService';
@override
Future<Object?> getCurrentPosition() {
// TODO: Реализовать получение текущей позиции в HarmonyOS
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,18 @@
import 'package:i_app_services/i_app_services.dart';
/// {@template app_path_provider}
/// Класс для HarmonyOS реализации сервиса работы с путями
/// {@endtemplate}
class AppPathProvider implements IPathProvider {
/// {@macro app_path_provider}
const AppPathProvider();
/// Наименование сервиса
static const name = 'HarmonyAppPathProvider';
@override
Future<String> getAppDocumentsDirectoryPath() async {
// TODO: Реализовать для HarmonyOS
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,50 @@
import 'package:i_app_services/i_app_services.dart';
/// {@template app_secure_storage}
/// Класс для HarmonyOS реализации сервиса по работе с защищенным хранилищем
/// {@endtemplate}
final class AppSecureStorage implements ISecureStorage {
/// Создает сервис для работы с защищенным хранилищем
///
/// Принимает:
/// - [secretKey] - ключ шифрования данных, если требуется
AppSecureStorage({required this.secretKey});
@override
final String secretKey;
static const name = 'HarmonyAppSecureStorage';
@override
Future<void> delete(String key) async {
// TODO: Реализовать удаление ключа из HarmonyOS Secure Storage
throw UnimplementedError();
}
@override
Future<String?> read(String key) async {
// TODO: Реализовать чтение значения по ключу из HarmonyOS Secure Storage
throw UnimplementedError();
}
@override
Future<void> write(String key, String value) async {
// TODO: Реализовать запись значения по ключу в HarmonyOS Secure Storage
throw UnimplementedError();
}
@override
String get nameImpl => AppSecureStorage.name;
@override
Future<bool> containsKey(String key) {
// TODO: Реализовать проверку наличия ключа в HarmonyOS Secure Storage
throw UnimplementedError();
}
@override
Future<void> deleteAll() {
// TODO: Реализовать удаление всех ключей из HarmonyOS Secure Storage
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,18 @@
name: app_services
description: "Huawei сервисы для приложения"
version: 0.0.1
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
# Обязательные интерфейсы
i_app_services:
path: ../../i_app_services
dev_dependencies:
flutter_lints: 6.0.0

View File

@@ -1,89 +1,135 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Включает правила из: # Персонализированные настройки анализатора и линтера.
# - package:lints/core.yaml: основные правила критических проблем # Базовый набор flutter_lints + дополнительные ужесточения.
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "android/**" - "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы)
- "assets/**" - "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером
- "build/**" - "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа
- "config/**" - "assets/**" # Статические ассеты (шрифты, изображения, json и др.)
- "core/**" - "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable
- "res/**" - "**/*.freezed.dart" # Сгенерированные модели пакетом freezed
- "ios/**" - "**/*.gen.dart" # Общий паттерн для доп. генераторов
- "**/*.g.dart" - "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие)
- "**/*.config.dart" - "**/*.config.dart" # Сгенерированные конфигурационные файлы
- "**/*.gen.dart" - "**/generated/**" # Папки с автогенерируемыми исходниками
- "**/*.freezed.dart" - "**/*.lock" # Временные/lock файлы генераторов (если создаются)
- "**/generated/*" - "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён
- "**/*.gr.dart" errors:
- "**/*.yaml" avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность)
- "app_services/aurora/**" avoid_returning_null_for_future: error # Future не должен завершаться null без явной модели
- "/app_services/aurora/**" avoid_slow_async_io: warning # Подсказка о потенциально медленных IO операциях
- "**/app_services/aurora/**" avoid_type_to_string: warning # Предупреждение против использования Type.toString для логики
- "**/*.lock.dart" cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек
errors: close_sinks: error # Обязательное закрытие Sink (ресурсное управление)
# Переопределения уровней ошибок (error/warning/info) comment_references: warning # Проверка корректности ссылок в документационных комментариях
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов avoid_void_async: error # async void нежелателен (трудно ловить ошибки)
comment_references: warning # Проверяет корректность ссылок в комментариях constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE)
always_declare_return_types: error # Требует явного указания возвращаемых типов методов unnecessary_new: warning # Снижение шума: new не нужен в современном Dart
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов
avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона
avoid_return_types_on_setters: warning # Запрещает возвращаемые типы для сеттеров
avoid_returning_null: error # Запрещает возврат null
avoid_setters_without_getters: error # Требует создания геттера при наличии сеттера
avoid_void_async: error # Запрещает использование void для асинхронных функций
constant_identifier_names: error # Проверяет правильность именования констант
unnecessary_new: warning # Запрещает избыточное использование ключевого слова new
use_decorated_box: warning # Рекомендует использовать DecoratedBox вместо Container
use_colored_box: warning # Рекомендует использовать ColoredBox вместо Container с цветом
linter: linter:
rules: rules:
# Нестандартные правила или правила с измененными значениями # === Именование и стиль ===
always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми - always_put_required_named_parameters_first # Обязательные именованные параметры первыми повышают читаемость
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения - always_use_package_imports # Единый стиль импортов через package: упрощает refactoring
avoid_catching_errors: true # Избегать перехвата ошибок типа Error - curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах - directives_ordering # Упорядочивание импортов (dart → package → relative)
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек - eol_at_end_of_file # Пустая строка в конце файла (POSIX standard)
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах - prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов - slash_for_doc_comments # Использовать /// вместо /** */ для документации
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке - sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
avoid_private_typedef_functions: true # Избегать приватных typedef-функций # === Обработка ошибок и типобезопасность ===
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов - avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
avoid_returning_this: true # Избегать возврата this - await_only_futures # await только для Future (ловит ошибки типизации)
cascade_invocations: true # Использовать каскадные вызовы - control_flow_in_finally # Запрет return/break/continue в finally блоках
deprecated_consistency: true # Поддерживать согласованность устаревших элементов - empty_catches # Предупреждение о пустых catch (скрытые баги)
do_not_use_environment: false # Разрешить использование Environment - hash_and_equals # Требует переопределять hashCode и == вместе
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки - only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты
no_runtimeType_toString: true # Не использовать runtimeType.toString() - test_types_in_equals # Проверка типа в equals для безопасности
one_member_abstracts: false # Разрешать абстрактные классы с одним методом - unrelated_type_equality_checks # Запрет сравнения несовместимых типов
only_throw_errors: true # Выбрасывать только объекты Error - use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap
parameter_assignments: true # Запрещать присваивание значений параметрам - use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace)
prefer_asserts_with_message: true # Использовать сообщения с assert - discarded_futures # Выявляет неожиданные Future без await
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам - unawaited_futures # Явно помечать намеренно не ожидаемые Future
prefer_final_in_for_each: true # Использовать final в for-each циклах
prefer_final_locals: true # Использовать final для локальных переменных # === Иммутабельность и const ===
public_member_api_docs: false # Не требовать документацию для всех публичных членов - avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования - prefer_const_constructors # Константные конструкторы для производительности
sort_constructors_first: true # Требовать размещать конструкторы первыми - prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec - prefer_const_literals_to_create_immutables # Константные литералы коллекций
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми - prefer_final_fields # final для полей где возможно
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0 - prefer_final_in_for_each # final в forEach предотвращает случайные изменения
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей - prefer_final_locals # final локальные переменные — стремимся к иммутабельности
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств - parameter_assignments # Не переназначать параметры — вводит путаницу
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости # === Flutter оптимизации ===
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false - avoid_unnecessary_containers # Убирает лишние Container виджеты
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets - sized_box_for_whitespace # SizedBox вместо Container для отступов
always_use_package_imports: true # Всегда использовать package: импорты - sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand
- sort_child_properties_last # child/children последними в виджетах
- use_colored_box # ColoredBox для простого цвета фона
- use_decorated_box # DecoratedBox вместо контейнера без лишних виджетов
- use_key_in_widget_constructors # Ключ позволяет корректно применить дифф к дереву виджетов
# === Современный Dart (2.17+, 3.x) ===
- use_super_parameters # super параметры для краткости
- combinators_ordering # Сортировка show/hide в импортах
- implicit_call_tearoffs # Разрешить неявные tearoffs
- matching_super_parameters # Автоматическое соответствие параметрам супер-конструктора
- use_enums # Предпочитать enums вместо статических констант
# === Читаемость кода ===
- avoid_escaping_inner_quotes # Предпочтительно менять тип кавычек вместо экранирования
- avoid_field_initializers_in_const_classes # Инициализация в константных классах тяжеловесна/избыточна
- avoid_implementing_value_types # Не реализовывать value-type интерфейсы вручную (риск несовпадений)
- avoid_positional_boolean_parameters # Булевые позиционные параметры плохо читаются (использовать именованные)
- avoid_private_typedef_functions # Приватные typedef затрудняют повторное использование / читаемость
- avoid_redundant_argument_values # Удалять аргументы совпадающие с значениями по умолчанию
- avoid_returning_this # Возврат this усложняет fluent API и может скрывать ошибки
- cascade_invocations # Использовать каскады для последовательности операций над объектом
- deprecated_consistency # Единый стиль пометок @deprecated
- leading_newlines_in_multiline_strings # Многострочные строки начинают с новой строки — чище diff
- no_literal_bool_comparisons # Исключить сравнения вида flag == true
- no_runtimeType_toString # runtimeType.toString нестабилен для логики (только отладка)
- prefer_asserts_with_message # Сообщение в assert облегчает диагностику
- prefer_constructors_over_static_methods # Конструкторы лучше выражают создание экземпляра
- comment_references # Корректные ссылки в комментариях (небитые, видимые)
# === Сахар и идиомы ===
- avoid_init_to_null # Не писать = null явно (по умолчанию)
- prefer_if_null_operators # ?? оператор вместо тернарника с null
- prefer_interpolation_to_compose_strings # Интерполяция вместо конкатенации
- prefer_is_empty # .isEmpty вместо .length == 0
- prefer_is_not_empty # .isNotEmpty вместо .length > 0
- use_is_even_rather_than_modulo # isEven более явно и может быть эффективнее
- use_setters_to_change_properties # Изменение полей через сеттеры (инкапсуляция)
- use_string_buffers # StringBuffer эффективнее при конкатенации в циклах
- use_to_and_as_if_applicable # Использовать to*/as* если семантика преобразования доступна
# === Удаление избыточности ===
- avoid_return_types_on_setters # Сеттеры не должны объявлять тип возврата
- unnecessary_const # Убирает лишние const
- unnecessary_new # Убирает лишние new
- unnecessary_this # Убирает лишние this
- unnecessary_parenthesis # Лишние скобки
# === Продакшн / отладка ===
- avoid_print # Запрет print в продакшене (использовать logger)
bloc:
rules:
- avoid_flutter_imports # Логика блока должна быть платформенно независимой
- avoid_public_bloc_methods # Публичные методы могут нарушать чистоту паттерна (использовать события)
- avoid_public_fields # Публичные поля обходят инкапсуляцию; использовать состояние/события
- prefer_file_naming_conventions # Единообразие именования файлов блоков повышает навигацию

View File

@@ -1,4 +1,5 @@
library; library;
export 'src/i_location_service.dart';
export 'src/i_path_provider.dart'; export 'src/i_path_provider.dart';
export 'src/i_secure_storage.dart'; export 'src/i_secure_storage.dart';

View File

@@ -0,0 +1,9 @@
/// {@template i_location_service}
/// Интерфейс для работы с геопозицией пользователя
/// {@endtemplate}
abstract interface class ILocationService {
static const name = 'ILocationService';
/// Метод для получения координат пользователя
Future<dynamic> getCurrentPosition();
}

View File

@@ -4,6 +4,6 @@ abstract interface class IPathProvider {
/// Наименования интерфейса /// Наименования интерфейса
static const name = 'IPathProvider'; static const name = 'IPathProvider';
/// Получение path на внутренне хранилище приложения /// Получение path на внутреннее хранилище приложения
Future<String?> getAppDocumentsDirectoryPath(); Future<String?> getAppDocumentsDirectoryPath();
} }

View File

@@ -1,10 +1,10 @@
name: i_app_services name: i_app_services
description: "Хранит в себе все интерфейсы для реализации общих сервисов" description: "Хранит в себе все интерфейсы для реализации сервисов"
version: 0.0.1 version: 0.0.1
publish_to: "none" publish_to: "none"
environment: environment:
sdk: ^3.8.0 sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
flutter: flutter:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,4 +2,3 @@ arb-dir: lib/l10n
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-dir: lib/l10n/gen output-dir: lib/l10n/gen
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
synthetic-package: false

View File

@@ -1,15 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_providers.dart'; import 'package:friflex_starter/app/app_providers.dart';
import 'package:friflex_starter/app/depends_providers.dart'; import 'package:friflex_starter/app/depends_providers.dart';
import 'package:friflex_starter/app/theme/app_theme.dart'; import 'package:friflex_starter/app/theme/app_theme.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart'; 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/features/error/error_screen.dart'; import 'package:friflex_starter/features/error/error_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart'; import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
import 'package:friflex_starter/features/update/update_routes.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}
@@ -24,10 +27,7 @@ import 'package:go_router/go_router.dart';
/// {@endtemplate} /// {@endtemplate}
class App extends StatefulWidget { class App extends StatefulWidget {
/// {@macro app} /// {@macro app}
const App({required this.router, required this.initDependencies, super.key}); const App({required this.initDependencies, super.key});
/// Роутер приложения для навигации между экранами
final GoRouter router;
/// Функция для инициализации зависимостей приложения /// Функция для инициализации зависимостей приложения
/// Возвращает Future с контейнером зависимостей /// Возвращает Future с контейнером зависимостей
@@ -67,37 +67,22 @@ class _AppState extends State<App> {
builder: () => FutureBuilder<DiContainer>( builder: () => FutureBuilder<DiContainer>(
future: _initFuture, future: _initFuture,
builder: (_, snapshot) { builder: (_, snapshot) {
switch (snapshot.connectionState) { return switch (snapshot.connectionState) {
case ConnectionState.none: // Если состояние не определено, ожидается или активно, то отображаем экран загрузки
case ConnectionState.waiting: ConnectionState.none ||
case ConnectionState.active: ConnectionState.waiting ||
// Пока инициализация показываем Splash ConnectionState.active => const SplashScreen(),
return const SplashScreen(); ConnectionState.done =>
case ConnectionState.done: // Если данные получены и не равны null, то отображаем внутренний виджет приложения
if (snapshot.hasError) { // Иначе отображаем экран ошибки
return ErrorScreen( (snapshot.hasData && snapshot.data != null)
? _AppInternal(diContainer: snapshot.data!)
: ErrorScreen(
error: snapshot.error, error: snapshot.error,
stackTrace: snapshot.stackTrace, stackTrace: snapshot.stackTrace,
onRetry: _retryInit, onRetry: _retryInit,
);
}
final diContainer = snapshot.data;
if (diContainer == null) {
return ErrorScreen(
error:
'Ошибка инициализации зависимостей, diContainer = null',
stackTrace: null,
onRetry: _retryInit,
);
}
return DependsProviders(
diContainer: diContainer,
child: ThemeConsumer(
builder: () => _App(router: widget.router),
), ),
); };
}
}, },
), ),
), ),
@@ -119,16 +104,65 @@ class _AppState extends State<App> {
/// ///
/// Настраивает MaterialApp с роутером, темами и локализацией. /// Настраивает MaterialApp с роутером, темами и локализацией.
/// {@endtemplate} /// {@endtemplate}
class _App extends StatelessWidget { class _AppInternal extends StatefulWidget {
/// {@macro app_internal} /// {@macro app_internal}
const _App({required this.router}); const _AppInternal({
required this.diContainer,
@visibleForTesting this.mockRouter, // ignore: unused_element_parameter
});
/// Контейнер зависимостей
final DiContainer diContainer;
/// Роутер приложения для навигации для тестирования
final GoRouter? mockRouter;
@override
State<_AppInternal> createState() => _AppInternalState();
}
class _AppInternalState extends State<_AppInternal> {
/// Роутер приложения для навигации /// Роутер приложения для навигации
final GoRouter router; late final GoRouter router;
@override
void initState() {
super.initState();
router =
widget.mockRouter ??
AppRouter.createRouter(widget.diContainer.debugService);
}
@override
void dispose() {
router.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp.router( return DependsProviders(
diContainer: widget.diContainer,
child: BlocConsumer<UpdateCubit, UpdateState>(
listener: (context, state) {
if (state is UpdateSuccessState &&
state.updateInfo.updateType == .hard &&
context.mounted) {
router.goNamed(UpdateRoutes.hardUpdateScreenName);
}
},
builder: (context, state) {
// Если состояние загрузки, то отображаем экран загрузки
if (state is UpdateLoadingState) {
return const SplashScreen();
}
return ThemeConsumer(
builder: () => MediaQuery(
key: const ValueKey('prevent_rebuild'),
data: MediaQuery.of(
context,
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
child: MaterialApp.router(
routerConfig: router, routerConfig: router,
darkTheme: AppTheme.dark, darkTheme: AppTheme.dark,
theme: AppTheme.light, theme: AppTheme.light,
@@ -136,6 +170,11 @@ class _App extends StatelessWidget {
locale: context.localization.locale, locale: context.localization.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
),
),
);
},
),
); );
} }
} }

View File

@@ -41,7 +41,7 @@ class AppConfigDev implements IAppConfig {
AppConfigDev(); AppConfigDev();
@override @override
AppEnv get env => AppEnv.dev; AppEnv get env => .dev;
@override @override
String get name => 'AppConfigDev'; String get name => 'AppConfigDev';
@@ -67,7 +67,7 @@ class AppConfigProd implements IAppConfig {
AppConfigProd(); AppConfigProd();
@override @override
AppEnv get env => AppEnv.prod; AppEnv get env => .prod;
@override @override
String get name => 'AppConfigProd'; String get name => 'AppConfigProd';
@@ -93,7 +93,7 @@ class AppConfigStage implements IAppConfig {
AppConfigStage(); AppConfigStage();
@override @override
AppEnv get env => AppEnv.stage; AppEnv get env => .stage;
@override @override
String get name => 'AppConfigStage'; String get name => 'AppConfigStage';

View File

@@ -13,15 +13,15 @@ final class _Dev {
static const String baseUrl = 'https://dev'; static const String baseUrl = 'https://dev';
static const List<int> _enviedkeysecretKey = <int>[ static const List<int> _enviedkeysecretKey = <int>[
1144186709, 1250675737,
921404830, 1700106436,
4081271781, 3701613773,
]; ];
static const List<int> _envieddatasecretKey = <int>[ static const List<int> _envieddatasecretKey = <int>[
1144186673, 1250675837,
921404923, 1700106401,
4081271699, 3701613755,
]; ];
static final String secretKey = String.fromCharCodes( static final String secretKey = String.fromCharCodes(
@@ -38,33 +38,33 @@ final class _Dev {
// generated_from: env/prod.env // generated_from: env/prod.env
final class _Prod { final class _Prod {
static const List<int> _enviedkeybaseUrl = <int>[ static const List<int> _enviedkeybaseUrl = <int>[
3830062036, 2313418965,
1024953349, 1278864221,
2723997296, 2117667009,
2959773775, 1514310825,
4255295633, 996679495,
114701674, 1388625655,
1920572043, 2368060187,
3423730200, 1047428716,
3647804248, 502950869,
2815792265, 2499923893,
3038381766, 963407594,
655048609, 1679400929,
]; ];
static const List<int> _envieddatabaseUrl = <int>[ static const List<int> _envieddatabaseUrl = <int>[
3830062012, 2313418941,
1024953457, 1278864169,
2723997188, 2117666997,
2959773759, 1514310873,
4255295714, 996679476,
114701648, 1388625613,
1920572068, 2368060212,
3423730231, 1047428675,
3647804200, 502950821,
2815792379, 2499923911,
3038381737, 963407493,
655048645, 1679400837,
]; ];
static final String baseUrl = String.fromCharCodes( static final String baseUrl = String.fromCharCodes(
@@ -76,17 +76,17 @@ final class _Prod {
); );
static const List<int> _enviedkeysecretKey = <int>[ static const List<int> _enviedkeysecretKey = <int>[
359753139, 1650398676,
3208048313, 1481444979,
1722903860, 21401329,
3044498179, 1103991377,
]; ];
static const List<int> _envieddatasecretKey = <int>[ static const List<int> _envieddatasecretKey = <int>[
359753155, 1650398628,
3208048331, 1481444865,
1722903899, 21401246,
3044498279, 1103991349,
]; ];
static final String secretKey = String.fromCharCodes( static final String secretKey = String.fromCharCodes(
@@ -103,35 +103,35 @@ final class _Prod {
// generated_from: env/stage.env // generated_from: env/stage.env
final class _Stage { final class _Stage {
static const List<int> _enviedkeybaseUrl = <int>[ static const List<int> _enviedkeybaseUrl = <int>[
2791397647, 1623636460,
1739207173, 1720327728,
306419752, 1318998227,
1371918084, 1880230818,
4062400488, 1752001284,
3004897854, 726589281,
2820011348, 1688681936,
1751321626, 2751223200,
3103957517, 1403987498,
2168627914, 1289212622,
1003673382, 1492662645,
1168070657, 2947900480,
568662299, 809806156,
]; ];
static const List<int> _envieddatabaseUrl = <int>[ static const List<int> _envieddatabaseUrl = <int>[
2791397735, 1623636356,
1739207281, 1720327748,
306419804, 1318998183,
1371918196, 1880230866,
4062400411, 1752001399,
3004897796, 726589275,
2820011387, 1688681983,
1751321653, 2751223183,
3103957630, 1403987545,
2168627902, 1289212602,
1003673415, 1492662548,
1168070758, 2947900455,
568662398, 809806121,
]; ];
static final String baseUrl = String.fromCharCodes( static final String baseUrl = String.fromCharCodes(
@@ -143,19 +143,19 @@ final class _Stage {
); );
static const List<int> _enviedkeysecretKey = <int>[ static const List<int> _enviedkeysecretKey = <int>[
2132342089, 5890476,
2579069434, 3047051242,
3904165526, 2874959210,
3391356107, 758075519,
1192880530, 3738110555,
]; ];
static const List<int> _envieddatasecretKey = <int>[ static const List<int> _envieddatasecretKey = <int>[
2132342074, 5890527,
2579069326, 3047051166,
3904165623, 2874959115,
3391356076, 758075416,
1192880631, 3738110526,
]; ];
static final String secretKey = String.fromCharCodes( static final String secretKey = String.fromCharCodes(

View File

@@ -1,5 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// Класс для внедрения глобальных зависимостей /// Класс для внедрения глобальных зависимостей
@@ -19,6 +23,21 @@ final class DependsProviders extends StatelessWidget {
providers: [ providers: [
// Сюда добавляем глобальные блоки, inherited и т.д. // Сюда добавляем глобальные блоки, inherited и т.д.
Provider.value(value: diContainer), // Передаем контейнер зависимостей Provider.value(value: diContainer), // Передаем контейнер зависимостей
BlocProvider(
create: (_) {
final updateCubit = UpdateCubit(
diContainer.repositories.updatesRepository,
);
unawaited(
updateCubit.checkForUpdates(
versionCode:
'1.0.0', // TODO(yura): заменить на получение из diContainer
platform: 'android',
),
);
return updateCubit;
},
),
], ],
child: child, child: child,
); );

View File

@@ -46,20 +46,20 @@ class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
final Color infoSnackbarBackground; final Color infoSnackbarBackground;
/// Цвета светлой темы /// Цвета светлой темы
static final AppColors light = AppColors( static const AppColors light = AppColors(
testColor: Colors.red, testColor: Colors.red,
errorSnackbarBackground: const Color(0xFFD24720), errorSnackbarBackground: Color(0xFFD24720),
successSnackbarBackground: const Color(0xFF6FB62C), successSnackbarBackground: Color(0xFF6FB62C),
infoSnackbarBackground: const Color.fromARGB(255, 220, 108, 77), infoSnackbarBackground: .fromARGB(255, 220, 108, 77),
itemTextColor: const Color(0xFFFAF3EB), itemTextColor: Color(0xFFFAF3EB),
); );
/// Цвета тёмной темы /// Цвета тёмной темы
static final AppColors dark = AppColors( static const AppColors dark = AppColors(
testColor: Colors.green, testColor: Colors.green,
errorSnackbarBackground: const Color(0xFF638B8B), errorSnackbarBackground: Color(0xFF638B8B),
successSnackbarBackground: const Color(0xFF93C499), successSnackbarBackground: Color(0xFF93C499),
infoSnackbarBackground: const Color.fromARGB(255, 35, 147, 178), infoSnackbarBackground: .fromARGB(255, 35, 147, 178),
itemTextColor: Colors.white, itemTextColor: Colors.white,
); );
} }

View File

@@ -63,7 +63,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: TypeSnackBar.error, type: .error,
displayDuration: displayDuration, displayDuration: displayDuration,
); );
} }
@@ -81,7 +81,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: TypeSnackBar.info, type: .info,
displayDuration: displayDuration, displayDuration: displayDuration,
); );
} }
@@ -99,7 +99,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: TypeSnackBar.success, type: .success,
displayDuration: displayDuration, displayDuration: displayDuration,
); );
} }
@@ -180,11 +180,11 @@ class _AppSnackBarState extends State<AppSnackBar>
CurvedAnimation(parent: _animationController, curve: Curves.easeOut), CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
); );
_animationController.forward(); unawaited(_animationController.forward());
} }
/// Запуск таймера для автоматического закрытия снекбара /// Запуск таймера для автоматического закрытия снекбара
/// Таймер срабатывает по истечении [widget.displayDuration] /// Таймер срабатывает по истечении widget.displayDuration
/// и вызывает метод [_dismiss] для закрытия снекбара /// и вызывает метод [_dismiss] для закрытия снекбара
void _startDismissTimer() { void _startDismissTimer() {
_dismissTimer = Timer(widget.displayDuration, _dismiss); _dismissTimer = Timer(widget.displayDuration, _dismiss);
@@ -192,17 +192,19 @@ class _AppSnackBarState extends State<AppSnackBar>
/// Закрытие снекбара /// Закрытие снекбара
/// Отменяет таймер, если он существует, и запускает обратную анимацию /// Отменяет таймер, если он существует, и запускает обратную анимацию
/// После завершения анимации вызывает функцию [widget.onDismiss], если она задана /// После завершения анимации вызывает функцию widget.onDismiss, если она задана
/// Если виджет не смонтирован, ничего не делает /// Если виджет не смонтирован, ничего не делает
void _dismiss() { void _dismiss() {
if (!mounted) return; if (!mounted) return;
_dismissTimer?.cancel(); _dismissTimer?.cancel();
unawaited(
_animationController.reverse().then((_) { _animationController.reverse().then((_) {
if (mounted) { if (mounted) {
widget.onDismiss?.call(); widget.onDismiss?.call();
} }
}); }),
);
} }
@override @override
@@ -243,7 +245,7 @@ class _AppSnackBarState extends State<AppSnackBar>
Flexible( Flexible(
child: Text( child: Text(
widget.message, widget.message,
style: TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -264,9 +266,9 @@ class _AppSnackBarState extends State<AppSnackBar>
/// [TypeSnackBar.error] - цвет ошибки /// [TypeSnackBar.error] - цвет ошибки
Color _getBackgroundColor(TypeSnackBar type) { Color _getBackgroundColor(TypeSnackBar type) {
return switch (type) { return switch (type) {
TypeSnackBar.success => context.appColors.successSnackbarBackground, .success => context.appColors.successSnackbarBackground,
TypeSnackBar.error => context.appColors.errorSnackbarBackground, .error => context.appColors.errorSnackbarBackground,
TypeSnackBar.info => context.appColors.infoSnackbarBackground, .info => context.appColors.infoSnackbarBackground,
}; };
} }
} }
@@ -288,13 +290,21 @@ class _Icon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return switch (type) { return switch (type) {
TypeSnackBar.success => Icon( .success => const Icon(
Icons.check_circle, Icons.check_circle,
color: Colors.white, color: Colors.white,
size: 32, size: 32,
), ),
TypeSnackBar.error => Icon(Icons.error, color: Colors.white, size: 32), .error => const Icon(
TypeSnackBar.info => Icon(Icons.info, color: Colors.white, size: 32), Icons.error,
color: Colors.white,
size: 32,
),
.info => const Icon(
Icons.info,
color: Colors.white,
size: 32,
),
}; };
} }
} }

View File

@@ -41,9 +41,9 @@ final class DiContainer {
}) async { }) async {
// Инициализация конфигурации приложения // Инициализация конфигурации приложения
appConfig = switch (env) { appConfig = switch (env) {
AppEnv.dev => AppConfigDev(), .dev => AppConfigDev(),
AppEnv.prod => AppConfigProd(), .prod => AppConfigProd(),
AppEnv.stage => AppConfigStage(), .stage => AppConfigStage(),
}; };
// Инициализация HTTP клиента // Инициализация HTTP клиента

View File

@@ -11,19 +11,21 @@ import 'package:friflex_starter/features/main/domain/repository/i_main_repositor
import 'package:friflex_starter/features/profile/data/repository/profile_mock_repository.dart'; import 'package:friflex_starter/features/profile/data/repository/profile_mock_repository.dart';
import 'package:friflex_starter/features/profile/data/repository/profile_repository.dart'; import 'package:friflex_starter/features/profile/data/repository/profile_repository.dart';
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart'; import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
import 'package:friflex_starter/features/update/data/repository/update_mock_repository.dart';
import 'package:friflex_starter/features/update/data/repository/update_repository.dart';
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
/// Список названий моковых репозиториев, которые должны быть подменены /// Список названий моковых репозиториев, которые должны быть подменены
/// для использования в сборке stage окружения. /// для использования в сборке stage окружения.
/// ///
/// Для того, чтобы репозиторий был автоматически подменен на моковый в stage /// Для того, чтобы репозиторий был автоматически подменен на моковый в stage
/// сборке, необходимо в этом списке указать название мокового репозитория, /// сборке, необходимо в этом списке указать тип интерфейса репозитория
/// обращаясь к соответствующему полю name.
/// ///
/// Пример: /// Пример:
/// ``` /// ```
/// [ AuthCheckRepositoryMock().name, ] /// <Type>{ IUpdateRepository }
/// ``` /// ```
final List<String> _mockReposToSwitch = []; const _mockReposToSwitch = <Type>{IUpdateRepository};
/// {@template di_repositories} /// {@template di_repositories}
/// Класс для инициализации и управления репозиториями приложения. /// Класс для инициализации и управления репозиториями приложения.
@@ -52,6 +54,9 @@ final class DiRepositories {
/// Интерфейс для работы с репозиторием профиля /// Интерфейс для работы с репозиторием профиля
late final IProfileRepository profileRepository; late final IProfileRepository profileRepository;
/// Интерфейс для работы с репозиторием обновлений
late final IUpdateRepository updatesRepository;
/// Метод для инициализации репозиториев в приложении. /// Метод для инициализации репозиториев в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -68,7 +73,17 @@ final class DiRepositories {
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
try { onProgress('Начинаем инициализацию репозиториев...');
// Инициализация репозитория обновлений
updatesRepository = _lazyInitRepo<IUpdateRepository>(
mockFactory: UpdateMockRepository.new,
mainFactory: UpdateRepository.new,
onProgress: onProgress,
onError: onError,
environment: diContainer.env,
);
// Инициализация репозитория авторизации // Инициализация репозитория авторизации
authRepository = _lazyInitRepo<IAuthRepository>( authRepository = _lazyInitRepo<IAuthRepository>(
mockFactory: AuthMockRepository.new, mockFactory: AuthMockRepository.new,
@@ -79,18 +94,10 @@ final class DiRepositories {
), ),
), ),
onProgress: onProgress, onProgress: onProgress,
onError: onError,
environment: diContainer.env, environment: diContainer.env,
); );
onProgress(authRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IAuthRepository',
error,
stackTrace,
);
}
try {
// Инициализация репозитория сервиса управления токеном доступа // Инициализация репозитория сервиса управления токеном доступа
mainRepository = _lazyInitRepo<IMainRepository>( mainRepository = _lazyInitRepo<IMainRepository>(
mockFactory: MainMockRepository.new, mockFactory: MainMockRepository.new,
@@ -101,18 +108,10 @@ final class DiRepositories {
), ),
), ),
onProgress: onProgress, onProgress: onProgress,
onError: onError,
environment: diContainer.env, environment: diContainer.env,
); );
onProgress(mainRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IMainRepository',
error,
stackTrace,
);
}
try {
// Инициализация репозитория профиля // Инициализация репозитория профиля
profileRepository = _lazyInitRepo<IProfileRepository>( profileRepository = _lazyInitRepo<IProfileRepository>(
mockFactory: ProfileMockRepository.new, mockFactory: ProfileMockRepository.new,
@@ -123,16 +122,9 @@ final class DiRepositories {
), ),
), ),
onProgress: onProgress, onProgress: onProgress,
onError: onError,
environment: diContainer.env, environment: diContainer.env,
); );
onProgress(profileRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IProfileRepository',
error,
stackTrace,
);
}
onProgress( onProgress(
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})', 'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
@@ -146,27 +138,36 @@ final class DiRepositories {
/// - [mockFactory] - функция-фабрика для инициализации мокового репозитория /// - [mockFactory] - функция-фабрика для инициализации мокового репозитория
/// - [mainFactory] - функция-фабрика для инициализации основного репозитория /// - [mainFactory] - функция-фабрика для инициализации основного репозитория
/// - [onProgress] - обратный вызов для уведомления о прогрессе /// - [onProgress] - обратный вызов для уведомления о прогрессе
/// - [onError] - обратный вызов для обработки ошибок инициализации
/// - [environment] - окружение приложения для определения стратегии инициализации /// - [environment] - окружение приложения для определения стратегии инициализации
/// ///
/// Возвращает: /// Возвращает:
/// - Экземпляр репозитория в зависимости от окружения /// - Экземпляр репозитория в зависимости от окружения
///
/// Throws:
/// - Перебрасывает исключение, если инициализация репозитория завершилась с ошибкой
T _lazyInitRepo<T extends DiBaseRepo>({ T _lazyInitRepo<T extends DiBaseRepo>({
required AppEnv environment, required AppEnv environment,
required T Function() mainFactory, required T Function() mainFactory,
required T Function() mockFactory, required T Function() mockFactory,
required OnProgress onProgress, required OnProgress onProgress,
required OnError onError,
}) { }) {
final mockRepo = mockFactory(); try {
final mainRepo = mainFactory();
final repo = switch (environment) { final repo = switch (environment) {
AppEnv.dev => mockRepo, .dev => mockFactory(),
AppEnv.prod => mainRepo, .prod => mainFactory(),
AppEnv.stage => .stage =>
_mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo, _mockReposToSwitch.contains(T) ? mockFactory() : mainFactory(),
}; };
// throw Exception('Тестовая - ошибка инициализации репозитория $T');
onProgress(repo.name); onProgress(repo.name);
return repo; return repo;
} on Object catch (error, stackTrace) {
onError('Ошибка инициализации репозитория $T', error, stackTrace);
// Перебрасываем исключение дальше, чтобы не скрыть ошибку инициализации
rethrow;
}
} }
} }

View File

@@ -22,6 +22,9 @@ final class DiServices {
/// Сервис для работы с защищенным локальным хранилищем /// Сервис для работы с защищенным локальным хранилищем
late final ISecureStorage secureStorage; late final ISecureStorage secureStorage;
/// Сервис для работы с геолокацией
late final ILocationService locationService;
/// Метод для инициализации сервисов в приложении. /// Метод для инициализации сервисов в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -38,6 +41,7 @@ final class DiServices {
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
try { try {
// throw Exception('Тестовая - ошибка инициализации сервиса путей');
pathProvider = const AppPathProvider(); pathProvider = const AppPathProvider();
onProgress(AppPathProvider.name); onProgress(AppPathProvider.name);
} on Object catch (error, stackTrace) { } on Object catch (error, stackTrace) {
@@ -52,6 +56,17 @@ final class DiServices {
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace); onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
} }
try {
locationService = const AppLocationService();
onProgress(AppLocationService.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации ${ILocationService.name}',
error,
stackTrace,
);
}
onProgress('Инициализация сервисов завершена!'); onProgress('Инициализация сервисов завершена!');
} }
} }

View File

@@ -1,17 +1,19 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
/// Интерфейс для сервиса отладки /// Интерфейс для сервиса отладки
abstract interface class IDebugService { abstract interface class IDebugService {
static const name = 'IDebugService'; static const name = 'IDebugService';
/// Наблюдение за dio /// Наблюдение за dio
dynamic get dioLogger; Interceptor get dioLogger;
/// Наблюдение за роутами /// Наблюдение за роутами
dynamic get routeObserver; NavigatorObserver get routeObserver;
/// Наблюдение за BLoC /// Наблюдение за BLoC
dynamic get blocObserver; BlocObserver get blocObserver;
/// Метод для логирования сообщений /// Метод для логирования сообщений
void log(Object message, {Object logLevel, Map<String, dynamic>? args}); void log(Object message, {Object logLevel, Map<String, dynamic>? args});

View File

@@ -1,6 +1,13 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart'; import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart'; import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
import 'package:friflex_starter/features/update/presentation/components/soft_modal_sheet.dart';
import 'package:friflex_starter/features/update/update_routes.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
/// {@template components_screen} /// {@template components_screen}
/// Экран для демонстрации и тестирования компонентов приложения. /// Экран для демонстрации и тестирования компонентов приложения.
@@ -66,6 +73,35 @@ class _ComponentsScreenState extends State<ComponentsScreen> {
}, },
child: const Text('Показать снекбар с информацией'), child: const Text('Показать снекбар с информацией'),
), ),
const HBox(16),
ElevatedButton(
onPressed: () {
final updateCubitState = context.read<UpdateCubit>().state;
if (updateCubitState is UpdateSuccessState &&
updateCubitState.updateInfo.updateType == .soft) {
unawaited(
SoftUpdateModal.show(
context,
updateEntity: updateCubitState.updateInfo,
onUpdate: () {
AppSnackBar.showSuccess(
context: context,
message: 'Начато обновление приложения',
);
},
),
);
}
},
child: const Text('Показать модальное окно обновления'),
),
const HBox(16),
ElevatedButton(
onPressed: () {
unawaited(context.pushNamed(UpdateRoutes.hardUpdateScreenName));
},
child: const Text('Переход на экран Hard Update обновления'),
),
], ],
), ),
), ),

View File

@@ -1,6 +1,8 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/features/debug/debug_routes.dart'; import 'package:friflex_starter/features/debug/debug_routes.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -35,42 +37,42 @@ class DebugScreen extends StatelessWidget {
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.iconsScreenName); unawaited(context.pushNamed(DebugRoutes.iconsScreenName));
}, },
child: const Text('Экран с иконками'), child: const Text('Экран с иконками'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.themeScreenName); unawaited(context.pushNamed(DebugRoutes.themeScreenName));
}, },
child: const Text('Экран настроек темы'), child: const Text('Экран настроек темы'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.tokensScreenName); unawaited(context.pushNamed(DebugRoutes.tokensScreenName));
}, },
child: const Text('Экран с токенами'), child: const Text('Экран с токенами'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.uiKitScreenName); unawaited(context.pushNamed(DebugRoutes.uiKitScreenName));
}, },
child: const Text('Экран UI Kit'), child: const Text('Экран UI Kit'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.langScreenName); unawaited(context.pushNamed(DebugRoutes.langScreenName));
}, },
child: const Text('Экран локализации'), child: const Text('Экран локализации'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () async {
context.pushNamed(DebugRoutes.componentsScreenName); await context.pushNamed(DebugRoutes.componentsScreenName);
}, },
child: const Text('Экран компонентов'), child: const Text('Экран компонентов'),
), ),

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/theme/app_colors_scheme.dart'; import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
import 'package:friflex_starter/gen/assets.gen.dart';
import 'package:friflex_starter/gen/fonts.gen.dart';
/// {@template lang_screen} /// {@template lang_screen}
/// Экран для отладки и тестирования локализации приложения. /// Экран для отладки и тестирования локализации приложения.
@@ -42,18 +40,12 @@ class LangScreen extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Тестовое слово bold: ${context.l10n.helloWorld}', 'Тестовое слово bold: ${context.l10n.helloWorld}',
style: TextStyle( style: TextStyle(color: context.appColors.testColor),
color: context.appColors.testColor,
fontFamily: Assets.fonts.montserratBold,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Тестовое слово medium: ${context.l10n.helloWorld}', 'Тестовое слово medium: ${context.l10n.helloWorld}',
style: TextStyle( style: TextStyle(color: context.appColors.testColor),
color: context.appColors.testColor,
fontFamily: FontFamily.montserrat,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text('Текущий язык: ${context.l10n.localeName}'), Text('Текущий язык: ${context.l10n.localeName}'),

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart'; import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/features/main/presentation/main_routes.dart'; import 'package:friflex_starter/features/main/presentation/main_routes.dart';
@@ -23,7 +25,7 @@ class MainScreen extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
// Переход на экран с деталями // Переход на экран с деталями
context.pushNamed(MainRoutes.mainDetailScreenName); unawaited(context.pushNamed(MainRoutes.mainDetailScreenName));
}, },
child: const Text('Переход на экран с деталями'), child: const Text('Переход на экран с деталями'),
), ),

View File

@@ -16,9 +16,14 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) { ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
// Регистрируем обработчики событий в конструкторе // Регистрируем обработчики событий в конструкторе
on<ProfileEvent>((event, emit) async { on<ProfileEvent>((event, emit) async {
// Обрабатываем событие загрузки профиля
if (event is ProfileFetchProfileEvent) { if (event is ProfileFetchProfileEvent) {
await _fetchProfile(event, emit); await _fetchProfile(event, emit);
} }
// Обрабатываем событие выхода из профиля
else if (event is ProfileLogoutProfileEvent) {
await _logout(event, emit);
}
}); });
} }
@@ -44,6 +49,7 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
final data = await _profileRepository.fetchUserProfile(event.id); final data = await _profileRepository.fetchUserProfile(event.id);
emit(ProfileSuccessState(data: data)); emit(ProfileSuccessState(data: data));
} on Object catch (error, stackTrace) { } on Object catch (error, stackTrace) {
// Обработка ошибки при загрузке профиля
emit( emit(
ProfileErrorState( ProfileErrorState(
message: 'Ошибка при загрузке профиля', message: 'Ошибка при загрузке профиля',
@@ -51,6 +57,19 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
stackTrace: stackTrace, stackTrace: stackTrace,
), ),
); );
// Пробрасываем исключение в BlocObserver, для логирования или обработки
addError(error, stackTrace);
} }
} }
/// Метод для выхода из профиля пользователя.
Future<void> _logout(
ProfileLogoutProfileEvent event,
Emitter<ProfileState> emit,
) async {
// Здесь можно добавить логику выхода из профиля
// Например, очистка токенов, данных пользователя и т.д.
// В данном примере просто эмитим начальное состояние
emit(ProfileInitialState());
}
} }

View File

@@ -1,16 +1,37 @@
part of 'profile_bloc.dart'; part of 'profile_bloc.dart';
/// {@template profile_event}
/// События для управления состоянием профиля пользователя.
/// {@endtemplate}
sealed class ProfileEvent extends Equatable { sealed class ProfileEvent extends Equatable {
/// {@macro profile_event}
const ProfileEvent(); const ProfileEvent();
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
/// {@template profile_event}
/// Событие для загрузки профиля пользователя.
/// {@endtemplate}
final class ProfileFetchProfileEvent extends ProfileEvent { final class ProfileFetchProfileEvent extends ProfileEvent {
/// {@macro profile_event}
const ProfileFetchProfileEvent({required this.id}); const ProfileFetchProfileEvent({required this.id});
/// ID пользователя для загрузки профиля
final String id; final String id;
@override @override
List<Object> get props => [id]; List<Object> get props => [id];
} }
/// {@template profile_logout_event}
/// Событие для выхода из профиля пользователя.
/// {@endtemplate}
final class ProfileLogoutProfileEvent extends ProfileEvent {
/// {@macro profile_logout_event}
const ProfileLogoutProfileEvent();
@override
List<Object> get props => [];
}

View File

@@ -1,7 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/features/debug/debug_routes.dart'; import 'package:friflex_starter/features/debug/debug_routes.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
import 'package:friflex_starter/features/update/presentation/components/soft_modal_sheet.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template root_screen} /// {@template root_screen}
@@ -13,7 +17,7 @@ import 'package:go_router/go_router.dart';
/// - Отображение кнопки отладки в не-продакшн окружениях /// - Отображение кнопки отладки в не-продакшн окружениях
/// - Интеграцию с GoRouter для навигации /// - Интеграцию с GoRouter для навигации
/// {@endtemplate} /// {@endtemplate}
class RootScreen extends StatelessWidget { class RootScreen extends StatefulWidget {
/// {@macro root_screen} /// {@macro root_screen}
const RootScreen({required this.navigationShell, super.key}); const RootScreen({required this.navigationShell, super.key});
@@ -21,25 +25,59 @@ class RootScreen extends StatelessWidget {
/// Содержит информацию о текущем состоянии навигации /// Содержит информацию о текущем состоянии навигации
final StatefulNavigationShell navigationShell; final StatefulNavigationShell navigationShell;
@override
State<RootScreen> createState() => _RootScreenState();
}
class _RootScreenState extends State<RootScreen> {
@override
void initState() {
super.initState();
// После построения виджета, проверяем состояние кубита обновлений
// и если есть обновление, то показываем модальное окно
_checkSoftUpdate();
}
/// Проверяет состояние кубита обновлений и показывает модальное окно при наличии мягкого обновления
void _checkSoftUpdate() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final updateState = context.read<UpdateCubit>().state;
// Проверяем только состояние успеха с доступной информацией об обновлении
if (updateState is UpdateSuccessState &&
updateState.updateInfo.updateType == .soft) {
unawaited(
SoftUpdateModal.show(
context,
updateEntity: updateState.updateInfo,
onUpdate: () {
// TODO(yura): реализовать логику обновления приложения
},
),
);
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
floatingActionButton: context.di.env != AppEnv.prod floatingActionButton: context.di.env != .prod
? FloatingActionButton( ? FloatingActionButton(
child: const Icon(Icons.bug_report), child: const Icon(Icons.bug_report),
onPressed: () { onPressed: () {
context.pushNamed(DebugRoutes.debugScreenName); unawaited(context.pushNamed(DebugRoutes.debugScreenName));
}, },
) )
: null, : null,
body: navigationShell, body: widget.navigationShell,
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[ items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'), BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'), BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
], ],
currentIndex: navigationShell.currentIndex, currentIndex: widget.navigationShell.currentIndex,
onTap: navigationShell.goBranch, onTap: widget.navigationShell.goBranch,
), ),
); );
} }

View File

@@ -0,0 +1,88 @@
# Модуль Hard/Soft Updates
Модуль для управления обновлениями приложения. Поддерживает мягкие (soft) и обязательные (hard) обновления.
## Ключевые сущности и состояния
- **`UpdateEntity`**: доменная сущность с данными об обновлении
- `availableVersion`: доступная версия
- `updateUrl`: ссылка на обновление
- `updateType`: тип (`soft` | `hard`), см. `UpdateType`
- `whatIsNew`: описание изменений
- **`UpdateType`**: перечисление типов обновления
- `UpdateType.soft`
- `UpdateType.hard`
- `UpdateType.none`
- **`UpdateCubit`**: управление состоянием проверки обновлений
- Состояния: `UpdateInitialState`, `UpdateLoadingState`, `UpdateSuccessState(UpdateEntity?)`, `UpdateErrorState(message)`
- Метод: `checkForUpdates({required String versionCode, required String platform})`
## Репозитории
- **`IUpdateRepository`**: Интерфейс, описывающий методы для проверки обновлений.
- Возвращает `Future<UpdateEntity>` (не может быть `null`)
- **`UpdateRepository`**: заготовка для реальной интеграции (бэкенд/стор)
- Реализуйте логику в `checkForUpdates`
- **`UpdateMockRepository`**: мок-реализация для разработки/демо
- Возвращает фиктивное обновление (по умолчанию soft)
## UI
- **Soft update** — `SoftUpdateModal`
- BottomSheet с заголовком, списком изменений и кнопками: «Отложить» и «Обновить»
- Статический метод `show` безопасно не откроет модалку, если `updateEntity == null`
Пример показа модального окна:
```dart
await SoftUpdateModal.show(
context,
updateEntity: updateEntity, // экземпляр UpdateEntity
onUpdate: () {
// TODO: переход в стор/браузер по updateEntity.updateUrl
},
);
```
- **Hard update** — `HardUpdateScreen`
- Блокирующий экран, информирует и не даёт продолжить без обновления
## Роуты
- `UpdateRoutes.buildRoutes()` — регистрирует экран hard-обновления по пути `/update`
## Структура модуля
```
features/update/
├── data/
│ └── repository/
│ ├── update_repository.dart # реализация для интеграции
│ └── update_mock_repository.dart # мок-репозиторий
├── domain/
│ ├── entity/
│ │ └── update_entity.dart # доменная сущность
│ ├── repository/
│ │ └── i_update_repository.dart # контракт репозитория
│ └── state/
│ └── cubit/
│ ├── update_cubit.dart # кубит и логика
│ └── update_state.dart # состояния
├── presentation/
│ ├── components/
│ │ └── soft_modal_sheet.dart # модалка для soft-обновления
│ └── screens/
│ └── hard_update_screen.dart # экран для hard-обновления
├── update_type.dart # константы типов обновления
└── update_routes.dart # роут на hard-экран
```
## Заметки по реализации
- Для продакшена реализуйте переход в магазин или браузер по `updateUrl`
- Для soft-обновления не блокируйте UX; для hard — перенаправляйте на блокирующий экран
- Возвращайте `null` из репозитория, если обновлений нет

View File

@@ -0,0 +1,42 @@
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
/// Мок обновления обязательное, можно использовать для тестирования
const mockHardUpdateEntity = UpdateEntity(
availableVersion: '2.0.0',
updateUrl: 'https://example.com/update',
updateType: .hard,
whatIsNew: 'Добавлены новые функции и исправлены ошибки.',
);
/// Мок обновления мягкое, можно использовать для тестирования
const mockSoftUpdateEntity = UpdateEntity(
availableVersion: '2.0.0',
updateUrl: 'https://example.com/update',
updateType: .soft,
whatIsNew: 'Добавлены новые функции и исправлены ошибки.',
);
/// {@template UpdateMockRepository}
/// Репозиторий для моковой реализации проверки обновлений
/// {@endtemplate}
final class UpdateMockRepository implements IUpdateRepository {
/// {@macro UpdateMockRepository}
const UpdateMockRepository();
@override
Future<UpdateEntity> checkForUpdates({
required String versionCode,
required String platform,
}) async {
// Имитация задержки для асинхронной операции
await Future<void>.delayed(const Duration(seconds: 1));
// Возвращаем фиктивные данные об обновлении
// Можно возвращать [_mockHardUpdateEntity] или [_mockSoftUpdateEntity]
return mockSoftUpdateEntity;
}
@override
String get name => 'UpdateMockRepository';
}

View File

@@ -0,0 +1,23 @@
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
/// {@template UpdateRepository}
/// Репозиторий для реализации проверки обновлений
/// {@endtemplate}
final class UpdateRepository implements IUpdateRepository {
/// {@macro UpdateRepository}
const UpdateRepository();
@override
Future<UpdateEntity> checkForUpdates({
required String versionCode,
required String platform,
}) {
// TODO: Реализовать реальную логику проверки обновлений
// Если обновления нет, возвращаем null
throw UnimplementedError();
}
@override
String get name => 'UpdateRepository';
}

View File

@@ -0,0 +1,35 @@
import 'package:equatable/equatable.dart';
import 'package:friflex_starter/features/update/update_type.dart';
/// {@template UpdateEntity}
/// Сущность для представления информации об обновлении
/// {@endtemplate}
class UpdateEntity extends Equatable {
/// {@macro UpdateEntity}
const UpdateEntity({
required this.availableVersion,
required this.updateUrl,
required this.updateType,
required this.whatIsNew,
});
/// Доступная версия обновления
final String availableVersion;
/// URL для загрузки обновления
final String updateUrl;
/// Тип обновления (например, 'hard' или 'soft', или не требуется)
final UpdateType updateType;
/// Описание изменений в обновлении
final String whatIsNew;
@override
List<Object?> get props => [
availableVersion,
updateUrl,
updateType,
whatIsNew,
];
}

View File

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

View File

@@ -0,0 +1,38 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
part 'update_state.dart';
/// {@template UpdateCubit}
/// Кубит для управления состояниями обновления приложения
/// {@endtemplate}
class UpdateCubit extends Cubit<UpdateState> {
/// {@macro UpdateCubit}
UpdateCubit(this._updatesRepository) : super(const UpdateInitialState());
/// Репозиторий для проверки обновлений
final IUpdateRepository _updatesRepository;
/// Метод для проверки доступности обновлений
/// [versionCode] - текущий код версии приложения
/// [platform] - платформа (например, 'android' или 'ios')
Future<void> checkForUpdates({
required String versionCode,
required String platform,
}) async {
if (state is UpdateLoadingState) return;
emit(const UpdateLoadingState());
try {
final updateInfo = await _updatesRepository.checkForUpdates(
versionCode: versionCode,
platform: platform,
);
emit(UpdateSuccessState(updateInfo));
} on Object catch (e, st) {
emit(UpdateErrorState(e.toString()));
addError(e, st);
}
}
}

View File

@@ -0,0 +1,56 @@
part of 'update_cubit.dart';
/// {@template UpdateState}
/// Состояния для управления процессом обновления приложения
/// {@endtemplate}
sealed class UpdateState extends Equatable {
/// {@macro UpdateState}
const UpdateState();
@override
List<Object?> get props => [];
}
/// {@template UpdateInitialState}
/// Состояние начальной инициализации
/// {@endtemplate}
final class UpdateInitialState extends UpdateState {
/// {@macro UpdateInitialState}
const UpdateInitialState();
}
/// {@template UpdateLoadingState}
/// Состояние загрузки информации об обновлении
/// {@endtemplate}
final class UpdateLoadingState extends UpdateState {
/// {@macro UpdateLoadingState}
const UpdateLoadingState();
}
/// {@template UpdateSuccessState}
/// Состояние успешного получения информации об обновлении
/// {@endtemplate}
final class UpdateSuccessState extends UpdateState {
/// {@macro UpdateSuccessState}
const UpdateSuccessState(this.updateInfo);
/// Информация об обновлении
final UpdateEntity updateInfo;
@override
List<Object?> get props => [updateInfo];
}
/// {@template UpdateErrorState}
/// Состояние ошибки при получении информации об обновлении
/// {@endtemplate}
final class UpdateErrorState extends UpdateState {
/// {@macro UpdateErrorState}
const UpdateErrorState(this.message);
/// Сообщение об ошибке в UI
final String message;
@override
List<Object> get props => [message];
}

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
import 'package:go_router/go_router.dart';
/// {@template soft_update_modal}
/// Модальное окно для уведомления о доступности новой версии приложения.
///
/// Отвечает за:
/// - Отображение информации о новой версии приложения
/// - Предоставление возможности обновления или отложения
/// - Показ описания изменений в новой версии
/// - Мягкое уведомление пользователя без принуждения к обновлению
/// {@endtemplate}
class SoftUpdateModal extends StatelessWidget {
/// {@macro soft_update_modal}
const SoftUpdateModal({required this.updateEntity, this.onUpdate, super.key});
/// Информация об обновлении
final UpdateEntity updateEntity;
/// Обратный вызов при нажатии "Обновить"
final VoidCallback? onUpdate;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Заголовок
Text(
'Доступна новая версия: ${updateEntity.availableVersion} ',
style: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
),
const HBox(16),
// Описание изменений
Text(
'Что нового:',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
const HBox(8),
Text(
updateEntity.whatIsNew,
style: Theme.of(context).textTheme.bodyMedium,
),
const HBox(24),
// Кнопки действий
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
context.pop();
},
child: const Text('Отложить'),
),
),
const WBox(12),
Expanded(
child: ElevatedButton(
onPressed: () {
context.pop();
onUpdate?.call();
},
child: const Text('Обновить'),
),
),
],
),
],
),
);
}
/// Показать модальное окно обновления
///
/// [context] - контекст для отображения модального окна
/// [updateEntity] - информация об обновлении
/// [onUpdate] - функция при нажатии "Обновить"
static Future<void> show(
BuildContext context, {
required UpdateEntity updateEntity,
VoidCallback? onUpdate,
}) {
return showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (context) =>
SoftUpdateModal(updateEntity: updateEntity, onUpdate: onUpdate),
);
}
}

View File

@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
/// Блокирующий экран для обязательного обновления приложения
class HardUpdateScreen extends StatelessWidget {
const HardUpdateScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hard Обновление')),
body: Center(
child: BlocBuilder<UpdateCubit, UpdateState>(
builder: (context, updateCubitState) {
final updateEntity = updateCubitState is UpdateSuccessState
? updateCubitState.updateInfo
: null;
return Column(
children: [
const Text(
'Доступна новая версия приложения. Пожалуйста, обновите его.',
),
const HBox(16),
Text(
'Доступная версия: ${updateEntity?.availableVersion ?? ''}',
),
const HBox(8),
Text('Что нового: ${updateEntity?.whatIsNew ?? ''}'),
const HBox(8),
Text('Тип обновления: ${updateEntity?.updateType ?? ''}'),
const HBox(8),
Text('URL для обновления: ${updateEntity?.updateUrl ?? ''}'),
],
);
},
),
),
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:friflex_starter/features/update/presentation/screens/hard_update_screen.dart';
import 'package:go_router/go_router.dart';
abstract final class UpdateRoutes {
/// Название роута главной страницы
static const String hardUpdateScreenName = 'update_screen';
/// Путь роута экрана обновления
static const String _hardUpdateScreenPath = '/update';
/// Метод для построения роутов по Update
///
/// Принимает:
/// - [routes] - вложенные роуты
static GoRoute buildRoutes({List<RouteBase> routes = const []}) => GoRoute(
path: _hardUpdateScreenPath,
name: hardUpdateScreenName,
builder: (context, state) => const HardUpdateScreen(),
);
}

View File

@@ -0,0 +1,13 @@
/// {@template UpdateType}
/// Тип обновления
/// {@endtemplate}
enum UpdateType {
/// Обязательное обновление
hard,
/// Мягкое обновление
soft,
/// Не требуется обновление
none,
}

View File

@@ -1,3 +1,5 @@
// dart format width=80
/// GENERATED CODE - DO NOT MODIFY BY HAND /// GENERATED CODE - DO NOT MODIFY BY HAND
/// ***************************************************** /// *****************************************************
/// FlutterGen /// FlutterGen
@@ -5,7 +7,7 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use // ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@@ -13,34 +15,6 @@ import 'package:flutter_svg/flutter_svg.dart' as _svg;
import 'package:lottie/lottie.dart' as _lottie; import 'package:lottie/lottie.dart' as _lottie;
import 'package:vector_graphics/vector_graphics.dart' as _vg; import 'package:vector_graphics/vector_graphics.dart' as _vg;
class $AssetsFontsGen {
const $AssetsFontsGen();
/// File path: assets/fonts/Montserrat-Bold.ttf
String get montserratBold => 'assets/fonts/Montserrat-Bold.ttf';
/// File path: assets/fonts/Montserrat-ExtraBold.ttf
String get montserratExtraBold => 'assets/fonts/Montserrat-ExtraBold.ttf';
/// File path: assets/fonts/Montserrat-Medium.ttf
String get montserratMedium => 'assets/fonts/Montserrat-Medium.ttf';
/// File path: assets/fonts/Montserrat-Regular.ttf
String get montserratRegular => 'assets/fonts/Montserrat-Regular.ttf';
/// File path: assets/fonts/Montserrat-SemiBold.ttf
String get montserratSemiBold => 'assets/fonts/Montserrat-SemiBold.ttf';
/// List of all assets
List<String> get values => [
montserratBold,
montserratExtraBold,
montserratMedium,
montserratRegular,
montserratSemiBold,
];
}
class $AssetsIconsGen { class $AssetsIconsGen {
const $AssetsIconsGen(); const $AssetsIconsGen();
@@ -63,9 +37,8 @@ class $AssetsLottieGen {
} }
class Assets { class Assets {
Assets._(); const Assets._();
static const $AssetsFontsGen fonts = $AssetsFontsGen();
static const $AssetsIconsGen icons = $AssetsIconsGen(); static const $AssetsIconsGen icons = $AssetsIconsGen();
static const $AssetsLottieGen lottie = $AssetsLottieGen(); static const $AssetsLottieGen lottie = $AssetsLottieGen();
} }
@@ -96,6 +69,7 @@ class SvgGenImage {
String? semanticsLabel, String? semanticsLabel,
bool excludeFromSemantics = false, bool excludeFromSemantics = false,
_svg.SvgTheme? theme, _svg.SvgTheme? theme,
_svg.ColorMapper? colorMapper,
ColorFilter? colorFilter, ColorFilter? colorFilter,
Clip clipBehavior = Clip.hardEdge, Clip clipBehavior = Clip.hardEdge,
@deprecated Color? color, @deprecated Color? color,
@@ -115,6 +89,7 @@ class SvgGenImage {
assetBundle: bundle, assetBundle: bundle,
packageName: package, packageName: package,
theme: theme, theme: theme,
colorMapper: colorMapper,
); );
} }
return _svg.SvgPicture( return _svg.SvgPicture(
@@ -171,6 +146,9 @@ class LottieGenImage {
bool? addRepaintBoundary, bool? addRepaintBoundary,
FilterQuality? filterQuality, FilterQuality? filterQuality,
void Function(String)? onWarning, void Function(String)? onWarning,
_lottie.LottieDecoder? decoder,
_lottie.RenderCache? renderCache,
bool? backgroundLoading,
}) { }) {
return _lottie.Lottie.asset( return _lottie.Lottie.asset(
_assetName, _assetName,
@@ -195,6 +173,9 @@ class LottieGenImage {
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality, filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
decoder: decoder,
renderCache: renderCache,
backgroundLoading: backgroundLoading,
); );
} }

View File

@@ -1,15 +0,0 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
class FontFamily {
FontFamily._();
/// Font family: Montserrat
static const String montserrat = 'Montserrat';
}

View File

@@ -1,4 +1,3 @@
import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/runner/app_runner.dart'; import 'package:friflex_starter/runner/app_runner.dart';
void main() => AppRunner(AppEnv.prod).run(); void main() => AppRunner(.prod).run();

View File

@@ -5,6 +5,7 @@ import 'package:friflex_starter/features/main/presentation/main_routes.dart';
import 'package:friflex_starter/features/profile/presentation/profile_routes.dart'; import 'package:friflex_starter/features/profile/presentation/profile_routes.dart';
import 'package:friflex_starter/features/root/root_screen.dart'; import 'package:friflex_starter/features/root/root_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart'; import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:friflex_starter/features/update/update_routes.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template app_router} /// {@template app_router}
@@ -42,6 +43,7 @@ class AppRouter {
path: '/splash', path: '/splash',
builder: (context, state) => const SplashScreen(), builder: (context, state) => const SplashScreen(),
), ),
UpdateRoutes.buildRoutes(),
], ],
); );
} }

View File

@@ -65,14 +65,10 @@ class AppRunner {
// Инициализация метода обработки ошибок // Инициализация метода обработки ошибок
_initErrorHandlers(_debugService); _initErrorHandlers(_debugService);
// Инициализация роутера
router = AppRouter.createRouter(_debugService);
// throw Exception('Test error'); // throw Exception('Test error');
runApp( runApp(
App( App(
router: router,
initDependencies: () { initDependencies: () {
return _initDependencies( return _initDependencies(
debugService: _debugService, debugService: _debugService,
@@ -125,9 +121,6 @@ class AppRunner {
required AppEnv env, required AppEnv env,
required TimerRunner timerRunner, required TimerRunner timerRunner,
}) async { }) async {
// Имитация задержки инициализации
// TODO(yura): Удалить после проверки
await Future.delayed(const Duration(seconds: 3));
debugService.log(() => 'Тип сборки: ${env.name}'); debugService.log(() => 'Тип сборки: ${env.name}');
final diContainer = DiContainer(env: env, dService: debugService); final diContainer = DiContainer(env: env, dService: debugService);
await diContainer.init( await diContainer.init(
@@ -137,8 +130,11 @@ class AppRunner {
..logOnComplete(name) ..logOnComplete(name)
..stop(); ..stop();
}, },
onError: (message, error, [stackTrace]) => onError: (message, error, [stackTrace]) {
debugService.logError(message, error: error, stackTrace: stackTrace), timerRunner.stop();
_debugService.logError(message, error: error, stackTrace: stackTrace);
throw Exception('Ошибка инициализации зависимостей: $message');
},
); );
//throw Exception('Test error'); //throw Exception('Test error');
return diContainer; return diContainer;

View File

@@ -25,8 +25,8 @@ void _initErrorHandlers(IDebugService debugService) {
/// Метод для показа экрана ошибки /// Метод для показа экрана ошибки
void _showErrorScreen(Object error, StackTrace? stackTrace) { void _showErrorScreen(Object error, StackTrace? stackTrace) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) async {
AppRouter.rootNavigatorKey.currentState?.push( await AppRouter.rootNavigatorKey.currentState?.push(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace), builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
), ),

View File

@@ -1,4 +1,3 @@
import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/runner/app_runner.dart'; import 'package:friflex_starter/runner/app_runner.dart';
void main() => AppRunner(AppEnv.dev).run(); void main() => AppRunner(.dev).run();

View File

@@ -1,4 +1,3 @@
import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/runner/app_runner.dart'; import 'package:friflex_starter/runner/app_runner.dart';
void main() => AppRunner(AppEnv.prod).run(); void main() => AppRunner(.prod).run();

View File

@@ -1,4 +1,3 @@
import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/runner/app_runner.dart'; import 'package:friflex_starter/runner/app_runner.dart';
void main() => AppRunner(AppEnv.stage).run(); void main() => AppRunner(.stage).run();

View File

@@ -5,18 +5,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "82.0.0" version: "91.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.4.5" version: "8.4.1"
ansicolor: ansicolor:
dependency: transitive dependency: transitive
description: description:
@@ -76,18 +76,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "4.0.2"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.2.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
@@ -96,30 +96,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.4" version: "4.0.4"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev"
source: hosted
version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.15" version: "2.10.3"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@@ -132,10 +116,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.12.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -164,10 +148,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.10.1" version: "4.11.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@@ -220,10 +204,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.3"
dartx: dartx:
dependency: transitive dependency: transitive
description: description:
@@ -232,14 +216,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.8.0+1" version: "5.9.0"
dio_web_adapter: dio_web_adapter:
dependency: transitive dependency: transitive
description: description:
@@ -252,18 +244,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: envied name: envied
sha256: a4e2b1d0caa479b5d61332ae516518c175a6d09328a35a0bc0a53894cc5d7e4d sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.3.1"
envied_generator: envied_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: envied_generator name: envied_generator
sha256: "894f6c5eb624c60a1ce6f642b6fd7ec68bc3440aa6f1881837aa9acbbeade0c8" sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.3.1"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -321,26 +313,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_gen name: flutter_gen
sha256: "4117a3ea6b26a910c715bd58abcc5a90447e70930a5b98249e94c41da9e849bb" sha256: eac4863b65813aacbf16ecc07e7c271ab82fb2d95181825348f1fb7b03fd52da
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.10.0" version: "5.12.0"
flutter_gen_core: flutter_gen_core:
dependency: transitive dependency: transitive
description: description:
name: flutter_gen_core name: flutter_gen_core
sha256: "3eaa2d3d8be58267ac4cd5e215ac965dd23cae0410dc073de2e82e227be32bfc" sha256: b6bafbbd981da2f964eb45bcb8b8a7676a281084f8922c0c75de4cfbaa849311
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.10.0" version: "5.12.0"
flutter_gen_runner: flutter_gen_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_gen_runner name: flutter_gen_runner
sha256: e74b4ead01df3e8f02e73a26ca856759dbbe8cb3fd60941ba9f4005cd0cd19c9 sha256: c99b10af9d404e3f46fd1927e7d90099779e935e86022674c4c2a9e6c2a93b29
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.10.0" version: "5.12.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -406,10 +398,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.2.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -420,14 +412,70 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
frontend_server_client: geoclue:
dependency: transitive dependency: transitive
description: description:
name: frontend_server_client name: geoclue
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "0.1.1"
geolocator:
dependency: transitive
description:
name: geolocator
sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
url: "https://pub.dev"
source: hosted
version: "14.0.2"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4"
url: "https://pub.dev"
source: hosted
version: "5.0.1+1"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
url: "https://pub.dev"
source: hosted
version: "2.3.13"
geolocator_linux:
dependency: transitive
description:
name: geolocator_linux
sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3
url: "https://pub.dev"
source: hosted
version: "0.2.3"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
url: "https://pub.dev"
source: hosted
version: "4.2.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
url: "https://pub.dev"
source: hosted
version: "4.1.3"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
url: "https://pub.dev"
source: hosted
version: "0.2.5"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@@ -440,10 +488,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a" sha256: c92d18e1fe994cb06d48aa786c46b142a5633067e8297cff6b5a3ac742620104
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.1.2" version: "17.0.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@@ -460,6 +508,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.4" version: "5.3.4"
gsettings:
dependency: transitive
description:
name: gsettings
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
url: "https://pub.dev"
source: hosted
version: "0.2.8"
hashcodes: hashcodes:
dependency: transitive dependency: transitive
description: description:
@@ -499,6 +555,14 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" version: "0.0.1"
image:
dependency: transitive
description:
name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
url: "https://pub.dev"
source: hosted
version: "4.5.4"
image_size_getter: image_size_getter:
dependency: transitive dependency: transitive
description: description:
@@ -543,26 +607,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.9" version: "11.0.1"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -583,10 +647,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: lottie name: lottie
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.1" version: "3.3.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -607,10 +671,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -635,6 +699,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev"
source: hosted
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -775,26 +855,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: share_plus name: share_plus
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.1.4" version: "12.0.0"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "6.1.0"
shared_preferences: shared_preferences:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences name: shared_preferences
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.5.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@@ -868,18 +948,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "4.0.2"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.5" version: "1.3.8"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@@ -932,42 +1012,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: talker name: talker
sha256: a664f5eae5284e94aa8c535e0400ab71a78fa2480ad18a7344dec928b5c56805 sha256: "82de443cadfb6c41d457e7774c7890a91c73af3c2f17f3f7c01670bb58d5f5a1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "5.0.2"
talker_bloc_logger: talker_bloc_logger:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_bloc_logger name: talker_bloc_logger
sha256: "1af998d6cbafba00f66bbf49d77132275d3b113c6ac53ce0dc9d9981206f9cea" sha256: e631fcc9454cd86639888a9bb4654582bbc8c64c7dcc913401bfecb8892ec759
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "5.0.2"
talker_dio_logger: talker_dio_logger:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_dio_logger name: talker_dio_logger
sha256: "296a20ce600ccca7801deb5a530c28d03500c520f25f1e40c3bae727f63dd5eb" sha256: "5bbecc237f3d2c4af9348da5a0086321ed6dd6bf9857d723b1f54f61c810cff2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "5.0.2"
talker_flutter: talker_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_flutter name: talker_flutter
sha256: "480c51bba7ac0dcab23be5e9545214a43179625bc5f787326248e94af8316aee" sha256: "4f7a8d739237a3a3c8ba4dddcdbc1f9d9dec143811641dbafebd6b70f947f8ca"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "5.0.2"
talker_logger: talker_logger:
dependency: "direct main" dependency: "direct main"
description: description:
name: talker_logger name: talker_logger
sha256: b5d0bd04229eeccdf765cabf73e964ee28528c749104b56867c8d2eb764a45ee sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "5.0.2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -980,26 +1060,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.7"
theme_tailor: theme_tailor:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: theme_tailor name: theme_tailor
sha256: ba98be1d04856deef932757a3ca8fa7a5e2a6f96c30466a59c48924eeb608b97 sha256: "1ed7eeb5362e61aac4719e3ca4cd701fd6a74a507a911424eb6caef9b28a7fef"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.1.1"
theme_tailor_annotation: theme_tailor_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
name: theme_tailor_annotation name: theme_tailor_annotation
sha256: "0d5ecd13a6a52add2082aa60497179f6093acf482eb69e7fa3a9f37eb990ac34" sha256: "867848486f33dc4dff7649e3c6525133ecee410b5d97c77e22f1cb146baa1158"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.1.1"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1008,14 +1088,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.1.5"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -1092,10 +1164,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@@ -1169,5 +1241,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.8.0 <4.0.0" dart: ">=3.10.0 <4.0.0"
flutter: ">=3.32.0" flutter: ">=3.38.1"

View File

@@ -6,33 +6,33 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 0.0.1+1 version: 0.0.1+1
environment: environment:
sdk: ^3.8.0 sdk: ">=3.10.0 <4.0.0"
flutter: ">=3.32.0" flutter: ">=3.38.1"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: 1.0.8 cupertino_icons: 1.0.8
envied: 1.1.1 envied: 1.3.1
go_router: 15.1.2 go_router: 17.0.0
flutter_bloc: 9.1.1 flutter_bloc: 9.1.1
provider: 6.1.5 provider: 6.1.5
dio: 5.8.0+1 dio: 5.9.0
intl: 0.20.2 intl: 0.20.2
flutter_svg: 2.1.0 flutter_svg: 2.2.2
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
lottie: 3.3.1 lottie: 3.3.2
# Пакеты для отладки # Пакеты для отладки
talker_flutter: 4.8.0 talker_flutter: 5.0.2
talker_dio_logger: 4.8.0 talker_dio_logger: 5.0.2
talker_bloc_logger: 4.8.0 talker_bloc_logger: 5.0.2
talker_logger: 4.8.0 talker_logger: 5.0.2
equatable: 2.0.7 equatable: 2.0.7
theme_tailor_annotation: 3.0.2 theme_tailor_annotation: 3.1.1
### основной сервис с интерфейсами ### основной сервис с интерфейсами
i_app_services: i_app_services:
@@ -42,40 +42,27 @@ dependencies:
### В зависимости от платформы ### ### В зависимости от платформы ###
app_services: app_services:
path: app_services/base/app_services ### Базовая реализация ### path: app_services/base/app_services ### Базовая реализация ###
#path: app_services/aurora/app_services ### Аврора реализация ### # path: app_services/aurora/app_services ### Аврора реализация ###
# path: app_services/hms/app_services ### HarmonyOS реализация ###
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
envied_generator: 1.1.1 envied_generator: 1.3.1
build_runner: 2.4.15 build_runner: 2.10.3
flutter_gen_runner: 5.10.0 flutter_gen_runner: 5.12.0
flutter_gen: 5.10.0 flutter_gen: 5.12.0
flutter_lints: 6.0.0 flutter_lints: 6.0.0
theme_tailor: 3.0.3 theme_tailor: 3.1.1
flutter: flutter:
uses-material-design: true uses-material-design: true
generate: true generate: true
assets: assets:
- assets/icons/ - assets/icons/
- assets/fonts/
- assets/lottie/ - assets/lottie/
fonts:
- family: Montserrat
fonts:
- asset: assets/fonts/Montserrat-ExtraBold.ttf
weight: 800
- asset: assets/fonts/Montserrat-Bold.ttf
weight: 700
- asset: assets/fonts/Montserrat-SemiBold.ttf
weight: 600
- asset: assets/fonts/Montserrat-Medium.ttf
weight: 500
- asset: assets/fonts/Montserrat-Regular.ttf
weight: 400
flutter_gen: flutter_gen:
integrations: integrations:
flutter_svg: true flutter_svg: true

View File

@@ -631,12 +631,12 @@ void main() {
// Создаем приложение с кастомными отступами // Создаем приложение с кастомными отступами
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( const MaterialApp(
home: MediaQuery( home: MediaQuery(
data: const MediaQueryData( data: MediaQueryData(
padding: EdgeInsets.only(top: 50), // Симулируем статус бар padding: EdgeInsets.only(top: 50), // Симулируем статус бар
), ),
child: const Scaffold(body: Center(child: Text('Test'))), child: Scaffold(body: Center(child: Text('Test'))),
), ),
), ),
); );

View File

@@ -1,31 +1,37 @@
## Рекомендованный Readme для проектов # Приложение [ProjectName]
#### Приложение [ProjectName]
## Структура проекта ## Структура проекта
- проект архитектурно делится на три слоя: data, domain и presentation;
- все [features] реализуются в отдельных папках, с внутренним делением на слои; - проект архитектурно делится на три слоя: data, domain и presentation;
- все [features] реализуются в отдельных папках, с внутренним делением на слои;
## Основные пакеты и реализации (обновляется при добавлении или изменении) ## Основные пакеты и реализации (обновляется при добавлении или изменении)
- управление роутингом: [go_router](https://pub.dev/packages/go_router);
- основной state manager: [flutter_bloc](https://pub.dev/packages/flutter_bloc); - управление роутингом: [go_router](https://pub.dev/packages/go_router);
- di: ручная реализация через InheritedWidget; - основной state manager: [flutter_bloc](https://pub.dev/packages/flutter_bloc);
- работа с ресурсами: [flutter_gen](https://pub.dev/packages/flutter_gen); - di: ручная реализация через InheritedWidget;
- анализатор: используем [friflex_lint_rules](https://pub.friflex.com/packages/friflex_lint_rules), с правилами написания кода от компании.; - работа с ресурсами: [flutter_gen](https://pub.dev/packages/flutter_gen);
- для хранения защищенных данных - [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage); - анализатор: используем [friflex_lint_rules](https://pub.friflex.com/packages/friflex_lint_rules), с правилами написания кода от компании.;
- для хранения данных - [shared_preferences](https://pub.dev/packages/shared_preferences); - для хранения защищенных данных - [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage);
- для работы с API - [dio](https://pub.dev/packages/dio); - для хранения данных - [shared_preferences](https://pub.dev/packages/shared_preferences);
- для работы с API - [dio](https://pub.dev/packages/dio);
## Инструкция по запуску проекта ## Инструкция по запуску проекта
- [Инструкция по запуску проекта](./tools/rfc/RFC-build.md)
- [Инструкция по запуску проекта](./tools/rfc/RFC-build.md)
## Стиль написания кода ## Стиль написания кода
- [Стиль написания кода](./*ools*/rfc/RFC-codestyle.md)
- [Стиль написания кода](./*ools*/rfc/RFC-codestyle.md)
## Внесение изменений в код ## Внесение изменений в код
- [Внесение изменений в код](./tools/rfc/RFC-gitflow.md)
## Структура проекта - [Внесение изменений в код](./tools/rfc/RFC-gitflow.md)
- [Структура проекта](./tools/rfc/RFC-projects_structure.md)
## Документация по структуре проекта
- [Структура проекта](./tools/rfc/RFC-projects_structure.md)
## Ведение документации и комментариев в проекте ## Ведение документации и комментариев в проекте
- [Ведение документации и комментариев в проекте](./tools/rfc/RFC-documentation.md)
- [Ведение документации и комментариев в проекте](./tools/rfc/RFC-documentation.md)

View File

@@ -8,13 +8,16 @@
## Именование ## Именование
### Интерфейсы ### Интерфейсы
Утверждены два вида объявления интерфейсов: Утверждены два вида объявления интерфейсов:
1. Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**". 1. Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
Например: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д. Например: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
Таким образом, сразу видно, что работаешь с интерфейсом. Таким образом, сразу видно, что работаешь с интерфейсом.
Пример: Пример:
```dart ```dart
/// Интерфейс - **IUserRepository** /// Интерфейс - **IUserRepository**
abstract interface class IUserRepository {} abstract interface class IUserRepository {}
@@ -29,6 +32,7 @@ class UserRepositoryLocal implements IUserRepository {}
``` ```
### Классы - Репозитории ### Классы - Репозитории
Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище).\ Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище).\
Основная реализация, не должна содержать постфикса. Основная реализация, не должна содержать постфикса.
@@ -38,64 +42,82 @@ class UserRepositoryLocal implements IUserRepository {}
Локальное хранилище (например бд или просто имитация данных) - **AuthRepositoryLocal** Локальное хранилище (например бд или просто имитация данных) - **AuthRepositoryLocal**
### Файлы ### Файлы
Используется snake_case. Используется snake_case.
Название файла должно иметь следующую структуру: [раздел]_[тип].dart Название файла должно иметь следующую структуру: [раздел]_[тип].dart
Пример: user_details_screen.dart, shop_entity.dart Пример: user_details_screen.dart, shop_entity.dart
### Классы ### Классы
Название классов UpperCamelCase. Название классов UpperCamelCase.
Для создание приватных классов используем префикс _ . Название класса в конце должно содержать в себе тип. Для создание приватных классов используем префикс _ . Название класса в конце должно содержать в себе тип.
Пример: **UserEntity**, **AdultDialog** Пример: **UserEntity**, **AdultDialog**
## Методы ## Методы
Название метода в начале должно содержать в себе действие(глагол): Название метода в начале должно содержать в себе действие(глагол):
- fetch - fetch
- put - put
- update - update
- delete и так далее - delete и так далее
Пример: Пример:
```dart ```dart
int fetchFirstElement(){} int fetchFirstElement(){}
``` ```
Пример: Пример:
```dart ```dart
void updateFirstElement(){}; void updateFirstElement(){};
``` ```
Название метода не должно содержать в себе And/Or Название метода не должно содержать в себе And/Or
и метод соответственно не должен выполнять подобную логику. и метод соответственно не должен выполнять подобную логику.
## Переменные и константы ## Переменные и константы
Константы именуются также lowerCamelCase. Константы именуются также lowerCamelCase.
Пример: Пример:
```dart ```dart
const String carItem const String carItem
``` ```
или или
```dart ```dart
final userName; final userName;
``` ```
## Виджеты ## Виджеты
Виджеты именуются UpperCamelCase. Виджеты именуются UpperCamelCase.
В названии виджетов не должно содержаться слово widget. В названии виджетов не должно содержаться слово widget.
### Экраны ### Экраны
Экраны, используемые в роутинге, именуются с постфиксом Screen. Экраны, используемые в роутинге, именуются с постфиксом Screen.
Например, **ShopListScreen**. Например, **ShopListScreen**.
### Содержимое экрана ### Содержимое экрана
Виджеты, отображающие содержимое экрана, именуются с постфиксом View. Виджеты, отображающие содержимое экрана, именуются с постфиксом View.
Например, **ShopListView**. Например, **ShopListView**.
### Глобальные виджеты ### Глобальные виджеты
Глобальные виджеты именуются с приставкой App. Глобальные виджеты именуются с приставкой App.
Например, **AppButton**. Например, **AppButton**.
## Структура класса ## Структура класса
Объявления элементов класса должны располагаться в следующем порядке: Объявления элементов класса должны располагаться в следующем порядке:
1. **Constructors** 1. **Constructors**
- constructors - constructors
- named-constructors - named-constructors

View File

@@ -2,7 +2,8 @@
## Документация ## Документация
### Основные правила ведения документации в проекте: ### Основные правила ведения документации в проекте
- документация оформляется над описываемым объектом с использованием '///'; - документация оформляется над описываемым объектом с использованием '///';
- документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты; - документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты;
- документация должна: - документация должна:
@@ -13,14 +14,17 @@
## Шаблоны и примеры документации объектов ## Шаблоны и примеры документации объектов
### Документация классов: ### Документация классов
```dart ```dart
/// {@template new_class} /// {@template new_class}
/// Класс для реализации {описание назначения и реализуемого функционала в классе}. /// Класс для {описание назначения и реализуемого функционала в классе}.
/// {@endtemplate} /// {@endtemplate}
class NewClass {} class NewClass {}
``` ```
Пример: Пример:
```dart ```dart
/// {@template app_button} /// {@template app_button}
/// Класс для реализации кастомизированной кнопки. /// Класс для реализации кастомизированной кнопки.
@@ -28,11 +32,12 @@
class AppButton {} class AppButton {}
``` ```
### Документация конструкторов и фабрик: ### Документация конструкторов и фабрик
Если конструктор один, то достаточно указать {@macro new_class}. Если конструктор один, то достаточно указать {@macro new_class}.
{@macro new_class} дублирует на конструктор указанную в рамках описания класса документацию, поэтому описание класса должно в таком случае включать все передаваемые в конструктор параметры {@macro new_class} дублирует на конструктор указанную в рамках описания класса документацию, поэтому описание класса должно в таком случае включать все передаваемые в конструктор параметры
Если конструкторов несколько, описания должны отражать их отличия друг от друга. Фабрики описываются по такому же принципу. Если конструкторов несколько, описания должны отражать их отличия друг от друга. Фабрики описываются по такому же принципу.
```dart ```dart
///{@macro new_class} ///{@macro new_class}
const NewClass(); const NewClass();
@@ -43,8 +48,10 @@
factory NewClass.fromJson(Map<String, dynamic> json) factory NewClass.fromJson(Map<String, dynamic> json)
``` ```
### Документация полей классов: ### Документация полей классов
В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании. В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании.
```dart ```dart
/// Возраст пользователя. /// Возраст пользователя.
final int age; final int age;
@@ -53,7 +60,8 @@
final bool isAdult; final bool isAdult;
``` ```
### Документация геттеров/сеттеров: ### Документация геттеров/сеттеров
```dart ```dart
/// Получения доступа к {описание данных, которые получает геттер} /// Получения доступа к {описание данных, которые получает геттер}
int get newGetter => ... int get newGetter => ...
@@ -62,8 +70,10 @@
set newSetter (int setterValue) => ... set newSetter (int setterValue) => ...
``` ```
### Документация методов: ### Документация методов
Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки. Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки.
```dart ```dart
/// Метод для {описание назначения метода}. /// Метод для {описание назначения метода}.
/// {описание особенностей работы метода}. /// {описание особенностей работы метода}.
@@ -73,7 +83,9 @@
... ...
} }
``` ```
Пример: Пример:
```dart ```dart
/// Метод для расчета температуры. /// Метод для расчета температуры.
/// Принимает: /// Принимает:
@@ -88,16 +100,20 @@
## Комментарии ## Комментарии
### Основные правила комментирования кода в проекте: ### Основные правила комментирования кода в проекте
- комментарии оформляются над описываемым участком кода с использованием '//'; - комментарии оформляются над описываемым участком кода с использованием '//';
- комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании; - комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании;
- комментарий не должен повторять легко читаемые участки кода - комментарий не должен повторять легко читаемые участки кода
Например: Например:
```dart ```dart
if(flag != true) // не нужно описывать как "Если значение не равно true... if(flag != true) // не нужно описывать как "Если значение не равно true...
``` ```
Например: Например:
```dart ```dart
if(isAurora){ if(isAurora){
// Так как Аврора не может открывать WebView, // Так как Аврора не может открывать WebView,
@@ -107,7 +123,8 @@
## TODO ## TODO
### Основные правила создания TODO в проекте: ### Основные правила создания TODO в проекте
- TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать; - TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать;
- TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки; - TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки;
- если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках. - если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках.

View File

@@ -1,21 +1,26 @@
# Ведение проекта в git # Ведение проекта в git
## Создание commits/pull-request ## Создание commits/pull-request
- язык описания PR - Русский; - язык описания PR - Русский;
- описание должно отражать краткую суть изменений; - описание должно отражать краткую суть изменений;
- коммит/PR должен содержать: - коммит/PR должен содержать:
- исчерпывающую информацию об изменениях; - исчерпывающую информацию об изменениях;
- ссылку на задачу в таск-трекер; - ссылку на задачу в таск-трекер;
- Перечисление deprecated-кода (если есть). - Перечисление deprecated-кода (если есть).
### Типы коммитов согласно convention ### Типы коммитов согласно convention
* **feat**: — новая функция
* **fix**: — исправление ошибок - **feat**: — новая функция
* **refactor**: — Изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода).
* **build**: — изменения, влияющие на систему сборки или внешние зависимости (примеры областей (scope): android, ios, linux и так далее). - **fix**: — исправление ошибок
* **docs**: — изменения только в документации - **refactor**: — Изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода).
* **chore** - добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml). - **build**: — изменения, влияющие на систему сборки или внешние зависимости (примеры областей (scope): android, ios, linux и так далее).
* **test**: — добавление недостающих тестов или исправление существующих тестов. - **docs**: — изменения только в документации
* **ci**: — изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI). - **chore** - добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml).
- **test**: — добавление недостающих тестов или исправление существующих тестов.
- **ci**: — изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI).
### Правила именования веток ### Правила именования веток
@@ -28,33 +33,37 @@
Пример: Пример:
* `feat/PRIME-123_authentication` - `feat/PRIME-123_authentication`
* `fix/PRIME-456_typo_in_button` - `fix/PRIME-456_typo_in_button`
* `fix/PRIME-456-typo-in-button` - `fix/PRIME-456-typo-in-button`
## Схема разработки новой features ## Схема разработки новой features
![](./tools/res/images/developing.jpg)
![Схема разработки новой features](./tools/res/images/developing.jpg)
## Создание релиза ## Создание релиза
![](./tools/res/images/release.jpg)
![Схема создания релиза](./tools/res/images/release.jpg)
### Добавление новых features ### Добавление новых features
- создаем ветку feat/(уникальный номер задачи) (название feature),
Пример: <b>PRIME-17 Добавить_локальный_репозиторий</b>; - создаем ветку feat/(уникальный номер задачи) (название feature),
- вносим изменения; Пример: **PRIME-17 Добавить_локальный_репозиторий**;
- делаем коммит только на то, что касается изменений связанных с этой задачей; - вносим изменения;
- по завершению задачи - создаем Pull Request с названием по правилам https://commitlint.io/. - делаем коммит только на то, что касается изменений связанных с этой задачей;
Пример: <b>feat(app,di,auth,scripts,pubspec): PRIME-17 Добавить локальный репозиторий</b>. - по завершению задачи - создаем Pull Request с названием по правилам <https://commitlint.io/>.
Пример: **feat(app,di,auth,scripts,pubspec): PRIME-17 Добавить локальный репозиторий**.
Где, PRIME- это обязательный префикс(с помощью него автоматически подтягивается ссылка на задачу), число 17 это уникальный номер задачи из трекера. Где, PRIME- это обязательный префикс(с помощью него автоматически подтягивается ссылка на задачу), число 17 это уникальный номер задачи из трекера.
В описание к PR добавляем ссылку на задачу и описание внесенных изменений, и пояснения(если требуется); В описание к PR добавляем ссылку на задачу и описание внесенных изменений, и пояснения(если требуется);
- после завершения CodeReview создаем squash commit в ветку main (название коммит(а), должно быть такое же как название pull request) ; - после завершения CodeReview создаем squash commit в ветку main (название коммит(а), должно быть такое же как название pull request) ;
- после того как ветка с новой feature попало в main - удаляем ветку; - после того как ветка с новой feature попало в main - удаляем ветку;
### Создание release ### Создание release
- создаем ветку из main (release/release_0.0.1+1);
- добавляем в changelog.md описание изменений в этой версии (обычно это список коммитов, от предыдущей версии): - создаем ветку из main (release/release_0.0.1+1);
- делаем сборки, отправляем на тестирование; - добавляем в changelog.md описание изменений в этой версии (обычно это список коммитов, от предыдущей версии):
- получаем баг-лист от тестировщиков, делаем фиксы; - делаем сборки, отправляем на тестирование;
- отправляем на следующий раунд тестирования и.т.д; - получаем баг-лист от тестировщиков, делаем фиксы;
- после проверки и получения разрешение от другого разработчика - создаем tag c номером версии (0.0.1+1), создаем релиз, фиксируем версию и squash commit в ветку main и development; - отправляем на следующий раунд тестирования и.т.д;
- после того как release попал в main - удаляем ветку; - после проверки и получения разрешение от другого разработчика - создаем tag c номером версии (0.0.1+1), создаем релиз, фиксируем версию и squash commit в ветку main и development;
- после того как release попал в main - удаляем ветку;

View File

@@ -0,0 +1,30 @@
# Управление сгенерированными файлами
На проекте встречаются файлы, автоматически создаваемые инструментами генерации кода
(например, файлы с расширениями *.g.dart,*.freezed.dart,
а также файлы, связанные с protobuf и другими генераторами кода).
Необходимость контролировать такие файлы в репозитории вызывает ряд обсуждений.
См [issue](https://github.com/smmarty/flutter_team/issues/22).
## Проблемы включения сгенерированных файлов в репозиторий
1. Частые изменения. Сгенерированные файлы могут автоматически обновляться даже при отсутствии изменений в исходном коде, что приводит к ненужным изменениям в репозитории.
2. Конфликты при слиянии веток. Различия в таких файлах могут вызывать конфликты, которые не связаны с реальными изменениями, усложняя процесс работы над кодом.
3. Усложнение кода при проверке. Изменения в автоматически сгенерированных файлах попадают в PR, затрудняя код-ревью.
4. Недостоверность содержимого main. Нет гарантии, что в основной ветке всегда будут корректные версии сгенерированных файлов.
## Проблемы при добавлении сгенерированных файлов в .gitignore
1. Необходимость предварительной генерации. При добавлении таких файлов в .gitignore для проверки кода в пайплайне необходимо добавлять этап генерации при каждом изменении в PR.
Это критично на крупных проектах, так как генерация файлов может занимать несколько минут.
2. Неудобства для разработчиков. Разработчикам потребуется вручную генерировать файлы на локальной машине при каждом чекауте.
3. Неактуальный код в main. Основная ветка без предварительно сгенерированных файлов станет неработоспособной, а сборка потребует добавления этапа генерации, что увеличит время и нагрузку на серверы.
## Рекомендации
1. Оставлять сгенерированные файлы в репозитории.
Это позволит сохранить работоспособность основного кода без необходимости постоянной генерации файлов на всех этапах.
При этом рекомендуется:
- Периодически актуализировать файлы в основных ветках;
- Контролировать конфликты при слиянии веток и исключать ненужные изменения в этих файлах.
2. Обучение команды. Важно информировать команду о причинах хранения сгенерированных файлов в репозитории и о правилах работы с ними.

View File

@@ -0,0 +1,25 @@
# Управление файлом pubspec.lock
На проекте возникает необходимость определить стратегию работы с файлом pubspec.lock.
Данный файл может либо храниться в репозитории, либо быть добавлен в .gitignore.
Оба варианта имеют свои преимущества и недостатки, которые следует учесть.
См [issue](https://github.com/smmarty/flutter_team/issues/20)
## Аргументы за хранение pubspec.lock
1. Повторяемость сборки. Файл фиксирует конкретные версии зависимостей, обеспечивая единообразие версий для всех разработчиков и сред сборки.
2. Избежание неожиданных изменений. Новые версии зависимостей могут внести изменения, способные нарушить сборку или логику приложения. Наличие pubspec.lock позволяет контролировать изменения и избегать неожиданных обновлений.
3. Стабильность CI/CD. Зафиксированные версии зависимостей способствуют стабильным сборкам и тестам в CI-процессах.
4. Рекомендации Dart. Dart рекомендует хранить pubspec.lock в репозитории для приложений, чтобы обеспечить стабильность окружения. (См. документацию.)
## Аргументы против хранения pubspec.lock
1. Частые изменения. pubspec.lock обновляется при каждом изменении зависимостей, что увеличивает количество коммитов и PR, связанных только с обновлением зависимостей.
## Рекомендации
1. ХРАНИТЬ pubspec.lock для ПРИЛОЖЕНИЙ.
2. НЕ ХРАНИТЬ pubspec.lock ДЛЯ ПАКЕТОВ.
3. Решение для переключаемых зависимостей (GMS/HMS).
При изменении зависимостей, таких как GMS/HMS, может возникать несовместимость версий. Flutter пока не поддерживает флаворы на уровне зависимостей pubspec (см. [Flutter issue #46979](https://github.com/flutter/flutter/issues/46979)),
поэтому рекомендуем хранить по дефолту GMS pubspec.lock как базовый.

View File

@@ -30,7 +30,6 @@
- **/dev.dart** - сборка разработки на моковых репозиториях - **/dev.dart** - сборка разработки на моковых репозиториях
- **/stage.dart** - сборка для stage окружения - **/stage.dart** - сборка для stage окружения
## Пример структуры feature папок ## Пример структуры feature папок
- **/data** - слой данных - **/data** - слой данных
@@ -44,32 +43,38 @@
- **/presentation** - слой представления - **/presentation** - слой представления
- **/screens** - все экраны должны заканчиваться на Screen, например UserProfileScreen. - **/screens** - все экраны должны заканчиваться на Screen, например UserProfileScreen.
- **/components** - виджеты, которые необходимы для работы в presentation слое. Например: SuperButton, AppTextFields итд. - **/components** - виджеты, которые необходимы для работы в presentation слое. Например: SuperButton, AppTextFields итд.
# Пояснение к структуре feature папок
## Data (слой данных) Этот слой является поставщиком данных. ## Пояснение к структуре feature папок
- Repository - сущность, которая реализует внутри себя предоставление данных. Должен реализовывать какой либо интерфейс репозитория из domain слоя.
- DTO - Dto(Data Transfer Object) модели, и модели с которыми происходит работа в data слое. Например: UserDto;
## Domain (слой бизнес логики) ### Data (слой данных) Этот слой является поставщиком данных
- Entity - должны быть в максимально удобном виде для работы внутри Domain и Presentation. Например: UserEntity, ShopEntity;
- State - управления состоянием - state manager
- Service - различные сервисы, для выполнения различных задач.
- interfaces - интерфейсы репозиториев, которые используются в domain слое.
## Presentation (слой представления) - Repository - сущность, которая реализует внутри себя предоставление данных. Должен реализовывать какой либо интерфейс репозитория из domain слоя.
- сomponents - widget'ы которые реализуют работу какого либо визуального компонента(Buttons,TextFields,Lists, итд). Например: ShopList, RateButton. Модальные окна. - DTO - Dto(Data Transfer Object) модели, и модели с которыми происходит работа в data слое. Например: UserDto;
- screens - widget'ы которые представляют собой экран приложения. Например: UserInfoScreen.
# Основные правила общения объектов между папками ### Domain (слой бизнес логики)
## В рамках всего приложения - Entity - должны быть в максимально удобном виде для работы внутри Domain и Presentation. Например: UserEntity, ShopEntity;
- объекты внутри фичи должны быть инкапсулированы и не могут использоваться в других feature; - State - управления состоянием - state manager
- если есть необходимость использовать объект в нескольких feature, его нужно вынести в папку app и использовать как глобальный для всего приложения; - Service - различные сервисы, для выполнения различных задач.
- сервис, который должен быть использован в нескольких feature, создается как отдельная feature; - interfaces - интерфейсы репозиториев, которые используются в domain слое.
- если создаваемый сервис является платформно-зависимым, его необходимо выносить в app_services. В приложении должен быть только интерфейс.
## В рамках одной feature ### Presentation (слой представления)
- объекты data слоя не должны ничего знать про объекты слоя presentation. Имеют доступ к объектам entity из domain слоя для преобразования DTO в Entity ;
- объекты domain слоя не должны ничего знать про объекты слоя data, используемый экземпляр репозитория передается в объекты domain слоя через интерфейс репозитория, расположенного в этом же domain слое; - сomponents - widget'ы которые реализуют работу какого либо визуального компонента(Buttons,TextFields,Lists, итд). Например: ShopList, RateButton. Модальные окна.
- объекты domain слоя не должны ничего знать про слой presentation, не должны использовать компоненты библиотек ui, material, cupertino, widget и прочих, не должны использовать context; - screens - widget'ы которые представляют собой экран приложения. Например: UserInfoScreen.
- объекты presentation слоя не должны ничего знать про объекты слоя data, все взаимодействия непосредственно через объекты слоя domain.
## Основные правила общения объектов между папками
### В рамках всего приложения
- объекты внутри фичи должны быть инкапсулированы и не могут использоваться в других feature;
- если есть необходимость использовать объект в нескольких feature, его нужно вынести в папку app и использовать как глобальный для всего приложения;
- сервис, который должен быть использован в нескольких feature, создается как отдельная feature;
- если создаваемый сервис является платформно-зависимым, его необходимо выносить в app_services. В приложении должен быть только интерфейс.
### В рамках одной feature
- объекты data слоя не должны ничего знать про объекты слоя presentation. Имеют доступ к объектам entity из domain слоя для преобразования DTO в Entity ;
- объекты domain слоя не должны ничего знать про объекты слоя data, используемый экземпляр репозитория передается в объекты domain слоя через интерфейс репозитория, расположенного в этом же domain слое;
- объекты domain слоя не должны ничего знать про слой presentation, не должны использовать компоненты библиотек ui, material, cupertino, widget и прочих, не должны использовать context;
- объекты presentation слоя не должны ничего знать про объекты слоя data, все взаимодействия непосредственно через объекты слоя domain.