mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 11:42:17 +00:00
Compare commits
1 Commits
6f839aea24
...
feat/add-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5f80e7d9 |
389
.cursorrules
389
.cursorrules
@@ -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
|
|
||||||
395
.github/copilot-instructions.md
vendored
395
.github/copilot-instructions.md
vendored
@@ -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
|
|
||||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -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
3
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
6
.idea/runConfigurations/DEV.xml
generated
6
.idea/runConfigurations/DEV.xml
generated
@@ -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>
|
|
||||||
6
.idea/runConfigurations/PROD.xml
generated
6
.idea/runConfigurations/PROD.xml
generated
@@ -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>
|
|
||||||
6
.idea/runConfigurations/STAGE.xml
generated
6
.idea/runConfigurations/STAGE.xml
generated
@@ -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>
|
|
||||||
6
.idea/runConfigurations/main_dart.xml
generated
6
.idea/runConfigurations/main_dart.xml
generated
@@ -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>
|
|
||||||
20
.vscode/dart.prime_snippets.code-snippets
vendored
20
.vscode/dart.prime_snippets.code-snippets
vendored
@@ -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
26
.vscode/tasks.json
vendored
@@ -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": [],
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
30
CODEOWNERS
30
CODEOWNERS
@@ -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
|
|
||||||
2
LICENSE
2
LICENSE
@@ -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
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,14 +1,21 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
# 🚀 Friflex Flutter Starter - Корпоративный шаблон
|
# 🚀 Friflex Flutter Starter - Корпоративный шаблон
|
||||||
|
|
||||||

|
</div>
|
||||||

|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||

|

|
||||||
|
|
||||||
Корпоративный стартовый шаблон для разработки масштабируемых 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>
|
||||||
|
|||||||
@@ -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 # Единообразие именования файлов блоков повышает навигацию
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
# Реализация сервисов для Аврора OC
|
# Базовые сервисы для приложения
|
||||||
@@ -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 # Единообразие именования файлов блоков повышает навигацию
|
|
||||||
|
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 # Единообразие именования файлов блоков повышает навигацию
|
|
||||||
|
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal file
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
29
app_services/hms/app_services/.gitignore
vendored
29
app_services/hms/app_services/.gitignore
vendored
@@ -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/
|
|
||||||
@@ -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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Реализация сервисов для HarmonyOS
|
|
||||||
@@ -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 # Единообразие именования файлов блоков повышает навигацию
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
library;
|
|
||||||
|
|
||||||
export 'src/app_location_service.dart';
|
|
||||||
export 'src/app_path_provider.dart';
|
|
||||||
export 'src/app_secure_storage.dart';
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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 # Единообразие именования файлов блоков повышает навигацию
|
|
||||||
|
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
/// {@template i_location_service}
|
|
||||||
/// Интерфейс для работы с геопозицией пользователя
|
|
||||||
/// {@endtemplate}
|
|
||||||
abstract interface class ILocationService {
|
|
||||||
static const name = 'ILocationService';
|
|
||||||
|
|
||||||
/// Метод для получения координат пользователя
|
|
||||||
Future<dynamic> getCurrentPosition();
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal file
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal 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);
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
BIN
assets/fonts/Montserrat-Bold.ttf
Normal file
BIN
assets/fonts/Montserrat-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Montserrat-ExtraBold.ttf
Normal file
BIN
assets/fonts/Montserrat-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Montserrat-Medium.ttf
Normal file
BIN
assets/fonts/Montserrat-Medium.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Montserrat-Regular.ttf
Normal file
BIN
assets/fonts/Montserrat-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/Montserrat-SemiBold.ttf
Normal file
BIN
assets/fonts/Montserrat-SemiBold.ttf
Normal file
Binary file not shown.
@@ -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
141
lib/app/app.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,73 +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:friflex_starter/router/app_router.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
/// {@template app}
|
|
||||||
/// Главный виджет приложения, отображающий основной интерфейс приложения
|
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Настройку провайдеров для темы и локализации
|
|
||||||
/// - Инициализацию роутера приложения
|
|
||||||
/// {@endtemplate}
|
|
||||||
class AppRoot extends StatefulWidget {
|
|
||||||
/// {@macro app_root}
|
|
||||||
const AppRoot({required this.diContainer, super.key});
|
|
||||||
|
|
||||||
/// Контейнер зависимостей
|
|
||||||
final DiContainer diContainer;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AppRoot> createState() => _AppRootState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppRootState extends State<AppRoot> {
|
|
||||||
/// Роутер приложения
|
|
||||||
late final GoRouter router;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
router = AppRouter.createRouter(widget.diContainer.debugService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
router.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AppProviders(
|
|
||||||
diContainer: widget.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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
lib/app/depends_providers.dart
Normal file
26
lib/app/depends_providers.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
94
lib/app/http/i_http_client.dart
Normal file
94
lib/app/http/i_http_client.dart
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>(
|
||||||
updateRepository = _lazyInitRepo<IUpdateRepository>(
|
mockFactory: AuthMockRepository.new,
|
||||||
mockFactory: () => const UpdateMockRepository(),
|
mainFactory: () => AuthRepository(
|
||||||
mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient),
|
httpClient: diContainer.httpClientFactory(
|
||||||
|
diContainer.debugService,
|
||||||
|
diContainer.appConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
onError: onError,
|
|
||||||
environment: diContainer.env,
|
environment: diContainer.env,
|
||||||
);
|
);
|
||||||
|
onProgress(authRepository.name);
|
||||||
|
} on Object catch (error, stackTrace) {
|
||||||
|
onError(
|
||||||
|
'Ошибка инициализации репозитория IAuthRepository',
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Инициализация репозитория сервиса управления токеном доступа
|
// Инициализация репозитория сервиса управления токеном доступа
|
||||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||||
mockFactory: () => const MainMockRepository(),
|
mockFactory: MainMockRepository.new,
|
||||||
mainFactory: () => MainRepository(httpClient: diContainer.httpClient),
|
mainFactory: () => MainRepository(
|
||||||
|
httpClient: diContainer.httpClientFactory(
|
||||||
|
diContainer.debugService,
|
||||||
|
diContainer.appConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
onError: onError,
|
|
||||||
environment: diContainer.env,
|
environment: diContainer.env,
|
||||||
);
|
);
|
||||||
|
onProgress(mainRepository.name);
|
||||||
|
} on Object catch (error, stackTrace) {
|
||||||
|
onError(
|
||||||
|
'Ошибка инициализации репозитория IMainRepository',
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Инициализация репозитория профиля
|
// Инициализация репозитория профиля
|
||||||
profileRepository = _lazyInitRepo<IProfileRepository>(
|
profileRepository = _lazyInitRepo<IProfileRepository>(
|
||||||
mockFactory: () => const ProfileMockRepository(),
|
mockFactory: ProfileMockRepository.new,
|
||||||
mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient),
|
mainFactory: () => ProfileRepository(
|
||||||
|
httpClient: diContainer.httpClientFactory(
|
||||||
|
diContainer.debugService,
|
||||||
|
diContainer.appConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
onError: onError,
|
|
||||||
environment: diContainer.env,
|
environment: diContainer.env,
|
||||||
);
|
);
|
||||||
|
onProgress(profileRepository.name);
|
||||||
|
} on Object catch (error, stackTrace) {
|
||||||
|
onError(
|
||||||
|
'Ошибка инициализации репозитория IProfileRepository',
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onProgress(
|
onProgress(
|
||||||
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
||||||
@@ -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 mainRepo = mainFactory();
|
||||||
|
|
||||||
final repo = switch (environment) {
|
final repo = switch (environment) {
|
||||||
.dev => mockFactory(),
|
AppEnv.dev => mockRepo,
|
||||||
.prod => mainFactory(),
|
AppEnv.prod => mainRepo,
|
||||||
.stage =>
|
AppEnv.stage =>
|
||||||
_mockReposToSwitch.contains(T) ? mockFactory() : mainFactory(),
|
_mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
|
||||||
};
|
};
|
||||||
|
|
||||||
// throw Exception('Тестовая - ошибка инициализации репозитория $T');
|
|
||||||
onProgress(repo.name);
|
onProgress(repo.name);
|
||||||
return repo;
|
return repo;
|
||||||
} on Object catch (error, stackTrace) {
|
|
||||||
onError('Ошибка инициализации репозитория $T', error, stackTrace);
|
|
||||||
// Перебрасываем исключение дальше, чтобы не скрыть ошибку инициализации
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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('Инициализация сервисов завершена!');
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
14
lib/features/auth/data/repository/auth_repository.dart
Normal file
14
lib/features/auth/data/repository/auth_repository.dart
Normal 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';
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||||
|
|
||||||
|
/// {@template IAuthRepository}
|
||||||
|
/// Интерфейс для работы с репозиторием авторизации
|
||||||
|
/// {@endtemplate}
|
||||||
|
abstract interface class IAuthRepository with DiBaseRepo {}
|
||||||
24
lib/features/auth/presentation/screens/auth_screen.dart
Normal file
24
lib/features/auth/presentation/screens/auth_screen.dart
Normal 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')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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});
|
||||||
|
|||||||
@@ -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 обновления'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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}'),
|
||||||
|
|||||||
147
lib/features/debug/screens/path_provider_screen.dart
Normal file
147
lib/features/debug/screens/path_provider_screen.dart
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
125
lib/features/debug/screens/secure_storage_screen.dart
Normal file
125
lib/features/debug/screens/secure_storage_screen.dart
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
lib/features/debug/screens/url_launcher_screen.dart
Normal file
133
lib/features/debug/screens/url_launcher_screen.dart
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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('Переход на экран с деталями'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 => [];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
16
lib/features/splash/splash_screen.dart
Normal file
16
lib/features/splash/splash_screen.dart
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`
|
|
||||||
|
|
||||||
|
|
||||||
## Структура модуля
|
|
||||||
|
|
||||||
```
|
|
||||||
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` из репозитория, если обновлений нет
|
|
||||||
@@ -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 versionCode,
|
|
||||||
required String platform,
|
|
||||||
}) async {
|
|
||||||
// Имитация задержки для асинхронной операции
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
|
||||||
|
|
||||||
// Возвращаем фиктивные данные об обновлении
|
|
||||||
// Можно возвращать [_mockHardUpdateEntity] или [_mockSoftUpdateEntity]
|
|
||||||
return mockSoftUpdateEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => 'UpdateMockRepository';
|
|
||||||
}
|
|
||||||
@@ -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 versionCode,
|
|
||||||
required String platform,
|
|
||||||
}) {
|
|
||||||
// TODO: Реализовать реальную логику проверки обновлений
|
|
||||||
// Если обновления нет, возвращаем null
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => 'UpdateRepository';
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -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 {
|
|
||||||
/// Проверяет наличие обновлений
|
|
||||||
/// [versionCode] - текущий код версии приложения
|
|
||||||
/// [platform] - платформа (например, 'android' или 'ios')
|
|
||||||
/// Возвращает [UpdateEntity] с информацией об обновлении
|
|
||||||
Future<UpdateEntity> checkForUpdates({
|
|
||||||
required String versionCode,
|
|
||||||
required String platform,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
|
|
||||||
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
|
|
||||||
|
|
||||||
part 'update_state.dart';
|
|
||||||
|
|
||||||
/// {@template UpdateCubit}
|
|
||||||
/// Кубит для управления состояниями обновления приложения
|
|
||||||
/// {@endtemplate}
|
|
||||||
class UpdateCubit extends Cubit<UpdateState> {
|
|
||||||
/// {@macro UpdateCubit}
|
|
||||||
UpdateCubit(this._updatesRepository) : super(const UpdateInitialState());
|
|
||||||
|
|
||||||
/// Репозиторий для проверки обновлений
|
|
||||||
final IUpdateRepository _updatesRepository;
|
|
||||||
|
|
||||||
/// Метод для проверки доступности обновлений
|
|
||||||
/// [versionCode] - текущий код версии приложения
|
|
||||||
/// [platform] - платформа (например, 'android' или 'ios')
|
|
||||||
Future<void> checkForUpdates({
|
|
||||||
required String versionCode,
|
|
||||||
required String platform,
|
|
||||||
}) async {
|
|
||||||
if (state is UpdateLoadingState) return;
|
|
||||||
emit(const UpdateLoadingState());
|
|
||||||
try {
|
|
||||||
final updateInfo = await _updatesRepository.checkForUpdates(
|
|
||||||
versionCode: versionCode,
|
|
||||||
platform: platform,
|
|
||||||
);
|
|
||||||
emit(UpdateSuccessState(updateInfo));
|
|
||||||
} on Object catch (e, st) {
|
|
||||||
emit(UpdateErrorState(e.toString()));
|
|
||||||
addError(e, st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
part of 'update_cubit.dart';
|
|
||||||
|
|
||||||
/// {@template UpdateState}
|
|
||||||
/// Состояния для управления процессом обновления приложения
|
|
||||||
/// {@endtemplate}
|
|
||||||
sealed class UpdateState extends Equatable {
|
|
||||||
/// {@macro UpdateState}
|
|
||||||
const UpdateState();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template UpdateInitialState}
|
|
||||||
/// Состояние начальной инициализации
|
|
||||||
/// {@endtemplate}
|
|
||||||
final class UpdateInitialState extends UpdateState {
|
|
||||||
/// {@macro UpdateInitialState}
|
|
||||||
const UpdateInitialState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template UpdateLoadingState}
|
|
||||||
/// Состояние загрузки информации об обновлении
|
|
||||||
/// {@endtemplate}
|
|
||||||
final class UpdateLoadingState extends UpdateState {
|
|
||||||
/// {@macro UpdateLoadingState}
|
|
||||||
const UpdateLoadingState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template UpdateSuccessState}
|
|
||||||
/// Состояние успешного получения информации об обновлении
|
|
||||||
/// {@endtemplate}
|
|
||||||
final class UpdateSuccessState extends UpdateState {
|
|
||||||
/// {@macro UpdateSuccessState}
|
|
||||||
const UpdateSuccessState(this.updateInfo);
|
|
||||||
|
|
||||||
/// Информация об обновлении
|
|
||||||
final UpdateEntity updateInfo;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [updateInfo];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template UpdateErrorState}
|
|
||||||
/// Состояние ошибки при получении информации об обновлении
|
|
||||||
/// {@endtemplate}
|
|
||||||
final class UpdateErrorState extends UpdateState {
|
|
||||||
/// {@macro UpdateErrorState}
|
|
||||||
const UpdateErrorState(this.message);
|
|
||||||
|
|
||||||
/// Сообщение об ошибке в UI
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [message];
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
|
||||||
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
/// {@template soft_update_modal}
|
|
||||||
/// Модальное окно для уведомления о доступности новой версии приложения.
|
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Отображение информации о новой версии приложения
|
|
||||||
/// - Предоставление возможности обновления или отложения
|
|
||||||
/// - Показ описания изменений в новой версии
|
|
||||||
/// - Мягкое уведомление пользователя без принуждения к обновлению
|
|
||||||
/// {@endtemplate}
|
|
||||||
class SoftUpdateModal extends StatelessWidget {
|
|
||||||
/// {@macro soft_update_modal}
|
|
||||||
const SoftUpdateModal({required this.updateEntity, this.onUpdate, super.key});
|
|
||||||
|
|
||||||
/// Информация об обновлении
|
|
||||||
final UpdateEntity updateEntity;
|
|
||||||
|
|
||||||
/// Обратный вызов при нажатии "Обновить"
|
|
||||||
final VoidCallback? onUpdate;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Заголовок
|
|
||||||
Text(
|
|
||||||
'Доступна новая версия: ${updateEntity.availableVersion} ',
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
|
|
||||||
const HBox(16),
|
|
||||||
|
|
||||||
// Описание изменений
|
|
||||||
Text(
|
|
||||||
'Что нового:',
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
const HBox(8),
|
|
||||||
Text(
|
|
||||||
updateEntity.whatIsNew,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
|
|
||||||
const HBox(24),
|
|
||||||
|
|
||||||
// Кнопки действий
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
},
|
|
||||||
child: const Text('Отложить'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const WBox(12),
|
|
||||||
Expanded(
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
onUpdate?.call();
|
|
||||||
},
|
|
||||||
child: const Text('Обновить'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Показать модальное окно обновления
|
|
||||||
///
|
|
||||||
/// [context] - контекст для отображения модального окна
|
|
||||||
/// [updateEntity] - информация об обновлении
|
|
||||||
/// [onUpdate] - функция при нажатии "Обновить"
|
|
||||||
static Future<void> show(
|
|
||||||
BuildContext context, {
|
|
||||||
required UpdateEntity updateEntity,
|
|
||||||
VoidCallback? onUpdate,
|
|
||||||
}) {
|
|
||||||
return showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
builder: (context) =>
|
|
||||||
SoftUpdateModal(updateEntity: updateEntity, onUpdate: onUpdate),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
|
||||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
|
||||||
|
|
||||||
/// Блокирующий экран для обязательного обновления приложения
|
|
||||||
class HardUpdateScreen extends StatelessWidget {
|
|
||||||
const HardUpdateScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text('Hard Обновление')),
|
|
||||||
body: Center(
|
|
||||||
child: BlocBuilder<UpdateCubit, UpdateState>(
|
|
||||||
builder: (context, updateCubitState) {
|
|
||||||
final updateEntity = updateCubitState is UpdateSuccessState
|
|
||||||
? updateCubitState.updateInfo
|
|
||||||
: null;
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Доступна новая версия приложения. Пожалуйста, обновите его.',
|
|
||||||
),
|
|
||||||
const HBox(16),
|
|
||||||
Text(
|
|
||||||
'Доступная версия: ${updateEntity?.availableVersion ?? ''}',
|
|
||||||
),
|
|
||||||
const HBox(8),
|
|
||||||
Text('Что нового: ${updateEntity?.whatIsNew ?? ''}'),
|
|
||||||
const HBox(8),
|
|
||||||
Text('Тип обновления: ${updateEntity?.updateType ?? ''}'),
|
|
||||||
const HBox(8),
|
|
||||||
Text('URL для обновления: ${updateEntity?.updateUrl ?? ''}'),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:friflex_starter/features/update/presentation/screens/hard_update_screen.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
|
|
||||||
abstract final class UpdateRoutes {
|
|
||||||
/// Название роута главной страницы
|
|
||||||
static const String hardUpdateScreenName = 'update_screen';
|
|
||||||
|
|
||||||
/// Путь роута экрана обновления
|
|
||||||
static const String _hardUpdateScreenPath = '/update';
|
|
||||||
|
|
||||||
/// Метод для построения роутов по Update
|
|
||||||
///
|
|
||||||
/// Принимает:
|
|
||||||
/// - [routes] - вложенные роуты
|
|
||||||
static GoRoute buildRoutes({List<RouteBase> routes = const []}) => GoRoute(
|
|
||||||
path: _hardUpdateScreenPath,
|
|
||||||
name: hardUpdateScreenName,
|
|
||||||
builder: (context, state) => const HardUpdateScreen(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/// {@template UpdateType}
|
|
||||||
/// Тип обновления
|
|
||||||
/// {@endtemplate}
|
|
||||||
enum UpdateType {
|
|
||||||
/// Обязательное обновление
|
|
||||||
hard,
|
|
||||||
|
|
||||||
/// Мягкое обновление
|
|
||||||
soft,
|
|
||||||
|
|
||||||
/// Не требуется обновление
|
|
||||||
none,
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// dart format width=80
|
|
||||||
|
|
||||||
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
/// *****************************************************
|
/// *****************************************************
|
||||||
/// FlutterGen
|
/// FlutterGen
|
||||||
@@ -7,7 +5,7 @@
|
|||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import
|
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@@ -15,6 +13,34 @@ import 'package:flutter_svg/flutter_svg.dart' as _svg;
|
|||||||
import 'package:lottie/lottie.dart' as _lottie;
|
import 'package:lottie/lottie.dart' as _lottie;
|
||||||
import 'package:vector_graphics/vector_graphics.dart' as _vg;
|
import 'package:vector_graphics/vector_graphics.dart' as _vg;
|
||||||
|
|
||||||
|
class $AssetsFontsGen {
|
||||||
|
const $AssetsFontsGen();
|
||||||
|
|
||||||
|
/// File path: assets/fonts/Montserrat-Bold.ttf
|
||||||
|
String get montserratBold => 'assets/fonts/Montserrat-Bold.ttf';
|
||||||
|
|
||||||
|
/// File path: assets/fonts/Montserrat-ExtraBold.ttf
|
||||||
|
String get montserratExtraBold => 'assets/fonts/Montserrat-ExtraBold.ttf';
|
||||||
|
|
||||||
|
/// File path: assets/fonts/Montserrat-Medium.ttf
|
||||||
|
String get montserratMedium => 'assets/fonts/Montserrat-Medium.ttf';
|
||||||
|
|
||||||
|
/// File path: assets/fonts/Montserrat-Regular.ttf
|
||||||
|
String get montserratRegular => 'assets/fonts/Montserrat-Regular.ttf';
|
||||||
|
|
||||||
|
/// File path: assets/fonts/Montserrat-SemiBold.ttf
|
||||||
|
String get montserratSemiBold => 'assets/fonts/Montserrat-SemiBold.ttf';
|
||||||
|
|
||||||
|
/// List of all assets
|
||||||
|
List<String> get values => [
|
||||||
|
montserratBold,
|
||||||
|
montserratExtraBold,
|
||||||
|
montserratMedium,
|
||||||
|
montserratRegular,
|
||||||
|
montserratSemiBold,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
class $AssetsIconsGen {
|
class $AssetsIconsGen {
|
||||||
const $AssetsIconsGen();
|
const $AssetsIconsGen();
|
||||||
|
|
||||||
@@ -37,8 +63,9 @@ class $AssetsLottieGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Assets {
|
class Assets {
|
||||||
const Assets._();
|
Assets._();
|
||||||
|
|
||||||
|
static const $AssetsFontsGen fonts = $AssetsFontsGen();
|
||||||
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
||||||
static const $AssetsLottieGen lottie = $AssetsLottieGen();
|
static const $AssetsLottieGen lottie = $AssetsLottieGen();
|
||||||
}
|
}
|
||||||
@@ -69,7 +96,6 @@ class SvgGenImage {
|
|||||||
String? semanticsLabel,
|
String? semanticsLabel,
|
||||||
bool excludeFromSemantics = false,
|
bool excludeFromSemantics = false,
|
||||||
_svg.SvgTheme? theme,
|
_svg.SvgTheme? theme,
|
||||||
_svg.ColorMapper? colorMapper,
|
|
||||||
ColorFilter? colorFilter,
|
ColorFilter? colorFilter,
|
||||||
Clip clipBehavior = Clip.hardEdge,
|
Clip clipBehavior = Clip.hardEdge,
|
||||||
@deprecated Color? color,
|
@deprecated Color? color,
|
||||||
@@ -89,7 +115,6 @@ class SvgGenImage {
|
|||||||
assetBundle: bundle,
|
assetBundle: bundle,
|
||||||
packageName: package,
|
packageName: package,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
colorMapper: colorMapper,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _svg.SvgPicture(
|
return _svg.SvgPicture(
|
||||||
@@ -146,9 +171,6 @@ class LottieGenImage {
|
|||||||
bool? addRepaintBoundary,
|
bool? addRepaintBoundary,
|
||||||
FilterQuality? filterQuality,
|
FilterQuality? filterQuality,
|
||||||
void Function(String)? onWarning,
|
void Function(String)? onWarning,
|
||||||
_lottie.LottieDecoder? decoder,
|
|
||||||
_lottie.RenderCache? renderCache,
|
|
||||||
bool? backgroundLoading,
|
|
||||||
}) {
|
}) {
|
||||||
return _lottie.Lottie.asset(
|
return _lottie.Lottie.asset(
|
||||||
_assetName,
|
_assetName,
|
||||||
@@ -173,9 +195,6 @@ class LottieGenImage {
|
|||||||
addRepaintBoundary: addRepaintBoundary,
|
addRepaintBoundary: addRepaintBoundary,
|
||||||
filterQuality: filterQuality,
|
filterQuality: filterQuality,
|
||||||
onWarning: onWarning,
|
onWarning: onWarning,
|
||||||
decoder: decoder,
|
|
||||||
renderCache: renderCache,
|
|
||||||
backgroundLoading: backgroundLoading,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
lib/gen/fonts.gen.dart
Normal file
15
lib/gen/fonts.gen.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
/// *****************************************************
|
||||||
|
/// FlutterGen
|
||||||
|
/// *****************************************************
|
||||||
|
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
|
||||||
|
|
||||||
|
class FontFamily {
|
||||||
|
FontFamily._();
|
||||||
|
|
||||||
|
/// Font family: Montserrat
|
||||||
|
static const String montserrat = 'Montserrat';
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user