1 Commits

Author SHA1 Message Date
Artem Barkalov
9b5f80e7d9 feat(debug): Добавить экраны отладки для плагинов 2025-06-23 01:03:38 +03:00
127 changed files with 2075 additions and 3419 deletions

Binary file not shown.

View File

@@ -1,32 +0,0 @@
---
name: flutter_dev
description: Скилл для разработки Flutter-приложений по стандартам компании Friflex. Используйте этот скилл при написании кода, создании новых фич, проведении ревью или настройке архитектуры проекта. Включает правила именования, структуру слоев (data/domain/presentation) и стандарты Git.
---
# Flutter Dev Skill (Friflex Standards)
Этот скилл содержит набор правил и инструкций для разработки Flutter-приложений. Основная цель — соблюдение единого стиля кода, архитектурных подходов и процессов разработки.
## Основные принципы
1. **Архитектура**: Проект делится на слои: `data`, `domain` и `presentation`.
2. **Именование**: Интерфейсы всегда начинаются с префикса `I`. Экраны имеют постфикс `Screen`.
3. **Документация**: Весь публичный API должен быть покрыт документацией `///`.
4. **Git**: Коммиты и PR на русском языке по стандарту Conventional Commits.
## Справочники (References)
Для получения детальной информации по конкретным областям обращайтесь к следующим файлам:
- [Правила именования и стиль кода](references/codestyle.md) — именование классов, методов, переменных и структура файлов.
- [Структура проекта и слои](references/project_structure.md) — детальное описание папок и взаимодействия между уровнями архитектуры.
- [Работа с Git и ветками](references/gitflow.md) — типы коммитов, именование веток и процессы релизов.
- [Документирование кода](references/documentation.md) — стандарты `///`, использование шаблонов и правила для TODO.
- [Стандарты проекта](references/project_standards.md) — управление сгенерированными файлами, `pubspec.lock` и сборка.
## Когда использовать этот скилл
- При создании новых классов или файлов (проверка именования).
- При реализации новой feature (выбор структуры папок).
- Перед созданием Pull Request (проверка соответствия стандартам).
- При возникновении вопросов по архитектурному взаимодействию слоев.

View File

@@ -1,34 +0,0 @@
# Правила именования и стиль кода
Мы придерживаемся рекомендаций **Effective Dart** и внутренних правил компании.
## Именование
### Интерфейсы
- Начинаются с заглавной буквы **I**.
- Пример: `IAuthRepository`, `IUserRepository`.
### Классы и файлы
- **Классы**: `UpperCamelCase`. Приватные — с префиксом `_`. Должны содержать тип в конце (например, `UserEntity`).
- **Файлы**: `snake_case`. Структура: `[раздел]_[тип].dart`. Пример: `user_details_screen.dart`.
### Репозитории
- Основная реализация — без постфикса (`AuthRepository`).
- Альтернативные реализации — с постфиксами: `Network`, `Local`, `Mock`.
### Виджеты
- **Экраны**: Постфикс `Screen` (`ShopListScreen`).
- **Контент экрана**: Постфикс `View` (`ShopListView`).
- **Глобальные виджеты**: Префикс `App` (`AppButton`).
- В названии **не должно** быть слова `widget`.
## Методы и переменные
- **Методы**: Начинаются с глагола (`fetch`, `put`, `update`, `delete`). Не должны содержать `And/Or`.
- **Переменные/Константы**: `lowerCamelCase`.
## Структура класса (порядок элементов)
1. Конструкторы (default, named, factory).
2. Static элементы (methods, const fields).
3. Инстанс-поля (final, потом обычные; public, потом private).
4. Геттеры/Сеттеры.
5. Методы (overridden, public, protected, private).

View File

@@ -1,29 +0,0 @@
# Документирование кода
## Документация (///)
- Оформляется с использованием `///` над объектом.
- Обязательна для всех классов, конструкторов, полей, методов и фабрик.
- Должна быть краткой, емкой и указывать на назначение.
### Шаблоны
- **Классы**: Используйте `{@template name}` и `{@endtemplate}`.
- **Конструкторы**: Если один — `{@macro name}`.
- **Параметры**: Используйте ссылки в квадратных скобках `[paramName]`.
### Пример метода
```dart
/// Метод для расчета температуры.
/// Принимает:
/// - [grad] - параметр для расчета.
/// Возвращает температуру в градусах. Null при ошибке.
int? calcTemperature({required int grad}) { ... }
```
## Комментарии (//)
- Используются только там, где код не очевиден.
- Не должны повторять то, что и так понятно из имен переменных или структуры.
## TODO
- Формат определяется линтером.
- Указывать имя разработчика в контексте.
- Указывать ссылку на задачу в скобках, если она известна.

View File

@@ -1,26 +0,0 @@
# Работа с Git и ветками
## Pull Requests
- Язык описания — **Русский**.
- Описание должно содержать суть изменений, ссылку на задачу и список deprecated-кода.
## Коммиты (Conventional Commits)
Типы:
- `feat`: Новая функциональность.
- `fix`: Исправление ошибок.
- `refactor`: Рефакторинг без смены логики.
- `docs`: Документация.
- `chore`: Инструменты, зависимости (`pubspec.yaml`).
- `test`, `build`, `ci`.
## Именование веток
Формат: `тип/PRIME-номер_описание`
- `feat/PRIME-123_auth`
- `fix/PRIME-456_typo`
## Процесс Feature-разработки
1. Создаем ветку от `main`.
2. Вносим изменения, делаем коммиты.
3. PR с названием по правилам (например, `feat(auth): PRIME-17 Добавить вход`).
4. После Review — **squash commit** в `main`.
5. Удаление ветки.

View File

@@ -1,26 +0,0 @@
# Стандарты проекта
## Управление файлами
### Сгенерированные файлы (*.g.dart, *.freezed.dart)
- **Хранить в репозитории**.
- Это обеспечивает работоспособность `main` ветки сразу после чекаута без долгого ожидания генерации.
- Нужно контролировать конфликты при слиянии и периодически актуализировать.
### pubspec.lock
- **Хранить** для приложений (applications).
- **Не хранить** для пакетов (packages).
- По умолчанию хранить GMS версию как базовую.
## Сборка и запуск
- Используйте анализатор `friflex_lint_rules`.
- Перед созданием PR обязательно:
1. Форматирование кода (`dart format`).
2. Проверка анализатором на отсутствие ошибок.
## Технологический стек
- Роутинг: `go_router`.
- State Manager: `flutter_bloc`.
- DI: Ручная реализация через `InheritedWidget`.
- API: `dio`.
- Ресурсы: `flutter_gen`.

View File

@@ -1,33 +0,0 @@
# Структура проекта
Проект строится на основе фич и слоев.
## Общая иерархия
- `/assets` — графические ресурсы.
- `/lib` — основной код.
- `/app` — глобальные настройки, интерфейсы и реализации.
- `/di` — конфигурация зависимостей.
- `/routing` — описание путей.
- `/features` — функциональные модули приложения.
- `/gen` — сгенерированный код.
## Структура Feature-папки
Каждая фича делится на три слоя:
1. **Data** (Поставщик данных):
- `/dto` — модели данных для API.
- `/repository` — реализация интерфейсов репозиториев.
2. **Domain** (Бизнес-логика):
- `/entity` — чистые модели для использования в UI.
- `/repository`**интерфейсы** репозиториев.
- `/state` — управление состоянием (BLoC).
- `/service` — реализации бизнес-сервисов.
3. **Presentation** (Представление):
- `/screens` — виджеты экранов (`*Screen`).
- `/components` — переиспользуемые компоненты внутри фичи.
## Правила взаимодействия
- **Data** → доступ к **Entity** (для маппинга), не знает про UI.
- **Domain** → не знает про **Data** (работает через интерфейсы) и **Presentation**.
- **Presentation** → работает через **Domain**, не знает про **Data**.
- Объекты внутри фичи инкапсулированы. Глобальные объекты выносятся в `/app`.

View File

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

View File

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

View File

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

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "66dd93f9a27ffe2a9bfc8297506ce066ff51265f" revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2"
channel: "stable" channel: "stable"
project_type: app project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- platform: web - platform: android
create_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
base_revision: 66dd93f9a27ffe2a9bfc8297506ce066ff51265f base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
# User provided section # User provided section

View File

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

26
.vscode/tasks.json vendored
View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
import 'package:i_app_services/i_app_services.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
/// {@template app_url_launcher}
/// Класс для Аврора реализации сервиса работы с URL
/// {@endtemplate}
class AppUrlLauncher implements IUrlLauncher {
/// {@macro app_url_launcher}
AppUrlLauncher();
/// Наименование сервиса
static const String name = 'AuroraAppUrlLauncher';
@override
String get nameImpl => AppUrlLauncher.name;
@override
Future<bool> canLaunchUrl(Uri url) async {
return url_launcher.canLaunchUrl(url);
}
@override
Future<bool> launchUrl(Uri url) async {
return url_launcher.launchUrl(url);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,9 @@ class AppPathProvider implements IPathProvider {
/// Наименование сервиса /// Наименование сервиса
static const name = 'BaseAppPathProvider'; static const name = 'BaseAppPathProvider';
@override
String get nameImpl => AppPathProvider.name;
@override @override
Future<String> getAppDocumentsDirectoryPath() async { Future<String> getAppDocumentsDirectoryPath() async {
return (await getApplicationDocumentsDirectory()).path; return (await getApplicationDocumentsDirectory()).path;

View File

@@ -11,12 +11,15 @@ final class AppSecureStorage implements ISecureStorage {
/// {@macro app_secure_storage} /// {@macro app_secure_storage}
AppSecureStorage({this.secretKey}); AppSecureStorage({this.secretKey});
@override
final String? secretKey;
/// Наименование сервиса /// Наименование сервиса
static const name = 'BaseAppSecureStorage'; static const name = 'BaseAppSecureStorage';
@override
String get nameImpl => AppSecureStorage.name;
@override
final String? secretKey;
/// Экземпляр хранилища данных /// Экземпляр хранилища данных
final _box = const FlutterSecureStorage(); final _box = const FlutterSecureStorage();
@@ -44,7 +47,4 @@ final class AppSecureStorage implements ISecureStorage {
Future<void> write(String key, String value) async { Future<void> write(String key, String value) async {
await _box.write(key: key, value: value); await _box.write(key: key, value: value);
} }
@override
String get nameImpl => AppSecureStorage.name;
} }

View File

@@ -0,0 +1,26 @@
import 'package:i_app_services/i_app_services.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
/// {@template app_url_launcher}
/// Класс для базовой реализации сервиса работы с URL
/// {@endtemplate}
class AppUrlLauncher implements IUrlLauncher {
/// {@macro app_url_launcher}
AppUrlLauncher();
/// Наименование сервиса
static const String name = 'BaseAppUrlLauncher';
@override
String get nameImpl => AppUrlLauncher.name;
@override
Future<bool> canLaunchUrl(Uri url) async {
return url_launcher.canLaunchUrl(url);
}
@override
Future<bool> launchUrl(Uri url) async {
return url_launcher.launchUrl(url);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
/// Класс интерфейса для работы с защищенным хранилищем /// Класс для описания интерфейса для работы с защищенным хранилищем
abstract interface class ISecureStorage { abstract interface class ISecureStorage {
/// Описывает обязательные параметры имплементаций /// Описывает обязательные параметры имплементаций
/// ///
@@ -6,14 +6,17 @@ abstract interface class ISecureStorage {
/// - [secretKey] - секретный ключ для шифрования данных /// - [secretKey] - секретный ключ для шифрования данных
const ISecureStorage._({required this.secretKey}); const ISecureStorage._({required this.secretKey});
/// Наименования интерфейса
static const name = 'ISecureStorage';
/// Получение имени имплементации
String get nameImpl;
/// Секретный ключ для шифрования данных /// Секретный ключ для шифрования данных
/// Нужен, если надо передать ключ в реализацию /// Нужен, если надо передать ключ в реализацию
/// например, в Aurora /// например, в Aurora
final String? secretKey; final String? secretKey;
/// Наименования интерфейса
static const name = 'ISecureStorage';
/// Метод для получения значения из защищенного хранилища /// Метод для получения значения из защищенного хранилища
/// ///
/// Принимает: /// Принимает:
@@ -41,6 +44,4 @@ abstract interface class ISecureStorage {
/// Принимает: /// Принимает:
/// - [key] - ключ /// - [key] - ключ
Future<bool> containsKey(String key); Future<bool> containsKey(String key);
String get nameImpl;
} }

View File

@@ -0,0 +1,18 @@
/// Класс для описания интерфейса сервиса для запуска URL
abstract interface class IUrlLauncher {
/// Наименования интерфейса
static const name = 'IUrlLauncher';
/// Получение имени имплементации
String get nameImpl;
/// Метод для проверки возможности запуска ссылки
///
/// - [url] - ссылка для проверки
Future<bool> canLaunchUrl(Uri url);
/// Метод для запуска ссылки
///
/// - [url] - ссылка для запуска
Future<bool> launchUrl(Uri url);
}

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

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

@@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/app_providers.dart';
import 'package:friflex_starter/app/depends_providers.dart';
import 'package:friflex_starter/app/theme/app_theme.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/error/error_screen.dart';
import 'package:friflex_starter/features/splash/splash_screen.dart';
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:go_router/go_router.dart';
/// {@template app}
/// Главный виджет приложения, управляющий инициализацией зависимостей
/// и отображением основного интерфейса приложения.
///
/// Отвечает за:
/// - Инициализацию зависимостей приложения
/// - Отображение экрана загрузки во время инициализации
/// - Обработку ошибок инициализации
/// - Настройку провайдеров для темы и локализации
/// {@endtemplate}
class App extends StatefulWidget {
/// {@macro app}
const App({required this.router, required this.initDependencies, super.key});
/// Роутер приложения для навигации между экранами
final GoRouter router;
/// Функция для инициализации зависимостей приложения
/// Возвращает Future с контейнером зависимостей
final Future<DiContainer> Function() initDependencies;
@override
State<App> createState() => _AppState();
}
/// {@template app_state}
/// Состояние главного виджета приложения.
///
/// Управляет процессом инициализации зависимостей и отображением
/// соответствующих экранов в зависимости от состояния инициализации.
/// {@endtemplate}
class _AppState extends State<App> {
/// {@macro app_state}
_AppState();
/// Мутабельная Future для инициализации зависимостей
/// Позволяет перезапускать инициализацию при ошибках
late Future<DiContainer> _initFuture;
@override
void initState() {
super.initState();
_initFuture = widget.initDependencies();
}
@override
Widget build(BuildContext context) {
return AppProviders(
// Consumer для локализации добавляем выше чем DependsProviders
// чтобы при изменении локализации перестраивался весь виджет
// Но, это не обязательно, можно добавить в DependsProviders
child: LocalizationConsumer(
builder: () => FutureBuilder<DiContainer>(
future: _initFuture,
builder: (_, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
// Пока инициализация показываем Splash
return const SplashScreen();
case ConnectionState.done:
if (snapshot.hasError) {
return ErrorScreen(
error: snapshot.error,
stackTrace: snapshot.stackTrace,
onRetry: _retryInit,
);
}
final diContainer = snapshot.data;
if (diContainer == null) {
return ErrorScreen(
error:
'Ошибка инициализации зависимостей, diContainer = null',
stackTrace: null,
onRetry: _retryInit,
);
}
return DependsProviders(
diContainer: diContainer,
child: ThemeConsumer(
builder: () => _App(router: widget.router),
),
);
}
},
),
),
);
}
/// Метод для перезапуска инициализации зависимостей
/// Вызывается при ошибках инициализации для повторной попытки
void _retryInit() {
setState(() {
_initFuture = widget.initDependencies();
});
}
}
/// {@template app_internal}
/// Внутренний виджет приложения, отображающий основной интерфейс
/// после успешной инициализации зависимостей.
///
/// Настраивает MaterialApp с роутером, темами и локализацией.
/// {@endtemplate}
class _App extends StatelessWidget {
/// {@macro app_internal}
const _App({required this.router});
/// Роутер приложения для навигации
final GoRouter router;
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
darkTheme: AppTheme.dark,
theme: AppTheme.light,
themeMode: context.theme.themeMode,
locale: context.localization.locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
);
}
}

View File

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

View File

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

View File

@@ -1,28 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:friflex_starter/app/theme/theme_notifier.dart'; import 'package:friflex_starter/app/theme/theme_notifier.dart';
import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
/// {@template app_providers} /// Класс для добавления провайдеров темы и локализации
/// Класс для добавления зависимостей приложения
/// {@endtemplate}
final class AppProviders extends StatelessWidget { final class AppProviders extends StatelessWidget {
/// {@macro app_providers} const AppProviders({required this.child, super.key});
const AppProviders({
required this.child,
required this.diContainer,
super.key,
});
/// Виджет, который будет отображаться внутри провайдеров
final Widget child; final Widget child;
/// Контейнер зависимостей
final DiContainer diContainer;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
@@ -33,10 +19,6 @@ final class AppProviders extends StatelessWidget {
ChangeNotifierProvider( ChangeNotifierProvider(
create: (_) => LocalizationNotifier(), create: (_) => LocalizationNotifier(),
), // Провайдер для локализации ), // Провайдер для локализации
Provider.value(value: diContainer), // Передаем контейнер зависимостей
BlocProvider(
create: (_) => UpdateCubit(diContainer.repositories.updateRepository),
),
], ],
child: child, child: child,
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,27 +2,28 @@ import 'package:friflex_starter/app/app_env.dart';
import 'package:friflex_starter/di/di_base_repo.dart'; import 'package:friflex_starter/di/di_base_repo.dart';
import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/di/di_container.dart';
import 'package:friflex_starter/di/di_typedefs.dart'; import 'package:friflex_starter/di/di_typedefs.dart';
import 'package:friflex_starter/features/auth/data/repository/auth_mock_repository.dart';
import 'package:friflex_starter/features/auth/data/repository/auth_repository.dart';
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
import 'package:friflex_starter/features/main/data/repository/main_mock_repository.dart'; import 'package:friflex_starter/features/main/data/repository/main_mock_repository.dart';
import 'package:friflex_starter/features/main/data/repository/main_repository.dart'; import 'package:friflex_starter/features/main/data/repository/main_repository.dart';
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart'; import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
import 'package:friflex_starter/features/profile/data/repository/profile_mock_repository.dart'; import 'package:friflex_starter/features/profile/data/repository/profile_mock_repository.dart';
import 'package:friflex_starter/features/profile/data/repository/profile_repository.dart'; import 'package:friflex_starter/features/profile/data/repository/profile_repository.dart';
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart'; import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
import 'package:friflex_starter/features/update/data/repository/update_mock_repository.dart';
import 'package:friflex_starter/features/update/data/repository/update_repository.dart';
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
/// Список названий моковых репозиториев, которые должны быть подменены /// Список названий моковых репозиториев, которые должны быть подменены
/// для использования в сборке stage окружения. /// для использования в сборке stage окружения.
/// ///
/// Для того, чтобы репозиторий был автоматически подменен на моковый в stage /// Для того, чтобы репозиторий был автоматически подменен на моковый в stage
/// сборке, необходимо в этом списке указать тип интерфейса репозитория /// сборке, необходимо в этом списке указать название мокового репозитория,
/// обращаясь к соответствующему полю name.
/// ///
/// Пример: /// Пример:
/// ``` /// ```
/// <Type>{ IUpdateRepository } /// [ AuthCheckRepositoryMock().name, ]
/// ``` /// ```
const _mockReposToSwitch = <Type>{IUpdateRepository}; final List<String> _mockReposToSwitch = [];
/// {@template di_repositories} /// {@template di_repositories}
/// Класс для инициализации и управления репозиториями приложения. /// Класс для инициализации и управления репозиториями приложения.
@@ -42,15 +43,15 @@ final class DiRepositories {
/// {@macro di_repositories} /// {@macro di_repositories}
DiRepositories(); DiRepositories();
/// Интерфейс для работы с репозиторием авторизации
late final IAuthRepository authRepository;
/// Интерфейс для работы с репозиторием главного сервиса /// Интерфейс для работы с репозиторием главного сервиса
late final IMainRepository mainRepository; late final IMainRepository mainRepository;
/// Интерфейс для работы с репозиторием профиля /// Интерфейс для работы с репозиторием профиля
late final IProfileRepository profileRepository; late final IProfileRepository profileRepository;
/// Интерфейс для работы с репозиторием обновлений
late final IUpdateRepository updateRepository;
/// Метод для инициализации репозиториев в приложении. /// Метод для инициализации репозиториев в приложении.
/// ///
/// Принимает: /// Принимает:
@@ -67,34 +68,71 @@ final class DiRepositories {
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
onProgress('Начинаем инициализацию репозиториев...'); try {
// Инициализация репозитория авторизации
authRepository = _lazyInitRepo<IAuthRepository>(
mockFactory: AuthMockRepository.new,
mainFactory: () => AuthRepository(
httpClient: diContainer.httpClientFactory(
diContainer.debugService,
diContainer.appConfig,
),
),
onProgress: onProgress,
environment: diContainer.env,
);
onProgress(authRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IAuthRepository',
error,
stackTrace,
);
}
// Инициализация репозитория обновлений try {
updateRepository = _lazyInitRepo<IUpdateRepository>( // Инициализация репозитория сервиса управления токеном доступа
mockFactory: () => const UpdateMockRepository(), mainRepository = _lazyInitRepo<IMainRepository>(
mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient), mockFactory: MainMockRepository.new,
onProgress: onProgress, mainFactory: () => MainRepository(
onError: onError, httpClient: diContainer.httpClientFactory(
environment: diContainer.env, diContainer.debugService,
); diContainer.appConfig,
),
),
onProgress: onProgress,
environment: diContainer.env,
);
onProgress(mainRepository.name);
} on Object catch (error, stackTrace) {
onError(
'Ошибка инициализации репозитория IMainRepository',
error,
stackTrace,
);
}
// Инициализация репозитория сервиса управления токеном доступа try {
mainRepository = _lazyInitRepo<IMainRepository>( // Инициализация репозитория профиля
mockFactory: () => const MainMockRepository(), profileRepository = _lazyInitRepo<IProfileRepository>(
mainFactory: () => MainRepository(httpClient: diContainer.httpClient), mockFactory: ProfileMockRepository.new,
onProgress: onProgress, mainFactory: () => ProfileRepository(
onError: onError, httpClient: diContainer.httpClientFactory(
environment: diContainer.env, diContainer.debugService,
); diContainer.appConfig,
),
// Инициализация репозитория профиля ),
profileRepository = _lazyInitRepo<IProfileRepository>( onProgress: onProgress,
mockFactory: () => const ProfileMockRepository(), environment: diContainer.env,
mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient), );
onProgress: onProgress, onProgress(profileRepository.name);
onError: onError, } on Object catch (error, stackTrace) {
environment: diContainer.env, onError(
); 'Ошибка инициализации репозитория IProfileRepository',
error,
stackTrace,
);
}
onProgress( onProgress(
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})', 'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
@@ -108,36 +146,27 @@ final class DiRepositories {
/// - [mockFactory] - функция-фабрика для инициализации мокового репозитория /// - [mockFactory] - функция-фабрика для инициализации мокового репозитория
/// - [mainFactory] - функция-фабрика для инициализации основного репозитория /// - [mainFactory] - функция-фабрика для инициализации основного репозитория
/// - [onProgress] - обратный вызов для уведомления о прогрессе /// - [onProgress] - обратный вызов для уведомления о прогрессе
/// - [onError] - обратный вызов для обработки ошибок инициализации
/// - [environment] - окружение приложения для определения стратегии инициализации /// - [environment] - окружение приложения для определения стратегии инициализации
/// ///
/// Возвращает: /// Возвращает:
/// - Экземпляр репозитория в зависимости от окружения /// - Экземпляр репозитория в зависимости от окружения
///
/// Throws:
/// - Перебрасывает исключение, если инициализация репозитория завершилась с ошибкой
T _lazyInitRepo<T extends DiBaseRepo>({ T _lazyInitRepo<T extends DiBaseRepo>({
required AppEnv environment, required AppEnv environment,
required T Function() mainFactory, required T Function() mainFactory,
required T Function() mockFactory, required T Function() mockFactory,
required OnProgress onProgress, required OnProgress onProgress,
required OnError onError,
}) { }) {
try { final mockRepo = mockFactory();
final repo = switch (environment) { final mainRepo = mainFactory();
.dev => mockFactory(),
.prod => mainFactory(),
.stage =>
_mockReposToSwitch.contains(T) ? mockFactory() : mainFactory(),
};
// throw Exception('Тестовая - ошибка инициализации репозитория $T'); final repo = switch (environment) {
onProgress(repo.name); AppEnv.dev => mockRepo,
return repo; AppEnv.prod => mainRepo,
} on Object catch (error, stackTrace) { AppEnv.stage =>
onError('Ошибка инициализации репозитория $T', error, stackTrace); _mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
// Перебрасываем исключение дальше, чтобы не скрыть ошибку инициализации };
rethrow;
} onProgress(repo.name);
return repo;
} }
} }

View File

@@ -22,8 +22,8 @@ final class DiServices {
/// Сервис для работы с защищенным локальным хранилищем /// Сервис для работы с защищенным локальным хранилищем
late final ISecureStorage secureStorage; late final ISecureStorage secureStorage;
/// Сервис для работы с геолокацией /// Сервис для работы с URL
late final ILocationService locationService; late final IUrlLauncher urlLauncher;
/// Метод для инициализации сервисов в приложении. /// Метод для инициализации сервисов в приложении.
/// ///
@@ -35,36 +35,30 @@ final class DiServices {
/// Последовательность инициализации: /// Последовательность инициализации:
/// 1. Инициализация сервиса путей (AppPathProvider) /// 1. Инициализация сервиса путей (AppPathProvider)
/// 2. Инициализация защищенного хранилища (AppSecureStorage) /// 2. Инициализация защищенного хранилища (AppSecureStorage)
/// 3. Инициализация сервиса URL (AppUrlLauncherService)
void init({ void init({
required OnProgress onProgress, required OnProgress onProgress,
required OnError onError, required OnError onError,
required DiContainer diContainer, required DiContainer diContainer,
}) { }) {
try { try {
// throw Exception('Тестовая - ошибка инициализации сервиса путей');
pathProvider = const AppPathProvider(); pathProvider = const AppPathProvider();
onProgress(AppPathProvider.name); onProgress(AppPathProvider.name);
} on Object catch (error, stackTrace) { } on Object catch (error, stackTrace) {
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace); onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
} }
try { try {
secureStorage = AppSecureStorage( secureStorage = AppSecureStorage(secretKey: diContainer.appConfig.secretKey);
secretKey: diContainer.appConfig.secretKey,
);
onProgress(AppSecureStorage.name); onProgress(AppSecureStorage.name);
} on Object catch (error, stackTrace) { } on Object catch (error, stackTrace) {
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace); onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
} }
try { try {
locationService = const AppLocationService(); urlLauncher = AppUrlLauncher();
onProgress(AppLocationService.name); onProgress(AppUrlLauncher.name);
} on Object catch (error, stackTrace) { } on Object catch (error, stackTrace) {
onError( onError('Ошибка инициализации ${IUrlLauncher.name}', error, stackTrace);
'Ошибка инициализации ${ILocationService.name}',
error,
stackTrace,
);
} }
onProgress('Инициализация сервисов завершена!'); onProgress('Инициализация сервисов завершена!');

View File

@@ -0,0 +1,9 @@
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
/// {@template AuthMockRepository}
/// Mock реализация репозитория авторизации
/// {@endtemplate}
final class AuthMockRepository implements IAuthRepository {
@override
String get name => 'AuthMockRepository';
}

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,12 @@ import 'package:friflex_starter/features/debug/screens/components_screen.dart';
import 'package:friflex_starter/features/debug/screens/debug_screen.dart'; import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
import 'package:friflex_starter/features/debug/screens/icons_screen.dart'; import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
import 'package:friflex_starter/features/debug/screens/lang_screen.dart'; import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
import 'package:friflex_starter/features/debug/screens/path_provider_screen.dart';
import 'package:friflex_starter/features/debug/screens/secure_storage_screen.dart';
import 'package:friflex_starter/features/debug/screens/theme_screen.dart'; import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart'; import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart'; import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
import 'package:friflex_starter/features/debug/screens/url_launcher_screen.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
/// {@template debug_routes} /// {@template debug_routes}
@@ -20,6 +23,9 @@ abstract final class DebugRoutes {
static const String themeScreenName = 'theme_screen'; static const String themeScreenName = 'theme_screen';
static const String langScreenName = 'lang_screen'; static const String langScreenName = 'lang_screen';
static const String componentsScreenName = 'components_screen'; static const String componentsScreenName = 'components_screen';
static const String pathProviderScreenName = 'path_provider_screen';
static const String secureStorageScreenName = 'secure_storage_screen';
static const String urlLauncherScreenName = 'url_launcher_screen';
/// Пути к экранам /// Пути к экранам
static const String debugScreenPath = '/debug'; static const String debugScreenPath = '/debug';
@@ -29,6 +35,9 @@ abstract final class DebugRoutes {
static const String themeScreenPath = 'debug/theme'; static const String themeScreenPath = 'debug/theme';
static const String langScreenPath = 'debug/lang'; static const String langScreenPath = 'debug/lang';
static const String componentsScreenPath = 'debug/components'; static const String componentsScreenPath = 'debug/components';
static const String pathProviderScreenPath = 'debug/path_provider';
static const String secureStorageScreenPath = 'debug/secure_storage';
static const String urlLauncherScreenPath = 'debug/url_launcher';
/// Метод для создания роутов для отладки /// Метод для создания роутов для отладки
/// ///
@@ -70,6 +79,21 @@ abstract final class DebugRoutes {
name: componentsScreenName, name: componentsScreenName,
builder: (context, state) => const ComponentsScreen(), builder: (context, state) => const ComponentsScreen(),
), ),
GoRoute(
path: pathProviderScreenPath,
name: pathProviderScreenName,
builder: (context, state) => const PathProviderScreen(),
),
GoRoute(
path: secureStorageScreenPath,
name: secureStorageScreenName,
builder: (context, state) => const SecureStorageScreen(),
),
GoRoute(
path: urlLauncherScreenPath,
name: urlLauncherScreenName,
builder: (context, state) => const UrlLauncherScreen(),
),
], ],
); );
} }

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart'; import 'package:friflex_starter/app/ui_kit/app_box.dart';
@@ -22,9 +20,7 @@ class DebugScreen extends StatelessWidget {
children: [ children: [
Text('Окружение: ${context.di.appConfig.env.name}'), Text('Окружение: ${context.di.appConfig.env.name}'),
const HBox(22), const HBox(22),
Text( Text('Реализация AppServices: ${context.di.services.secureStorage.nameImpl}'),
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
),
const HBox(22), const HBox(22),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
@@ -37,53 +33,72 @@ class DebugScreen extends StatelessWidget {
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.iconsScreenName)); context.pushNamed(DebugRoutes.iconsScreenName);
}, },
child: const Text('Экран с иконками'), child: const Text('Экран с иконками'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.themeScreenName)); context.pushNamed(DebugRoutes.themeScreenName);
}, },
child: const Text('Экран настроек темы'), child: const Text('Экран настроек темы'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.tokensScreenName)); context.pushNamed(DebugRoutes.tokensScreenName);
}, },
child: const Text('Экран с токенами'), child: const Text('Экран с токенами'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.uiKitScreenName)); context.pushNamed(DebugRoutes.uiKitScreenName);
}, },
child: const Text('Экран UI Kit'), child: const Text('Экран UI Kit'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
unawaited(context.pushNamed(DebugRoutes.langScreenName)); context.pushNamed(DebugRoutes.langScreenName);
}, },
child: const Text('Экран локализации'), child: const Text('Экран локализации'),
), ),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () {
await context.pushNamed(DebugRoutes.componentsScreenName); context.pushNamed(DebugRoutes.componentsScreenName);
}, },
child: const Text('Экран компонентов'), child: const Text('Экран компонентов'),
), ),
const HBox(16),
ElevatedButton(
onPressed: () {
context.pushNamed(DebugRoutes.pathProviderScreenName);
},
child: const Text('Экран Path Provider'),
),
const HBox(16),
ElevatedButton(
onPressed: () {
context.pushNamed(DebugRoutes.secureStorageScreenName);
},
child: const Text('Экран Secure Storage'),
),
const HBox(16),
ElevatedButton(
onPressed: () {
context.pushNamed(DebugRoutes.urlLauncherScreenName);
},
child: const Text('Экран Url Launcher'),
),
const HBox(22), const HBox(22),
const Text('Имитирование ошибок:'), const Text('Имитирование ошибок:'),
const HBox(16), const HBox(16),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
throw Exception( throw Exception('Тестовая ошибка Exception для отладки FlutterError');
'Тестовая ошибка Exception для отладки FlutterError',
);
}, },
child: const Text('Вызывать ошибку FlutterError'), child: const Text('Вызывать ошибку FlutterError'),
), ),

View File

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

View File

@@ -0,0 +1,147 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:i_app_services/i_app_services.dart';
/// {@template path_provider_screen}
/// Экран для отладки и тестирования плагина path_provider.
///
/// Отвечает за:
/// - Тестирование работы реализаций плагина для получения путей к директориям приложения
/// - Демонстрацию содержимого директории файлов приложения
/// {@endtemplate}
class PathProviderScreen extends StatefulWidget {
/// {@macro path_provider_screen}
const PathProviderScreen({super.key});
@override
State<PathProviderScreen> createState() => _PathProviderScreenState();
}
class _PathProviderScreenState extends State<PathProviderScreen> {
/// Плагин для работы с путями в приложении
late final IPathProvider _pathProvider;
/// Корневой путь к директории файлов приложения
String? _rootPath;
/// Текущий путь к директории, отображаемой в списке
String? _currentPath;
/// Загрузка файлов
Future<List<FileSystemEntity>?>? _loadFilesFuture;
@override
void initState() {
super.initState();
_pathProvider = context.di.services.pathProvider;
_loadFilesFuture = _initRoot();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Path Provider')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Реализация Path Provider: ${context.di.services.pathProvider.nameImpl}'),
const HBox(8),
Text('Содержимое папки документов приложения:'),
const HBox(8),
Text('Текущий путь:'),
const HBox(8),
Text(_currentPath ?? ''),
const HBox(8),
ElevatedButton(
onPressed: _currentPath != null && _rootPath != null && _currentPath != _rootPath
? _goBack
: null,
child: const Text('Назад'),
),
Expanded(
child: FutureBuilder<List<FileSystemEntity>?>(
future: _loadFilesFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Ошибка: \\${snapshot.error}'));
}
final files = snapshot.data;
if (files == null) {
return const Center(child: Text('Недоступно'));
}
if (files.isEmpty) {
return const Center(child: Text('Папка пуста'));
}
return ListView(
children: files
.map(
(item) => ListTile(
leading: Icon(
item is Directory ? Icons.folder : Icons.insert_drive_file,
),
title: Text(item.path.split(Platform.pathSeparator).last),
onTap: item is Directory ? () => _openDir(item.path) : null,
),
)
.toList(),
);
},
),
),
],
),
),
);
}
/// Метод для инициализации корневой директории и загрузки её содержимого
Future<List<FileSystemEntity>?> _initRoot() async {
final dirPath = await _pathProvider.getAppDocumentsDirectoryPath();
if (dirPath == null) {
setState(() {
_rootPath = null;
_currentPath = null;
});
return null;
}
final files = Directory(dirPath).listSync();
setState(() {
_rootPath = dirPath;
_currentPath = dirPath;
});
return files;
}
/// Метод для загрузки файлов в указанной директории
List<FileSystemEntity>? _loadFiles(String path) {
return Directory(path).listSync();
}
/// Метод для открытия директории и загрузки её содержимого
void _openDir(String path) async {
setState(() {
_currentPath = path;
_loadFilesFuture = Future.value(_loadFiles(path));
});
}
/// Метод для перехода к родительской директории
void _goBack() async {
if (_currentPath == null || _rootPath == null || _currentPath == _rootPath) return;
final parent = Directory(_currentPath!).parent.path;
if (parent.length < _rootPath!.length) return;
final files = _loadFiles(parent);
setState(() {
_currentPath = parent;
_loadFilesFuture = Future.value(files);
});
}
}

View File

@@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
import 'package:i_app_services/i_app_services.dart';
/// {@template secure_storage_screen}
/// Экран для отладки и тестирования плагина flutter_secure_storage.
///
/// Отвечает за:
/// - Тестирование работы реализаций плагина для провеки записи и чтения защищенных данных
/// {@endtemplate}
class SecureStorageScreen extends StatefulWidget {
/// {@macro secure_storage_screen}
const SecureStorageScreen({super.key});
@override
State<SecureStorageScreen> createState() => _SecureStorageScreenState();
}
class _SecureStorageScreenState extends State<SecureStorageScreen> {
/// Плагин для работы с защищенным хранилищем
late final ISecureStorage _secureStorage;
/// Контроллер для ввода ключа
final TextEditingController _keyController = TextEditingController();
/// Контроллер для ввода значения
final TextEditingController _valueController = TextEditingController();
@override
void initState() {
super.initState();
_secureStorage = context.di.services.secureStorage;
}
@override
void dispose() {
_keyController.dispose();
_valueController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Secure Storage')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Реализация Secure Storage: ${context.di.services.secureStorage.nameImpl}'),
const HBox(8),
TextField(
controller: _keyController,
onChanged: (value) {
_valueController.clear();
},
decoration: const InputDecoration(labelText: 'Ключ'),
),
const HBox(8),
TextField(
controller: _valueController,
decoration: const InputDecoration(labelText: 'Значение'),
),
const HBox(8),
ElevatedButton(
onPressed: () => _handleWrite(context),
child: const Text('Записать в Secure Storage'),
),
const HBox(8),
ElevatedButton(
onPressed: () => _handleRead(context),
child: const Text('Прочитать из Secure Storage'),
),
const HBox(8),
ElevatedButton(
onPressed: () => _handleDelete(context),
child: const Text('Удалить из Secure Storage'),
),
],
),
),
);
}
/// Обработчик для записи значения в Secure Storage
Future<void> _handleWrite(BuildContext context) async {
final key = _keyController.text;
final value = _valueController.text;
try {
await _secureStorage.write(key, value);
if (!context.mounted) return;
AppSnackBar.showSuccess(context: context, message: 'Значение записано в Secure Storage');
} on Object catch (e) {
AppSnackBar.showError(context, message: 'Ошибка записи: $e');
}
}
/// Обработчик для чтения значения из Secure Storage
Future<void> _handleRead(BuildContext context) async {
final key = _keyController.text;
try {
final value = await _secureStorage.read(key) ?? 'Значение не найдено';
_valueController.value = TextEditingValue(text: value);
} on Object catch (e) {
if (!context.mounted) return;
AppSnackBar.showError(context, message: 'Ошибка чтения: $e');
}
}
/// Обработчик для удаления значения из Secure Storage
Future<void> _handleDelete(BuildContext context) async {
final key = _keyController.text;
try {
await _secureStorage.delete(key);
if (!context.mounted) return;
_valueController.clear();
AppSnackBar.showSuccess(context: context, message: 'Значение удалено из Secure Storage');
} on Object catch (e) {
AppSnackBar.showError(context, message: 'Ошибка удаления: $e');
}
}
}

View File

@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:friflex_starter/app/app_context_ext.dart';
import 'package:friflex_starter/app/ui_kit/app_box.dart';
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
import 'package:i_app_services/i_app_services.dart';
/// {@template url_launcher_screen}
/// Экран для отладки и тестирования плагина url_launcher.
///
/// Отвечает за:
/// - Тестирование работы реализаций плагина для проверки открытия URL
/// {@endtemplate}
class UrlLauncherScreen extends StatefulWidget {
/// {@macro url_launcher_screen}
const UrlLauncherScreen({super.key});
@override
State<UrlLauncherScreen> createState() => _UrlLauncherScreenState();
}
class _UrlLauncherScreenState extends State<UrlLauncherScreen> {
/// Плагин для работы с URL
late final IUrlLauncher _urlLauncher;
/// Контроллер для ввода URL для открытия
final TextEditingController _urlController = TextEditingController();
/// Контроллер для ввода URL для проверки возможности открытия
final TextEditingController _canOpenUrlController = TextEditingController();
@override
void initState() {
super.initState();
_urlLauncher = context.di.services.urlLauncher;
}
@override
void dispose() {
_urlController.dispose();
_canOpenUrlController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('URL Launcher')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Реализация Url Launcher: ${context.di.services.urlLauncher.nameImpl}'),
const HBox(8),
TextField(
controller: _urlController,
decoration: const InputDecoration(labelText: 'Введите ссылку'),
),
const HBox(16),
ElevatedButton(
onPressed: () => _launchUrl(context),
child: const Text('Открыть ссылку'),
),
const HBox(16),
TextField(
controller: _canOpenUrlController,
decoration: const InputDecoration(labelText: 'Введите ссылку'),
),
const HBox(16),
ElevatedButton(
onPressed: () => _checkCanOpenUrl(context),
child: const Text('Проверить возможность открытия'),
),
],
),
),
);
}
/// Метод для открытия URL
Future<void> _launchUrl(BuildContext context) async {
final url = _urlController.text.trim();
if (url.isEmpty) {
AppSnackBar.showInfo(context, message: 'Введите ссылку для открытия');
return;
}
final uri = Uri.tryParse(url);
if (uri == null) {
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
return;
}
try {
final success = await _urlLauncher.launchUrl(uri);
if (!context.mounted) return;
if (!success) {
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
}
} on Object catch (e) {
if (!context.mounted) return;
AppSnackBar.showError(context, message: 'Ошибка при открытии ссылки: $e');
}
}
/// Метод для проверки возможности открытия URL
Future<void> _checkCanOpenUrl(BuildContext context) async {
final url = _canOpenUrlController.text.trim();
if (url.isEmpty) {
AppSnackBar.showInfo(context, message: 'Введите ссылку для проверки');
return;
}
final uri = Uri.tryParse(url);
if (uri == null) {
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
return;
}
try {
final canOpen = await _urlLauncher.canLaunchUrl(uri);
if (!context.mounted) return;
if (canOpen) {
AppSnackBar.showSuccess(context: context, message: 'Возможно открыть ссылку: $url');
} else {
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
}
} on Object catch (e) {
if (!context.mounted) return;
AppSnackBar.showError(context, message: 'Ошибка при проверке ссылки: $e');
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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