From 73a13c99c6aac8a52e0d1ceb0dfba7886bb016de Mon Sep 17 00:00:00 2001 From: petrovyuri Date: Wed, 26 Nov 2025 22:37:35 +0300 Subject: [PATCH 1/5] =?UTF-8?q?fix(linter):=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=BE?= =?UTF-8?q?=20eol=5Fat=5Fend=5Fof=5Ffile=20=D0=B8=D0=B7=20=D0=BD=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20=D0=BB=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analysis_options.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 221a9b2..4708b49 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -43,7 +43,6 @@ 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 From ab64fb9246a2e0ca2e68f7e2d1eeb666ce7f6460 Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Wed, 10 Dec 2025 12:42:49 +0300 Subject: [PATCH 2/5] =?UTF-8?q?docs(cursorrules,copilot-instructions):=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20=D1=80=D0=B0=D0=B7=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B8=20=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20AI-?= =?UTF-8?q?=D0=B0=D0=B3=D0=B5=D0=BD=D1=82=D0=B0=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursorrules | 421 +++++++++++++++++++++++++++---- .github/copilot-instructions.md | 427 ++++++++++++++++++++++++++++---- 2 files changed, 750 insertions(+), 98 deletions(-) diff --git a/.cursorrules b/.cursorrules index 6ca7e36..685a117 100644 --- a/.cursorrules +++ b/.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 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 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b5a0dfa..5583e1d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,63 +1,390 @@ -Инструкция для 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//{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, валидация данных. -- **Кодстайл**: именование, структура файлов и папок. -- **Цикломатическая сложность**: методы и классы должны быть простыми, без избыточных ветвлений. +# Стиль кода +## Именование ---- +### Интерфейсы -## Пример ответа -**Отклонить** -**Оценка: 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 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 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 From 4a49083ef354dc5525980b23174adcf67bc1a14f Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:27:19 +0300 Subject: [PATCH 3/5] =?UTF-8?q?refactor(http):=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=20IHttpClient=20=D0=B8=20=D1=83=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D1=82=D1=8C=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8E=20AppHttpClient=20=20=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(http): удалить интерфейс IHttpClient и упростить реализацию AppHttpClient - Удален интерфейс IHttpClient, что упростило структуру кода. - AppHttpClient теперь напрямую использует Dio без промежуточного интерфейса. - Обновлены зависимости в репозиториях для использования нового HTTP клиента. * refactor(code): dart format * chore(pr-template): удалить отключения markdownlint из шаблона PR * docs(copilot-instructions): добавить правила проведения Code Review --------- Co-authored-by: petrovyuri --- .github/copilot-instructions.md | 5 + .github/pull_request_template.md | 2 - lib/app/http/app_http_client.dart | 109 +----------------- lib/app/http/i_http_client.dart | 94 --------------- lib/app/theme/app_colors_scheme.dart | 2 +- lib/app/ui_kit/app_snackbar.dart | 18 +-- lib/di/di_container.dart | 9 +- lib/di/di_repositories.dart | 31 ++--- .../data/repository/auth_mock_repository.dart | 3 + .../auth/data/repository/auth_repository.dart | 6 +- .../data/repository/main_mock_repository.dart | 5 +- .../main/data/repository/main_repository.dart | 9 +- .../repository/profile_mock_repository.dart | 5 +- .../data/repository/profile_repository.dart | 8 +- .../data/repository/update_repository.dart | 6 +- 15 files changed, 54 insertions(+), 258 deletions(-) delete mode 100644 lib/app/http/i_http_client.dart diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5583e1d..fee43c8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -6,6 +6,11 @@ --- +# Правила проведения Code Review + +## Основные правила проведения Code Review +- Комментарии и описание Pull Request должны быть на русском языке. + # Стиль кода ## Именование diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ed98222..8764cbf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,3 @@ - - ## Ссылка на задачу или issue (обязательно) diff --git a/lib/app/http/app_http_client.dart b/lib/app/http/app_http_client.dart index 253a3eb..2bef6ac 100644 --- a/lib/app/http/app_http_client.dart +++ b/lib/app/http/app_http_client.dart @@ -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 get( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.get( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } - - @override - Future post( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.post( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } - - @override - Future patch( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.patch( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } - - @override - Future put( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.put( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } - - @override - Future delete( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.delete( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } - - @override - Future head( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }) async { - _httpClient.options.baseUrl = _appConfig.baseUrl; - - return _httpClient.head( - path, - data: data, - queryParameters: queryParameters, - options: options, - ); - } + Dio get client => _httpClient; } diff --git a/lib/app/http/i_http_client.dart b/lib/app/http/i_http_client.dart deleted file mode 100644 index 34d8315..0000000 --- a/lib/app/http/i_http_client.dart +++ /dev/null @@ -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 get( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); - - /// Метод для реализации запроса POST - /// - /// Принимает: - /// - [path] - путь к ресурсу - /// - [data] - тело запроса - /// - [queryParameters] - параметры запроса - /// - [options] - конфигурация запроса - Future post( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); - - /// Метод для реализации запроса PATCH - /// - /// Принимает: - /// - [path] - путь к ресурсу - /// - [data] - тело запроса - /// - [queryParameters] - параметры запроса - /// - [options] - конфигурация запроса - Future patch( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); - - /// Метод для реализации запроса PUT - /// - /// Принимает: - /// - [path] - путь к ресурсу - /// - [data] - тело запроса - /// - [queryParameters] - параметры запроса - /// - [options] - конфигурация запроса - Future put( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); - - /// Метод для реализации запроса DELETE - /// - /// Принимает: - /// - [path] - путь к ресурсу - /// - [data] - тело запроса - /// - [queryParameters] - параметры запроса - /// - [options] - конфигурация запроса - Future delete( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); - - /// Метод для реализации запроса POST - /// - /// Принимает: - /// - [path] - путь к ресурсу - /// - [data] - тело запроса - /// - [queryParameters] - параметры запроса - /// - [options] - конфигурация запроса - Future head( - String path, { - Object? data, - Map? queryParameters, - Options? options, - }); -} diff --git a/lib/app/theme/app_colors_scheme.dart b/lib/app/theme/app_colors_scheme.dart index 4b3c653..5cf254f 100644 --- a/lib/app/theme/app_colors_scheme.dart +++ b/lib/app/theme/app_colors_scheme.dart @@ -55,7 +55,7 @@ class AppColors extends ThemeExtension with _$AppColorsTailorMixin { ); /// Цвета тёмной темы - static const AppColors dark = AppColors( + static const AppColors dark = AppColors( testColor: Colors.green, errorSnackbarBackground: Color(0xFF638B8B), successSnackbarBackground: Color(0xFF93C499), diff --git a/lib/app/ui_kit/app_snackbar.dart b/lib/app/ui_kit/app_snackbar.dart index adfa2f7..85f5aee 100644 --- a/lib/app/ui_kit/app_snackbar.dart +++ b/lib/app/ui_kit/app_snackbar.dart @@ -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), }; } } diff --git a/lib/di/di_container.dart b/lib/di/di_container.dart index f4fcdb0..5197471 100644 --- a/lib/di/di_container.dart +++ b/lib/di/di_container.dart @@ -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() diff --git a/lib/di/di_repositories.dart b/lib/di/di_repositories.dart index dcbc2b9..9493589 100644 --- a/lib/di/di_repositories.dart +++ b/lib/di/di_repositories.dart @@ -77,8 +77,8 @@ final class DiRepositories { // Инициализация репозитория обновлений updatesRepository = _lazyInitRepo( - mockFactory: UpdateMockRepository.new, - mainFactory: UpdateRepository.new, + mockFactory: () => const UpdateMockRepository(), + mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient), onProgress: onProgress, onError: onError, environment: diContainer.env, @@ -86,13 +86,8 @@ final class DiRepositories { // Инициализация репозитория авторизации authRepository = _lazyInitRepo( - mockFactory: AuthMockRepository.new, - mainFactory: () => AuthRepository( - httpClient: diContainer.httpClientFactory( - diContainer.debugService, - diContainer.appConfig, - ), - ), + mockFactory: () => const AuthMockRepository(), + mainFactory: () => AuthRepository(httpClient: diContainer.httpClient), onProgress: onProgress, onError: onError, environment: diContainer.env, @@ -100,13 +95,8 @@ final class DiRepositories { // Инициализация репозитория сервиса управления токеном доступа mainRepository = _lazyInitRepo( - 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 +104,8 @@ final class DiRepositories { // Инициализация репозитория профиля profileRepository = _lazyInitRepo( - 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, diff --git a/lib/features/auth/data/repository/auth_mock_repository.dart b/lib/features/auth/data/repository/auth_mock_repository.dart index df2cd5f..531289b 100644 --- a/lib/features/auth/data/repository/auth_mock_repository.dart +++ b/lib/features/auth/data/repository/auth_mock_repository.dart @@ -4,6 +4,9 @@ import 'package:friflex_starter/features/auth/domain/repository/i_auth_repositor /// Mock реализация репозитория авторизации /// {@endtemplate} final class AuthMockRepository implements IAuthRepository { + /// {@macro AuthMockRepository} + const AuthMockRepository(); + @override String get name => 'AuthMockRepository'; } diff --git a/lib/features/auth/data/repository/auth_repository.dart b/lib/features/auth/data/repository/auth_repository.dart index 5ee45ef..799648d 100644 --- a/lib/features/auth/data/repository/auth_repository.dart +++ b/lib/features/auth/data/repository/auth_repository.dart @@ -1,4 +1,4 @@ -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/auth/domain/repository/i_auth_repository.dart'; @@ -7,7 +7,9 @@ import 'package:friflex_starter/features/auth/domain/repository/i_auth_repositor /// {@endtemplate} final class AuthRepository implements IAuthRepository { AuthRepository({required this.httpClient}); - final IHttpClient httpClient; + + /// Экземпляр HTTP клиента для взаимодействия с сервером + final AppHttpClient httpClient; @override String get name => 'AuthRepository'; diff --git a/lib/features/main/data/repository/main_mock_repository.dart b/lib/features/main/data/repository/main_mock_repository.dart index 839a6fe..995dfdc 100644 --- a/lib/features/main/data/repository/main_mock_repository.dart +++ b/lib/features/main/data/repository/main_mock_repository.dart @@ -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'; } diff --git a/lib/features/main/data/repository/main_repository.dart b/lib/features/main/data/repository/main_repository.dart index 5be3b6c..336db50 100644 --- a/lib/features/main/data/repository/main_repository.dart +++ b/lib/features/main/data/repository/main_repository.dart @@ -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'; diff --git a/lib/features/profile/data/repository/profile_mock_repository.dart b/lib/features/profile/data/repository/profile_mock_repository.dart index 7406455..4c22342 100644 --- a/lib/features/profile/data/repository/profile_mock_repository.dart +++ b/lib/features/profile/data/repository/profile_mock_repository.dart @@ -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'; diff --git a/lib/features/profile/data/repository/profile_repository.dart b/lib/features/profile/data/repository/profile_repository.dart index a3700af..2fb675c 100644 --- a/lib/features/profile/data/repository/profile_repository.dart +++ b/lib/features/profile/data/repository/profile_repository.dart @@ -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'; diff --git a/lib/features/update/data/repository/update_repository.dart b/lib/features/update/data/repository/update_repository.dart index f20812a..a44ad11 100644 --- a/lib/features/update/data/repository/update_repository.dart +++ b/lib/features/update/data/repository/update_repository.dart @@ -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 checkForUpdates({ From 84f7fa8889bc3cd7fceea168477722cd59709aad Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:54:19 +0300 Subject: [PATCH 4/5] =?UTF-8?q?refactor(auth):=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=B9=20=D0=B0=D0=B2=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=20=D1=81=D0=B2=D1=8F?= =?UTF-8?q?=D0=B7=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B8=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D1=80=D1=84=D0=B5=D0=B9=D1=81=D1=8B=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: petrovyuri --- lib/di/di_repositories.dart | 15 ------------ .../data/repository/auth_mock_repository.dart | 12 ---------- .../auth/data/repository/auth_repository.dart | 16 ------------- .../domain/repository/i_auth_repository.dart | 6 ----- .../presentation/screens/auth_screen.dart | 24 ------------------- 5 files changed, 73 deletions(-) delete mode 100644 lib/features/auth/data/repository/auth_mock_repository.dart delete mode 100644 lib/features/auth/data/repository/auth_repository.dart delete mode 100644 lib/features/auth/domain/repository/i_auth_repository.dart delete mode 100644 lib/features/auth/presentation/screens/auth_screen.dart diff --git a/lib/di/di_repositories.dart b/lib/di/di_repositories.dart index 9493589..d03a52b 100644 --- a/lib/di/di_repositories.dart +++ b/lib/di/di_repositories.dart @@ -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; @@ -84,15 +78,6 @@ final class DiRepositories { environment: diContainer.env, ); - // Инициализация репозитория авторизации - authRepository = _lazyInitRepo( - mockFactory: () => const AuthMockRepository(), - mainFactory: () => AuthRepository(httpClient: diContainer.httpClient), - onProgress: onProgress, - onError: onError, - environment: diContainer.env, - ); - // Инициализация репозитория сервиса управления токеном доступа mainRepository = _lazyInitRepo( mockFactory: () => const MainMockRepository(), diff --git a/lib/features/auth/data/repository/auth_mock_repository.dart b/lib/features/auth/data/repository/auth_mock_repository.dart deleted file mode 100644 index 531289b..0000000 --- a/lib/features/auth/data/repository/auth_mock_repository.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart'; - -/// {@template AuthMockRepository} -/// Mock реализация репозитория авторизации -/// {@endtemplate} -final class AuthMockRepository implements IAuthRepository { - /// {@macro AuthMockRepository} - const AuthMockRepository(); - - @override - String get name => 'AuthMockRepository'; -} diff --git a/lib/features/auth/data/repository/auth_repository.dart b/lib/features/auth/data/repository/auth_repository.dart deleted file mode 100644 index 799648d..0000000 --- a/lib/features/auth/data/repository/auth_repository.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:friflex_starter/app/http/app_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}); - - /// Экземпляр HTTP клиента для взаимодействия с сервером - final AppHttpClient httpClient; - - @override - String get name => 'AuthRepository'; -} diff --git a/lib/features/auth/domain/repository/i_auth_repository.dart b/lib/features/auth/domain/repository/i_auth_repository.dart deleted file mode 100644 index ca3474a..0000000 --- a/lib/features/auth/domain/repository/i_auth_repository.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:friflex_starter/di/di_base_repo.dart'; - -/// {@template IAuthRepository} -/// Интерфейс для работы с репозиторием авторизации -/// {@endtemplate} -abstract interface class IAuthRepository with DiBaseRepo {} diff --git a/lib/features/auth/presentation/screens/auth_screen.dart b/lib/features/auth/presentation/screens/auth_screen.dart deleted file mode 100644 index e9ea3c0..0000000 --- a/lib/features/auth/presentation/screens/auth_screen.dart +++ /dev/null @@ -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')), - ); - } -} From 6500b917c52ca6e781b341e0a173b50ee833e7cd Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:18:24 +0300 Subject: [PATCH 5/5] =?UTF-8?q?refactor(app):=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D1=83=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D1=8B=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(app): обновить структуру приложения и удалить устаревшие провайдеры * refactor(runner): упростить обработку ошибок и улучшить логирование времени инициализации * refactor(runner): улучшить порядок инициализации приложения и обработку ошибок * refactor(app): исправить контекст MediaQuery для предотвращения перерисовки * refactor(pp): удалить главный виджет приложения и заменить его на AppRoot * docs(copilot-instructions): уточнить правила проведения Code Review на русском языке * refactor(linter): добавить правило avoid_catches_without_on_clauses для улучшения обработки исключений --------- Co-authored-by: petrovyuri --- .github/copilot-instructions.md | 2 +- analysis_options.yaml | 1 + lib/app/app.dart | 157 ------------------------- lib/app/app_providers.dart | 22 +++- lib/app/app_root.dart | 54 +++++++++ lib/app/depends_providers.dart | 45 ------- lib/app/theme/theme_notifier.dart | 6 +- lib/di/di_repositories.dart | 4 +- lib/features/splash/splash_screen.dart | 16 --- lib/l10n/localization_notifier.dart | 6 +- lib/main.dart | 4 +- lib/router/app_router.dart | 5 - lib/runner/app_runner.dart | 90 +++++++------- lib/runner/errors_handlers.dart | 13 -- lib/runner/timer_runner.dart | 5 + lib/targets/dev.dart | 2 +- lib/targets/prod.dart | 2 +- lib/targets/stage.dart | 2 +- 18 files changed, 137 insertions(+), 299 deletions(-) delete mode 100644 lib/app/app.dart create mode 100644 lib/app/app_root.dart delete mode 100644 lib/app/depends_providers.dart delete mode 100644 lib/features/splash/splash_screen.dart diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fee43c8..4e63334 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,7 +9,7 @@ # Правила проведения Code Review ## Основные правила проведения Code Review -- Комментарии и описание Pull Request должны быть на русском языке. +**ВАЖНО:** - Комментарии, обзоры и описание Pull Request при проведении code review должны быть на РУССКОМ языке. # Стиль кода diff --git a/analysis_options.yaml b/analysis_options.yaml index 4708b49..a75c6ed 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -49,6 +49,7 @@ linter: # === Обработка ошибок и типобезопасность === - 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 (скрытые баги) diff --git a/lib/app/app.dart b/lib/app/app.dart deleted file mode 100644 index 3bb931f..0000000 --- a/lib/app/app.dart +++ /dev/null @@ -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 Function() initDependencies; - - @override - State createState() => _AppState(); -} - -/// {@template app_state} -/// Состояние главного виджета приложения. -/// -/// Управляет процессом инициализации зависимостей и отображением -/// соответствующих экранов в зависимости от состояния инициализации. -/// {@endtemplate} -class _AppState extends State { - /// {@macro app_state} - _AppState(); - - /// Мутабельная Future для инициализации зависимостей - /// Позволяет перезапускать инициализацию при ошибках - late Future _initFuture; - - @override - void initState() { - super.initState(); - _initFuture = widget.initDependencies(); - } - - @override - Widget build(BuildContext context) { - return AppProviders( - // Consumer для локализации добавляем выше чем DependsProviders - // чтобы при изменении локализации перестраивался весь виджет - // Но, это не обязательно, можно добавить в DependsProviders - child: LocalizationConsumer( - builder: () => FutureBuilder( - 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( - 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, - ), - ), - ); - }, - ), - ); - } -} diff --git a/lib/app/app_providers.dart b/lib/app/app_providers.dart index fede8c4..6f30ce0 100644 --- a/lib/app/app_providers.dart +++ b/lib/app/app_providers.dart @@ -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, ); diff --git a/lib/app/app_root.dart b/lib/app/app_root.dart new file mode 100644 index 0000000..f2ca9c5 --- /dev/null +++ b/lib/app/app_root.dart @@ -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, + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/app/depends_providers.dart b/lib/app/depends_providers.dart deleted file mode 100644 index 7ec1352..0000000 --- a/lib/app/depends_providers.dart +++ /dev/null @@ -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, - ); - } -} diff --git a/lib/app/theme/theme_notifier.dart b/lib/app/theme/theme_notifier.dart index fdc6f19..c74964f 100644 --- a/lib/app/theme/theme_notifier.dart +++ b/lib/app/theme/theme_notifier.dart @@ -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( - builder: (_, _, _) { - return builder(); + builder: (context, _, _) { + return builder(context); }, ); } diff --git a/lib/di/di_repositories.dart b/lib/di/di_repositories.dart index d03a52b..50016f4 100644 --- a/lib/di/di_repositories.dart +++ b/lib/di/di_repositories.dart @@ -49,7 +49,7 @@ final class DiRepositories { late final IProfileRepository profileRepository; /// Интерфейс для работы с репозиторием обновлений - late final IUpdateRepository updatesRepository; + late final IUpdateRepository updateRepository; /// Метод для инициализации репозиториев в приложении. /// @@ -70,7 +70,7 @@ final class DiRepositories { onProgress('Начинаем инициализацию репозиториев...'); // Инициализация репозитория обновлений - updatesRepository = _lazyInitRepo( + updateRepository = _lazyInitRepo( mockFactory: () => const UpdateMockRepository(), mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient), onProgress: onProgress, diff --git a/lib/features/splash/splash_screen.dart b/lib/features/splash/splash_screen.dart deleted file mode 100644 index 71eebc1..0000000 --- a/lib/features/splash/splash_screen.dart +++ /dev/null @@ -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()); - } -} diff --git a/lib/l10n/localization_notifier.dart b/lib/l10n/localization_notifier.dart index bd83fa7..f559eb7 100644 --- a/lib/l10n/localization_notifier.dart +++ b/lib/l10n/localization_notifier.dart @@ -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( - builder: (_, _, _) { - return builder(); + builder: (context, _, _) { + return builder(context); }, ); } diff --git a/lib/main.dart b/lib/main.dart index 07f2cff..7a4e24b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 arguments) => prod.main(arguments); diff --git a/lib/router/app_router.dart b/lib/router/app_router.dart index eefd037..1be0562 100644 --- a/lib/router/app_router.dart +++ b/lib/router/app_router.dart @@ -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(), ], ); diff --git a/lib/runner/app_runner.dart b/lib/runner/app_runner.dart index c481474..4e70ef0 100644 --- a/lib/runner/app_runner.dart +++ b/lib/runner/app_runner.dart @@ -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 run() async { + Future run(List 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; } } diff --git a/lib/runner/errors_handlers.dart b/lib/runner/errors_handlers.dart index 02f060d..3f0fe66 100644 --- a/lib/runner/errors_handlers.dart +++ b/lib/runner/errors_handlers.dart @@ -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), - ), - ); - }); -} diff --git a/lib/runner/timer_runner.dart b/lib/runner/timer_runner.dart index be54608..473fa3f 100644 --- a/lib/runner/timer_runner.dart +++ b/lib/runner/timer_runner.dart @@ -24,6 +24,11 @@ class TimerRunner { ); } + /// Метод для сброса секундомера + void reset() { + _stopwatch.reset(); + } + /// Метод для обработки прогресса инициализации зависимостей void logOnProgress(String name) { _debugService.log( diff --git a/lib/targets/dev.dart b/lib/targets/dev.dart index 708320e..9334991 100644 --- a/lib/targets/dev.dart +++ b/lib/targets/dev.dart @@ -1,3 +1,3 @@ import 'package:friflex_starter/runner/app_runner.dart'; -void main() => AppRunner(.dev).run(); +void main(List arguments) => AppRunner(.dev).run(arguments); diff --git a/lib/targets/prod.dart b/lib/targets/prod.dart index 07f2cff..9653962 100644 --- a/lib/targets/prod.dart +++ b/lib/targets/prod.dart @@ -1,3 +1,3 @@ import 'package:friflex_starter/runner/app_runner.dart'; -void main() => AppRunner(.prod).run(); +void main(List arguments) => AppRunner(.prod).run(arguments); diff --git a/lib/targets/stage.dart b/lib/targets/stage.dart index 72eddca..0dc8590 100644 --- a/lib/targets/stage.dart +++ b/lib/targets/stage.dart @@ -1,3 +1,3 @@ import 'package:friflex_starter/runner/app_runner.dart'; -void main() => AppRunner(.stage).run(); +void main(List arguments) => AppRunner(.stage).run(arguments);