mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-22 01:20:46 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6500b917c5 | ||
|
|
84f7fa8889 | ||
|
|
4a49083ef3 | ||
|
|
ab64fb9246 | ||
|
|
73a13c99c6 |
421
.cursorrules
421
.cursorrules
@@ -1,64 +1,389 @@
|
||||
# Правила для Cursor AI
|
||||
# Правила разработки Flutter проекта
|
||||
|
||||
## Соглашение о коммитах
|
||||
Этот файл содержит правила и стандарты разработки для проекта. Все правила обязательны к соблюдению при написании кода.
|
||||
|
||||
При генерации сообщений коммитов ВСЕГДА используй следующий формат:
|
||||
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
|
||||
**ВАЖНО:** Перед каждым PR необходимо отформатировать код (`dart format .`) и проверить анализатор на отсутствие сообщений (`dart analyze`).
|
||||
|
||||
Для Pull Request формат:
|
||||
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
|
||||
---
|
||||
|
||||
Где:
|
||||
- `<тип>` - тип коммита (см. ниже)
|
||||
- `<контекст>` - модули/компоненты, которые изменяются (можно указать несколько через запятую)
|
||||
- `<короткое описание>` - краткое описание изменений на русском языке
|
||||
# Стиль кода
|
||||
|
||||
### Типы коммитов согласно convention:
|
||||
## Именование
|
||||
|
||||
- **feat** - новая функция
|
||||
- **fix** - исправление ошибок
|
||||
- **refactor** - изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода)
|
||||
- **build** - изменения, влияющие на систему сборки или внешние зависимости (примеры областей: android, ios, linux и так далее)
|
||||
- **docs** - изменения только в документации
|
||||
- **chore** - добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml)
|
||||
- **test** - добавление недостающих тестов или исправление существующих тестов
|
||||
- **ci** - изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI)
|
||||
### Интерфейсы
|
||||
|
||||
### Контекст (scope):
|
||||
Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
|
||||
|
||||
Указывай модуль или компонент, который изменяется. Можно указать несколько через запятую:
|
||||
- `app` - основное приложение
|
||||
- `di` - dependency injection
|
||||
- `auth` - аутентификация
|
||||
- `api` - API endpoints
|
||||
- `db` - база данных
|
||||
- `config` - конфигурация
|
||||
- `i18n` - интернационализация
|
||||
- `scripts` - скрипты
|
||||
- `pubspec` - зависимости проекта
|
||||
- `android`, `ios`, `linux` - платформы
|
||||
- другие модули проекта
|
||||
Примеры: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
|
||||
|
||||
### Примеры правильных коммитов:
|
||||
Таким образом, сразу видно, что работаешь с интерфейсом.
|
||||
|
||||
- `feat(app,di,auth): Добавить локальный репозиторий`
|
||||
- `fix(api): Исправить валидацию запросов`
|
||||
- `docs(i18n): Обновить руководство по генерации`
|
||||
- `refactor(handler): Оптимизировать обработку запросов`
|
||||
- `test(security): Добавить тесты для rate limiter`
|
||||
- `chore(pubspec): Обновить зависимости`
|
||||
Пример:
|
||||
|
||||
## Язык
|
||||
```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
|
||||
|
||||
- Следуй Dart/Flutter conventions
|
||||
- Используй осмысленные имена переменных и функций
|
||||
- Добавляй комментарии к публичным функциям
|
||||
- Группируй импорты (стандартные, внешние, внутренние)
|
||||
- **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
|
||||
|
||||
432
.github/copilot-instructions.md
vendored
432
.github/copilot-instructions.md
vendored
@@ -1,63 +1,395 @@
|
||||
Инструкция для AI-агента (ревью Flutter-кода)
|
||||
# Правила разработки Flutter проекта
|
||||
|
||||
Вы — строгий ревьюер кода Flutter-приложения по принципам **Clean Architecture**.
|
||||
Ваша задача — проводить ревью кода, выявлять ошибки и давать корректные рекомендации.
|
||||
Bloc и Cubit должны находиться в слое domain.
|
||||
Контекст (справочно, не включать в ответ):
|
||||
- Архитектура: три слоя (presentation, domain, data), каждый в своей папке.
|
||||
- State management: flutter_bloc.
|
||||
- Навигация: go_router.
|
||||
- HTTP: dio.
|
||||
- Анализатор: flutter_lint_rules.
|
||||
- Feature-first структура: lib/features/<feature_name>/{data,domain,presentation}.
|
||||
Этот файл содержит правила и стандарты разработки для проекта. Все правила обязательны к соблюдению при написании кода.
|
||||
|
||||
---
|
||||
## Обзор запроса на вытягивание
|
||||
1. В начале ответа укажите решение: **«Принять»** или **«Отклонить»**.
|
||||
2. Укажите оценку: **Оценка: X/100** (X — фактический балл).
|
||||
3. Кратко и строгим тоном перечислите ключевые проблемы:
|
||||
- Архитектура (слои `presentation`, `domain`, `data`).
|
||||
- Принципы **DRY, KISS, SOLID**.
|
||||
- Разделение ответственности (BLoC, репозитории, DTO, UI).
|
||||
- Безопасность (`dio`, SSL, валидация данных).
|
||||
- Кодстайл и соглашения (`flutter_lint_rules`).
|
||||
- Цикломатическая сложность: избегать чрезмерно сложных функций и классов (оптимально ≤ 10).
|
||||
|
||||
4. При необходимости предоставьте исправленный фрагмент кода или улучшенное решение.
|
||||
5. Ответ должен быть в **Markdown** и полностью на **русском языке**.
|
||||
**ВАЖНО:** Перед каждым PR необходимо отформатировать код (`dart format .`) и проверить анализатор на отсутствие сообщений (`dart analyze`).
|
||||
|
||||
---
|
||||
|
||||
## Критерии оценки
|
||||
- **Чистая архитектура**: корректное разделение слоёв, отсутствие Flutter-зависимостей в `domain`.
|
||||
- **KISS**: минимальная сложность и читаемость решений.
|
||||
- **DRY**: отсутствие дублирования логики, использование утилит/виджетов.
|
||||
- **SOLID**: правильная декомпозиция классов и интерфейсы вместо жёстких связей.
|
||||
- **Безопасность**: корректная работа с API, валидация данных.
|
||||
- **Кодстайл**: именование, структура файлов и папок.
|
||||
- **Цикломатическая сложность**: методы и классы должны быть простыми, без избыточных ветвлений.
|
||||
# Правила проведения Code Review
|
||||
|
||||
## Основные правила проведения Code Review
|
||||
**ВАЖНО:** - Комментарии, обзоры и описание Pull Request при проведении code review должны быть на РУССКОМ языке.
|
||||
|
||||
---
|
||||
# Стиль кода
|
||||
|
||||
## Пример ответа
|
||||
**Отклонить**
|
||||
**Оценка: 58/100**
|
||||
- Нарушен принцип DRY: HTTP-запрос продублирован в двух репозиториях.
|
||||
- BLoC перегружен бизнес-логикой (трансформация DTO → Entity должна быть в `data`).
|
||||
- Domain-слой содержит зависимость от Flutter — это недопустимо.
|
||||
- Отсутствует обработка ошибок и валидация данных в `dio`.
|
||||
## Именование
|
||||
|
||||
### Интерфейсы
|
||||
|
||||
Все интерфейсы в приложении должны начинаться с заглавной буквы "**I**".
|
||||
|
||||
Примеры: **IAuthRepository**, **IProfileRepository**, **IMainRunner** и т.д.
|
||||
|
||||
Таким образом, сразу видно, что работаешь с интерфейсом.
|
||||
|
||||
Пример:
|
||||
|
||||
**Исправленный фрагмент:**
|
||||
```dart
|
||||
// Вместо дублирования запроса используем общий DataSource
|
||||
class UserRemoteDataSource {
|
||||
final Dio dio;
|
||||
UserRemoteDataSource(this.dio);
|
||||
/// Интерфейс - **IUserRepository**
|
||||
abstract interface class IUserRepository {}
|
||||
|
||||
Future<UserEntity> fetchUser(String id) async {
|
||||
final response = await dio.get('/users/$id');
|
||||
return UserDto.fromJson(response.data).toEntity();
|
||||
}
|
||||
/// Основная реализация (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
|
||||
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -1,5 +1,3 @@
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
## Ссылка на задачу или issue (обязательно)
|
||||
<!--- https://tracker.yandex.ru/XXX -->
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ linter:
|
||||
- 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) — ошибки системные
|
||||
- avoid_catches_without_on_clauses # Требует явного указания типа исключения в catch (on Type catch)
|
||||
- await_only_futures # await только для Future (ловит ошибки типизации)
|
||||
- control_flow_in_finally # Запрет return/break/continue в finally блоках
|
||||
- empty_catches # Предупреждение о пустых catch (скрытые баги)
|
||||
|
||||
157
lib/app/app.dart
157
lib/app/app.dart
@@ -1,157 +0,0 @@
|
||||
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_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/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:friflex_starter/features/update/update_routes.dart';
|
||||
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
||||
import 'package:friflex_starter/l10n/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) {
|
||||
return switch (snapshot.connectionState) {
|
||||
// Если состояние не определено, ожидается или активно, то отображаем экран загрузки
|
||||
ConnectionState.none ||
|
||||
ConnectionState.waiting ||
|
||||
ConnectionState.active => const SplashScreen(),
|
||||
ConnectionState.done =>
|
||||
// Если данные получены и не равны null, то отображаем внутренний виджет приложения
|
||||
// Иначе отображаем экран ошибки
|
||||
(snapshot.hasData && snapshot.data != null)
|
||||
? _App(router: widget.router, diContainer: snapshot.data!)
|
||||
: ErrorScreen(
|
||||
error: snapshot.error,
|
||||
stackTrace: snapshot.stackTrace,
|
||||
onRetry: _retryInit,
|
||||
),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Метод для перезапуска инициализации зависимостей
|
||||
/// Вызывается при ошибках инициализации для повторной попытки
|
||||
void _retryInit() {
|
||||
setState(() {
|
||||
_initFuture = widget.initDependencies();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template app_internal}
|
||||
/// Внутренний виджет приложения, отображающий основной интерфейс
|
||||
/// после успешной инициализации зависимостей.
|
||||
///
|
||||
/// Настраивает MaterialApp с роутером, темами и локализацией.
|
||||
/// {@endtemplate}
|
||||
class _App extends StatelessWidget {
|
||||
/// {@macro app_internal}
|
||||
const _App({required this.router, required this.diContainer});
|
||||
|
||||
/// Роутер приложения для навигации
|
||||
final GoRouter router;
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DependsProviders(
|
||||
diContainer: diContainer,
|
||||
child: BlocConsumer<UpdateCubit, UpdateState>(
|
||||
listener: (context, state) {
|
||||
if (state is UpdateSuccessState &&
|
||||
state.updateInfo.updateType == .hard &&
|
||||
context.mounted) {
|
||||
router.goNamed(UpdateRoutes.hardUpdateScreenName);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
// Если состояние загрузки, то отображаем экран загрузки
|
||||
if (state is UpdateLoadingState) {
|
||||
return const SplashScreen();
|
||||
}
|
||||
return ThemeConsumer(
|
||||
builder: () => MediaQuery(
|
||||
key: const ValueKey('prevent_rebuild'),
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
|
||||
child: MaterialApp.router(
|
||||
routerConfig: router,
|
||||
darkTheme: AppTheme.dark,
|
||||
theme: AppTheme.light,
|
||||
themeMode: context.theme.themeMode,
|
||||
locale: context.localization.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,28 @@
|
||||
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/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:provider/provider.dart';
|
||||
|
||||
/// Класс для добавления провайдеров темы и локализации
|
||||
/// {@template app_providers}
|
||||
/// Класс для добавления зависимостей приложения
|
||||
/// {@endtemplate}
|
||||
final class AppProviders extends StatelessWidget {
|
||||
const AppProviders({required this.child, super.key});
|
||||
/// {@macro app_providers}
|
||||
const AppProviders({
|
||||
required this.child,
|
||||
required this.diContainer,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Виджет, который будет отображаться внутри провайдеров
|
||||
final Widget child;
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
@@ -19,6 +33,10 @@ final class AppProviders extends StatelessWidget {
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => LocalizationNotifier(),
|
||||
), // Провайдер для локализации
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
BlocProvider(
|
||||
create: (_) => UpdateCubit(diContainer.repositories.updateRepository),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
||||
54
lib/app/app_root.dart
Normal file
54
lib/app/app_root.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/app_providers.dart';
|
||||
import 'package:friflex_starter/app/theme/app_theme.dart';
|
||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// {@template app}
|
||||
/// Главный виджет приложения, отображающий основной интерфейс приложения
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Настройку провайдеров для темы и локализации
|
||||
/// {@endtemplate}
|
||||
class AppRoot extends StatelessWidget {
|
||||
/// {@macro app_root}
|
||||
const AppRoot({required this.diContainer, required this.router, super.key});
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
/// Роутер приложения
|
||||
final GoRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppProviders(
|
||||
diContainer: diContainer,
|
||||
child: LocalizationConsumer(
|
||||
builder: (localizationContext) {
|
||||
return ThemeConsumer(
|
||||
builder: (themeContext) => MediaQuery(
|
||||
key: const ValueKey('prevent_rebuild'),
|
||||
data: MediaQuery.of(
|
||||
themeContext,
|
||||
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
|
||||
child: MaterialApp.router(
|
||||
darkTheme: AppTheme.dark,
|
||||
theme: AppTheme.light,
|
||||
themeMode: themeContext.theme.themeMode,
|
||||
locale: localizationContext.localization.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: router,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для внедрения глобальных зависимостей
|
||||
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), // Передаем контейнер зависимостей
|
||||
BlocProvider(
|
||||
create: (_) {
|
||||
final updateCubit = UpdateCubit(
|
||||
diContainer.repositories.updatesRepository,
|
||||
);
|
||||
unawaited(
|
||||
updateCubit.checkForUpdates(
|
||||
versionCode:
|
||||
'1.0.0', // TODO(yura): заменить на получение из diContainer
|
||||
platform: 'android',
|
||||
),
|
||||
);
|
||||
return updateCubit;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import 'package:dio/dio.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';
|
||||
|
||||
/// {@template app_http_client}
|
||||
/// Класс для реализации HTTP-клиента для управления запросами
|
||||
/// {@endtemplate}
|
||||
final class AppHttpClient implements IHttpClient {
|
||||
final class AppHttpClient {
|
||||
/// Создает HTTP клиент
|
||||
///
|
||||
/// Принимает:
|
||||
@@ -18,7 +17,6 @@ final class AppHttpClient implements IHttpClient {
|
||||
required IAppConfig appConfig,
|
||||
}) {
|
||||
_httpClient = Dio();
|
||||
_appConfig = appConfig;
|
||||
|
||||
_httpClient.options
|
||||
..baseUrl = appConfig.baseUrl
|
||||
@@ -30,111 +28,8 @@ final class AppHttpClient implements IHttpClient {
|
||||
_httpClient.interceptors.add(debugService.dioLogger);
|
||||
}
|
||||
|
||||
/// Конфигурация приложения
|
||||
late final IAppConfig _appConfig;
|
||||
|
||||
/// Экземпляр HTTP клиента
|
||||
late final Dio _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,
|
||||
);
|
||||
}
|
||||
Dio get client => _httpClient;
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// Класс для описания интерфейса сервиса по управлению HTTP запросами
|
||||
abstract interface class IHttpClient {
|
||||
/// Описывает поля HTTP клиента
|
||||
const IHttpClient();
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'IHttpClient';
|
||||
|
||||
/// Метод для реализации запроса GET
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> get(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса POST
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> post(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса PATCH
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> patch(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса PUT
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> put(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса DELETE
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> delete(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса POST
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> head(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
|
||||
);
|
||||
|
||||
/// Цвета тёмной темы
|
||||
static const AppColors dark = AppColors(
|
||||
static const AppColors dark = AppColors(
|
||||
testColor: Colors.green,
|
||||
errorSnackbarBackground: Color(0xFF638B8B),
|
||||
successSnackbarBackground: Color(0xFF93C499),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом темы
|
||||
typedef ThemeBuilder = Widget Function();
|
||||
typedef ThemeBuilder = Widget Function(BuildContext context);
|
||||
|
||||
/// {@template theme_consumer}
|
||||
/// Виджет для подписки на изменения темы приложения.
|
||||
@@ -20,8 +20,8 @@ class ThemeConsumer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ThemeNotifier>(
|
||||
builder: (_, _, _) {
|
||||
return builder();
|
||||
builder: (context, _, _) {
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,21 +290,9 @@ class _Icon extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (type) {
|
||||
.success => const Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.error => const Icon(
|
||||
Icons.error,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.info => const Icon(
|
||||
Icons.info,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.success => const Icon(Icons.check_circle, color: Colors.white, size: 32),
|
||||
.error => const Icon(Icons.error, color: Colors.white, size: 32),
|
||||
.info => const Icon(Icons.info, color: Colors.white, size: 32),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:friflex_starter/app/app_config/app_config.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/i_http_client.dart';
|
||||
import 'package:friflex_starter/di/di_repositories.dart';
|
||||
import 'package:friflex_starter/di/di_services.dart';
|
||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||
@@ -25,7 +24,7 @@ final class DiContainer {
|
||||
late final IAppConfig appConfig;
|
||||
|
||||
/// Сервис для работы с HTTP запросами
|
||||
late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
|
||||
late final AppHttpClient httpClient;
|
||||
|
||||
/// Репозитории приложения
|
||||
late final DiRepositories repositories;
|
||||
@@ -47,8 +46,10 @@ final class DiContainer {
|
||||
};
|
||||
|
||||
// Инициализация HTTP клиента
|
||||
httpClientFactory = (debugService, appConfig) =>
|
||||
AppHttpClient(debugService: debugService, appConfig: appConfig);
|
||||
httpClient = AppHttpClient(
|
||||
debugService: debugService,
|
||||
appConfig: appConfig,
|
||||
);
|
||||
|
||||
// Инициализация сервисов
|
||||
services = DiServices()
|
||||
|
||||
@@ -2,9 +2,6 @@ import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
import 'package:friflex_starter/di/di_container.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_repository.dart';
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
@@ -45,9 +42,6 @@ final class DiRepositories {
|
||||
/// {@macro di_repositories}
|
||||
DiRepositories();
|
||||
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
late final IAuthRepository authRepository;
|
||||
|
||||
/// Интерфейс для работы с репозиторием главного сервиса
|
||||
late final IMainRepository mainRepository;
|
||||
|
||||
@@ -55,7 +49,7 @@ final class DiRepositories {
|
||||
late final IProfileRepository profileRepository;
|
||||
|
||||
/// Интерфейс для работы с репозиторием обновлений
|
||||
late final IUpdateRepository updatesRepository;
|
||||
late final IUpdateRepository updateRepository;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении.
|
||||
///
|
||||
@@ -76,23 +70,9 @@ final class DiRepositories {
|
||||
onProgress('Начинаем инициализацию репозиториев...');
|
||||
|
||||
// Инициализация репозитория обновлений
|
||||
updatesRepository = _lazyInitRepo<IUpdateRepository>(
|
||||
mockFactory: UpdateMockRepository.new,
|
||||
mainFactory: UpdateRepository.new,
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
// Инициализация репозитория авторизации
|
||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
updateRepository = _lazyInitRepo<IUpdateRepository>(
|
||||
mockFactory: () => const UpdateMockRepository(),
|
||||
mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
@@ -100,13 +80,8 @@ final class DiRepositories {
|
||||
|
||||
// Инициализация репозитория сервиса управления токеном доступа
|
||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||
mockFactory: MainMockRepository.new,
|
||||
mainFactory: () => MainRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
mockFactory: () => const MainMockRepository(),
|
||||
mainFactory: () => MainRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
@@ -114,13 +89,8 @@ final class DiRepositories {
|
||||
|
||||
// Инициализация репозитория профиля
|
||||
profileRepository = _lazyInitRepo<IProfileRepository>(
|
||||
mockFactory: ProfileMockRepository.new,
|
||||
mainFactory: () => ProfileRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
mockFactory: () => const ProfileMockRepository(),
|
||||
mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthMockRepository}
|
||||
/// Mock реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthMockRepository implements IAuthRepository {
|
||||
@override
|
||||
String get name => 'AuthMockRepository';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthRepository}
|
||||
/// Реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthRepository implements IAuthRepository {
|
||||
AuthRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'AuthRepository';
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IAuthRepository}
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
/// {@endtemplate}
|
||||
abstract interface class IAuthRepository with DiBaseRepo {}
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template auth_screen}
|
||||
/// Экран авторизации пользователя.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение формы входа в приложение
|
||||
/// - Обработку процесса аутентификации
|
||||
/// - Навигацию после успешной авторизации
|
||||
///
|
||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||
/// {@endtemplate}
|
||||
class AuthScreen extends StatelessWidget {
|
||||
/// {@macro auth_screen}
|
||||
const AuthScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('AuthScreen')),
|
||||
body: const Center(child: Text('AuthScreen')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
|
||||
/// {@template MainMockRepository}
|
||||
///
|
||||
/// Мок реализация репозитория главного сервиса
|
||||
/// {@endtemplate}
|
||||
final class MainMockRepository implements IMainRepository {
|
||||
/// {@macro MainMockRepository}
|
||||
const MainMockRepository();
|
||||
|
||||
@override
|
||||
String get name => 'MainMockRepository';
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
|
||||
/// {@template MainRepository}
|
||||
///
|
||||
/// Реализация репозитория главного сервиса
|
||||
/// {@endtemplate}
|
||||
final class MainRepository implements IMainRepository {
|
||||
MainRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'MainRepository';
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileMockRepository}
|
||||
///
|
||||
/// Мок реализация репозитория профиля пользователя
|
||||
/// {@endtemplate}
|
||||
final class ProfileMockRepository implements IProfileRepository {
|
||||
/// {@macro ProfileMockRepository}
|
||||
const ProfileMockRepository();
|
||||
|
||||
@override
|
||||
String get name => 'ProfileMockRepository';
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileRepository}
|
||||
///
|
||||
/// Реализация репозитория профиля пользователя
|
||||
/// {@endtemplate}
|
||||
final class ProfileRepository implements IProfileRepository {
|
||||
ProfileRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'ProfileRepository';
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
|
||||
/// {@template SplashScreen}
|
||||
/// Экран загрузки приложения.
|
||||
/// {@endtemplate}
|
||||
class SplashScreen extends StatelessWidget {
|
||||
/// {@macro SplashScreen}
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(child: Assets.lottie.splash.lottie());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
|
||||
@@ -6,7 +7,10 @@ import 'package:friflex_starter/features/update/domain/repository/i_update_repos
|
||||
/// {@endtemplate}
|
||||
final class UpdateRepository implements IUpdateRepository {
|
||||
/// {@macro UpdateRepository}
|
||||
const UpdateRepository();
|
||||
UpdateRepository({required this.httpClient});
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
Future<UpdateEntity> checkForUpdates({
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом локализации
|
||||
typedef LocalizationBuilder = Widget Function();
|
||||
typedef LocalizationBuilder = Widget Function(BuildContext context);
|
||||
|
||||
/// {@template localization_consumer}
|
||||
/// Виджет для подписки на изменения локализации приложения.
|
||||
@@ -20,8 +20,8 @@ class LocalizationConsumer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<LocalizationNotifier>(
|
||||
builder: (_, _, _) {
|
||||
return builder();
|
||||
builder: (context, _, _) {
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
import 'package:friflex_starter/targets/prod.dart' as prod;
|
||||
|
||||
void main() => AppRunner(.prod).run();
|
||||
void main(List<String> arguments) => prod.main(arguments);
|
||||
|
||||
@@ -4,7 +4,6 @@ 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/profile/presentation/profile_routes.dart';
|
||||
import 'package:friflex_starter/features/root/root_screen.dart';
|
||||
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||
import 'package:friflex_starter/features/update/update_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -39,10 +38,6 @@ class AppRouter {
|
||||
],
|
||||
),
|
||||
DebugRoutes.buildRoutes(),
|
||||
GoRoute(
|
||||
path: '/splash',
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
UpdateRoutes.buildRoutes(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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_root.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_service.dart';
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
@@ -16,11 +16,6 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
part 'errors_handlers.dart';
|
||||
|
||||
/// Время ожидания инициализации зависимостей
|
||||
/// Если время превышено, то будет показан экран ошибки
|
||||
/// В дальнейшем нужно убрать в env
|
||||
const _initTimeout = Duration(seconds: 7);
|
||||
|
||||
/// Класс, реализующий раннер для конфигурирования приложения при запуске
|
||||
///
|
||||
/// Порядок инициализации:
|
||||
@@ -48,7 +43,7 @@ class AppRunner {
|
||||
late TimerRunner _timerRunner;
|
||||
|
||||
/// Метод для запуска приложения
|
||||
Future<void> run() async {
|
||||
Future<void> run(List<String> arguments) async {
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Инициализация сервиса отладки
|
||||
@@ -62,42 +57,31 @@ class AppRunner {
|
||||
// Инициализация приложения
|
||||
await _initApp();
|
||||
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
|
||||
// Инициализация роутера
|
||||
router = AppRouter.createRouter(_debugService);
|
||||
|
||||
// throw Exception('Test error');
|
||||
|
||||
runApp(
|
||||
App(
|
||||
router: router,
|
||||
initDependencies: () {
|
||||
return _initDependencies(
|
||||
debugService: _debugService,
|
||||
env: env,
|
||||
timerRunner: _timerRunner,
|
||||
).timeout(
|
||||
_initTimeout,
|
||||
onTimeout: () {
|
||||
return Future.error(
|
||||
TimeoutException(
|
||||
'Превышено время ожидания инициализации зависимостей',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
final diContainer = await _initDependencies(
|
||||
debugService: _debugService,
|
||||
env: env,
|
||||
timerRunner: _timerRunner,
|
||||
);
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
runApp(AppRoot(diContainer: diContainer, router: router));
|
||||
await _onAppLoaded();
|
||||
} on Object catch (e, stackTrace) {
|
||||
await _onAppLoaded();
|
||||
_timerRunner.stop();
|
||||
|
||||
/// Если произошла ошибка при инициализации приложения,
|
||||
/// то запускаем экран ошибки
|
||||
runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run));
|
||||
runApp(
|
||||
ErrorScreen(
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
onRetry: () => run(arguments),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,20 +111,32 @@ class AppRunner {
|
||||
}) async {
|
||||
debugService.log(() => 'Тип сборки: ${env.name}');
|
||||
final diContainer = DiContainer(env: env, dService: debugService);
|
||||
await diContainer.init(
|
||||
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||
onComplete: (name) {
|
||||
timerRunner
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) {
|
||||
timerRunner.stop();
|
||||
_debugService.logError(message, error: error, stackTrace: stackTrace);
|
||||
throw Exception('Ошибка инициализации зависимостей: $message');
|
||||
},
|
||||
);
|
||||
//throw Exception('Test error');
|
||||
await diContainer
|
||||
.init(
|
||||
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||
onComplete: (name) {
|
||||
timerRunner
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) {
|
||||
timerRunner.stop();
|
||||
_debugService.logError(
|
||||
message,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
throw Exception('Ошибка инициализации зависимостей: $message');
|
||||
},
|
||||
)
|
||||
.timeout(
|
||||
const Duration(seconds: 7),
|
||||
onTimeout: () {
|
||||
throw Exception(
|
||||
'Превышено время ожидания инициализации зависимостей',
|
||||
);
|
||||
},
|
||||
);
|
||||
return diContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ part of 'app_runner.dart';
|
||||
void _initErrorHandlers(IDebugService debugService) {
|
||||
// Обработка ошибок в приложении
|
||||
FlutterError.onError = (details) {
|
||||
_showErrorScreen(details.exception, details.stack);
|
||||
debugService.logError(
|
||||
() => 'FlutterError.onError: ${details.exceptionAsString()}',
|
||||
error: details.exception,
|
||||
@@ -13,7 +12,6 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
};
|
||||
// Обработка асинхронных ошибок в приложении
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
_showErrorScreen(error, stack);
|
||||
debugService.logError(
|
||||
() => 'PlatformDispatcher.instance.onError',
|
||||
error: error,
|
||||
@@ -22,14 +20,3 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/// Метод для показа экрана ошибки
|
||||
void _showErrorScreen(Object error, StackTrace? stackTrace) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await AppRouter.rootNavigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ class TimerRunner {
|
||||
);
|
||||
}
|
||||
|
||||
/// Метод для сброса секундомера
|
||||
void reset() {
|
||||
_stopwatch.reset();
|
||||
}
|
||||
|
||||
/// Метод для обработки прогресса инициализации зависимостей
|
||||
void logOnProgress(String name) {
|
||||
_debugService.log(
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.dev).run();
|
||||
void main(List<String> arguments) => AppRunner(.dev).run(arguments);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.prod).run();
|
||||
void main(List<String> arguments) => AppRunner(.prod).run(arguments);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.stage).run();
|
||||
void main(List<String> arguments) => AppRunner(.stage).run(arguments);
|
||||
|
||||
Reference in New Issue
Block a user