30 Commits

Author SHA1 Message Date
PetrovY
7ca756ffa7 fix(app_runner): исправить описание контейнера зависимостей на сервис отладки 2026-01-12 10:34:08 +03:00
PetrovY
71204fbe9d fix(update): изменить название параметра versionCode на versionApp в методах проверки обновлений 2026-01-12 10:30:21 +03:00
Yuri Petrov
6500b917c5 refactor(app): обновить структуру приложения и удалить устаревшие провайдеры (#44)
* refactor(app): обновить структуру приложения и удалить устаревшие провайдеры
* refactor(runner): упростить обработку ошибок и улучшить логирование времени инициализации
* refactor(runner): улучшить порядок инициализации приложения и обработку ошибок
* refactor(app): исправить контекст MediaQuery для предотвращения перерисовки
* refactor(pp): удалить главный виджет приложения и заменить его на AppRoot
* docs(copilot-instructions): уточнить правила проведения Code Review на русском языке
* refactor(linter): добавить правило avoid_catches_without_on_clauses для улучшения обработки исключений

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-12-11 21:18:24 +03:00
Yuri Petrov
84f7fa8889 refactor(auth): удалить репозиторий авторизации и связанные интерфейсы (#43)
Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-12-11 10:54:19 +03:00
Yuri Petrov
4a49083ef3 refactor(http): удалить интерфейс IHttpClient и упростить реализацию AppHttpClient (#42)
* refactor(http): удалить интерфейс IHttpClient и упростить реализацию AppHttpClient

- Удален интерфейс IHttpClient, что упростило структуру кода.
- AppHttpClient теперь напрямую использует Dio без промежуточного интерфейса.
- Обновлены зависимости в репозиториях для использования нового HTTP клиента.

* refactor(code):  dart format

* chore(pr-template): удалить отключения markdownlint из шаблона PR

* docs(copilot-instructions): добавить правила проведения Code Review

---------

Co-authored-by: petrovyuri <petrovyuri@example.com>
2025-12-11 10:27:19 +03:00
Yuri Petrov
ab64fb9246 docs(cursorrules,copilot-instructions): обновить правила разработки и нструкции для AI-агента (#41) 2025-12-10 12:42:49 +03:00
petrovyuri
73a13c99c6 fix(linter): удалить правило eol_at_end_of_file из настроек линтера 2025-11-26 22:37:35 +03: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
104 changed files with 3181 additions and 1469 deletions

389
.cursorrules Normal file
View File

@@ -0,0 +1,389 @@
# Правила разработки Flutter проекта
Этот файл содержит правила и стандарты разработки для проекта. Все правила обязательны к соблюдению при написании кода.
**ВАЖНО:** Перед каждым PR необходимо отформатировать код (`dart format .`) и проверить анализатор на отсутствие сообщений (`dart analyze`).
---
# Стиль кода
## Именование
### Интерфейсы
Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
Примеры: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
Таким образом, сразу видно, что работаешь с интерфейсом.
Пример:
```dart
/// Интерфейс - **IUserRepository**
abstract interface class IUserRepository {}
/// Основная реализация (prod и stage окружения)
class UserRepository implements IUserRepository {}
/// Иная реализация (мок, локальное хранилище) должна содержать
/// постфикс функциональности:
/// - Network - сетевое взаимодействие.
/// - Local - локальное хранилище.
/// - Mock - мок репозиторий.
class UserRepositoryLocal implements IUserRepository {}
```
### Классы - Репозитории
Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище). Основная реализация не должна содержать постфикса.
Примеры:
- Интерфейс - **IAuthRepository**
- Основная реализация (prod и stage окружения) - **AuthRepository**
- Мок (мок данные) - **AuthRepositoryMock**
- Локальное хранилище (например бд или просто имитация данных) - **AuthRepositoryLocal**
### Файлы
Используется snake_case. Название файла должно иметь следующую структуру: `[раздел]_[тип].dart`
Примеры: `user_details_screen.dart`, `shop_entity.dart`
### Классы
Название классов UpperCamelCase. Для создания приватных классов используем префикс `_`. Название класса в конце должно содержать в себе тип.
Примеры: **UserEntity**, **AdultDialog**
## Методы
Название метода в начале должно содержать в себе действие (глагол):
- fetch
- put
- update
- delete
- и так далее
Примеры:
```dart
int fetchFirstElement() {}
```
```dart
void updateFirstElement() {}
```
**ВАЖНО:** Название метода не должно содержать в себе `And`/`Or`, и метод соответственно не должен выполнять подобную логику.
## Переменные и константы
Константы именуются также lowerCamelCase.
Примеры:
```dart
const String carItem = 'default';
```
или
```dart
final String userName = 'default';
## Виджеты
Виджеты именуются UpperCamelCase. В названии виджетов не должно содержаться слово `widget`.
### Экраны
Экраны, используемые в роутинге, именуются с постфиксом `Screen`.
Пример: **ShopListScreen**
### Содержимое экрана
Виджеты, отображающие содержимое экрана, именуются с постфиксом `View`.
Пример: **ShopListView**
### Глобальные виджеты
Глобальные виджеты именуются с приставкой `App`.
Пример: **AppButton**
## Структура класса
Объявления элементов класса должны располагаться в следующем порядке:
1. **Constructors**
- constructors
- named-constructors
- factory-constructors
2. **Static**
- public-static-methods
- private-static-methods
- public-static-const-fields
- private-static-const-fields
- public-static-final-fields
- private-static-final-fields
- public-static-fields
- private-static-fields
3. **Fields**
- public-final-fields
- private-final-fields
- public-fields
- private-fields
4. **Getters/Setters**
- public-getters-setters
- private-getters-setters
5. **Methods**
- overridden-methods
- public-methods
- protected-methods
- private-methods
---
# Ведение документации и комментариев
## Документация
### Основные правила ведения документации в проекте
- Документация оформляется над описываемым объектом с использованием `///`
- Документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты
- Документация должна:
- указывать на назначение объекта
- содержать исчерпывающее описание объекта
- быть краткой и емкой
- быть понятной для любого разработчика
### Шаблоны и примеры документации объектов
#### Документация классов
```dart
/// {@template new_class}
/// Класс для {описание назначения и реализуемого функционала в классе}.
/// {@endtemplate}
class NewClass {}
```
Пример:
```dart
/// {@template app_button}
/// Класс для реализации кастомизированной кнопки.
/// {@endtemplate}
class AppButton {}
```
#### Документация конструкторов и фабрик
Если конструктор один, то достаточно указать `{@macro new_class}`. `{@macro new_class}` дублирует на конструктор указанную в рамках описания класса документацию, поэтому описание класса должно в таком случае включать все передаваемые в конструктор параметры.
Если конструкторов несколько, описания должны отражать их отличия друг от друга. Фабрики описываются по такому же принципу.
```dart
/// {@macro new_class}
const NewClass();
/// Создает {описание создаваемого фабрикой объекта}.
/// Принимает:
/// - [json] - {описание параметра}
factory NewClass.fromJson(Map<String, dynamic> json) {}
```
#### Документация полей классов
В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании.
```dart
/// Возраст пользователя.
final int age;
/// Индикатор совершеннолетия пользователя. Принимает значение true, когда [age] больше 18 лет.
final bool isAdult;
```
#### Документация геттеров/сеттеров
```dart
/// Получения доступа к {описание данных, которые получает геттер}
int get newGetter => ...
/// Установка значения для {описание работы сеттера}
set newSetter(int setterValue) => ...
```
#### Документация методов
Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки.
```dart
/// Метод для {описание назначения метода}.
/// {описание особенностей работы метода}.
/// Принимает:
/// - [param] - {описание назначения параметра}.
void newMethod({required String param}) {
...
}
```
Пример:
```dart
/// Метод для расчета температуры.
/// Принимает:
/// - [grad] - параметр необходим для расчета температуры.
/// Возвращает:
/// - температуру в градусах.
/// При ошибке расчета возвращается null.
int? calcTemperature({required int grad}) {
// Реализация
}
```
## Комментарии
### Основные правила комментирования кода в проекте
- Комментарии оформляются над описываемым участком кода с использованием `//`
- Комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании
- Комментарий не должен повторять легко читаемые участки кода
Примеры неправильного использования:
```dart
if (flag != true) // не нужно описывать как "Если значение не равно true..."
```
Примеры правильного использования:
```dart
if (isAurora) {
// Так как Аврора не может открывать WebView,
// то заменяем на открытие внешнего браузера
}
```
## TODO
### Основные правила создания TODO в проекте
- TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать
- TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки
- Если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задачу необходимо оставить в скобках
Пример:
```dart
// TODO(username): Оптимизировать алгоритм сортировки
```
---
# Ведение проекта в git
## Создание commits/pull-request
- Язык описания PR - Русский
- Описание должно отражать краткую суть изменений
- Коммит/PR должен содержать:
- исчерпывающую информацию об изменениях
- ссылку на задачу в таск-трекер
- Перечисление deprecated-кода (если есть)
### Типы коммитов согласно convention
- **feat**: — новая функция
- **fix**: — исправление ошибок
- **refactor**: — Изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода)
- **build**: — изменения, влияющие на систему сборки или внешние зависимости (примеры областей (scope): android, ios, linux и так далее)
- **docs**: — изменения только в документации
- **chore**: — добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml)
- **test**: — добавление недостающих тестов или исправление существующих тестов
- **ci**: — изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI)
# Структура проекта
Рекомендуемая структура проекта (может отличаться в зависимости от проекта и согласований)
- **/** - папка проекта
- **/assets** - директория расположения графических ресурсов
- **/tools/** - все необходимые инструменты для проекта
- **/docs** - документация проекта
- **/env** - папка, с внешними переменными окружения
- **/lib** - код на Dart, Flutter-приложение
- **/app** - содержит основные настройки нашего приложения
- **/data** - общие поставщики данных
- **/domain** - общий слой
- **/presentation** - общий слой
- **/di** - файлы конфигурации зависимостей
- **/router** - все, что касается роутинга
- **/features** - фичи приложения, для каждой фичи создается отдельная папка
- **/feature_name** - подробнее см в разделе Структура feature папок
- **/data**
- **/domain**
- **/presentation**
- **/gen** - для сгенерированных файлов
- **/targets** - таргеты для сборок
- **/prod.dart** - сборка для prod
- **/dev.dart** - сборка разработки на моковых репозиториях
- **/stage.dart** - сборка для stage окружения
## Пример структуры feature папок
- **/data** - слой данных
- **/dto** - реализация DTO (data transfer object)
- **/repository** - реализации репозиториев
- **/domain** - слой бизнес логики
- **/entity** - модели которые используются для работы в domain/presentation слоях
- **/repository** - интерфейсы репозиториев, которые используются в domain слое
- **/state** - state-management
- **/service** - реализации сервисов
- **/presentation** - слой представления
- **/screens** - все экраны должны заканчиваться на Screen, например UserProfileScreen
- **/components** - виджеты, которые необходимы для работы в presentation слое. Например: SuperButton, AppTextFields итд
## Пояснение к структуре feature папок
### Data (слой данных)
Этот слой является поставщиком данных.
- **Repository** - сущность, которая реализует внутри себя предоставление данных. Должен реализовывать какой либо интерфейс репозитория из domain слоя
- **DTO** - Dto(Data Transfer Object) модели, и модели с которыми происходит работа в data слое. Например: UserDto
### Domain (слой бизнес логики)
- **Entity** - должны быть в максимально удобном виде для работы внутри Domain и Presentation. Например: UserEntity, ShopEntity
- **State** - управления состоянием - state manager
- **Service** - различные сервисы, для выполнения различных задач
- **interfaces** - интерфейсы репозиториев, которые используются в domain слое
### Presentation (слой представления)
- **components** - widget'ы которые реализуют работу какого либо визуального компонента (Buttons, TextFields, Lists, итд). Например: ShopList, RateButton. Модальные окна
- **screens** - widget'ы которые представляют собой экран приложения. Например: UserInfoScreen
## Основные правила общения объектов между папками
### В рамках всего приложения
- Объекты внутри фичи должны быть инкапсулированы и не могут использоваться в других 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

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

@@ -0,0 +1,395 @@
# Правила разработки Flutter проекта
Этот файл содержит правила и стандарты разработки для проекта. Все правила обязательны к соблюдению при написании кода.
**ВАЖНО:** Перед каждым PR необходимо отформатировать код (`dart format .`) и проверить анализатор на отсутствие сообщений (`dart analyze`).
---
# Правила проведения Code Review
## Основные правила проведения Code Review
**ВАЖНО:** - Комментарии, обзоры и описание Pull Request при проведении code review должны быть на РУССКОМ языке.
# Стиль кода
## Именование
### Интерфейсы
Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
Примеры: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
Таким образом, сразу видно, что работаешь с интерфейсом.
Пример:
```dart
/// Интерфейс - **IUserRepository**
abstract interface class IUserRepository {}
/// Основная реализация (prod и stage окружения)
class UserRepository implements IUserRepository {}
/// Иная реализация (мок, локальное хранилище) должна содержать
/// постфикс функциональности:
/// - Network - сетевое взаимодействие.
/// - Local - локальное хранилище.
/// - Mock - мок репозиторий.
class UserRepositoryLocal implements IUserRepository {}
```
### Классы - Репозитории
Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище). Основная реализация не должна содержать постфикса.
Примеры:
- Интерфейс - **IAuthRepository**
- Основная реализация (prod и stage окружения) - **AuthRepository**
- Мок (мок данные) - **AuthRepositoryMock**
- Локальное хранилище (например бд или просто имитация данных) - **AuthRepositoryLocal**
### Файлы
Используется snake_case. Название файла должно иметь следующую структуру: `[раздел]_[тип].dart`
Примеры: `user_details_screen.dart`, `shop_entity.dart`
### Классы
Название классов UpperCamelCase. Для создания приватных классов используем префикс `_`. Название класса в конце должно содержать в себе тип.
Примеры: **UserEntity**, **AdultDialog**
## Методы
Название метода в начале должно содержать в себе действие (глагол):
- fetch
- put
- update
- delete
- и так далее
Примеры:
```dart
int fetchFirstElement() {}
```
```dart
void updateFirstElement() {}
```
**ВАЖНО:** Название метода не должно содержать в себе `And`/`Or`, и метод соответственно не должен выполнять подобную логику.
## Переменные и константы
Константы именуются также lowerCamelCase.
Примеры:
```dart
const String carItem = 'default';
```
или
```dart
final String userName = 'user';
```
## Виджеты
Виджеты именуются UpperCamelCase. В названии виджетов не должно содержаться слово `widget`.
### Экраны
Экраны, используемые в роутинге, именуются с постфиксом `Screen`.
Пример: **ShopListScreen**
### Содержимое экрана
Виджеты, отображающие содержимое экрана, именуются с постфиксом `View`.
Пример: **ShopListView**
### Глобальные виджеты
Глобальные виджеты именуются с приставкой `App`.
Пример: **AppButton**
## Структура класса
Объявления элементов класса должны располагаться в следующем порядке:
1. **Constructors**
- constructors
- named-constructors
- factory-constructors
2. **Static**
- public-static-methods
- private-static-methods
- public-static-const-fields
- private-static-const-fields
- public-static-final-fields
- private-static-final-fields
- public-static-fields
- private-static-fields
3. **Fields**
- public-final-fields
- private-final-fields
- public-fields
- private-fields
4. **Getters/Setters**
- public-getters-setters
- private-getters-setters
5. **Methods**
- overridden-methods
- public-methods
- protected-methods
- private-methods
---
# Ведение документации и комментариев
## Документация
### Основные правила ведения документации в проекте
- Документация оформляется над описываемым объектом с использованием `///`
- Документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты
- Документация должна:
- указывать на назначение объекта
- содержать исчерпывающее описание объекта
- быть краткой и емкой
- быть понятной для любого разработчика
### Шаблоны и примеры документации объектов
#### Документация классов
```dart
/// {@template new_class}
/// Класс для {описание назначения и реализуемого функционала в классе}.
/// {@endtemplate}
class NewClass {}
```
Пример:
```dart
/// {@template app_button}
/// Класс для реализации кастомизированной кнопки.
/// {@endtemplate}
class AppButton {}
```
#### Документация конструкторов и фабрик
Если конструктор один, то достаточно указать `{@macro new_class}`. `{@macro new_class}` дублирует на конструктор указанную в рамках описания класса документацию, поэтому описание класса должно в таком случае включать все передаваемые в конструктор параметры.
Если конструкторов несколько, описания должны отражать их отличия друг от друга. Фабрики описываются по такому же принципу.
```dart
/// {@macro new_class}
const NewClass();
/// Создает {описание создаваемого фабрикой объекта}.
/// Принимает:
/// - [json] - {описание параметра}
factory NewClass.fromJson(Map<String, dynamic> json) {}
```
#### Документация полей классов
В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании.
```dart
/// Возраст пользователя.
final int age;
/// Индикатор совершеннолетия пользователя. Принимает значение true, когда [age] больше 18 лет.
final bool isAdult;
```
#### Документация геттеров/сеттеров
```dart
/// Получения доступа к {описание данных, которые получает геттер}
int get newGetter => ...
/// Установка значения для {описание работы сеттера}
set newSetter(int setterValue) => ...
```
#### Документация методов
Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки.
```dart
/// Метод для {описание назначения метода}.
/// {описание особенностей работы метода}.
/// Принимает:
/// - [param] - {описание назначения параметра}.
void newMethod({required String param}) {
...
}
```
Пример:
```dart
/// Метод для расчета температуры.
/// Принимает:
/// - [grad] - параметр необходим для расчета температуры.
/// Возвращает:
/// - температуру в градусах.
/// При ошибке расчета возвращается null.
int? calcTemperature({required int grad}) {
// Реализация
}
```
## Комментарии
### Основные правила комментирования кода в проекте
- Комментарии оформляются над описываемым участком кода с использованием `//`
- Комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании
- Комментарий не должен повторять легко читаемые участки кода
Примеры неправильного использования:
```dart
if (flag != true) // не нужно описывать как "Если значение не равно true..."
```
Примеры правильного использования:
```dart
if (isAurora) {
// Так как Аврора не может открывать WebView,
// то заменяем на открытие внешнего браузера
}
```
## TODO
### Основные правила создания TODO в проекте
- TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать
- TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки
- Если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задачу необходимо оставить в скобках
Пример:
```dart
// TODO(username): Оптимизировать алгоритм сортировки
```
---
# Ведение проекта в git
## Создание commits/pull-request
- Язык описания PR - Русский
- Описание должно отражать краткую суть изменений
- Коммит/PR должен содержать:
- исчерпывающую информацию об изменениях
- ссылку на задачу в таск-трекер
- Перечисление deprecated-кода (если есть)
### Типы коммитов согласно convention
- **feat**: — новая функция
- **fix**: — исправление ошибок
- **refactor**: — Изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода)
- **build**: — изменения, влияющие на систему сборки или внешние зависимости (примеры областей (scope): android, ios, linux и так далее)
- **docs**: — изменения только в документации
- **chore**: — добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml)
- **test**: — добавление недостающих тестов или исправление существующих тестов
- **ci**: — изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI)
# Структура проекта
Рекомендуемая структура проекта (может отличаться в зависимости от проекта и согласований)
- **/** - папка проекта
- **/assets** - директория расположения графических ресурсов
- **/tools/** - все необходимые инструменты для проекта
- **/docs** - документация проекта
- **/env** - папка, с внешними переменными окружения
- **/lib** - код на Dart, Flutter-приложение
- **/app** - содержит основные настройки нашего приложения
- **/data** - общие поставщики данных
- **/domain** - общий слой
- **/presentation** - общий слой
- **/di** - файлы конфигурации зависимостей
- **/router** - все, что касается роутинга
- **/features** - фичи приложения, для каждой фичи создается отдельная папка
- **/feature_name** - подробнее см в разделе Структура feature папок
- **/data**
- **/domain**
- **/presentation**
- **/gen** - для сгенерированных файлов
- **/targets** - таргеты для сборок
- **/prod.dart** - сборка для prod
- **/dev.dart** - сборка разработки на моковых репозиториях
- **/stage.dart** - сборка для stage окружения
## Пример структуры feature папок
- **/data** - слой данных
- **/dto** - реализация DTO (data transfer object)
- **/repository** - реализации репозиториев
- **/domain** - слой бизнес логики
- **/entity** - модели которые используются для работы в domain/presentation слоях
- **/repository** - интерфейсы репозиториев, которые используются в domain слое
- **/state** - state-management
- **/service** - реализации сервисов
- **/presentation** - слой представления
- **/screens** - все экраны должны заканчиваться на Screen, например UserProfileScreen
- **/components** - виджеты, которые необходимы для работы в presentation слое. Например: SuperButton, AppTextFields итд
## Пояснение к структуре feature папок
### Data (слой данных)
Этот слой является поставщиком данных.
- **Repository** - сущность, которая реализует внутри себя предоставление данных. Должен реализовывать какой либо интерфейс репозитория из domain слоя
- **DTO** - Dto(Data Transfer Object) модели, и модели с которыми происходит работа в data слое. Например: UserDto
### Domain (слой бизнес логики)
- **Entity** - должны быть в максимально удобном виде для работы внутри Domain и Presentation. Например: UserEntity, ShopEntity
- **State** - управления состоянием - state manager
- **Service** - различные сервисы, для выполнения различных задач
- **interfaces** - интерфейсы репозиториев, которые используются в domain слое
### Presentation (слой представления)
- **components** - widget'ы которые реализуют работу какого либо визуального компонента (Buttons, TextFields, Lists, итд). Например: ShopList, RateButton. Модальные окна
- **screens** - widget'ы которые представляют собой экран приложения. Например: UserInfoScreen
## Основные правила общения объектов между папками
### В рамках всего приложения
- Объекты внутри фичи должны быть инкапсулированы и не могут использоваться в других 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

View File

@@ -5,6 +5,7 @@
<!--- Напишите здесь какую проблему решают изменения в этом запросе. --> <!--- Напишите здесь какую проблему решают изменения в этом запросе. -->
## Чек лист (обязательно) ## Чек лист (обязательно)
- [ ] Код соответствует рекомендациям и требованиям проекта. - [ ] Код соответствует рекомендациям и требованиям проекта.
- [ ] Проверил анализатор на предмет ошибок и предупреждений. - [ ] Проверил анализатор на предмет ошибок и предупреждений.
- [ ] Название ПР соответствует [требованиям проекта](../tools/rfc/RFC-documentation.md). - [ ] Название ПР соответствует [требованиям проекта](../tools/rfc/RFC-documentation.md).
@@ -12,6 +13,7 @@
- [ ] Добавлены необходимые тесты, если требуется. - [ ] Добавлены необходимые тесты, если требуется.
## Скриншоты (желательно) ## Скриншоты (желательно)
<details> <details>
<summary>Показать</summary> <summary>Показать</summary>

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 # Избегать экранирования внутренних кавычек - prefer_single_quotes # Единый стиль кавычек (одинарные быстрее печатать)
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах - slash_for_doc_comments # Использовать /// вместо /** */ для документации
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов - sort_constructors_first # Конструкторы в начале класса — быстрый обзор API
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров # === Обработка ошибок и типобезопасность ===
avoid_private_typedef_functions: true # Избегать приватных typedef-функций - avoid_catching_errors # Не перехватывать Error (только Exception) — ошибки системные
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов - avoid_catches_without_on_clauses # Требует явного указания типа исключения в catch (on Type catch)
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,141 +0,0 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_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/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/error/error_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:go_router/go_router.dart';
/// {@template app}
/// Главный виджет приложения, управляющий инициализацией зависимостей
/// и отображением основного интерфейса приложения.
///
/// Отвечает за:
/// - Инициализацию зависимостей приложения
/// - Отображение экрана загрузки во время инициализации
/// - Обработку ошибок инициализации
/// - Настройку провайдеров для темы и локализации
/// {@endtemplate}
class App extends StatefulWidget {
/// {@macro app}
const App({required this.router, required this.initDependencies, super.key});
/// Роутер приложения для навигации между экранами
final GoRouter router;
/// Функция для инициализации зависимостей приложения
/// Возвращает Future с контейнером зависимостей
final Future<DiContainer> Function() initDependencies;
@override
State<App> createState() => _AppState();
}
/// {@template app_state}
/// Состояние главного виджета приложения.
///
/// Управляет процессом инициализации зависимостей и отображением
/// соответствующих экранов в зависимости от состояния инициализации.
/// {@endtemplate}
class _AppState extends State<App> {
/// {@macro app_state}
_AppState();
/// Мутабельная Future для инициализации зависимостей
/// Позволяет перезапускать инициализацию при ошибках
late Future<DiContainer> _initFuture;
@override
void initState() {
super.initState();
_initFuture = widget.initDependencies();
}
@override
Widget build(BuildContext context) {
return AppProviders(
// Consumer для локализации добавляем выше чем DependsProviders
// чтобы при изменении локализации перестраивался весь виджет
// Но, это не обязательно, можно добавить в DependsProviders
child: LocalizationConsumer(
builder: () => FutureBuilder<DiContainer>(
future: _initFuture,
builder: (_, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
// Пока инициализация показываем Splash
return const SplashScreen();
case ConnectionState.done:
if (snapshot.hasError) {
return ErrorScreen(
error: snapshot.error,
stackTrace: snapshot.stackTrace,
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),
),
);
}
},
),
),
);
}
/// Метод для перезапуска инициализации зависимостей
/// Вызывается при ошибках инициализации для повторной попытки
void _retryInit() {
setState(() {
_initFuture = widget.initDependencies();
});
}
}
/// {@template app_internal}
/// Внутренний виджет приложения, отображающий основной интерфейс
/// после успешной инициализации зависимостей.
///
/// Настраивает MaterialApp с роутером, темами и локализацией.
/// {@endtemplate}
class _App extends StatelessWidget {
/// {@macro app_internal}
const _App({required this.router});
/// Роутер приложения для навигации
final GoRouter router;
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
darkTheme: AppTheme.dark,
theme: AppTheme.light,
themeMode: context.theme.themeMode,
locale: context.localization.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
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,14 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/features/update/domain/state/cubit/update_cubit.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// Класс для добавления провайдеров темы и локализации /// {@template app_providers}
/// Класс для добавления зависимостей приложения
/// {@endtemplate}
final class AppProviders extends StatelessWidget { final class AppProviders extends StatelessWidget {
const AppProviders({required this.child, super.key}); /// {@macro app_providers}
const AppProviders({
required this.child,
required this.diContainer,
super.key,
});
/// Виджет, который будет отображаться внутри провайдеров
final Widget child; final Widget child;
/// Контейнер зависимостей
final DiContainer diContainer;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
@@ -19,6 +33,10 @@ final class AppProviders extends StatelessWidget {
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => LocalizationNotifier(), create: (_) => LocalizationNotifier(),
), // Провайдер для локализации ), // Провайдер для локализации
Provider.value(value: diContainer), // Передаем контейнер зависимостей
BlocProvider(
create: (_) => UpdateCubit(diContainer.repositories.updateRepository),
),
], ],
child: child, child: child,
); );

54
lib/app/app_root.dart Normal file
View File

@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_providers.dart';
import 'package:friflex_starter/app/theme/app_theme.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:go_router/go_router.dart';
/// {@template app}
/// Главный виджет приложения, отображающий основной интерфейс приложения
///
/// Отвечает за:
/// - Настройку провайдеров для темы и локализации
/// {@endtemplate}
class AppRoot extends StatelessWidget {
/// {@macro app_root}
const AppRoot({required this.diContainer, required this.router, super.key});
/// Контейнер зависимостей
final DiContainer diContainer;
/// Роутер приложения
final GoRouter router;
@override
Widget build(BuildContext context) {
return AppProviders(
diContainer: diContainer,
child: LocalizationConsumer(
builder: (localizationContext) {
return ThemeConsumer(
builder: (themeContext) => MediaQuery(
key: const ValueKey('prevent_rebuild'),
data: MediaQuery.of(
themeContext,
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
child: MaterialApp.router(
darkTheme: AppTheme.dark,
theme: AppTheme.light,
themeMode: themeContext.theme.themeMode,
locale: localizationContext.localization.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
routerConfig: router,
),
),
);
},
),
);
}
}

View File

@@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:provider/provider.dart';
/// Класс для внедрения глобальных зависимостей
final class DependsProviders extends StatelessWidget {
const DependsProviders({
required this.child,
required this.diContainer,
super.key,
});
final Widget child;
final DiContainer diContainer;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Сюда добавляем глобальные блоки, inherited и т.д.
Provider.value(value: diContainer), // Передаем контейнер зависимостей
],
child: child,
);
}
}

View File

@@ -1,13 +1,12 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:friflex_starter/app/app_config/app_config.dart'; import 'package:friflex_starter/app/app_config/app_config.dart';
import 'package:friflex_starter/app/http/i_http_client.dart';
import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart';
/// {@template app_http_client} /// {@template app_http_client}
/// Класс для реализации HTTP-клиента для управления запросами /// Класс для реализации HTTP-клиента для управления запросами
/// {@endtemplate} /// {@endtemplate}
final class AppHttpClient implements IHttpClient { final class AppHttpClient {
/// Создает HTTP клиент /// Создает HTTP клиент
/// ///
/// Принимает: /// Принимает:
@@ -18,7 +17,6 @@ final class AppHttpClient implements IHttpClient {
required IAppConfig appConfig, required IAppConfig appConfig,
}) { }) {
_httpClient = Dio(); _httpClient = Dio();
_appConfig = appConfig;
_httpClient.options _httpClient.options
..baseUrl = appConfig.baseUrl ..baseUrl = appConfig.baseUrl
@@ -30,111 +28,8 @@ final class AppHttpClient implements IHttpClient {
_httpClient.interceptors.add(debugService.dioLogger); _httpClient.interceptors.add(debugService.dioLogger);
} }
/// Конфигурация приложения
late final IAppConfig _appConfig;
/// Экземпляр HTTP клиента /// Экземпляр HTTP клиента
late final Dio _httpClient; late final Dio _httpClient;
@override Dio get client => _httpClient;
Future<Response> get(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.get(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
@override
Future<Response> post(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.post(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
@override
Future<Response> patch(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.patch(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
@override
Future<Response> put(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.put(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
@override
Future<Response> delete(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.delete(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
@override
Future<Response> head(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
}) async {
_httpClient.options.baseUrl = _appConfig.baseUrl;
return _httpClient.head(
path,
data: data,
queryParameters: queryParameters,
options: options,
);
}
} }

View File

@@ -1,94 +0,0 @@
import 'package:dio/dio.dart';
/// Класс для описания интерфейса сервиса по управлению HTTP запросами
abstract interface class IHttpClient {
/// Описывает поля HTTP клиента
const IHttpClient();
/// Наименование сервиса
static const name = 'IHttpClient';
/// Метод для реализации запроса GET
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> get(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
/// Метод для реализации запроса POST
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> post(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
/// Метод для реализации запроса PATCH
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> patch(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
/// Метод для реализации запроса PUT
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> put(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
/// Метод для реализации запроса DELETE
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> delete(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
/// Метод для реализации запроса POST
///
/// Принимает:
/// - [path] - путь к ресурсу
/// - [data] - тело запроса
/// - [queryParameters] - параметры запроса
/// - [options] - конфигурация запроса
Future<Response> head(
String path, {
Object? data,
Map<String, dynamic>? queryParameters,
Options? options,
});
}

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

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// Тип функции для построения виджета с учетом темы /// Тип функции для построения виджета с учетом темы
typedef ThemeBuilder = Widget Function(); typedef ThemeBuilder = Widget Function(BuildContext context);
/// {@template theme_consumer} /// {@template theme_consumer}
/// Виджет для подписки на изменения темы приложения. /// Виджет для подписки на изменения темы приложения.
@@ -20,8 +20,8 @@ class ThemeConsumer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<ThemeNotifier>( return Consumer<ThemeNotifier>(
builder: (_, _, _) { builder: (context, _, _) {
return builder(); return builder(context);
}, },
); );
} }

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,9 @@ 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, color: Colors.white, size: 32),
Icons.check_circle, .error => const Icon(Icons.error, color: Colors.white, size: 32),
color: Colors.white, .info => const Icon(Icons.info, color: Colors.white, size: 32),
size: 32,
),
TypeSnackBar.error => Icon(Icons.error, color: Colors.white, size: 32),
TypeSnackBar.info => Icon(Icons.info, color: Colors.white, size: 32),
}; };
} }
} }

View File

@@ -1,7 +1,6 @@
import 'package:friflex_starter/app/app_config/app_config.dart'; import 'package:friflex_starter/app/app_config/app_config.dart';
import 'package:friflex_starter/app/app_env.dart'; import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/app/http/app_http_client.dart'; import 'package:friflex_starter/app/http/app_http_client.dart';
import 'package:friflex_starter/app/http/i_http_client.dart';
import 'package:friflex_starter/di/di_repositories.dart'; import 'package:friflex_starter/di/di_repositories.dart';
import 'package:friflex_starter/di/di_services.dart'; import 'package:friflex_starter/di/di_services.dart';
import 'package:friflex_starter/di/di_typedefs.dart'; import 'package:friflex_starter/di/di_typedefs.dart';
@@ -25,7 +24,7 @@ final class DiContainer {
late final IAppConfig appConfig; late final IAppConfig appConfig;
/// Сервис для работы с HTTP запросами /// Сервис для работы с HTTP запросами
late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory; late final AppHttpClient httpClient;
/// Репозитории приложения /// Репозитории приложения
late final DiRepositories repositories; late final DiRepositories repositories;
@@ -41,14 +40,16 @@ 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 клиента
httpClientFactory = (debugService, appConfig) => httpClient = AppHttpClient(
AppHttpClient(debugService: debugService, appConfig: appConfig); debugService: debugService,
appConfig: appConfig,
);
// Инициализация сервисов // Инициализация сервисов
services = DiServices() services = DiServices()

View File

@@ -2,28 +2,27 @@ import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/di/di_base_repo.dart'; import 'package:friflex_starter/di/di_base_repo.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/di/di_typedefs.dart'; import 'package:friflex_starter/di/di_typedefs.dart';
import 'package:friflex_starter/features/auth/data/repository/auth_mock_repository.dart';
import 'package:friflex_starter/features/auth/data/repository/auth_repository.dart';
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
import 'package:friflex_starter/features/main/data/repository/main_mock_repository.dart'; import 'package:friflex_starter/features/main/data/repository/main_mock_repository.dart';
import 'package:friflex_starter/features/main/data/repository/main_repository.dart'; import 'package:friflex_starter/features/main/data/repository/main_repository.dart';
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart'; import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
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}
/// Класс для инициализации и управления репозиториями приложения. /// Класс для инициализации и управления репозиториями приложения.
@@ -43,15 +42,15 @@ final class DiRepositories {
/// {@macro di_repositories} /// {@macro di_repositories}
DiRepositories(); DiRepositories();
/// Интерфейс для работы с репозиторием авторизации
late final IAuthRepository authRepository;
/// Интерфейс для работы с репозиторием главного сервиса /// Интерфейс для работы с репозиторием главного сервиса
late final IMainRepository mainRepository; late final IMainRepository mainRepository;
/// Интерфейс для работы с репозиторием профиля /// Интерфейс для работы с репозиторием профиля
late final IProfileRepository profileRepository; late final IProfileRepository profileRepository;
/// Интерфейс для работы с репозиторием обновлений
late final IUpdateRepository updateRepository;
/// Метод для инициализации репозиториев в приложении. /// Метод для инициализации репозиториев в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -68,71 +67,34 @@ final class DiRepositories {
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
try { onProgress('Начинаем инициализацию репозиториев...');
// Инициализация репозитория авторизации
authRepository = _lazyInitRepo<IAuthRepository>( // Инициализация репозитория обновлений
mockFactory: AuthMockRepository.new, updateRepository = _lazyInitRepo<IUpdateRepository>(
mainFactory: () => AuthRepository( mockFactory: () => const UpdateMockRepository(),
httpClient: diContainer.httpClientFactory( mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient),
diContainer.debugService,
diContainer.appConfig,
),
),
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: () => const MainMockRepository(),
mainFactory: () => MainRepository( mainFactory: () => MainRepository(httpClient: diContainer.httpClient),
httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
),
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: () => const ProfileMockRepository(),
mainFactory: () => ProfileRepository( mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient),
httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
),
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 +108,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,9 +0,0 @@
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
/// {@template AuthMockRepository}
/// Mock реализация репозитория авторизации
/// {@endtemplate}
final class AuthMockRepository implements IAuthRepository {
@override
String get name => 'AuthMockRepository';
}

View File

@@ -1,14 +0,0 @@
import 'package:friflex_starter/app/http/i_http_client.dart';
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
/// {@template AuthRepository}
/// Реализация репозитория авторизации
/// {@endtemplate}
final class AuthRepository implements IAuthRepository {
AuthRepository({required this.httpClient});
final IHttpClient httpClient;
@override
String get name => 'AuthRepository';
}

View File

@@ -1,6 +0,0 @@
import 'package:friflex_starter/di/di_base_repo.dart';
/// {@template IAuthRepository}
/// Интерфейс для работы с репозиторием авторизации
/// {@endtemplate}
abstract interface class IAuthRepository with DiBaseRepo {}

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
/// {@template auth_screen}
/// Экран авторизации пользователя.
///
/// Отвечает за:
/// - Отображение формы входа в приложение
/// - Обработку процесса аутентификации
/// - Навигацию после успешной авторизации
///
/// В текущей реализации является заглушкой для будущей функциональности.
/// {@endtemplate}
class AuthScreen extends StatelessWidget {
/// {@macro auth_screen}
const AuthScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AuthScreen')),
body: const Center(child: Text('AuthScreen')),
);
}
}

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,9 +1,12 @@
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart'; import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
/// {@template MainMockRepository} /// {@template MainMockRepository}
/// /// Мок реализация репозитория главного сервиса
/// {@endtemplate} /// {@endtemplate}
final class MainMockRepository implements IMainRepository { final class MainMockRepository implements IMainRepository {
/// {@macro MainMockRepository}
const MainMockRepository();
@override @override
String get name => 'MainMockRepository'; String get name => 'MainMockRepository';
} }

View File

@@ -1,13 +1,14 @@
import 'package:friflex_starter/app/http/i_http_client.dart'; import 'package:friflex_starter/app/http/app_http_client.dart';
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart'; import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
/// {@template MainRepository} /// {@template MainRepository}
/// /// Реализация репозитория главного сервиса
/// {@endtemplate} /// {@endtemplate}
final class MainRepository implements IMainRepository { final class MainRepository implements IMainRepository {
MainRepository({required this.httpClient}); MainRepository({required this.httpClient});
final IHttpClient httpClient;
/// Экземпляр HTTP клиента для взаимодействия с сервером
final AppHttpClient httpClient;
@override @override
String get name => 'MainRepository'; String get name => 'MainRepository';

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

@@ -1,9 +1,12 @@
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart'; import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
/// {@template ProfileMockRepository} /// {@template ProfileMockRepository}
/// /// Мок реализация репозитория профиля пользователя
/// {@endtemplate} /// {@endtemplate}
final class ProfileMockRepository implements IProfileRepository { final class ProfileMockRepository implements IProfileRepository {
/// {@macro ProfileMockRepository}
const ProfileMockRepository();
@override @override
String get name => 'ProfileMockRepository'; String get name => 'ProfileMockRepository';

View File

@@ -1,13 +1,15 @@
import 'package:friflex_starter/app/http/i_http_client.dart'; import 'package:friflex_starter/app/http/app_http_client.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';
/// {@template ProfileRepository} /// {@template ProfileRepository}
/// /// Реализация репозитория профиля пользователя
/// {@endtemplate} /// {@endtemplate}
final class ProfileRepository implements IProfileRepository { final class ProfileRepository implements IProfileRepository {
ProfileRepository({required this.httpClient}); ProfileRepository({required this.httpClient});
final IHttpClient httpClient;
/// Экземпляр HTTP клиента для взаимодействия с сервером
final AppHttpClient httpClient;
@override @override
String get name => 'ProfileRepository'; String get name => 'ProfileRepository';

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

@@ -1,16 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:friflex_starter/gen/assets.gen.dart';
/// {@template SplashScreen}
/// Экран загрузки приложения.
/// {@endtemplate}
class SplashScreen extends StatelessWidget {
/// {@macro SplashScreen}
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
return Center(child: Assets.lottie.splash.lottie());
}
}

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`
## Структура модуля
```md
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 versionApp,
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,27 @@
import 'package:friflex_starter/app/http/app_http_client.dart';
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}
UpdateRepository({required this.httpClient});
/// Экземпляр HTTP клиента для взаимодействия с сервером
final AppHttpClient httpClient;
@override
Future<UpdateEntity> checkForUpdates({
required String versionApp,
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 {
/// Проверяет наличие обновлений
/// [versionApp] - текущий версия приложения
/// [platform] - платформа (например, 'android' или 'ios')
/// Возвращает [UpdateEntity] с информацией об обновлении
Future<UpdateEntity> checkForUpdates({
required String versionApp,
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;
/// Метод для проверки доступности обновлений
/// [versionApp] - текущая версия приложения
/// [platform] - платформа (например, 'android' или 'ios')
Future<void> checkForUpdates({
required String versionApp,
required String platform,
}) async {
if (state is UpdateLoadingState) return;
emit(const UpdateLoadingState());
try {
final updateInfo = await _updatesRepository.checkForUpdates(
versionApp: versionApp,
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

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// Тип функции для построения виджета с учетом локализации /// Тип функции для построения виджета с учетом локализации
typedef LocalizationBuilder = Widget Function(); typedef LocalizationBuilder = Widget Function(BuildContext context);
/// {@template localization_consumer} /// {@template localization_consumer}
/// Виджет для подписки на изменения локализации приложения. /// Виджет для подписки на изменения локализации приложения.
@@ -20,8 +20,8 @@ class LocalizationConsumer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<LocalizationNotifier>( return Consumer<LocalizationNotifier>(
builder: (_, _, _) { builder: (context, _, _) {
return builder(); return builder(context);
}, },
); );
} }

View File

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

View File

@@ -4,7 +4,7 @@ import 'package:friflex_starter/features/debug/i_debug_service.dart';
import 'package:friflex_starter/features/main/presentation/main_routes.dart'; 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/update/update_routes.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template app_router} /// {@template app_router}
@@ -38,10 +38,7 @@ class AppRouter {
], ],
), ),
DebugRoutes.buildRoutes(), DebugRoutes.buildRoutes(),
GoRoute( UpdateRoutes.buildRoutes(),
path: '/splash',
builder: (context, state) => const SplashScreen(),
),
], ],
); );
} }

View File

@@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/app/app.dart';
import 'package:friflex_starter/app/app_env.dart'; import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/app/app_root.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/debug/debug_service.dart'; import 'package:friflex_starter/features/debug/debug_service.dart';
import 'package:friflex_starter/features/debug/i_debug_service.dart'; import 'package:friflex_starter/features/debug/i_debug_service.dart';
@@ -16,11 +16,6 @@ import 'package:go_router/go_router.dart';
part 'errors_handlers.dart'; part 'errors_handlers.dart';
/// Время ожидания инициализации зависимостей
/// Если время превышено, то будет показан экран ошибки
/// В дальнейшем нужно убрать в env
const _initTimeout = Duration(seconds: 7);
/// Класс, реализующий раннер для конфигурирования приложения при запуске /// Класс, реализующий раннер для конфигурирования приложения при запуске
/// ///
/// Порядок инициализации: /// Порядок инициализации:
@@ -38,7 +33,7 @@ class AppRunner {
/// Тип окружения сборки приложения¬ /// Тип окружения сборки приложения¬
final AppEnv env; final AppEnv env;
/// Контейнер зависимостей приложения /// Сервис отладки
late IDebugService _debugService; late IDebugService _debugService;
/// Роутер приложения /// Роутер приложения
@@ -48,7 +43,7 @@ class AppRunner {
late TimerRunner _timerRunner; late TimerRunner _timerRunner;
/// Метод для запуска приложения /// Метод для запуска приложения
Future<void> run() async { Future<void> run(List<String> arguments) async {
try { try {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Инициализация сервиса отладки // Инициализация сервиса отладки
@@ -62,42 +57,31 @@ class AppRunner {
// Инициализация приложения // Инициализация приложения
await _initApp(); await _initApp();
// Инициализация метода обработки ошибок
_initErrorHandlers(_debugService);
// Инициализация роутера // Инициализация роутера
router = AppRouter.createRouter(_debugService); router = AppRouter.createRouter(_debugService);
// throw Exception('Test error'); final diContainer = await _initDependencies(
runApp(
App(
router: router,
initDependencies: () {
return _initDependencies(
debugService: _debugService, debugService: _debugService,
env: env, env: env,
timerRunner: _timerRunner, timerRunner: _timerRunner,
).timeout(
_initTimeout,
onTimeout: () {
return Future.error(
TimeoutException(
'Превышено время ожидания инициализации зависимостей',
),
);
},
);
},
),
); );
// Инициализация метода обработки ошибок
_initErrorHandlers(_debugService);
runApp(AppRoot(diContainer: diContainer, router: router));
await _onAppLoaded(); await _onAppLoaded();
} on Object catch (e, stackTrace) { } on Object catch (e, stackTrace) {
await _onAppLoaded(); await _onAppLoaded();
_timerRunner.stop();
/// Если произошла ошибка при инициализации приложения, /// Если произошла ошибка при инициализации приложения,
/// то запускаем экран ошибки /// то запускаем экран ошибки
runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run)); runApp(
ErrorScreen(
error: e,
stackTrace: stackTrace,
onRetry: () => run(arguments),
),
);
} }
} }
@@ -125,22 +109,34 @@ 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(
onProgress: (name) => timerRunner.logOnProgress(name), onProgress: (name) => timerRunner.logOnProgress(name),
onComplete: (name) { onComplete: (name) {
timerRunner timerRunner
..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');
},
)
.timeout(
const Duration(seconds: 7),
onTimeout: () {
throw Exception(
'Превышено время ожидания инициализации зависимостей',
);
},
); );
//throw Exception('Test error');
return diContainer; return diContainer;
} }
} }

View File

@@ -4,7 +4,6 @@ part of 'app_runner.dart';
void _initErrorHandlers(IDebugService debugService) { void _initErrorHandlers(IDebugService debugService) {
// Обработка ошибок в приложении // Обработка ошибок в приложении
FlutterError.onError = (details) { FlutterError.onError = (details) {
_showErrorScreen(details.exception, details.stack);
debugService.logError( debugService.logError(
() => 'FlutterError.onError: ${details.exceptionAsString()}', () => 'FlutterError.onError: ${details.exceptionAsString()}',
error: details.exception, error: details.exception,
@@ -13,7 +12,6 @@ void _initErrorHandlers(IDebugService debugService) {
}; };
// Обработка асинхронных ошибок в приложении // Обработка асинхронных ошибок в приложении
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
_showErrorScreen(error, stack);
debugService.logError( debugService.logError(
() => 'PlatformDispatcher.instance.onError', () => 'PlatformDispatcher.instance.onError',
error: error, error: error,
@@ -22,14 +20,3 @@ void _initErrorHandlers(IDebugService debugService) {
return true; return true;
}; };
} }
/// Метод для показа экрана ошибки
void _showErrorScreen(Object error, StackTrace? stackTrace) {
WidgetsBinding.instance.addPostFrameCallback((_) {
AppRouter.rootNavigatorKey.currentState?.push(
MaterialPageRoute(
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
),
);
});
}

View File

@@ -24,6 +24,11 @@ class TimerRunner {
); );
} }
/// Метод для сброса секундомера
void reset() {
_stopwatch.reset();
}
/// Метод для обработки прогресса инициализации зависимостей /// Метод для обработки прогресса инициализации зависимостей
void logOnProgress(String name) { void logOnProgress(String name) {
_debugService.log( _debugService.log(

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(List<String> arguments) => AppRunner(.dev).run(arguments);

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(List<String> arguments) => AppRunner(.prod).run(arguments);

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(List<String> arguments) => AppRunner(.stage).run(arguments);

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 необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки;
- если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках. - если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках.

Some files were not shown because too many files have changed in this diff Show More