1 Commits

Author SHA1 Message Date
Artem Barkalov
d218bd21e1 chore: Добавить конфигурации запуска для IntelliJ 2025-06-22 18:59:43 +03:00
104 changed files with 1469 additions and 3181 deletions

View File

@@ -1,389 +0,0 @@
# Правила разработки 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

View File

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

View File

@@ -14,23 +14,5 @@
"/// {@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,12 +1,24 @@
{ {
"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": "flutter", "command": "dart",
"args": [ "args": [
"pub",
"run", "run",
"build_runner", "build_runner",
"build", "build",
@@ -50,16 +62,6 @@
"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": [],
} }
] ]
} }

View File

View File

@@ -1,29 +1 @@
# Файл CODEOWNERS определяет владельцев кода для автоматического назначения ревьюеров Friflex LCC
# 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 LLC Copyright (c) 2025 Friflex
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,14 +1,21 @@
<div align="center">
# 🚀 Friflex Flutter Starter - Корпоративный шаблон # 🚀 Friflex Flutter Starter - Корпоративный шаблон
![Flutter](https://img.shields.io/badge/Flutter-3.38.1+-02569B?style=for-the-badge&logo=flutter&logoColor=white) </div>
![Dart](https://img.shields.io/badge/Dart-3.10.0+-0175C2?style=for-the-badge&logo=dart&logoColor=white) <div align="center">
![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>
--- ---
## 📖 Описание проекта ## 📖 Описание проекта
@@ -24,7 +31,6 @@
- 🌍 Поддержка интернационализации - 🌍 Поддержка интернационализации
- 🎨 UI Kit и система токенов дизайна - 🎨 UI Kit и система токенов дизайна
- 🔍 Инструменты отладки и мониторинга - 🔍 Инструменты отладки и мониторинга
- ⚡ Современный Dart 3.10+ с dot shorthands
## 🎯 Для чего нужен стартер ## 🎯 Для чего нужен стартер
@@ -91,27 +97,20 @@ features/
| Категория | Библиотека | Версия | Описание | | Категория | Библиотека | Версия | Описание |
|-----------|------------|--------|----------| |-----------|------------|--------|----------|
| 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `17.0.0` | Декларативный роутинг | | 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `15.1.2` | Декларативный роутинг |
| 🔄 **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.12.0` | Генерация ресурсов | | 🎨 **Resources** | [flutter_gen](https://pub.dev/packages/flutter_gen) | `5.10.0` | Генерация ресурсов |
| 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.9.0` | HTTP клиент | | 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.8.0+1` | 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) | `5.0.2` | Логирование и отладка | | 📊 **Logging** | [talker](https://pub.dev/packages/talker_flutter) | `4.8.0` | Логирование и отладка |
| 🎨 **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_lints: 6.0.0` | Корпоративные правила кода | - **🌍 Локализация**: `flutter_localizations` + `intl`
| **🏗️ Code Generation** | `build_runner: 2.10.3` | Генерация кода | - **⚙️ Окружения**: `envied` для управления переменными
| **🌍 Локализация** | `intl: 0.20.2` | Интернационализация |
| **⚙️ Environment** | `envied: 1.3.1` + `envied_generator: 1.3.1` | Управление переменными окружения |
| **🎨 Theme Generator** | `theme_tailor: 3.1.1` | Генерация тем |
## 🗂️ Структура проекта ## 🗂️ Структура проекта
@@ -163,33 +162,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. **Замените название пакета на ваш**
### 🎯 Запуск с разными окружениями ### 🎯 Запуск с разными окружениями
@@ -263,8 +262,6 @@ 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 |
## 🎯 Особенности и нюансы ## 🎯 Особенности и нюансы
@@ -289,10 +286,15 @@ dart run build_runner build --delete-conflicting-outputs
## 📄 Лицензия ## 📄 Лицензия
Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENSE). Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENCE).
Copyright © 2025 Friflex LLC. Все права защищены. Copyright © 2025 Friflex LLC. Все права защищены.
--- ---
### Разработано с любовью командой Friflex ❤️
<div align="right">
*Разработано с любовью командой Friflex ❤️*
</div>

View File

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

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

View File

@@ -1,3 +1,5 @@
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}
@@ -10,8 +12,7 @@ final class AppSecureStorage implements ISecureStorage {
/// Принимает: /// Принимает:
/// - [secretKey] - ключ шифрования данных /// - [secretKey] - ключ шифрования данных
AppSecureStorage({required this.secretKey}) { AppSecureStorage({required this.secretKey}) {
// Инициализация Aurora Secure Storage с ключом шифрования FlutterSecureStorageAurora.setSecret(secretKey);
// FlutterSecureStorageAurora.setSecret(secretKey);
} }
@override @override
@@ -19,36 +20,34 @@ 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 {
// TODO: Реализовать удаление ключа из Aurora Secure Storage await _box.delete(key: key);
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 {
// TODO: Реализовать чтение значения по ключу из Aurora Secure Storage return _box.read(key: key);
throw UnimplementedError();
} }
@override @override
Future<void> write(String key, String value) async { Future<void> write(String key, String value) async {
// TODO: Реализовать запись значения по ключу в Aurora Secure Storage await _box.write(key: key, value: value);
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,12 +4,22 @@ version: 0.0.1
publish_to: none publish_to: none
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: '>=3.16.2 <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,135 +1,89 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Персонализированные настройки анализатора и линтера. # Включает правила из:
# Базовый набор flutter_lints + дополнительные ужесточения. # - package:lints/core.yaml: основные правила критических проблем
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы) - "android/**"
- "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером - "assets/**"
- "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа - "build/**"
- "assets/**" # Статические ассеты (шрифты, изображения, json и др.) - "config/**"
- "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable - "core/**"
- "**/*.freezed.dart" # Сгенерированные модели пакетом freezed - "res/**"
- "**/*.gen.dart" # Общий паттерн для доп. генераторов - "ios/**"
- "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие) - "**/*.g.dart"
- "**/*.config.dart" # Сгенерированные конфигурационные файлы - "**/*.config.dart"
- "**/generated/**" # Папки с автогенерируемыми исходниками - "**/*.gen.dart"
- "**/*.lock" # Временные/lock файлы генераторов (если создаются) - "**/*.freezed.dart"
- "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён - "**/generated/*"
errors: - "**/*.gr.dart"
avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность) - "**/*.yaml"
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 для логики - "**/app_services/aurora/**"
cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек - "**/*.lock.dart"
close_sinks: error # Обязательное закрытие Sink (ресурсное управление) errors:
comment_references: warning # Проверка корректности ссылок в документационных комментариях # Переопределения уровней ошибок (error/warning/info)
always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
avoid_void_async: error # async void нежелателен (трудно ловить ошибки) close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE) comment_references: warning # Проверяет корректность ссылок в комментариях
unnecessary_new: warning # Снижение шума: new не нужен в современном Dart always_declare_return_types: error # Требует явного указания возвращаемых типов методов
use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях
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 # Обязательные именованные параметры первыми повышают читаемость always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми
- always_use_package_imports # Единый стиль импортов через package: упрощает refactoring avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
- curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности avoid_catching_errors: true # Избегать перехвата ошибок типа Error
- directives_ordering # Упорядочивание импортов (dart → package → relative) avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
- eol_at_end_of_file # Пустая строка в конце файла (POSIX standard) 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 # Избегать избыточных значений аргументов
- await_only_futures # await только для Future (ловит ошибки типизации) avoid_returning_this: true # Избегать возврата this
- control_flow_in_finally # Запрет return/break/continue в finally блоках cascade_invocations: true # Использовать каскадные вызовы
- empty_catches # Предупреждение о пустых catch (скрытые баги) deprecated_consistency: true # Поддерживать согласованность устаревших элементов
- hash_and_equals # Требует переопределять hashCode и == вместе do_not_use_environment: false # Разрешить использование Environment
- only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
- test_types_in_equals # Проверка типа в equals для безопасности no_runtimeType_toString: true # Не использовать runtimeType.toString()
- unrelated_type_equality_checks # Запрет сравнения несовместимых типов one_member_abstracts: false # Разрешать абстрактные классы с одним методом
- use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap only_throw_errors: true # Выбрасывать только объекты Error
- use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace) parameter_assignments: true # Запрещать присваивание значений параметрам
- discarded_futures # Выявляет неожиданные Future без await prefer_asserts_with_message: true # Использовать сообщения с assert
- unawaited_futures # Явно помечать намеренно не ожидаемые Future prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
prefer_final_in_for_each: true # Использовать final в for-each циклах
# === Иммутабельность и const === prefer_final_locals: true # Использовать final для локальных переменных
- avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях public_member_api_docs: false # Не требовать документацию для всех публичных членов
- prefer_const_constructors # Константные конструкторы для производительности require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
- prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах sort_constructors_first: true # Требовать размещать конструкторы первыми
- prefer_const_literals_to_create_immutables # Константные литералы коллекций sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
- prefer_final_fields # final для полей где возможно sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
- prefer_final_in_for_each # final в forEach предотвращает случайные изменения use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
- prefer_final_locals # final локальные переменные — стремимся к иммутабельности use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
- parameter_assignments # Не переназначать параметры — вводит путаницу use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
# === Flutter оптимизации === use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
- avoid_unnecessary_containers # Убирает лишние Container виджеты no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
- sized_box_for_whitespace # SizedBox вместо Container для отступов use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
- sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand always_use_package_imports: true # Всегда использовать package: импорты
- 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,5 +1,4 @@
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

@@ -1,50 +0,0 @@
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.0.0 <4.0.0" sdk: ^3.8.0
dependencies: dependencies:
flutter: flutter:
@@ -14,14 +14,11 @@ dependencies:
flutter_secure_storage: 9.2.4 flutter_secure_storage: 9.2.4
# Зависимости для сервиса незащищенного хранилища # Зависимости для сервиса незащищенного хранилища
shared_preferences: 2.5.3 shared_preferences: 2.3.5
# для работы с путями в хранилища # для работы с путями в хранилища
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

@@ -1,29 +0,0 @@
# 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

@@ -1,10 +0,0 @@
# 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

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

View File

@@ -1,135 +0,0 @@
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

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

View File

@@ -1,18 +0,0 @@
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

@@ -1,18 +0,0 @@
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

@@ -1,50 +0,0 @@
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

@@ -1,18 +0,0 @@
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,135 +1,89 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
# Персонализированные настройки анализатора и линтера. # Включает правила из:
# Базовый набор flutter_lints + дополнительные ужесточения. # - package:lints/core.yaml: основные правила критических проблем
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
analyzer: analyzer:
exclude: exclude:
- "build/**" # Исключаем артефакты сборки (генерируемый код, кэш, ресурсы) - "android/**"
- "android/**" # Платформенный код Android (Java/Kotlin, Gradle) не анализируем Dart линтером - "assets/**"
- "ios/**" # Платформенный код iOS (Swift/ObjC) вне области Dart анализа - "build/**"
- "assets/**" # Статические ассеты (шрифты, изображения, json и др.) - "config/**"
- "**/*.g.dart" # Сгенерированные файлы build_runner / json_serializable - "core/**"
- "**/*.freezed.dart" # Сгенерированные модели пакетом freezed - "res/**"
- "**/*.gen.dart" # Общий паттерн для доп. генераторов - "ios/**"
- "**/*.gr.dart" # Генерируемые маршруты (auto_route / другие) - "**/*.g.dart"
- "**/*.config.dart" # Сгенерированные конфигурационные файлы - "**/*.config.dart"
- "**/generated/**" # Папки с автогенерируемыми исходниками - "**/*.gen.dart"
- "**/*.lock" # Временные/lock файлы генераторов (если создаются) - "**/*.freezed.dart"
- "**/app_services/aurora/**" # Внутренний сервисный код (предположительно внешняя интеграция) исключён - "**/generated/*"
errors: - "**/*.gr.dart"
avoid_dynamic_calls: error # Запрет неявных dynamic вызовов (типобезопасность) - "**/*.yaml"
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 для логики - "**/app_services/aurora/**"
cancel_subscriptions: error # Требование отменять StreamSubscription для предотвращения утечек - "**/*.lock.dart"
close_sinks: error # Обязательное закрытие Sink (ресурсное управление) errors:
comment_references: warning # Проверка корректности ссылок в документационных комментариях # Переопределения уровней ошибок (error/warning/info)
always_declare_return_types: error # Явный тип возвращаемого значения повышает читаемость avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
avoid_bool_literals_in_conditional_expressions: warning # Избегать выражений вида condition ? true : false avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
avoid_return_types_on_setters: warning # Сеттеры не должны объявлять тип возврата avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
avoid_returning_null: warning # Предпочтительнее nullable типы / Option объекты вместо raw null avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
avoid_setters_without_getters: error # Сеттер без геттера может скрывать состояние cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
avoid_void_async: error # async void нежелателен (трудно ловить ошибки) close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
constant_identifier_names: error # Единый стиль именования констант (UPPER_CASE) comment_references: warning # Проверяет корректность ссылок в комментариях
unnecessary_new: warning # Снижение шума: new не нужен в современном Dart always_declare_return_types: error # Требует явного указания возвращаемых типов методов
use_decorated_box: warning # Оптимизация: DecoratedBox вместо контейнера без лишних виджетов always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
use_colored_box: warning # Оптимизация: ColoredBox для простого цвета фона avoid_bool_literals_in_conditional_expressions: warning # Запрещает избыточные булевы литералы в условных выражениях
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 # Обязательные именованные параметры первыми повышают читаемость always_put_required_named_parameters_first: true # Требовать размещать обязательные именованные параметры первыми
- always_use_package_imports # Единый стиль импортов через package: упрощает refactoring avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
- curly_braces_in_flow_control_structures # Обязательные фигурные скобки в if/for/while для безопасности avoid_catching_errors: true # Избегать перехвата ошибок типа Error
- directives_ordering # Упорядочивание импортов (dart → package → relative) avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
- eol_at_end_of_file # Пустая строка в конце файла (POSIX standard) 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 # Избегать избыточных значений аргументов
- await_only_futures # await только для Future (ловит ошибки типизации) avoid_returning_this: true # Избегать возврата this
- control_flow_in_finally # Запрет return/break/continue в finally блоках cascade_invocations: true # Использовать каскадные вызовы
- empty_catches # Предупреждение о пустых catch (скрытые баги) deprecated_consistency: true # Поддерживать согласованность устаревших элементов
- hash_and_equals # Требует переопределять hashCode и == вместе do_not_use_environment: false # Разрешить использование Environment
- only_throw_errors # Бросать Error/Exception типы, а не произвольные объекты leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
- test_types_in_equals # Проверка типа в equals для безопасности no_runtimeType_toString: true # Не использовать runtimeType.toString()
- unrelated_type_equality_checks # Запрет сравнения несовместимых типов one_member_abstracts: false # Разрешать абстрактные классы с одним методом
- use_build_context_synchronously # КРИТИЧНО: запрет BuildContext после async gap only_throw_errors: true # Выбрасывать только объекты Error
- use_rethrow_when_possible # Использовать rethrow вместо throw e (сохраняет stacktrace) parameter_assignments: true # Запрещать присваивание значений параметрам
- discarded_futures # Выявляет неожиданные Future без await prefer_asserts_with_message: true # Использовать сообщения с assert
- unawaited_futures # Явно помечать намеренно не ожидаемые Future prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
prefer_final_in_for_each: true # Использовать final в for-each циклах
# === Иммутабельность и const === prefer_final_locals: true # Использовать final для локальных переменных
- avoid_equals_and_hash_code_on_mutable_classes # hashCode/== на изменяемых классах приводит к багам в коллекциях public_member_api_docs: false # Не требовать документацию для всех публичных членов
- prefer_const_constructors # Константные конструкторы для производительности require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
- prefer_const_constructors_in_immutables # Обязательные const в неизменяемых классах sort_constructors_first: true # Требовать размещать конструкторы первыми
- prefer_const_literals_to_create_immutables # Константные литералы коллекций sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
- prefer_final_fields # final для полей где возможно sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
- prefer_final_in_for_each # final в forEach предотвращает случайные изменения use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
- prefer_final_locals # final локальные переменные — стремимся к иммутабельности use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
- parameter_assignments # Не переназначать параметры — вводит путаницу use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
# === Flutter оптимизации === use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
- avoid_unnecessary_containers # Убирает лишние Container виджеты no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
- sized_box_for_whitespace # SizedBox вместо Container для отступов use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
- sized_box_shrink_expand # Специализированные конструкторы SizedBox.shrink/expand always_use_package_imports: true # Всегда использовать package: импорты
- 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,5 +1,4 @@
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

@@ -1,9 +0,0 @@
/// {@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.0.0 <4.0.0" sdk: ^3.8.0
dependencies: dependencies:
flutter: flutter:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,5 @@
arb-dir: lib/l10n 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

141
lib/app/app.dart Normal file
View File

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

View File

@@ -1,28 +1,14 @@
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 {
/// {@macro app_providers} const AppProviders({required this.child, super.key});
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(
@@ -33,10 +19,6 @@ 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,
); );

View File

@@ -1,54 +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/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

@@ -0,0 +1,26 @@
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,12 +1,13 @@
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 { final class AppHttpClient implements IHttpClient {
/// Создает HTTP клиент /// Создает HTTP клиент
/// ///
/// Принимает: /// Принимает:
@@ -17,6 +18,7 @@ final class AppHttpClient {
required IAppConfig appConfig, required IAppConfig appConfig,
}) { }) {
_httpClient = Dio(); _httpClient = Dio();
_appConfig = appConfig;
_httpClient.options _httpClient.options
..baseUrl = appConfig.baseUrl ..baseUrl = appConfig.baseUrl
@@ -28,8 +30,111 @@ final class AppHttpClient {
_httpClient.interceptors.add(debugService.dioLogger); _httpClient.interceptors.add(debugService.dioLogger);
} }
/// Конфигурация приложения
late final IAppConfig _appConfig;
/// Экземпляр HTTP клиента /// Экземпляр HTTP клиента
late final Dio _httpClient; late final Dio _httpClient;
Dio get client => _httpClient; @override
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

@@ -0,0 +1,94 @@
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 const AppColors light = AppColors( static final AppColors light = AppColors(
testColor: Colors.red, testColor: Colors.red,
errorSnackbarBackground: Color(0xFFD24720), errorSnackbarBackground: const Color(0xFFD24720),
successSnackbarBackground: Color(0xFF6FB62C), successSnackbarBackground: const Color(0xFF6FB62C),
infoSnackbarBackground: .fromARGB(255, 220, 108, 77), infoSnackbarBackground: const Color.fromARGB(255, 220, 108, 77),
itemTextColor: Color(0xFFFAF3EB), itemTextColor: const Color(0xFFFAF3EB),
); );
/// Цвета тёмной темы /// Цвета тёмной темы
static const AppColors dark = AppColors( static final AppColors dark = AppColors(
testColor: Colors.green, testColor: Colors.green,
errorSnackbarBackground: Color(0xFF638B8B), errorSnackbarBackground: const Color(0xFF638B8B),
successSnackbarBackground: Color(0xFF93C499), successSnackbarBackground: const Color(0xFF93C499),
infoSnackbarBackground: .fromARGB(255, 35, 147, 178), infoSnackbarBackground: const Color.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(BuildContext context); typedef ThemeBuilder = Widget Function();
/// {@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: (context, _, _) { builder: (_, _, _) {
return builder(context); return builder();
}, },
); );
} }

View File

@@ -63,7 +63,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: .error, type: TypeSnackBar.error,
displayDuration: displayDuration, displayDuration: displayDuration,
); );
} }
@@ -81,7 +81,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: .info, type: TypeSnackBar.info,
displayDuration: displayDuration, displayDuration: displayDuration,
); );
} }
@@ -99,7 +99,7 @@ class AppSnackBar extends StatefulWidget {
_show( _show(
context: context, context: context,
message: message, message: message,
type: .success, type: TypeSnackBar.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),
); );
unawaited(_animationController.forward()); _animationController.forward();
} }
/// Запуск таймера для автоматического закрытия снекбара /// Запуск таймера для автоматического закрытия снекбара
/// Таймер срабатывает по истечении widget.displayDuration /// Таймер срабатывает по истечении [widget.displayDuration]
/// и вызывает метод [_dismiss] для закрытия снекбара /// и вызывает метод [_dismiss] для закрытия снекбара
void _startDismissTimer() { void _startDismissTimer() {
_dismissTimer = Timer(widget.displayDuration, _dismiss); _dismissTimer = Timer(widget.displayDuration, _dismiss);
@@ -192,19 +192,17 @@ 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
@@ -245,7 +243,7 @@ class _AppSnackBarState extends State<AppSnackBar>
Flexible( Flexible(
child: Text( child: Text(
widget.message, widget.message,
style: const TextStyle(color: Colors.white), style: TextStyle(color: Colors.white),
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -266,9 +264,9 @@ class _AppSnackBarState extends State<AppSnackBar>
/// [TypeSnackBar.error] - цвет ошибки /// [TypeSnackBar.error] - цвет ошибки
Color _getBackgroundColor(TypeSnackBar type) { Color _getBackgroundColor(TypeSnackBar type) {
return switch (type) { return switch (type) {
.success => context.appColors.successSnackbarBackground, TypeSnackBar.success => context.appColors.successSnackbarBackground,
.error => context.appColors.errorSnackbarBackground, TypeSnackBar.error => context.appColors.errorSnackbarBackground,
.info => context.appColors.infoSnackbarBackground, TypeSnackBar.info => context.appColors.infoSnackbarBackground,
}; };
} }
} }
@@ -290,9 +288,13 @@ class _Icon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return switch (type) { return switch (type) {
.success => const Icon(Icons.check_circle, color: Colors.white, size: 32), TypeSnackBar.success => Icon(
.error => const Icon(Icons.error, color: Colors.white, size: 32), Icons.check_circle,
.info => const Icon(Icons.info, color: Colors.white, size: 32), color: Colors.white,
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,6 +1,7 @@
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';
@@ -24,7 +25,7 @@ final class DiContainer {
late final IAppConfig appConfig; late final IAppConfig appConfig;
/// Сервис для работы с HTTP запросами /// Сервис для работы с HTTP запросами
late final AppHttpClient httpClient; late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
/// Репозитории приложения /// Репозитории приложения
late final DiRepositories repositories; late final DiRepositories repositories;
@@ -40,16 +41,14 @@ final class DiContainer {
}) async { }) async {
// Инициализация конфигурации приложения // Инициализация конфигурации приложения
appConfig = switch (env) { appConfig = switch (env) {
.dev => AppConfigDev(), AppEnv.dev => AppConfigDev(),
.prod => AppConfigProd(), AppEnv.prod => AppConfigProd(),
.stage => AppConfigStage(), AppEnv.stage => AppConfigStage(),
}; };
// Инициализация HTTP клиента // Инициализация HTTP клиента
httpClient = AppHttpClient( httpClientFactory = (debugService, appConfig) =>
debugService: debugService, AppHttpClient(debugService: debugService, appConfig: appConfig);
appConfig: appConfig,
);
// Инициализация сервисов // Инициализация сервисов
services = DiServices() services = DiServices()

View File

@@ -2,27 +2,28 @@ 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.
/// ///
/// Пример: /// Пример:
/// ``` /// ```
/// <Type>{ IUpdateRepository } /// [ AuthCheckRepositoryMock().name, ]
/// ``` /// ```
const _mockReposToSwitch = <Type>{IUpdateRepository}; final List<String> _mockReposToSwitch = [];
/// {@template di_repositories} /// {@template di_repositories}
/// Класс для инициализации и управления репозиториями приложения. /// Класс для инициализации и управления репозиториями приложения.
@@ -42,15 +43,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;
/// Метод для инициализации репозиториев в приложении. /// Метод для инициализации репозиториев в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -67,34 +68,71 @@ final class DiRepositories {
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
onProgress('Начинаем инициализацию репозиториев...'); try {
// Инициализация репозитория авторизации
authRepository = _lazyInitRepo<IAuthRepository>(
mockFactory: AuthMockRepository.new,
mainFactory: () => AuthRepository(
httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
),
onProgress: onProgress,
environment: diContainer.env,
);
onProgress(authRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IAuthRepository',
error,
stackTrace,
);
}
// Инициализация репозитория обновлений try {
updateRepository = _lazyInitRepo<IUpdateRepository>( // Инициализация репозитория сервиса управления токеном доступа
mockFactory: () => const UpdateMockRepository(), mainRepository = _lazyInitRepo<IMainRepository>(
mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient), mockFactory: MainMockRepository.new,
onProgress: onProgress, mainFactory: () => MainRepository(
onError: onError, httpClient: diContainer.httpClientFactory(
environment: diContainer.env, diContainer.debugService,
); diContainer.appConfig,
),
),
onProgress: onProgress,
environment: diContainer.env,
);
onProgress(mainRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IMainRepository',
error,
stackTrace,
);
}
// Инициализация репозитория сервиса управления токеном доступа try {
mainRepository = _lazyInitRepo<IMainRepository>( // Инициализация репозитория профиля
mockFactory: () => const MainMockRepository(), profileRepository = _lazyInitRepo<IProfileRepository>(
mainFactory: () => MainRepository(httpClient: diContainer.httpClient), mockFactory: ProfileMockRepository.new,
onProgress: onProgress, mainFactory: () => ProfileRepository(
onError: onError, httpClient: diContainer.httpClientFactory(
environment: diContainer.env, diContainer.debugService,
); diContainer.appConfig,
),
// Инициализация репозитория профиля ),
profileRepository = _lazyInitRepo<IProfileRepository>( onProgress: onProgress,
mockFactory: () => const ProfileMockRepository(), environment: diContainer.env,
mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient), );
onProgress: onProgress, onProgress(profileRepository.name);
onError: onError, } on Object catch (error, stackTrace) {
environment: diContainer.env, onError(
); 'Ошибка инициализации репозитория IProfileRepository',
error,
stackTrace,
);
}
onProgress( onProgress(
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})', 'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
@@ -108,36 +146,27 @@ 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,
}) { }) {
try { final mockRepo = mockFactory();
final repo = switch (environment) { final mainRepo = mainFactory();
.dev => mockFactory(),
.prod => mainFactory(),
.stage =>
_mockReposToSwitch.contains(T) ? mockFactory() : mainFactory(),
};
// throw Exception('Тестовая - ошибка инициализации репозитория $T'); final repo = switch (environment) {
onProgress(repo.name); AppEnv.dev => mockRepo,
return repo; AppEnv.prod => mainRepo,
} on Object catch (error, stackTrace) { AppEnv.stage =>
onError('Ошибка инициализации репозитория $T', error, stackTrace); _mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
// Перебрасываем исключение дальше, чтобы не скрыть ошибку инициализации };
rethrow;
} onProgress(repo.name);
return repo;
} }
} }

View File

@@ -22,9 +22,6 @@ final class DiServices {
/// Сервис для работы с защищенным локальным хранилищем /// Сервис для работы с защищенным локальным хранилищем
late final ISecureStorage secureStorage; late final ISecureStorage secureStorage;
/// Сервис для работы с геолокацией
late final ILocationService locationService;
/// Метод для инициализации сервисов в приложении. /// Метод для инициализации сервисов в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -41,7 +38,6 @@ 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) {
@@ -56,17 +52,6 @@ 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

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,14 @@
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

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

View File

@@ -0,0 +1,24 @@
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,19 +1,17 @@
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
Interceptor get dioLogger; dynamic get dioLogger;
/// Наблюдение за роутами /// Наблюдение за роутами
NavigatorObserver get routeObserver; dynamic get routeObserver;
/// Наблюдение за BLoC /// Наблюдение за BLoC
BlocObserver get blocObserver; dynamic 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,13 +1,6 @@
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}
/// Экран для демонстрации и тестирования компонентов приложения. /// Экран для демонстрации и тестирования компонентов приложения.
@@ -73,35 +66,6 @@ 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,8 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.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/app_context_ext.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';
@@ -37,42 +35,42 @@ class DebugScreen extends StatelessWidget {
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.iconsScreenName)); context.pushNamed(DebugRoutes.iconsScreenName);
}, },
child: const Text('Экран с иконками'), child: const Text('Экран с иконками'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.themeScreenName)); context.pushNamed(DebugRoutes.themeScreenName);
}, },
child: const Text('Экран настроек темы'), child: const Text('Экран настроек темы'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.tokensScreenName)); context.pushNamed(DebugRoutes.tokensScreenName);
}, },
child: const Text('Экран с токенами'), child: const Text('Экран с токенами'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.uiKitScreenName)); context.pushNamed(DebugRoutes.uiKitScreenName);
}, },
child: const Text('Экран UI Kit'), child: const Text('Экран UI Kit'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.langScreenName)); context.pushNamed(DebugRoutes.langScreenName);
}, },
child: const Text('Экран локализации'), child: const Text('Экран локализации'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () {
await context.pushNamed(DebugRoutes.componentsScreenName); context.pushNamed(DebugRoutes.componentsScreenName);
}, },
child: const Text('Экран компонентов'), child: const Text('Экран компонентов'),
), ),

View File

@@ -1,6 +1,8 @@
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}
/// Экран для отладки и тестирования локализации приложения. /// Экран для отладки и тестирования локализации приложения.
@@ -40,12 +42,18 @@ class LangScreen extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Тестовое слово bold: ${context.l10n.helloWorld}', 'Тестовое слово bold: ${context.l10n.helloWorld}',
style: TextStyle(color: context.appColors.testColor), style: TextStyle(
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(color: context.appColors.testColor), style: TextStyle(
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,12 +1,9 @@
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,14 +1,13 @@
import 'package:friflex_starter/app/http/app_http_client.dart'; import 'package:friflex_starter/app/http/i_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,5 +1,3 @@
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';
@@ -25,7 +23,7 @@ class MainScreen extends StatelessWidget {
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
// Переход на экран с деталями // Переход на экран с деталями
unawaited(context.pushNamed(MainRoutes.mainDetailScreenName)); context.pushNamed(MainRoutes.mainDetailScreenName);
}, },
child: const Text('Переход на экран с деталями'), child: const Text('Переход на экран с деталями'),
), ),

View File

@@ -1,12 +1,9 @@
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,15 +1,13 @@
import 'package:friflex_starter/app/http/app_http_client.dart'; import 'package:friflex_starter/app/http/i_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,14 +16,9 @@ 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);
}
}); });
} }
@@ -49,7 +44,6 @@ 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: 'Ошибка при загрузке профиля',
@@ -57,19 +51,6 @@ 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,37 +1,16 @@
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,11 +1,7 @@
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}
@@ -17,7 +13,7 @@ import 'package:go_router/go_router.dart';
/// - Отображение кнопки отладки в не-продакшн окружениях /// - Отображение кнопки отладки в не-продакшн окружениях
/// - Интеграцию с GoRouter для навигации /// - Интеграцию с GoRouter для навигации
/// {@endtemplate} /// {@endtemplate}
class RootScreen extends StatefulWidget { class RootScreen extends StatelessWidget {
/// {@macro root_screen} /// {@macro root_screen}
const RootScreen({required this.navigationShell, super.key}); const RootScreen({required this.navigationShell, super.key});
@@ -25,59 +21,25 @@ class RootScreen extends StatefulWidget {
/// Содержит информацию о текущем состоянии навигации /// Содержит информацию о текущем состоянии навигации
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 != .prod floatingActionButton: context.di.env != AppEnv.prod
? FloatingActionButton( ? FloatingActionButton(
child: const Icon(Icons.bug_report), child: const Icon(Icons.bug_report),
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.debugScreenName)); context.pushNamed(DebugRoutes.debugScreenName);
}, },
) )
: null, : null,
body: widget.navigationShell, body: 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: widget.navigationShell.currentIndex, currentIndex: navigationShell.currentIndex,
onTap: widget.navigationShell.goBranch, onTap: navigationShell.goBranch,
), ),
); );
} }

View File

@@ -0,0 +1,16 @@
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

@@ -1,88 +0,0 @@
# Модуль 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

@@ -1,42 +0,0 @@
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

@@ -1,27 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,38 +0,0 @@
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

@@ -1,56 +0,0 @@
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

@@ -1,103 +0,0 @@
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

@@ -1,42 +0,0 @@
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

@@ -1,20 +0,0 @@
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

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

View File

@@ -1,5 +1,3 @@
// dart format width=80
/// GENERATED CODE - DO NOT MODIFY BY HAND /// GENERATED CODE - DO NOT MODIFY BY HAND
/// ***************************************************** /// *****************************************************
/// FlutterGen /// FlutterGen
@@ -7,7 +5,7 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import // ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@@ -15,6 +13,34 @@ 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();
@@ -37,8 +63,9 @@ class $AssetsLottieGen {
} }
class Assets { class Assets {
const Assets._(); 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();
} }
@@ -69,7 +96,6 @@ 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,
@@ -89,7 +115,6 @@ class SvgGenImage {
assetBundle: bundle, assetBundle: bundle,
packageName: package, packageName: package,
theme: theme, theme: theme,
colorMapper: colorMapper,
); );
} }
return _svg.SvgPicture( return _svg.SvgPicture(
@@ -146,9 +171,6 @@ 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,
@@ -173,9 +195,6 @@ class LottieGenImage {
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality, filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
decoder: decoder,
renderCache: renderCache,
backgroundLoading: backgroundLoading,
); );
} }

15
lib/gen/fonts.gen.dart Normal file
View File

@@ -0,0 +1,15 @@
/// 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(BuildContext context); typedef LocalizationBuilder = Widget Function();
/// {@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: (context, _, _) { builder: (_, _, _) {
return builder(context); return builder();
}, },
); );
} }

View File

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

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/update/update_routes.dart'; import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template app_router} /// {@template app_router}
@@ -38,7 +38,10 @@ class AppRouter {
], ],
), ),
DebugRoutes.buildRoutes(), DebugRoutes.buildRoutes(),
UpdateRoutes.buildRoutes(), GoRoute(
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,6 +16,11 @@ import 'package:go_router/go_router.dart';
part 'errors_handlers.dart'; part 'errors_handlers.dart';
/// Время ожидания инициализации зависимостей
/// Если время превышено, то будет показан экран ошибки
/// В дальнейшем нужно убрать в env
const _initTimeout = Duration(seconds: 7);
/// Класс, реализующий раннер для конфигурирования приложения при запуске /// Класс, реализующий раннер для конфигурирования приложения при запуске
/// ///
/// Порядок инициализации: /// Порядок инициализации:
@@ -33,7 +38,7 @@ class AppRunner {
/// Тип окружения сборки приложения¬ /// Тип окружения сборки приложения¬
final AppEnv env; final AppEnv env;
/// Сервис отладки /// Контейнер зависимостей приложения
late IDebugService _debugService; late IDebugService _debugService;
/// Роутер приложения /// Роутер приложения
@@ -43,7 +48,7 @@ class AppRunner {
late TimerRunner _timerRunner; late TimerRunner _timerRunner;
/// Метод для запуска приложения /// Метод для запуска приложения
Future<void> run(List<String> arguments) async { Future<void> run() async {
try { try {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Инициализация сервиса отладки // Инициализация сервиса отладки
@@ -57,31 +62,42 @@ class AppRunner {
// Инициализация приложения // Инициализация приложения
await _initApp(); await _initApp();
// Инициализация метода обработки ошибок
_initErrorHandlers(_debugService);
// Инициализация роутера // Инициализация роутера
router = AppRouter.createRouter(_debugService); router = AppRouter.createRouter(_debugService);
final diContainer = await _initDependencies( // throw Exception('Test error');
debugService: _debugService,
env: env, runApp(
timerRunner: _timerRunner, App(
router: router,
initDependencies: () {
return _initDependencies(
debugService: _debugService,
env: env,
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( runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run));
ErrorScreen(
error: e,
stackTrace: stackTrace,
onRetry: () => run(arguments),
),
);
} }
} }
@@ -109,34 +125,22 @@ 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 await diContainer.init(
.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( //throw Exception('Test error');
message,
error: error,
stackTrace: stackTrace,
);
throw Exception('Ошибка инициализации зависимостей: $message');
},
)
.timeout(
const Duration(seconds: 7),
onTimeout: () {
throw Exception(
'Превышено время ожидания инициализации зависимостей',
);
},
);
return diContainer; return diContainer;
} }
} }

View File

@@ -4,6 +4,7 @@ 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,
@@ -12,6 +13,7 @@ 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,
@@ -20,3 +22,14 @@ 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,11 +24,6 @@ class TimerRunner {
); );
} }
/// Метод для сброса секундомера
void reset() {
_stopwatch.reset();
}
/// Метод для обработки прогресса инициализации зависимостей /// Метод для обработки прогресса инициализации зависимостей
void logOnProgress(String name) { void logOnProgress(String name) {
_debugService.log( _debugService.log(

View File

@@ -1,3 +1,4 @@
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(List<String> arguments) => AppRunner(.dev).run(arguments); void main() => AppRunner(AppEnv.dev).run();

View File

@@ -1,3 +1,4 @@
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(List<String> arguments) => AppRunner(.prod).run(arguments); void main() => AppRunner(AppEnv.prod).run();

View File

@@ -1,3 +1,4 @@
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(List<String> arguments) => AppRunner(.stage).run(arguments); void main() => AppRunner(AppEnv.stage).run();

View File

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

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.10.0 <4.0.0" sdk: ^3.8.0
flutter: ">=3.38.1" flutter: ">=3.32.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: 1.0.8 cupertino_icons: 1.0.8
envied: 1.3.1 envied: 1.1.1
go_router: 17.0.0 go_router: 15.1.2
flutter_bloc: 9.1.1 flutter_bloc: 9.1.1
provider: 6.1.5 provider: 6.1.5
dio: 5.9.0 dio: 5.8.0+1
intl: 0.20.2 intl: 0.20.2
flutter_svg: 2.2.2 flutter_svg: 2.1.0
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
lottie: 3.3.2 lottie: 3.3.1
# Пакеты для отладки # Пакеты для отладки
talker_flutter: 5.0.2 talker_flutter: 4.8.0
talker_dio_logger: 5.0.2 talker_dio_logger: 4.8.0
talker_bloc_logger: 5.0.2 talker_bloc_logger: 4.8.0
talker_logger: 5.0.2 talker_logger: 4.8.0
equatable: 2.0.7 equatable: 2.0.7
theme_tailor_annotation: 3.1.1 theme_tailor_annotation: 3.0.2
### основной сервис с интерфейсами ### основной сервис с интерфейсами
i_app_services: i_app_services:
@@ -42,27 +42,40 @@ 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.3.1 envied_generator: 1.1.1
build_runner: 2.10.3 build_runner: 2.4.15
flutter_gen_runner: 5.12.0 flutter_gen_runner: 5.10.0
flutter_gen: 5.12.0 flutter_gen: 5.10.0
flutter_lints: 6.0.0 flutter_lints: 6.0.0
theme_tailor: 3.1.1 theme_tailor: 3.0.3
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(
const MaterialApp( MaterialApp(
home: MediaQuery( home: MediaQuery(
data: MediaQueryData( data: const MediaQueryData(
padding: EdgeInsets.only(top: 50), // Симулируем статус бар padding: EdgeInsets.only(top: 50), // Симулируем статус бар
), ),
child: Scaffold(body: Center(child: Text('Test'))), child: const Scaffold(body: Center(child: Text('Test'))),
), ),
), ),
); );

View File

@@ -1,37 +1,31 @@
# Приложение [ProjectName] ## Рекомендованный Readme для проектов
#### Приложение [ProjectName]
## Структура проекта ## Структура проекта
- проект архитектурно делится на три слоя: data, domain и presentation;
- проект архитектурно делится на три слоя: data, domain и presentation; - все [features] реализуются в отдельных папках, с внутренним делением на слои;
- все [features] реализуются в отдельных папках, с внутренним делением на слои;
## Основные пакеты и реализации (обновляется при добавлении или изменении) ## Основные пакеты и реализации (обновляется при добавлении или изменении)
- управление роутингом: [go_router](https://pub.dev/packages/go_router);
- управление роутингом: [go_router](https://pub.dev/packages/go_router); - основной state manager: [flutter_bloc](https://pub.dev/packages/flutter_bloc);
- основной state manager: [flutter_bloc](https://pub.dev/packages/flutter_bloc); - di: ручная реализация через InheritedWidget;
- di: ручная реализация через InheritedWidget; - работа с ресурсами: [flutter_gen](https://pub.dev/packages/flutter_gen);
- работа с ресурсами: [flutter_gen](https://pub.dev/packages/flutter_gen); - анализатор: используем [friflex_lint_rules](https://pub.friflex.com/packages/friflex_lint_rules), с правилами написания кода от компании.;
- анализатор: используем [friflex_lint_rules](https://pub.friflex.com/packages/friflex_lint_rules), с правилами написания кода от компании.; - для хранения защищенных данных - [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage);
- для хранения защищенных данных - [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage); - для хранения данных - [shared_preferences](https://pub.dev/packages/shared_preferences);
- для хранения данных - [shared_preferences](https://pub.dev/packages/shared_preferences); - для работы с API - [dio](https://pub.dev/packages/dio);
- для работы с 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,16 +8,13 @@
## Именование ## Именование
### Интерфейсы ### Интерфейсы
Утверждены два вида объявления интерфейсов: Утверждены два вида объявления интерфейсов:
1. Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**". 1. Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
Например: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д. Например: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
Таким образом, сразу видно, что работаешь с интерфейсом. Таким образом, сразу видно, что работаешь с интерфейсом.
Пример: Пример:
```dart ```dart
/// Интерфейс - **IUserRepository** /// Интерфейс - **IUserRepository**
abstract interface class IUserRepository {} abstract interface class IUserRepository {}
@@ -32,7 +29,6 @@ class UserRepositoryLocal implements IUserRepository {}
``` ```
### Классы - Репозитории ### Классы - Репозитории
Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище).\ Репозитории должны содержать в конце название источника данных (если используется мок или локальное хранилище).\
Основная реализация, не должна содержать постфикса. Основная реализация, не должна содержать постфикса.
@@ -40,84 +36,66 @@ class UserRepositoryLocal implements IUserRepository {}
Основная реализация (prod и stage окружения) - **AuthRepository** Основная реализация (prod и stage окружения) - **AuthRepository**
Мок (мок данные) - **AuthRepositoryMock**\ Мок (мок данные) - **AuthRepositoryMock**\
Локальное хранилище (например бд или просто имитация данных) - **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,29 +2,25 @@
## Документация ## Документация
### Основные правила ведения документации в проекте ### Основные правила ведения документации в проекте:
- документация оформляется над описываемым объектом с использованием '///'; - документация оформляется над описываемым объектом с использованием '///';
- документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты; - документацией необходимо покрывать все классы, конструкторы, поля, геттеры, сеттеры, методы, фабрики, все кастомные объекты;
- документация должна: - документация должна:
- указывать на назначение объекта; - указывать на назначение объекта;
- содержать исчерпывающее описание объекта; - содержать исчерпывающее описание объекта;
- быть краткой и емкой; - быть краткой и емкой;
- быть понятной для любого разработчика. - быть понятной для любого разработчика.
## Шаблоны и примеры документации объектов ## Шаблоны и примеры документации объектов
### Документация классов ### Документация классов:
```dart ```dart
/// {@template new_class} /// {@template new_class}
/// Класс для {описание назначения и реализуемого функционала в классе}. /// Класс для реализации {описание назначения и реализуемого функционала в классе}.
/// {@endtemplate} /// {@endtemplate}
class NewClass {} class NewClass {}
``` ```
Пример: Пример:
```dart ```dart
/// {@template app_button} /// {@template app_button}
/// Класс для реализации кастомизированной кнопки. /// Класс для реализации кастомизированной кнопки.
@@ -32,12 +28,11 @@
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();
@@ -48,10 +43,8 @@
factory NewClass.fromJson(Map<String, dynamic> json) factory NewClass.fromJson(Map<String, dynamic> json)
``` ```
### Документация полей классов ### Документация полей классов:
В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании. В классе необходимо описывать каждое поле по отдельности. Если поле ссылается на другое поле или зависит от него, необходимо это указывать в описании.
```dart ```dart
/// Возраст пользователя. /// Возраст пользователя.
final int age; final int age;
@@ -60,8 +53,7 @@
final bool isAdult; final bool isAdult;
``` ```
### Документация геттеров/сеттеров ### Документация геттеров/сеттеров:
```dart ```dart
/// Получения доступа к {описание данных, которые получает геттер} /// Получения доступа к {описание данных, которые получает геттер}
int get newGetter => ... int get newGetter => ...
@@ -70,10 +62,8 @@
set newSetter (int setterValue) => ... set newSetter (int setterValue) => ...
``` ```
### Документация методов ### Документация методов:
Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки. Методы должны описывать их основное назначение. Если метод сложный, требуется указывать дополнительное описание его работы ниже через пропущенную строку. Если метод принимает какие-либо параметры, каждый параметр должен быть описан по отдельности списком с прямой ссылкой. Если метод возвращает какие-либо значения, они должны быть описаны. Желательно указать, что вернет метод в случае возникновения ошибки.
```dart ```dart
/// Метод для {описание назначения метода}. /// Метод для {описание назначения метода}.
/// {описание особенностей работы метода}. /// {описание особенностей работы метода}.
@@ -83,9 +73,7 @@
... ...
} }
``` ```
Пример: Пример:
```dart ```dart
/// Метод для расчета температуры. /// Метод для расчета температуры.
/// Принимает: /// Принимает:
@@ -100,20 +88,16 @@
## Комментарии ## Комментарии
### Основные правила комментирования кода в проекте ### Основные правила комментирования кода в проекте:
- комментарии оформляются над описываемым участком кода с использованием '//'; - комментарии оформляются над описываемым участком кода с использованием '//';
- комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании; - комментировать необходимо те участки кода, которые действительно нуждаются в дополнительном описании;
- комментарий не должен повторять легко читаемые участки кода - комментарий не должен повторять легко читаемые участки кода
Например: Например:
```dart ```dart
if(flag != true) // не нужно описывать как "Если значение не равно true... if(flag != true) // не нужно описывать как "Если значение не равно true...
``` ```
Например: Например:
```dart ```dart
if(isAurora){ if(isAurora){
// Так как Аврора не может открывать WebView, // Так как Аврора не может открывать WebView,
@@ -123,8 +107,7 @@
## TODO ## TODO
### Основные правила создания TODO в проекте ### Основные правила создания TODO в проекте:
- TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать; - TODO оформляются согласно условиям форматирования линтера с указанием имени разработчика, кто находится в контексте проблемы и сможет ее прокомментировать;
- TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки; - TODO необходимо оставлять на тех участках кода, которые требуют дальнейшей доработки;
- если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках. - если известно, в рамках какой задачи будет доработан помечаемый участок кода, ссылку на задаче необходимо оставить в скобках.

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