mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-22 17:40:45 +00:00
Compare commits
33 Commits
feat/add-t
...
feat/перен
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4260c7cc65 | ||
|
|
2595692107 | ||
|
|
c86b4cc0bc | ||
|
|
d46c829959 | ||
|
|
84e5f5e869 | ||
|
|
fcf5048038 | ||
|
|
454f3b7929 | ||
|
|
d9c45eb57e | ||
|
|
55de1ad8d1 | ||
|
|
42b7c34d1a | ||
|
|
5d93fb1713 | ||
|
|
0351c768c4 | ||
|
|
dfe30a1762 | ||
|
|
8710792c4b | ||
|
|
e1fb99c86f | ||
|
|
c295412f4d | ||
|
|
03e189e46b | ||
|
|
d491a2f07f | ||
|
|
ac26aa4a89 | ||
|
|
d8110c23b3 | ||
|
|
35bceccbf7 | ||
|
|
d5a0602c8a | ||
|
|
63cd544184 | ||
|
|
8baddfb9f9 | ||
|
|
80a8bfb905 | ||
|
|
c21fa95b7a | ||
|
|
b54445be70 | ||
|
|
150a85ab24 | ||
|
|
fb92795b67 | ||
|
|
24bf652319 | ||
|
|
98630f744f | ||
|
|
ba5fdba9be | ||
|
|
427a821e5d |
64
.cursorrules
Normal file
64
.cursorrules
Normal file
@@ -0,0 +1,64 @@
|
||||
# Правила для Cursor AI
|
||||
|
||||
## Соглашение о коммитах
|
||||
|
||||
При генерации сообщений коммитов ВСЕГДА используй следующий формат:
|
||||
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
|
||||
|
||||
Для Pull Request формат:
|
||||
`<тип>(<контекст1>,<контекст2>,...): <короткое описание>`
|
||||
|
||||
Где:
|
||||
- `<тип>` - тип коммита (см. ниже)
|
||||
- `<контекст>` - модули/компоненты, которые изменяются (можно указать несколько через запятую)
|
||||
- `<короткое описание>` - краткое описание изменений на русском языке
|
||||
|
||||
### Типы коммитов согласно convention:
|
||||
|
||||
- **feat** - новая функция
|
||||
- **fix** - исправление ошибок
|
||||
- **refactor** - изменение кода, которое не исправляет ошибку и не добавляет функции (рефакторинг кода)
|
||||
- **build** - изменения, влияющие на систему сборки или внешние зависимости (примеры областей: android, ios, linux и так далее)
|
||||
- **docs** - изменения только в документации
|
||||
- **chore** - добавление/обновление/настройка инструментов и библиотек (пример: pubspec.yaml)
|
||||
- **test** - добавление недостающих тестов или исправление существующих тестов
|
||||
- **ci** - изменения в файлах конфигурации и скриптах CI (примеры областей: папка CI)
|
||||
|
||||
### Контекст (scope):
|
||||
|
||||
Указывай модуль или компонент, который изменяется. Можно указать несколько через запятую:
|
||||
- `app` - основное приложение
|
||||
- `di` - dependency injection
|
||||
- `auth` - аутентификация
|
||||
- `api` - API endpoints
|
||||
- `db` - база данных
|
||||
- `config` - конфигурация
|
||||
- `i18n` - интернационализация
|
||||
- `scripts` - скрипты
|
||||
- `pubspec` - зависимости проекта
|
||||
- `android`, `ios`, `linux` - платформы
|
||||
- другие модули проекта
|
||||
|
||||
### Примеры правильных коммитов:
|
||||
|
||||
- `feat(app,di,auth): Добавить локальный репозиторий`
|
||||
- `fix(api): Исправить валидацию запросов`
|
||||
- `docs(i18n): Обновить руководство по генерации`
|
||||
- `refactor(handler): Оптимизировать обработку запросов`
|
||||
- `test(security): Добавить тесты для rate limiter`
|
||||
- `chore(pubspec): Обновить зависимости`
|
||||
|
||||
## Язык
|
||||
|
||||
- Всегда отвечай на русском языке
|
||||
- Коммиты пиши на русском языке
|
||||
- Документацию веди на русском языке
|
||||
- Язык описания PR - Русский
|
||||
|
||||
## Стиль кода
|
||||
|
||||
- Следуй Dart/Flutter conventions
|
||||
- Используй осмысленные имена переменных и функций
|
||||
- Добавляй комментарии к публичным функциям
|
||||
- Группируй импорты (стандартные, внешние, внутренние)
|
||||
|
||||
63
.github/copilot-instructions.md
vendored
Normal file
63
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
Инструкция для AI-агента (ревью Flutter-кода)
|
||||
|
||||
Вы — строгий ревьюер кода Flutter-приложения по принципам **Clean Architecture**.
|
||||
Ваша задача — проводить ревью кода, выявлять ошибки и давать корректные рекомендации.
|
||||
Bloc и Cubit должны находиться в слое domain.
|
||||
Контекст (справочно, не включать в ответ):
|
||||
- Архитектура: три слоя (presentation, domain, data), каждый в своей папке.
|
||||
- State management: flutter_bloc.
|
||||
- Навигация: go_router.
|
||||
- HTTP: dio.
|
||||
- Анализатор: flutter_lint_rules.
|
||||
- Feature-first структура: lib/features/<feature_name>/{data,domain,presentation}.
|
||||
|
||||
---
|
||||
## Обзор запроса на вытягивание
|
||||
1. В начале ответа укажите решение: **«Принять»** или **«Отклонить»**.
|
||||
2. Укажите оценку: **Оценка: X/100** (X — фактический балл).
|
||||
3. Кратко и строгим тоном перечислите ключевые проблемы:
|
||||
- Архитектура (слои `presentation`, `domain`, `data`).
|
||||
- Принципы **DRY, KISS, SOLID**.
|
||||
- Разделение ответственности (BLoC, репозитории, DTO, UI).
|
||||
- Безопасность (`dio`, SSL, валидация данных).
|
||||
- Кодстайл и соглашения (`flutter_lint_rules`).
|
||||
- Цикломатическая сложность: избегать чрезмерно сложных функций и классов (оптимально ≤ 10).
|
||||
|
||||
4. При необходимости предоставьте исправленный фрагмент кода или улучшенное решение.
|
||||
5. Ответ должен быть в **Markdown** и полностью на **русском языке**.
|
||||
|
||||
---
|
||||
|
||||
## Критерии оценки
|
||||
- **Чистая архитектура**: корректное разделение слоёв, отсутствие Flutter-зависимостей в `domain`.
|
||||
- **KISS**: минимальная сложность и читаемость решений.
|
||||
- **DRY**: отсутствие дублирования логики, использование утилит/виджетов.
|
||||
- **SOLID**: правильная декомпозиция классов и интерфейсы вместо жёстких связей.
|
||||
- **Безопасность**: корректная работа с API, валидация данных.
|
||||
- **Кодстайл**: именование, структура файлов и папок.
|
||||
- **Цикломатическая сложность**: методы и классы должны быть простыми, без избыточных ветвлений.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Пример ответа
|
||||
**Отклонить**
|
||||
**Оценка: 58/100**
|
||||
- Нарушен принцип DRY: HTTP-запрос продублирован в двух репозиториях.
|
||||
- BLoC перегружен бизнес-логикой (трансформация DTO → Entity должна быть в `data`).
|
||||
- Domain-слой содержит зависимость от Flutter — это недопустимо.
|
||||
- Отсутствует обработка ошибок и валидация данных в `dio`.
|
||||
|
||||
**Исправленный фрагмент:**
|
||||
```dart
|
||||
// Вместо дублирования запроса используем общий DataSource
|
||||
class UserRemoteDataSource {
|
||||
final Dio dio;
|
||||
UserRemoteDataSource(this.dio);
|
||||
|
||||
Future<UserEntity> fetchUser(String id) async {
|
||||
final response = await dio.get('/users/$id');
|
||||
return UserDto.fromJson(response.data).toEntity();
|
||||
}
|
||||
}
|
||||
```
|
||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -1,3 +1,5 @@
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
## Ссылка на задачу или issue (обязательно)
|
||||
<!--- https://tracker.yandex.ru/XXX -->
|
||||
|
||||
@@ -5,6 +7,7 @@
|
||||
<!--- Напишите здесь какую проблему решают изменения в этом запросе. -->
|
||||
|
||||
## Чек лист (обязательно)
|
||||
|
||||
- [ ] Код соответствует рекомендациям и требованиям проекта.
|
||||
- [ ] Проверил анализатор на предмет ошибок и предупреждений.
|
||||
- [ ] Название ПР соответствует [требованиям проекта](../tools/rfc/RFC-documentation.md).
|
||||
@@ -12,6 +15,7 @@
|
||||
- [ ] Добавлены необходимые тесты, если требуется.
|
||||
|
||||
## Скриншоты (желательно)
|
||||
|
||||
<details>
|
||||
<summary>Показать</summary>
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,6 +17,9 @@ migrate_working_dir/
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
# Хранить конфигурации запуска
|
||||
!.idea/runConfigurations/
|
||||
!.idea/runConfigurations/**
|
||||
|
||||
# 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
|
||||
|
||||
6
.idea/runConfigurations/DEV.xml
generated
Normal file
6
.idea/runConfigurations/DEV.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<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
Normal file
6
.idea/runConfigurations/PROD.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<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
Normal file
6
.idea/runConfigurations/STAGE.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<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
Normal file
6
.idea/runConfigurations/main_dart.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<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,5 +14,23 @@
|
||||
"/// {@macro ${TM_FILENAME_BASE}}"
|
||||
],
|
||||
"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,24 +1,12 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"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",
|
||||
"type": "shell",
|
||||
"command": "dart",
|
||||
"command": "flutter",
|
||||
"args": [
|
||||
"pub",
|
||||
"run",
|
||||
"build_runner",
|
||||
"build",
|
||||
@@ -62,6 +50,16 @@
|
||||
"type": "shell",
|
||||
"command": "./tools/switch_service.sh aurora",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "flutter gen-l10n",
|
||||
"type": "shell",
|
||||
"command": "flutter",
|
||||
"args": [
|
||||
"gen-l10n"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
30
CODEOWNERS
30
CODEOWNERS
@@ -1 +1,29 @@
|
||||
Friflex LCC
|
||||
# Файл CODEOWNERS определяет владельцев кода для автоматического назначения ревьюеров
|
||||
|
||||
# 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
|
||||
|
||||
Copyright (c) 2025 Friflex
|
||||
Copyright (c) 2025 Friflex LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
60
README.md
60
README.md
@@ -1,21 +1,14 @@
|
||||
<div align="center">
|
||||
|
||||
# 🚀 Friflex Starter - Корпоративный шаблон
|
||||
# 🚀 Friflex Flutter Starter - Корпоративный шаблон
|
||||
|
||||
</div>
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**Корпоративный стартовый шаблон для разработки масштабируемых Flutter-приложений**
|
||||
Корпоративный стартовый шаблон для разработки масштабируемых Flutter-приложений
|
||||
|
||||
[📋 Документация](#-документация) • [🏗️ Архитектура](#️-архитектура) • [🚀 Быстрый старт](#-быстрый-старт) • [🔧 Конфигурация](#-конфигурация)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 📖 Описание проекта
|
||||
@@ -31,6 +24,7 @@
|
||||
- 🌍 Поддержка интернационализации
|
||||
- 🎨 UI Kit и система токенов дизайна
|
||||
- 🔍 Инструменты отладки и мониторинга
|
||||
- ⚡ Современный Dart 3.10+ с dot shorthands
|
||||
|
||||
## 🎯 Для чего нужен стартер
|
||||
|
||||
@@ -97,20 +91,27 @@ features/
|
||||
|
||||
| Категория | Библиотека | Версия | Описание |
|
||||
|-----------|------------|--------|----------|
|
||||
| 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `15.1.2` | Декларативный роутинг |
|
||||
| 🧭 **Навигация** | [go_router](https://pub.dev/packages/go_router) | `17.0.0` | Декларативный роутинг |
|
||||
| 🔄 **State Management** | [flutter_bloc](https://pub.dev/packages/flutter_bloc) | `9.1.1` | Управление состоянием |
|
||||
| 💉 **DI** | Custom InheritedWidget | - | Внедрение зависимостей |
|
||||
| 🎨 **Resources** | [flutter_gen](https://pub.dev/packages/flutter_gen) | `5.10.0` | Генерация ресурсов |
|
||||
| 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.8.0+1` | HTTP клиент |
|
||||
| 🎨 **Resources** | [flutter_gen](https://pub.dev/packages/flutter_gen) | `5.12.0` | Генерация ресурсов |
|
||||
| 🌐 **HTTP** | [dio](https://pub.dev/packages/dio) | `5.9.0` | 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) | - | Защищенное хранилище |
|
||||
| 📊 **Logging** | [talker](https://pub.dev/packages/talker_flutter) | `4.8.0` | Логирование и отладка |
|
||||
| 📊 **Logging** | [talker](https://pub.dev/packages/talker_flutter) | `5.0.2` | Логирование и отладка |
|
||||
| 🎨 **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_localizations` + `intl`
|
||||
- **⚙️ Окружения**: `envied` для управления переменными
|
||||
| Инструмент | Версия | Описание |
|
||||
|-----------|--------|----------|
|
||||
| **📝 Линтинг** | `flutter_lints: 6.0.0` | Корпоративные правила кода |
|
||||
| **🏗️ Code Generation** | `build_runner: 2.10.3` | Генерация кода |
|
||||
| **🌍 Локализация** | `intl: 0.20.2` | Интернационализация |
|
||||
| **⚙️ Environment** | `envied: 1.3.1` + `envied_generator: 1.3.1` | Управление переменными окружения |
|
||||
| **🎨 Theme Generator** | `theme_tailor: 3.1.1` | Генерация тем |
|
||||
|
||||
## 🗂️ Структура проекта
|
||||
|
||||
@@ -162,33 +163,33 @@ enum AppEnv {
|
||||
|
||||
### 🛠️ Установка
|
||||
|
||||
1. **Клонирование проекта**
|
||||
#### Клонирование проекта
|
||||
|
||||
```bash
|
||||
git clone https://github.com/smmarty/friflex_starter.git
|
||||
cd friflex_starter
|
||||
```
|
||||
|
||||
2. **Установка зависимостей**
|
||||
#### Установка зависимостей
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
3. **Генерация файлов**
|
||||
#### Генерация файлов
|
||||
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
flutter packages pub run flutter_gen
|
||||
```
|
||||
|
||||
4. **Запуск приложения**
|
||||
#### Запуск приложения
|
||||
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
5. **Замените название пакета на ваш**
|
||||
#### Замените название пакета на ваш
|
||||
|
||||
### 🎯 Запуск с разными окружениями
|
||||
|
||||
@@ -262,6 +263,8 @@ dart run build_runner build --delete-conflicting-outputs
|
||||
| 🔄 [RFC-gitflow.md](./tools/rfc/RFC-gitflow.md) | Git workflow |
|
||||
| 🏗️ [RFC-projects_structure.md](./tools/rfc/RFC-projects_structure.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 |
|
||||
|
||||
## 🎯 Особенности и нюансы
|
||||
|
||||
@@ -286,15 +289,10 @@ dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENCE).
|
||||
Этот проект распространяется под лицензией MIT License. Подробности смотрите в файле [LICENSE](./LICENSE).
|
||||
|
||||
Copyright © 2025 Friflex LLC. Все права защищены.
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="right">
|
||||
|
||||
*Разработано с любовью командой Friflex ❤️*
|
||||
|
||||
</div>
|
||||
### Разработано с любовью командой Friflex ❤️
|
||||
|
||||
@@ -1,89 +1,135 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Включает правила из:
|
||||
# - package:lints/core.yaml: основные правила критических проблем
|
||||
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
|
||||
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
|
||||
# Персонализированные настройки анализатора и линтера.
|
||||
# Базовый набор flutter_lints + дополнительные ужесточения.
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "android/**"
|
||||
- "assets/**"
|
||||
- "build/**"
|
||||
- "config/**"
|
||||
- "core/**"
|
||||
- "res/**"
|
||||
- "ios/**"
|
||||
- "**/*.g.dart"
|
||||
- "**/*.config.dart"
|
||||
- "**/*.gen.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "**/generated/*"
|
||||
- "**/*.gr.dart"
|
||||
- "**/*.yaml"
|
||||
- "app_services/aurora/**"
|
||||
- "/app_services/aurora/**"
|
||||
- "**/app_services/aurora/**"
|
||||
- "**/*.lock.dart"
|
||||
errors:
|
||||
# Переопределения уровней ошибок (error/warning/info)
|
||||
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
|
||||
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
|
||||
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
|
||||
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
|
||||
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
|
||||
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
|
||||
comment_references: warning # Проверяет корректность ссылок в комментариях
|
||||
always_declare_return_types: error # Требует явного указания возвращаемых типов методов
|
||||
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
|
||||
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 с цветом
|
||||
- "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: true # Требовать размещать обязательные именованные параметры первыми
|
||||
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
|
||||
avoid_catching_errors: true # Избегать перехвата ошибок типа Error
|
||||
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
|
||||
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек
|
||||
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах
|
||||
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов
|
||||
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке
|
||||
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
|
||||
avoid_private_typedef_functions: true # Избегать приватных typedef-функций
|
||||
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов
|
||||
avoid_returning_this: true # Избегать возврата this
|
||||
cascade_invocations: true # Использовать каскадные вызовы
|
||||
deprecated_consistency: true # Поддерживать согласованность устаревших элементов
|
||||
do_not_use_environment: false # Разрешить использование Environment
|
||||
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
|
||||
no_runtimeType_toString: true # Не использовать runtimeType.toString()
|
||||
one_member_abstracts: false # Разрешать абстрактные классы с одним методом
|
||||
only_throw_errors: true # Выбрасывать только объекты Error
|
||||
parameter_assignments: true # Запрещать присваивание значений параметрам
|
||||
prefer_asserts_with_message: true # Использовать сообщения с assert
|
||||
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
|
||||
prefer_final_in_for_each: true # Использовать final в for-each циклах
|
||||
prefer_final_locals: true # Использовать final для локальных переменных
|
||||
public_member_api_docs: false # Не требовать документацию для всех публичных членов
|
||||
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
|
||||
sort_constructors_first: true # Требовать размещать конструкторы первыми
|
||||
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
|
||||
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
|
||||
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
|
||||
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
|
||||
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
|
||||
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
|
||||
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
|
||||
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
|
||||
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
|
||||
always_use_package_imports: true # Всегда использовать package: импорты
|
||||
unawaited_futures: true # Требовать использование unawaited для неожидаемых Future
|
||||
# === Именование и стиль ===
|
||||
- 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 +1 @@
|
||||
# Базовые сервисы для приложения
|
||||
# Реализация сервисов для Аврора OC
|
||||
@@ -1,89 +1,135 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Включает правила из:
|
||||
# - package:lints/core.yaml: основные правила критических проблем
|
||||
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
|
||||
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
|
||||
# Персонализированные настройки анализатора и линтера.
|
||||
# Базовый набор flutter_lints + дополнительные ужесточения.
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "android/**"
|
||||
- "assets/**"
|
||||
- "build/**"
|
||||
- "config/**"
|
||||
- "core/**"
|
||||
- "res/**"
|
||||
- "ios/**"
|
||||
- "**/*.g.dart"
|
||||
- "**/*.config.dart"
|
||||
- "**/*.gen.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "**/generated/*"
|
||||
- "**/*.gr.dart"
|
||||
- "**/*.yaml"
|
||||
- "app_services/aurora/**"
|
||||
- "/app_services/aurora/**"
|
||||
- "**/app_services/aurora/**"
|
||||
- "**/*.lock.dart"
|
||||
errors:
|
||||
# Переопределения уровней ошибок (error/warning/info)
|
||||
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
|
||||
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
|
||||
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
|
||||
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
|
||||
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
|
||||
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
|
||||
comment_references: warning # Проверяет корректность ссылок в комментариях
|
||||
always_declare_return_types: error # Требует явного указания возвращаемых типов методов
|
||||
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
|
||||
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 с цветом
|
||||
- "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: true # Требовать размещать обязательные именованные параметры первыми
|
||||
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
|
||||
avoid_catching_errors: true # Избегать перехвата ошибок типа Error
|
||||
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
|
||||
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек
|
||||
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах
|
||||
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов
|
||||
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке
|
||||
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
|
||||
avoid_private_typedef_functions: true # Избегать приватных typedef-функций
|
||||
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов
|
||||
avoid_returning_this: true # Избегать возврата this
|
||||
cascade_invocations: true # Использовать каскадные вызовы
|
||||
deprecated_consistency: true # Поддерживать согласованность устаревших элементов
|
||||
do_not_use_environment: false # Разрешить использование Environment
|
||||
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
|
||||
no_runtimeType_toString: true # Не использовать runtimeType.toString()
|
||||
one_member_abstracts: false # Разрешать абстрактные классы с одним методом
|
||||
only_throw_errors: true # Выбрасывать только объекты Error
|
||||
parameter_assignments: true # Запрещать присваивание значений параметрам
|
||||
prefer_asserts_with_message: true # Использовать сообщения с assert
|
||||
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
|
||||
prefer_final_in_for_each: true # Использовать final в for-each циклах
|
||||
prefer_final_locals: true # Использовать final для локальных переменных
|
||||
public_member_api_docs: false # Не требовать документацию для всех публичных членов
|
||||
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
|
||||
sort_constructors_first: true # Требовать размещать конструкторы первыми
|
||||
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
|
||||
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
|
||||
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
|
||||
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
|
||||
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
|
||||
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
|
||||
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
|
||||
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
|
||||
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
|
||||
always_use_package_imports: true # Всегда использовать package: импорты
|
||||
# === Именование и стиль ===
|
||||
- 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,4 +1,5 @@
|
||||
library;
|
||||
|
||||
export 'src/app_location_service.dart';
|
||||
export 'src/app_path_provider.dart';
|
||||
export 'src/app_secure_storage.dart';
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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,5 +1,4 @@
|
||||
import 'package:i_app_services/i_app_services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
/// {@template app_path_provider}
|
||||
/// Класс для Аврора реализации сервиса работы с путями
|
||||
@@ -13,6 +12,7 @@ class AppPathProvider implements IPathProvider {
|
||||
|
||||
@override
|
||||
Future<String> getAppDocumentsDirectoryPath() async {
|
||||
return (await getApplicationDocumentsDirectory()).path;
|
||||
// TODO: Реализовать для AuroraOS
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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';
|
||||
|
||||
/// {@template app_secure_storage}
|
||||
@@ -12,7 +10,8 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
/// Принимает:
|
||||
/// - [secretKey] - ключ шифрования данных
|
||||
AppSecureStorage({required this.secretKey}) {
|
||||
FlutterSecureStorageAurora.setSecret(secretKey);
|
||||
// Инициализация Aurora Secure Storage с ключом шифрования
|
||||
// FlutterSecureStorageAurora.setSecret(secretKey);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -20,34 +19,36 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
|
||||
static const name = 'AuroraAppSecureStorage';
|
||||
|
||||
/// Экземпляр хранилища данных
|
||||
final _box = const FlutterSecureStorage();
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await _box.deleteAll();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String key) async {
|
||||
await _box.delete(key: key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> exists(String key) {
|
||||
return _box.containsKey(key: key);
|
||||
// TODO: Реализовать удаление ключа из Aurora Secure Storage
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> read(String key) async {
|
||||
return _box.read(key: key);
|
||||
// TODO: Реализовать чтение значения по ключу из Aurora Secure Storage
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write(String key, String value) async {
|
||||
await _box.write(key: key, value: value);
|
||||
// TODO: Реализовать запись значения по ключу в Aurora Secure Storage
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
String get nameImpl => AppSecureStorage.name;
|
||||
|
||||
@override
|
||||
Future<bool> containsKey(String key) {
|
||||
// TODO: Реализовать проверку наличия ключа в Aurora Secure Storage
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAll() {
|
||||
// TODO: Реализовать удаление всех ключей из Aurora Secure Storage
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
name: app_services
|
||||
description: "Google сервисы для приложения"
|
||||
description: "Аврора ОС сервисы для приложения"
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
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
|
||||
|
||||
# для работы с путями в хранилища
|
||||
path_provider: 2.1.4
|
||||
path_provider_aurora:
|
||||
git:
|
||||
url: https://gitlab.com/omprussia/flutter/packages.git
|
||||
ref: aurora-path_provider_aurora-0.6.0
|
||||
path: packages/path_provider_aurora
|
||||
|
||||
# Обязательные интерфейсы
|
||||
i_app_services:
|
||||
path: ../../i_app_services
|
||||
|
||||
@@ -1,89 +1,135 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Включает правила из:
|
||||
# - package:lints/core.yaml: основные правила критических проблем
|
||||
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
|
||||
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
|
||||
# Персонализированные настройки анализатора и линтера.
|
||||
# Базовый набор flutter_lints + дополнительные ужесточения.
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "android/**"
|
||||
- "assets/**"
|
||||
- "build/**"
|
||||
- "config/**"
|
||||
- "core/**"
|
||||
- "res/**"
|
||||
- "ios/**"
|
||||
- "**/*.g.dart"
|
||||
- "**/*.config.dart"
|
||||
- "**/*.gen.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "**/generated/*"
|
||||
- "**/*.gr.dart"
|
||||
- "**/*.yaml"
|
||||
- "app_services/aurora/**"
|
||||
- "/app_services/aurora/**"
|
||||
- "**/app_services/aurora/**"
|
||||
- "**/*.lock.dart"
|
||||
errors:
|
||||
# Переопределения уровней ошибок (error/warning/info)
|
||||
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
|
||||
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
|
||||
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
|
||||
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
|
||||
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
|
||||
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
|
||||
comment_references: warning # Проверяет корректность ссылок в комментариях
|
||||
always_declare_return_types: error # Требует явного указания возвращаемых типов методов
|
||||
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
|
||||
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 с цветом
|
||||
- "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: true # Требовать размещать обязательные именованные параметры первыми
|
||||
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
|
||||
avoid_catching_errors: true # Избегать перехвата ошибок типа Error
|
||||
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
|
||||
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек
|
||||
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах
|
||||
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов
|
||||
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке
|
||||
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
|
||||
avoid_private_typedef_functions: true # Избегать приватных typedef-функций
|
||||
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов
|
||||
avoid_returning_this: true # Избегать возврата this
|
||||
cascade_invocations: true # Использовать каскадные вызовы
|
||||
deprecated_consistency: true # Поддерживать согласованность устаревших элементов
|
||||
do_not_use_environment: false # Разрешить использование Environment
|
||||
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
|
||||
no_runtimeType_toString: true # Не использовать runtimeType.toString()
|
||||
one_member_abstracts: false # Разрешать абстрактные классы с одним методом
|
||||
only_throw_errors: true # Выбрасывать только объекты Error
|
||||
parameter_assignments: true # Запрещать присваивание значений параметрам
|
||||
prefer_asserts_with_message: true # Использовать сообщения с assert
|
||||
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
|
||||
prefer_final_in_for_each: true # Использовать final в for-each циклах
|
||||
prefer_final_locals: true # Использовать final для локальных переменных
|
||||
public_member_api_docs: false # Не требовать документацию для всех публичных членов
|
||||
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
|
||||
sort_constructors_first: true # Требовать размещать конструкторы первыми
|
||||
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
|
||||
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
|
||||
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
|
||||
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
|
||||
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
|
||||
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
|
||||
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
|
||||
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
|
||||
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
|
||||
always_use_package_imports: true # Всегда использовать package: импорты
|
||||
# === Именование и стиль ===
|
||||
- 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,4 +1,5 @@
|
||||
library;
|
||||
|
||||
export 'src/app_location_service.dart';
|
||||
export 'src/app_path_provider.dart';
|
||||
export 'src/app_secure_storage.dart';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,19 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:i_app_services/i_app_services.dart';
|
||||
|
||||
/// {@template app_secure_storage}
|
||||
/// Класс для Aurora реализации сервис по работе с защищенным хранилищем
|
||||
/// [secretKey] - ключ для шифрования данных, если нужен
|
||||
/// Класс для базовой реализации сервиса работы с защищенным хранилищем.
|
||||
///
|
||||
/// Использует flutter_secure_storage для безопасного хранения данных.
|
||||
/// Поддерживает все основные операции с защищенным хранилищем.
|
||||
/// {@endtemplate}
|
||||
final class AppSecureStorage implements ISecureStorage {
|
||||
/// {@macro app_secure_storage}
|
||||
AppSecureStorage({this.secretKey});
|
||||
|
||||
@override
|
||||
final String? secretKey;
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'BaseAppSecureStorage';
|
||||
|
||||
/// Экземпляр хранилища данных
|
||||
|
||||
@@ -4,7 +4,7 @@ version: 0.0.1
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -14,11 +14,14 @@ dependencies:
|
||||
flutter_secure_storage: 9.2.4
|
||||
|
||||
# Зависимости для сервиса незащищенного хранилища
|
||||
shared_preferences: 2.3.5
|
||||
shared_preferences: 2.5.3
|
||||
|
||||
# для работы с путями в хранилища
|
||||
path_provider: 2.1.5
|
||||
|
||||
# Работа с геолокацией
|
||||
geolocator: 14.0.2
|
||||
|
||||
# Обязательные интерфейсы
|
||||
i_app_services:
|
||||
path: ../../i_app_services
|
||||
|
||||
29
app_services/hms/app_services/.gitignore
vendored
Normal file
29
app_services/hms/app_services/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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/
|
||||
10
app_services/hms/app_services/.metadata
Normal file
10
app_services/hms/app_services/.metadata
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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
app_services/hms/app_services/README.md
Normal file
1
app_services/hms/app_services/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Реализация сервисов для HarmonyOS
|
||||
135
app_services/hms/app_services/analysis_options.yaml
Normal file
135
app_services/hms/app_services/analysis_options.yaml
Normal file
@@ -0,0 +1,135 @@
|
||||
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 # Единообразие именования файлов блоков повышает навигацию
|
||||
|
||||
5
app_services/hms/app_services/lib/app_services.dart
Normal file
5
app_services/hms/app_services/lib/app_services.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
library;
|
||||
|
||||
export 'src/app_location_service.dart';
|
||||
export 'src/app_path_provider.dart';
|
||||
export 'src/app_secure_storage.dart';
|
||||
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
18
app_services/hms/app_services/lib/src/app_path_provider.dart
Normal file
18
app_services/hms/app_services/lib/src/app_path_provider.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
18
app_services/hms/app_services/pubspec.yaml
Normal file
18
app_services/hms/app_services/pubspec.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
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,89 +1,135 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Включает правила из:
|
||||
# - package:lints/core.yaml: основные правила критических проблем
|
||||
# - package:lints/recommended.yaml: рекомендуемые правила для чистого кода
|
||||
# - package:flutter_lints/flutter.yaml: специфичные правила для Flutter
|
||||
# Персонализированные настройки анализатора и линтера.
|
||||
# Базовый набор flutter_lints + дополнительные ужесточения.
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "android/**"
|
||||
- "assets/**"
|
||||
- "build/**"
|
||||
- "config/**"
|
||||
- "core/**"
|
||||
- "res/**"
|
||||
- "ios/**"
|
||||
- "**/*.g.dart"
|
||||
- "**/*.config.dart"
|
||||
- "**/*.gen.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "**/generated/*"
|
||||
- "**/*.gr.dart"
|
||||
- "**/*.yaml"
|
||||
- "app_services/aurora/**"
|
||||
- "/app_services/aurora/**"
|
||||
- "**/app_services/aurora/**"
|
||||
- "**/*.lock.dart"
|
||||
errors:
|
||||
# Переопределения уровней ошибок (error/warning/info)
|
||||
avoid_dynamic_calls: error # Запрещает использование dynamic для вызовов методов
|
||||
avoid_returning_null_for_future: error # Запрещает возврат null вместо Future
|
||||
avoid_slow_async_io: warning # Предупреждает о медленных асинхронных операциях ввода/вывода
|
||||
avoid_type_to_string: warning # Предупреждает о неправильном использовании toString() для типов
|
||||
cancel_subscriptions: error # Требует отмены подписок, предотвращает утечки памяти
|
||||
close_sinks: error # Требует закрытия sink-ов, предотвращает утечки ресурсов
|
||||
comment_references: warning # Проверяет корректность ссылок в комментариях
|
||||
always_declare_return_types: error # Требует явного указания возвращаемых типов методов
|
||||
always_require_non_null_named_parameters: warning # Требует использования @required для ненулевых параметров
|
||||
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 с цветом
|
||||
- "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: true # Требовать размещать обязательные именованные параметры первыми
|
||||
avoid_catches_without_on_clauses: true # Избегать catch без указания типа исключения
|
||||
avoid_catching_errors: true # Избегать перехвата ошибок типа Error
|
||||
avoid_equals_and_hash_code_on_mutable_classes: true # Избегать equals и hashCode в изменяемых классах
|
||||
avoid_escaping_inner_quotes: true # Избегать экранирования внутренних кавычек
|
||||
avoid_field_initializers_in_const_classes: true # Избегать инициализаторов полей в константных классах
|
||||
avoid_implementing_value_types: true # Избегать реализации интерфейсов значимых типов
|
||||
avoid_multiple_declarations_per_line: false # Разрешать несколько объявлений в одной строке
|
||||
avoid_positional_boolean_parameters: true # Избегать позиционных булевых параметров
|
||||
avoid_private_typedef_functions: true # Избегать приватных typedef-функций
|
||||
avoid_redundant_argument_values: true # Избегать избыточных значений аргументов
|
||||
avoid_returning_this: true # Избегать возврата this
|
||||
cascade_invocations: true # Использовать каскадные вызовы
|
||||
deprecated_consistency: true # Поддерживать согласованность устаревших элементов
|
||||
do_not_use_environment: false # Разрешить использование Environment
|
||||
leading_newlines_in_multiline_strings: true # Начинать многострочные строки с новой строки
|
||||
no_runtimeType_toString: true # Не использовать runtimeType.toString()
|
||||
one_member_abstracts: false # Разрешать абстрактные классы с одним методом
|
||||
only_throw_errors: true # Выбрасывать только объекты Error
|
||||
parameter_assignments: true # Запрещать присваивание значений параметрам
|
||||
prefer_asserts_with_message: true # Использовать сообщения с assert
|
||||
prefer_constructors_over_static_methods: true # Предпочитать конструкторы статическим методам
|
||||
prefer_final_in_for_each: true # Использовать final в for-each циклах
|
||||
prefer_final_locals: true # Использовать final для локальных переменных
|
||||
public_member_api_docs: false # Не требовать документацию для всех публичных членов
|
||||
require_trailing_commas: true # Требовать запятые в конце для улучшения форматирования
|
||||
sort_constructors_first: true # Требовать размещать конструкторы первыми
|
||||
sort_pub_dependencies: false # Не требовать сортировки зависимостей в pubspec
|
||||
sort_unnamed_constructors_first: false # Не требовать размещать безымянные конструкторы первыми
|
||||
use_is_even_rather_than_modulo: true # Использовать isEven вместо % 2 == 0
|
||||
use_late_for_private_fields_and_variables: false # Не требовать late для приватных полей
|
||||
use_setters_to_change_properties: true # Использовать сеттеры для изменения свойств
|
||||
use_string_buffers: true # Использовать StringBuffer для сложной конкатенации
|
||||
use_to_and_as_if_applicable: true # Использовать методы to и as при применимости
|
||||
no_literal_bool_comparisons: true # Запрещать сравнения с литералами true/false
|
||||
use_key_in_widget_constructors: true # Обязательное указание ключа для stateful/stateless widgets
|
||||
always_use_package_imports: true # Всегда использовать package: импорты
|
||||
# === Именование и стиль ===
|
||||
- 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,4 +1,5 @@
|
||||
library;
|
||||
|
||||
export 'src/i_location_service.dart';
|
||||
export 'src/i_path_provider.dart';
|
||||
export 'src/i_secure_storage.dart';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/// {@template i_location_service}
|
||||
/// Интерфейс для работы с геопозицией пользователя
|
||||
/// {@endtemplate}
|
||||
abstract interface class ILocationService {
|
||||
static const name = 'ILocationService';
|
||||
|
||||
/// Метод для получения координат пользователя
|
||||
Future<dynamic> getCurrentPosition();
|
||||
}
|
||||
@@ -4,6 +4,6 @@ abstract interface class IPathProvider {
|
||||
/// Наименования интерфейса
|
||||
static const name = 'IPathProvider';
|
||||
|
||||
/// Получение path на внутренне хранилище приложения
|
||||
/// Получение path на внутреннее хранилище приложения
|
||||
Future<String?> getAppDocumentsDirectoryPath();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
name: i_app_services
|
||||
description: "Хранит в себе все интерфейсы для реализации общих сервисов"
|
||||
description: "Хранит в себе все интерфейсы для реализации сервисов"
|
||||
version: 0.0.1
|
||||
publish_to: "none"
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-dir: lib/l10n/gen
|
||||
output-localization-file: app_localizations.dart
|
||||
synthetic-package: false
|
||||
output-localization-file: app_localizations.dart
|
||||
168
lib/app/app.dart
168
lib/app/app.dart
@@ -1,33 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/app_providers.dart';
|
||||
import 'package:friflex_starter/app/depends_providers.dart';
|
||||
import 'package:friflex_starter/app/theme/app_theme.dart';
|
||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
|
||||
import 'package:friflex_starter/features/error/error_screen.dart';
|
||||
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:friflex_starter/features/update/update_routes.dart';
|
||||
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:friflex_starter/router/app_router.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Класс приложения
|
||||
/// {@template app}
|
||||
/// Главный виджет приложения, управляющий инициализацией зависимостей
|
||||
/// и отображением основного интерфейса приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Инициализацию зависимостей приложения
|
||||
/// - Отображение экрана загрузки во время инициализации
|
||||
/// - Обработку ошибок инициализации
|
||||
/// - Настройку провайдеров для темы и локализации
|
||||
/// {@endtemplate}
|
||||
class App extends StatefulWidget {
|
||||
const App({required this.router, required this.initDependencies, super.key});
|
||||
/// {@macro app}
|
||||
const App({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
|
||||
@@ -46,43 +67,30 @@ class _AppState extends State<App> {
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
return switch (snapshot.connectionState) {
|
||||
// Если состояние не определено, ожидается или активно, то отображаем экран загрузки
|
||||
ConnectionState.none ||
|
||||
ConnectionState.waiting ||
|
||||
ConnectionState.active => const SplashScreen(),
|
||||
ConnectionState.done =>
|
||||
// Если данные получены и не равны null, то отображаем внутренний виджет приложения
|
||||
// Иначе отображаем экран ошибки
|
||||
(snapshot.hasData && snapshot.data != null)
|
||||
? _AppInternal(diContainer: snapshot.data!)
|
||||
: ErrorScreen(
|
||||
error: snapshot.error,
|
||||
stackTrace: snapshot.stackTrace,
|
||||
onRetry: _retryInit,
|
||||
),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Метод для перезапуска инициализации зависимостей
|
||||
/// Вызывается при ошибках инициализации для повторной попытки
|
||||
void _retryInit() {
|
||||
setState(() {
|
||||
_initFuture = widget.initDependencies();
|
||||
@@ -90,21 +98,83 @@ class _AppState extends State<App> {
|
||||
}
|
||||
}
|
||||
|
||||
class _App extends StatelessWidget {
|
||||
const _App({required this.router});
|
||||
/// {@template app_internal}
|
||||
/// Внутренний виджет приложения, отображающий основной интерфейс
|
||||
/// после успешной инициализации зависимостей.
|
||||
///
|
||||
/// Настраивает MaterialApp с роутером, темами и локализацией.
|
||||
/// {@endtemplate}
|
||||
class _AppInternal extends StatefulWidget {
|
||||
/// {@macro app_internal}
|
||||
const _AppInternal({
|
||||
required this.diContainer,
|
||||
@visibleForTesting this.mockRouter, // ignore: unused_element_parameter
|
||||
});
|
||||
|
||||
final GoRouter router;
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
/// Роутер приложения для навигации для тестирования
|
||||
final GoRouter? mockRouter;
|
||||
|
||||
@override
|
||||
State<_AppInternal> createState() => _AppInternalState();
|
||||
}
|
||||
|
||||
class _AppInternalState extends State<_AppInternal> {
|
||||
/// Роутер приложения для навигации
|
||||
late final GoRouter router;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router =
|
||||
widget.mockRouter ??
|
||||
AppRouter.createRouter(widget.diContainer.debugService);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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,
|
||||
return DependsProviders(
|
||||
diContainer: widget.diContainer,
|
||||
child: BlocConsumer<UpdateCubit, UpdateState>(
|
||||
listener: (context, state) {
|
||||
if (state is UpdateSuccessState &&
|
||||
state.updateInfo.updateType == .hard &&
|
||||
context.mounted) {
|
||||
router.goNamed(UpdateRoutes.hardUpdateScreenName);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
// Если состояние загрузки, то отображаем экран загрузки
|
||||
if (state is UpdateLoadingState) {
|
||||
return const SplashScreen();
|
||||
}
|
||||
return ThemeConsumer(
|
||||
builder: () => MediaQuery(
|
||||
key: const ValueKey('prevent_rebuild'),
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
|
||||
child: MaterialApp.router(
|
||||
routerConfig: router,
|
||||
darkTheme: AppTheme.dark,
|
||||
theme: AppTheme.light,
|
||||
themeMode: context.theme.themeMode,
|
||||
locale: context.localization.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,47 @@
|
||||
import 'package:envied/envied.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_app_config.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
|
||||
part 'app_config.g.dart';
|
||||
|
||||
/// Класс для реализации конфигурации с моковыми данными
|
||||
/// {@template i_app_config}
|
||||
/// Интерфейс для конфигурации приложения.
|
||||
///
|
||||
/// Определяет обязательные параметры для всех реализаций конфигурации:
|
||||
/// - Наименование конфигурации
|
||||
/// - Базовый URL для API
|
||||
/// - Тип окружения (dev, prod, stage)
|
||||
/// - Секретный ключ для шифрования данных
|
||||
/// {@endtemplate}
|
||||
abstract interface class IAppConfig {
|
||||
/// {@macro i_app_config}
|
||||
IAppConfig();
|
||||
|
||||
/// Наименование сервиса конфигурации
|
||||
String get name => 'IAppConfig';
|
||||
|
||||
/// Основной адрес для запросов к API
|
||||
String get baseUrl;
|
||||
|
||||
/// Тип окружения (dev, prod, stage)
|
||||
AppEnv get env;
|
||||
|
||||
/// Секретный ключ для шифрования данных
|
||||
String get secretKey;
|
||||
}
|
||||
|
||||
/// {@template app_config_dev}
|
||||
/// Класс для реализации конфигурации приложения в режиме разработки.
|
||||
///
|
||||
/// Использует переменные окружения из файла env/dev.env.
|
||||
/// Предназначен для локальной разработки и тестирования.
|
||||
/// {@endtemplate}
|
||||
@Envied(name: 'Dev', path: 'env/dev.env')
|
||||
class AppConfigDev implements IAppConfig {
|
||||
/// {@macro app_config_dev}
|
||||
AppConfigDev();
|
||||
|
||||
@override
|
||||
AppEnv get env => AppEnv.dev;
|
||||
AppEnv get env => .dev;
|
||||
|
||||
@override
|
||||
String get name => 'AppConfigDev';
|
||||
@@ -22,11 +55,19 @@ class AppConfigDev implements IAppConfig {
|
||||
final String secretKey = _Dev.secretKey;
|
||||
}
|
||||
|
||||
/// Класс для реализации конфигурации с продакшн данными
|
||||
/// {@template app_config_prod}
|
||||
/// Класс для реализации конфигурации приложения в продакшн режиме.
|
||||
///
|
||||
/// Использует переменные окружения из файла env/prod.env.
|
||||
/// Предназначен для финальной сборки приложения.
|
||||
/// {@endtemplate}
|
||||
@Envied(name: 'Prod', path: 'env/prod.env')
|
||||
class AppConfigProd implements IAppConfig {
|
||||
/// {@macro app_config_prod}
|
||||
AppConfigProd();
|
||||
|
||||
@override
|
||||
AppEnv get env => AppEnv.prod;
|
||||
AppEnv get env => .prod;
|
||||
|
||||
@override
|
||||
String get name => 'AppConfigProd';
|
||||
@@ -40,11 +81,19 @@ class AppConfigProd implements IAppConfig {
|
||||
final String secretKey = _Prod.secretKey;
|
||||
}
|
||||
|
||||
/// Класс для реализации конфигурации с стейдж данными
|
||||
/// {@template app_config_stage}
|
||||
/// Класс для реализации конфигурации приложения в стейдж режиме.
|
||||
///
|
||||
/// Использует переменные окружения из файла env/stage.env.
|
||||
/// Предназначен для тестирования в среде, близкой к продакшн.
|
||||
/// {@endtemplate}
|
||||
@Envied(name: 'Stage', path: 'env/stage.env')
|
||||
class AppConfigStage implements IAppConfig {
|
||||
/// {@macro app_config_stage}
|
||||
AppConfigStage();
|
||||
|
||||
@override
|
||||
AppEnv get env => AppEnv.stage;
|
||||
AppEnv get env => .stage;
|
||||
|
||||
@override
|
||||
String get name => 'AppConfigStage';
|
||||
|
||||
@@ -13,22 +13,24 @@ final class _Dev {
|
||||
static const String baseUrl = 'https://dev';
|
||||
|
||||
static const List<int> _enviedkeysecretKey = <int>[
|
||||
1144186709,
|
||||
921404830,
|
||||
4081271781,
|
||||
1250675737,
|
||||
1700106436,
|
||||
3701613773,
|
||||
];
|
||||
|
||||
static const List<int> _envieddatasecretKey = <int>[
|
||||
1144186673,
|
||||
921404923,
|
||||
4081271699,
|
||||
1250675837,
|
||||
1700106401,
|
||||
3701613755,
|
||||
];
|
||||
|
||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||
static final String secretKey = String.fromCharCodes(
|
||||
List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
||||
);
|
||||
}
|
||||
|
||||
// coverage:ignore-file
|
||||
@@ -36,60 +38,64 @@ final class _Dev {
|
||||
// generated_from: env/prod.env
|
||||
final class _Prod {
|
||||
static const List<int> _enviedkeybaseUrl = <int>[
|
||||
3830062036,
|
||||
1024953349,
|
||||
2723997296,
|
||||
2959773775,
|
||||
4255295633,
|
||||
114701674,
|
||||
1920572043,
|
||||
3423730200,
|
||||
3647804248,
|
||||
2815792265,
|
||||
3038381766,
|
||||
655048609,
|
||||
2313418965,
|
||||
1278864221,
|
||||
2117667009,
|
||||
1514310825,
|
||||
996679495,
|
||||
1388625655,
|
||||
2368060187,
|
||||
1047428716,
|
||||
502950869,
|
||||
2499923893,
|
||||
963407594,
|
||||
1679400929,
|
||||
];
|
||||
|
||||
static const List<int> _envieddatabaseUrl = <int>[
|
||||
3830062012,
|
||||
1024953457,
|
||||
2723997188,
|
||||
2959773759,
|
||||
4255295714,
|
||||
114701648,
|
||||
1920572068,
|
||||
3423730231,
|
||||
3647804200,
|
||||
2815792379,
|
||||
3038381737,
|
||||
655048645,
|
||||
2313418941,
|
||||
1278864169,
|
||||
2117666997,
|
||||
1514310873,
|
||||
996679476,
|
||||
1388625613,
|
||||
2368060212,
|
||||
1047428675,
|
||||
502950821,
|
||||
2499923911,
|
||||
963407493,
|
||||
1679400837,
|
||||
];
|
||||
|
||||
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
||||
_envieddatabaseUrl.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
||||
static final String baseUrl = String.fromCharCodes(
|
||||
List<int>.generate(
|
||||
_envieddatabaseUrl.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
||||
);
|
||||
|
||||
static const List<int> _enviedkeysecretKey = <int>[
|
||||
359753139,
|
||||
3208048313,
|
||||
1722903860,
|
||||
3044498179,
|
||||
1650398676,
|
||||
1481444979,
|
||||
21401329,
|
||||
1103991377,
|
||||
];
|
||||
|
||||
static const List<int> _envieddatasecretKey = <int>[
|
||||
359753155,
|
||||
3208048331,
|
||||
1722903899,
|
||||
3044498279,
|
||||
1650398628,
|
||||
1481444865,
|
||||
21401246,
|
||||
1103991349,
|
||||
];
|
||||
|
||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||
static final String secretKey = String.fromCharCodes(
|
||||
List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
||||
);
|
||||
}
|
||||
|
||||
// coverage:ignore-file
|
||||
@@ -97,62 +103,66 @@ final class _Prod {
|
||||
// generated_from: env/stage.env
|
||||
final class _Stage {
|
||||
static const List<int> _enviedkeybaseUrl = <int>[
|
||||
2791397647,
|
||||
1739207173,
|
||||
306419752,
|
||||
1371918084,
|
||||
4062400488,
|
||||
3004897854,
|
||||
2820011348,
|
||||
1751321626,
|
||||
3103957517,
|
||||
2168627914,
|
||||
1003673382,
|
||||
1168070657,
|
||||
568662299,
|
||||
1623636460,
|
||||
1720327728,
|
||||
1318998227,
|
||||
1880230818,
|
||||
1752001284,
|
||||
726589281,
|
||||
1688681936,
|
||||
2751223200,
|
||||
1403987498,
|
||||
1289212622,
|
||||
1492662645,
|
||||
2947900480,
|
||||
809806156,
|
||||
];
|
||||
|
||||
static const List<int> _envieddatabaseUrl = <int>[
|
||||
2791397735,
|
||||
1739207281,
|
||||
306419804,
|
||||
1371918196,
|
||||
4062400411,
|
||||
3004897796,
|
||||
2820011387,
|
||||
1751321653,
|
||||
3103957630,
|
||||
2168627902,
|
||||
1003673415,
|
||||
1168070758,
|
||||
568662398,
|
||||
1623636356,
|
||||
1720327748,
|
||||
1318998183,
|
||||
1880230866,
|
||||
1752001399,
|
||||
726589275,
|
||||
1688681983,
|
||||
2751223183,
|
||||
1403987545,
|
||||
1289212602,
|
||||
1492662548,
|
||||
2947900455,
|
||||
809806121,
|
||||
];
|
||||
|
||||
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
||||
_envieddatabaseUrl.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
||||
static final String baseUrl = String.fromCharCodes(
|
||||
List<int>.generate(
|
||||
_envieddatabaseUrl.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
||||
);
|
||||
|
||||
static const List<int> _enviedkeysecretKey = <int>[
|
||||
2132342089,
|
||||
2579069434,
|
||||
3904165526,
|
||||
3391356107,
|
||||
1192880530,
|
||||
5890476,
|
||||
3047051242,
|
||||
2874959210,
|
||||
758075519,
|
||||
3738110555,
|
||||
];
|
||||
|
||||
static const List<int> _envieddatasecretKey = <int>[
|
||||
2132342074,
|
||||
2579069326,
|
||||
3904165623,
|
||||
3391356076,
|
||||
1192880631,
|
||||
5890527,
|
||||
3047051166,
|
||||
2874959115,
|
||||
758075416,
|
||||
3738110526,
|
||||
];
|
||||
|
||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||
static final String secretKey = String.fromCharCodes(
|
||||
List<int>.generate(
|
||||
_envieddatasecretKey.length,
|
||||
(int i) => i,
|
||||
growable: false,
|
||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
|
||||
/// Класс для описания интерфейса конфигурации
|
||||
abstract interface class IAppConfig {
|
||||
/// Наименование сервиса
|
||||
String get name => 'IAppConfig';
|
||||
|
||||
/// Основной адрес для запросов к API
|
||||
String get baseUrl;
|
||||
|
||||
/// Тип окружения
|
||||
AppEnv get env;
|
||||
|
||||
/// Секретный ключ для шифрования данных
|
||||
String get secretKey;
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для внедрения глобальных зависимостей
|
||||
@@ -19,6 +23,21 @@ final class DependsProviders extends StatelessWidget {
|
||||
providers: [
|
||||
// Сюда добавляем глобальные блоки, inherited и т.д.
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
BlocProvider(
|
||||
create: (_) {
|
||||
final updateCubit = UpdateCubit(
|
||||
diContainer.repositories.updatesRepository,
|
||||
);
|
||||
unawaited(
|
||||
updateCubit.checkForUpdates(
|
||||
versionCode:
|
||||
'1.0.0', // TODO(yura): заменить на получение из diContainer
|
||||
platform: 'android',
|
||||
),
|
||||
);
|
||||
return updateCubit;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_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';
|
||||
|
||||
@@ -46,20 +46,20 @@ class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
|
||||
final Color infoSnackbarBackground;
|
||||
|
||||
/// Цвета светлой темы
|
||||
static final AppColors light = AppColors(
|
||||
static const AppColors light = AppColors(
|
||||
testColor: Colors.red,
|
||||
errorSnackbarBackground: const Color(0xFFD24720),
|
||||
successSnackbarBackground: const Color(0xFF6FB62C),
|
||||
infoSnackbarBackground: const Color.fromARGB(255, 220, 108, 77),
|
||||
itemTextColor: const Color(0xFFFAF3EB),
|
||||
errorSnackbarBackground: Color(0xFFD24720),
|
||||
successSnackbarBackground: Color(0xFF6FB62C),
|
||||
infoSnackbarBackground: .fromARGB(255, 220, 108, 77),
|
||||
itemTextColor: Color(0xFFFAF3EB),
|
||||
);
|
||||
|
||||
/// Цвета тёмной темы
|
||||
static final AppColors dark = AppColors(
|
||||
static const AppColors dark = AppColors(
|
||||
testColor: Colors.green,
|
||||
errorSnackbarBackground: const Color(0xFF638B8B),
|
||||
successSnackbarBackground: const Color(0xFF93C499),
|
||||
infoSnackbarBackground: const Color.fromARGB(255, 35, 147, 178),
|
||||
errorSnackbarBackground: Color(0xFF638B8B),
|
||||
successSnackbarBackground: Color(0xFF93C499),
|
||||
infoSnackbarBackground: .fromARGB(255, 35, 147, 178),
|
||||
itemTextColor: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом темы
|
||||
typedef ThemeBuilder = Widget Function();
|
||||
|
||||
/// Виджет для подписки на изменение темы приложения
|
||||
/// {@template theme_consumer}
|
||||
/// Виджет для подписки на изменения темы приложения.
|
||||
///
|
||||
/// Автоматически перестраивает дочерние виджеты при изменении темы,
|
||||
/// обеспечивая реактивность интерфейса к изменениям настроек темы.
|
||||
/// {@endtemplate}
|
||||
class ThemeConsumer extends StatelessWidget {
|
||||
/// {@macro theme_consumer}
|
||||
const ThemeConsumer({required this.builder, super.key});
|
||||
|
||||
/// Функция для построения виджета с учетом текущей темы
|
||||
final ThemeBuilder builder;
|
||||
|
||||
@override
|
||||
@@ -19,12 +27,29 @@ class ThemeConsumer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Класс для управления темой приложения
|
||||
/// {@template theme_notifier}
|
||||
/// Класс для управления темой приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Хранение текущего режима темы (светлая/темная/системная)
|
||||
/// - Уведомление подписчиков об изменениях темы
|
||||
/// - Переключение между режимами темы
|
||||
/// {@endtemplate}
|
||||
final class ThemeNotifier extends ChangeNotifier {
|
||||
/// {@macro theme_notifier}
|
||||
ThemeNotifier();
|
||||
|
||||
/// Текущий режим темы приложения
|
||||
/// По умолчанию используется системная тема
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
|
||||
/// Получение текущего режима темы
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
|
||||
/// Метод для переключения темы приложения.
|
||||
///
|
||||
/// Переключает между светлой и темной темой.
|
||||
/// Если текущая тема светлая, переключает на темную и наоборот.
|
||||
void changeTheme() {
|
||||
_themeMode = _themeMode == ThemeMode.light
|
||||
? ThemeMode.dark
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// {@template h_box}
|
||||
/// HBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// Виджет для создания вертикального отступа.
|
||||
///
|
||||
/// Надстройка над SizedBox, предназначенная для создания
|
||||
/// отступов по вертикали с более понятным названием.
|
||||
/// {@endtemplate}
|
||||
class HBox extends SizedBox {
|
||||
/// {@macro h_box}
|
||||
@@ -9,7 +12,10 @@ class HBox extends SizedBox {
|
||||
}
|
||||
|
||||
/// {@template w_box}
|
||||
/// WBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// Виджет для создания горизонтального отступа.
|
||||
///
|
||||
/// Надстройка над SizedBox, предназначенная для создания
|
||||
/// отступов по горизонтали с более понятным названием.
|
||||
/// {@endtemplate}
|
||||
class WBox extends SizedBox {
|
||||
/// {@macro w_box}
|
||||
|
||||
@@ -63,7 +63,7 @@ class AppSnackBar extends StatefulWidget {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
type: TypeSnackBar.error,
|
||||
type: .error,
|
||||
displayDuration: displayDuration,
|
||||
);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class AppSnackBar extends StatefulWidget {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
type: TypeSnackBar.info,
|
||||
type: .info,
|
||||
displayDuration: displayDuration,
|
||||
);
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class AppSnackBar extends StatefulWidget {
|
||||
_show(
|
||||
context: context,
|
||||
message: message,
|
||||
type: TypeSnackBar.success,
|
||||
type: .success,
|
||||
displayDuration: displayDuration,
|
||||
);
|
||||
}
|
||||
@@ -180,11 +180,11 @@ class _AppSnackBarState extends State<AppSnackBar>
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
|
||||
);
|
||||
|
||||
_animationController.forward();
|
||||
unawaited(_animationController.forward());
|
||||
}
|
||||
|
||||
/// Запуск таймера для автоматического закрытия снекбара
|
||||
/// Таймер срабатывает по истечении [widget.displayDuration]
|
||||
/// Таймер срабатывает по истечении widget.displayDuration
|
||||
/// и вызывает метод [_dismiss] для закрытия снекбара
|
||||
void _startDismissTimer() {
|
||||
_dismissTimer = Timer(widget.displayDuration, _dismiss);
|
||||
@@ -192,17 +192,19 @@ class _AppSnackBarState extends State<AppSnackBar>
|
||||
|
||||
/// Закрытие снекбара
|
||||
/// Отменяет таймер, если он существует, и запускает обратную анимацию
|
||||
/// После завершения анимации вызывает функцию [widget.onDismiss], если она задана
|
||||
/// После завершения анимации вызывает функцию widget.onDismiss, если она задана
|
||||
/// Если виджет не смонтирован, ничего не делает
|
||||
void _dismiss() {
|
||||
if (!mounted) return;
|
||||
|
||||
_dismissTimer?.cancel();
|
||||
_animationController.reverse().then((_) {
|
||||
if (mounted) {
|
||||
widget.onDismiss?.call();
|
||||
}
|
||||
});
|
||||
unawaited(
|
||||
_animationController.reverse().then((_) {
|
||||
if (mounted) {
|
||||
widget.onDismiss?.call();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -243,7 +245,7 @@ class _AppSnackBarState extends State<AppSnackBar>
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.message,
|
||||
style: TextStyle(color: Colors.white),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -264,9 +266,9 @@ class _AppSnackBarState extends State<AppSnackBar>
|
||||
/// [TypeSnackBar.error] - цвет ошибки
|
||||
Color _getBackgroundColor(TypeSnackBar type) {
|
||||
return switch (type) {
|
||||
TypeSnackBar.success => context.appColors.successSnackbarBackground,
|
||||
TypeSnackBar.error => context.appColors.errorSnackbarBackground,
|
||||
TypeSnackBar.info => context.appColors.infoSnackbarBackground,
|
||||
.success => context.appColors.successSnackbarBackground,
|
||||
.error => context.appColors.errorSnackbarBackground,
|
||||
.info => context.appColors.infoSnackbarBackground,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -288,13 +290,21 @@ class _Icon extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (type) {
|
||||
TypeSnackBar.success => Icon(
|
||||
.success => const Icon(
|
||||
Icons.check_circle,
|
||||
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),
|
||||
.error => const Icon(
|
||||
Icons.error,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.info => const Icon(
|
||||
Icons.info,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
/// Миксин репозитория в приложении.
|
||||
/// Каждый интерфейс репозитория в приложении должен подмешивать текущий класс
|
||||
/// {@template di_base_repo}
|
||||
/// Базовый миксин для всех репозиториев в приложении.
|
||||
///
|
||||
/// Предоставляет общую функциональность для всех репозиториев:
|
||||
/// - Уникальное наименование репозитория
|
||||
/// - Базовую структуру для DI контейнера
|
||||
///
|
||||
/// Каждый репозиторий в приложении должен использовать этот миксин
|
||||
/// для обеспечения совместимости с системой зависимостей.
|
||||
/// {@endtemplate}
|
||||
mixin class DiBaseRepo {
|
||||
/// Наименование репозитория
|
||||
/// {@macro di_base_repo}
|
||||
DiBaseRepo();
|
||||
|
||||
/// Наименование репозитория для идентификации в DI контейнере
|
||||
String get name => 'DiBaseRepo';
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:friflex_starter/app/app_config/app_config.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_app_config.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
@@ -42,9 +41,9 @@ final class DiContainer {
|
||||
}) async {
|
||||
// Инициализация конфигурации приложения
|
||||
appConfig = switch (env) {
|
||||
AppEnv.dev => AppConfigDev(),
|
||||
AppEnv.prod => AppConfigProd(),
|
||||
AppEnv.stage => AppConfigStage(),
|
||||
.dev => AppConfigDev(),
|
||||
.prod => AppConfigProd(),
|
||||
.stage => AppConfigStage(),
|
||||
};
|
||||
|
||||
// Инициализация HTTP клиента
|
||||
|
||||
@@ -11,26 +11,40 @@ import 'package:friflex_starter/features/main/domain/repository/i_main_repositor
|
||||
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/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
|
||||
/// сборке, необходимо в этом списке указать название мокового репозитория,
|
||||
/// обращаясь к соответствующему полю name.
|
||||
/// сборке, необходимо в этом списке указать тип интерфейса репозитория
|
||||
///
|
||||
/// Пример:
|
||||
/// ```
|
||||
/// [ AuthCheckRepositoryMock().name, ]
|
||||
/// <Type>{ IUpdateRepository }
|
||||
/// ```
|
||||
final List<String> _mockReposToSwitch = [];
|
||||
const _mockReposToSwitch = <Type>{IUpdateRepository};
|
||||
|
||||
/// Класс для инициализации репозиториев в приложении
|
||||
/// {@template di_repositories}
|
||||
/// Класс для инициализации и управления репозиториями приложения.
|
||||
///
|
||||
/// По умолчанию репозиторию присваивается моковая реализация.
|
||||
/// В зависимости от окружения либо выполняется подмена репозиторий,
|
||||
/// либо используется моковый.
|
||||
/// Отвечает за:
|
||||
/// - Инициализацию репозиториев для работы с данными
|
||||
/// - Автоматическое переключение между моковыми и реальными репозиториями
|
||||
/// - Уведомление о прогрессе инициализации
|
||||
/// - Обработку ошибок инициализации репозиториев
|
||||
///
|
||||
/// Стратегия инициализации по окружениям:
|
||||
/// - dev: всегда используются моковые репозитории
|
||||
/// - prod: всегда используются реальные репозитории
|
||||
/// - stage: используются моковые репозитории из списка _mockReposToSwitch
|
||||
/// {@endtemplate}
|
||||
final class DiRepositories {
|
||||
/// {@macro di_repositories}
|
||||
DiRepositories();
|
||||
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
late final IAuthRepository authRepository;
|
||||
|
||||
@@ -40,81 +54,77 @@ final class DiRepositories {
|
||||
/// Интерфейс для работы с репозиторием профиля
|
||||
late final IProfileRepository profileRepository;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении
|
||||
/// Интерфейс для работы с репозиторием обновлений
|
||||
late final IUpdateRepository updatesRepository;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [diContainer] - контейнер зависимостей
|
||||
/// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации
|
||||
/// - [diContainer] - контейнер зависимостей с конфигурацией приложения
|
||||
/// - [onError] - обратный вызов для обработки ошибок инициализации
|
||||
///
|
||||
/// Последовательность инициализации:
|
||||
/// 1. Инициализация репозитория авторизации
|
||||
/// 2. Инициализация репозитория главного сервиса
|
||||
/// 3. Инициализация репозитория профиля
|
||||
void init({
|
||||
required OnProgress onProgress,
|
||||
required OnError onError,
|
||||
required DiContainer diContainer,
|
||||
}) {
|
||||
try {
|
||||
//Инициализация репозитория авторизации
|
||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
onProgress(authRepository.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации репозитория IAuthRepository',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
onProgress('Начинаем инициализацию репозиториев...');
|
||||
|
||||
try {
|
||||
// Инициализация репозитория сервиса управления токеном доступа
|
||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||
mockFactory: MainMockRepository.new,
|
||||
mainFactory: () => MainRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
onProgress(mainRepository.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации репозитория IMainRepository',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
// Инициализация репозитория обновлений
|
||||
updatesRepository = _lazyInitRepo<IUpdateRepository>(
|
||||
mockFactory: UpdateMockRepository.new,
|
||||
mainFactory: UpdateRepository.new,
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
try {
|
||||
// Инициализация репозитория профиля
|
||||
profileRepository = _lazyInitRepo<IProfileRepository>(
|
||||
mockFactory: ProfileMockRepository.new,
|
||||
mainFactory: () => ProfileRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
// Инициализация репозитория авторизации
|
||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
onProgress(profileRepository.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации репозитория IProfileRepository',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
// Инициализация репозитория сервиса управления токеном доступа
|
||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||
mockFactory: MainMockRepository.new,
|
||||
mainFactory: () => MainRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
// Инициализация репозитория профиля
|
||||
profileRepository = _lazyInitRepo<IProfileRepository>(
|
||||
mockFactory: ProfileMockRepository.new,
|
||||
mainFactory: () => ProfileRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
onProgress(
|
||||
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
||||
@@ -125,26 +135,39 @@ final class DiRepositories {
|
||||
/// В зависимости от окружения инициализируется моковый или сетевой репозиторий.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
||||
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [mockFactory] - функция-фабрика для инициализации мокового репозитория
|
||||
/// - [mainFactory] - функция-фабрика для инициализации основного репозитория
|
||||
/// - [onProgress] - обратный вызов для уведомления о прогрессе
|
||||
/// - [onError] - обратный вызов для обработки ошибок инициализации
|
||||
/// - [environment] - окружение приложения для определения стратегии инициализации
|
||||
///
|
||||
/// Возвращает:
|
||||
/// - Экземпляр репозитория в зависимости от окружения
|
||||
///
|
||||
/// Throws:
|
||||
/// - Перебрасывает исключение, если инициализация репозитория завершилась с ошибкой
|
||||
T _lazyInitRepo<T extends DiBaseRepo>({
|
||||
required AppEnv environment,
|
||||
required T Function() mainFactory,
|
||||
required T Function() mockFactory,
|
||||
required OnProgress onProgress,
|
||||
required OnError onError,
|
||||
}) {
|
||||
final mockRepo = mockFactory();
|
||||
final mainRepo = mainFactory();
|
||||
try {
|
||||
final repo = switch (environment) {
|
||||
.dev => mockFactory(),
|
||||
.prod => mainFactory(),
|
||||
.stage =>
|
||||
_mockReposToSwitch.contains(T) ? mockFactory() : mainFactory(),
|
||||
};
|
||||
|
||||
final repo = switch (environment) {
|
||||
AppEnv.dev => mockRepo,
|
||||
AppEnv.prod => mainRepo,
|
||||
AppEnv.stage =>
|
||||
_mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
|
||||
};
|
||||
|
||||
onProgress(repo.name);
|
||||
return repo;
|
||||
// throw Exception('Тестовая - ошибка инициализации репозитория $T');
|
||||
onProgress(repo.name);
|
||||
return repo;
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError('Ошибка инициализации репозитория $T', error, stackTrace);
|
||||
// Перебрасываем исключение дальше, чтобы не скрыть ошибку инициализации
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,45 @@ import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||
import 'package:i_app_services/i_app_services.dart';
|
||||
|
||||
/// Класс для инициализации сервисов
|
||||
/// {@template di_services}
|
||||
/// Класс для инициализации и управления сервисами приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Инициализацию сервисов для работы с путями
|
||||
/// - Инициализацию сервисов для работы с защищенным хранилищем
|
||||
/// - Уведомление о прогрессе инициализации
|
||||
/// - Обработку ошибок инициализации сервисов
|
||||
/// {@endtemplate}
|
||||
final class DiServices {
|
||||
/// Сервис для работы с путями
|
||||
/// {@macro di_services}
|
||||
DiServices();
|
||||
|
||||
/// Сервис для работы с путями файловой системы
|
||||
late final IPathProvider pathProvider;
|
||||
|
||||
/// Сервис для работы с локальным хранилищем
|
||||
/// Сервис для работы с защищенным локальным хранилищем
|
||||
late final ISecureStorage secureStorage;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении
|
||||
/// Сервис для работы с геолокацией
|
||||
late final ILocationService locationService;
|
||||
|
||||
/// Метод для инициализации сервисов в приложении.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [diContainer] - контейнер зависимостей
|
||||
/// - [onError] - обратный вызов при ошибке
|
||||
/// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации
|
||||
/// - [diContainer] - контейнер зависимостей с конфигурацией приложения
|
||||
/// - [onError] - обратный вызов для обработки ошибок инициализации
|
||||
///
|
||||
/// Последовательность инициализации:
|
||||
/// 1. Инициализация сервиса путей (AppPathProvider)
|
||||
/// 2. Инициализация защищенного хранилища (AppSecureStorage)
|
||||
void init({
|
||||
required OnProgress onProgress,
|
||||
required OnError onError,
|
||||
required DiContainer diContainer,
|
||||
}) {
|
||||
try {
|
||||
// throw Exception('Тестовая - ошибка инициализации сервиса путей');
|
||||
pathProvider = const AppPathProvider();
|
||||
onProgress(AppPathProvider.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
@@ -37,6 +56,17 @@ final class DiServices {
|
||||
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
||||
}
|
||||
|
||||
try {
|
||||
locationService = const AppLocationService();
|
||||
onProgress(AppLocationService.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации ${ILocationService.name}',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
onProgress('Инициализация сервисов завершена!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template AuthScreen}
|
||||
/// {@template auth_screen}
|
||||
/// Экран авторизации пользователя.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение формы входа в приложение
|
||||
/// - Обработку процесса аутентификации
|
||||
/// - Навигацию после успешной авторизации
|
||||
///
|
||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||
/// {@endtemplate}
|
||||
class AuthScreen extends StatelessWidget {
|
||||
/// {@macro AuthScreen}
|
||||
/// {@macro auth_screen}
|
||||
const AuthScreen({super.key});
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
/// Интерфейс для сервиса отладки
|
||||
abstract interface class IDebugService {
|
||||
static const name = 'IDebugService';
|
||||
|
||||
/// Наблюдение за dio
|
||||
dynamic get dioLogger;
|
||||
Interceptor get dioLogger;
|
||||
|
||||
/// Наблюдение за роутами
|
||||
dynamic get routeObserver;
|
||||
NavigatorObserver get routeObserver;
|
||||
|
||||
/// Наблюдение за BLoC
|
||||
dynamic get blocObserver;
|
||||
BlocObserver get blocObserver;
|
||||
|
||||
/// Метод для логирования сообщений
|
||||
void log(Object message, {Object logLevel, Map<String, dynamic>? args});
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.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/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 ComponentsScreen}
|
||||
/// Экран для демонстрации компонентов приложения.
|
||||
/// {@template components_screen}
|
||||
/// Экран для демонстрации и тестирования компонентов приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Демонстрацию различных типов снекбаров (ошибка, успех, информация)
|
||||
/// - Тестирование кастомных UI компонентов
|
||||
/// - Предоставление примеров использования компонентов
|
||||
/// - Валидацию корректности работы компонентов
|
||||
/// {@endtemplate}
|
||||
class ComponentsScreen extends StatefulWidget {
|
||||
/// {@macro ComponentsScreen}
|
||||
/// {@macro components_screen}
|
||||
const ComponentsScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ComponentsScreen> createState() => _ComponentsScreenState();
|
||||
}
|
||||
|
||||
/// {@template components_screen_state}
|
||||
/// Состояние экрана компонентов.
|
||||
///
|
||||
/// Управляет отображением различных типов снекбаров
|
||||
/// и демонстрирует их функциональность.
|
||||
/// {@endtemplate}
|
||||
class _ComponentsScreenState extends State<ComponentsScreen> {
|
||||
/// {@macro components_screen_state}
|
||||
_ComponentsScreenState();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -51,6 +73,35 @@ class _ComponentsScreenState extends State<ComponentsScreen> {
|
||||
},
|
||||
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,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -35,42 +37,42 @@ class DebugScreen extends StatelessWidget {
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.iconsScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.iconsScreenName));
|
||||
},
|
||||
child: const Text('Экран с иконками'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.themeScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.themeScreenName));
|
||||
},
|
||||
child: const Text('Экран настроек темы'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.tokensScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.tokensScreenName));
|
||||
},
|
||||
child: const Text('Экран с токенами'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.uiKitScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.uiKitScreenName));
|
||||
},
|
||||
child: const Text('Экран UI Kit'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.langScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.langScreenName));
|
||||
},
|
||||
child: const Text('Экран локализации'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.componentsScreenName);
|
||||
onPressed: () async {
|
||||
await context.pushNamed(DebugRoutes.componentsScreenName);
|
||||
},
|
||||
child: const Text('Экран компонентов'),
|
||||
),
|
||||
|
||||
@@ -2,11 +2,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
|
||||
/// {@template IconsScreen}
|
||||
/// Экран для отрисовки иконок
|
||||
/// {@template icons_screen}
|
||||
/// Экран для отображения всех доступных иконок приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение списка всех SVG иконок из assets/icons/
|
||||
/// - Предоставление возможности просмотра иконок для разработчиков
|
||||
/// - Демонстрацию использования системы генерации ресурсов
|
||||
/// {@endtemplate}
|
||||
class IconsScreen extends StatelessWidget {
|
||||
/// {@macro IconsScreen}
|
||||
/// {@macro icons_screen}
|
||||
const IconsScreen({super.key});
|
||||
|
||||
@override
|
||||
@@ -30,19 +35,20 @@ class IconsScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Приватный класс для реализации элемента списка иконок
|
||||
/// {@template item_icon}
|
||||
/// Виджет для отображения отдельной иконки в списке.
|
||||
///
|
||||
/// Отображает SVG иконку вместе с её названием файла
|
||||
/// для удобства идентификации в процессе разработки.
|
||||
/// {@endtemplate}
|
||||
class _ItemIcon extends StatelessWidget {
|
||||
/// Создает экземпляр элемента списка иконок
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [icon] - иконка
|
||||
/// - [name] - название иконки
|
||||
/// {@macro item_icon}
|
||||
const _ItemIcon({required this.icon, required this.name});
|
||||
|
||||
/// Иконка
|
||||
/// SVG иконка для отображения
|
||||
final Widget icon;
|
||||
|
||||
/// Название иконки
|
||||
/// Название файла иконки для идентификации
|
||||
final String name;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.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 LangScreen}
|
||||
/// Экран для отладки языков приложения
|
||||
/// {@template lang_screen}
|
||||
/// Экран для отладки и тестирования локализации приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Демонстрацию переключения между поддерживаемыми языками
|
||||
/// - Отображение локализованных строк с разными шрифтами
|
||||
/// - Тестирование системы локализации и шрифтов
|
||||
/// - Показ текущего языка приложения
|
||||
/// {@endtemplate}
|
||||
class LangScreen extends StatelessWidget {
|
||||
/// {@macro LangScreen}
|
||||
/// {@macro lang_screen}
|
||||
const LangScreen({super.key});
|
||||
|
||||
@override
|
||||
@@ -36,18 +40,12 @@ class LangScreen extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово bold: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.appColors.testColor,
|
||||
fontFamily: Assets.fonts.montserratBold,
|
||||
),
|
||||
style: TextStyle(color: context.appColors.testColor),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово medium: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.appColors.testColor,
|
||||
fontFamily: FontFamily.montserrat,
|
||||
),
|
||||
style: TextStyle(color: context.appColors.testColor),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('Текущий язык: ${context.l10n.localeName}'),
|
||||
|
||||
@@ -2,11 +2,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||
|
||||
/// {@template ThemeScreen}
|
||||
/// Экран для отладки темы приложения
|
||||
/// {@template theme_screen}
|
||||
/// Экран для отладки и тестирования темы приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Демонстрацию переключения между светлой и темной темами
|
||||
/// - Отображение тестовых цветов из цветовой схемы
|
||||
/// - Показ текущего режима темы
|
||||
/// - Тестирование системы управления темами
|
||||
/// {@endtemplate}
|
||||
class ThemeScreen extends StatelessWidget {
|
||||
/// {@macro ThemeScreen}
|
||||
/// {@macro theme_screen}
|
||||
const ThemeScreen({super.key});
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template TokensScreen}
|
||||
/// Экран для отображения токенов
|
||||
/// {@template tokens_screen}
|
||||
/// Экран для отображения и управления токенами аутентификации.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение текущих токенов доступа и обновления
|
||||
/// - Демонстрацию работы с токенами в приложении
|
||||
/// - Тестирование функциональности аутентификации
|
||||
///
|
||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||
/// {@endtemplate}
|
||||
class TokensScreen extends StatelessWidget {
|
||||
/// {@macro TokensScreen}
|
||||
/// {@macro tokens_screen}
|
||||
const TokensScreen({super.key});
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template UiKitScreen}
|
||||
/// Экран для отрисовки UI Kit
|
||||
/// и тестирования его компонентов.
|
||||
/// {@template ui_kit_screen}
|
||||
/// Экран для демонстрации и тестирования компонентов UI Kit.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение всех доступных компонентов UI Kit
|
||||
/// - Демонстрацию использования кастомных виджетов
|
||||
/// - Тестирование стилей и тем оформления
|
||||
/// - Предоставление примера использования UI компонентов
|
||||
///
|
||||
/// В текущей реализации является заглушкой для будущих компонентов.
|
||||
/// {@endtemplate}
|
||||
class UiKitScreen extends StatelessWidget {
|
||||
/// {@macro UiKitScreen}
|
||||
/// {@macro ui_kit_screen}
|
||||
const UiKitScreen({super.key});
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||
import 'package:friflex_starter/features/main/presentation/main_routes.dart';
|
||||
@@ -23,7 +25,7 @@ class MainScreen extends StatelessWidget {
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Переход на экран с деталями
|
||||
context.pushNamed(MainRoutes.mainDetailScreenName);
|
||||
unawaited(context.pushNamed(MainRoutes.mainDetailScreenName));
|
||||
},
|
||||
child: const Text('Переход на экран с деталями'),
|
||||
),
|
||||
|
||||
@@ -5,19 +5,41 @@ import 'package:friflex_starter/features/profile/domain/repository/i_profile_rep
|
||||
part 'profile_event.dart';
|
||||
part 'profile_state.dart';
|
||||
|
||||
/// {@template profile_bloc}
|
||||
/// Bloc для управления состоянием профиля пользователя.
|
||||
///
|
||||
/// Обрабатывает события загрузки данных профиля и управляет
|
||||
/// соответствующими состояниями (загрузка, успех, ошибка).
|
||||
/// {@endtemplate}
|
||||
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
/// {@macro profile_bloc}
|
||||
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
|
||||
// Вам необходимо добавлять только
|
||||
// один обработчик событий в конструкторе
|
||||
// Регистрируем обработчики событий в конструкторе
|
||||
on<ProfileEvent>((event, emit) async {
|
||||
// Обрабатываем событие загрузки профиля
|
||||
if (event is ProfileFetchProfileEvent) {
|
||||
await _fetchProfile(event, emit);
|
||||
}
|
||||
// Обрабатываем событие выхода из профиля
|
||||
else if (event is ProfileLogoutProfileEvent) {
|
||||
await _logout(event, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Репозиторий для работы с данными профиля
|
||||
final IProfileRepository _profileRepository;
|
||||
|
||||
/// Метод для загрузки данных профиля пользователя.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [event] - событие с ID пользователя для загрузки
|
||||
/// - [emit] - функция для эмиссии состояний
|
||||
///
|
||||
/// Последовательность состояний:
|
||||
/// 1. ProfileWaitingState - начало загрузки
|
||||
/// 2. ProfileSuccessState - успешная загрузка с данными
|
||||
/// 3. ProfileErrorState - ошибка загрузки с сообщением
|
||||
Future<void> _fetchProfile(
|
||||
ProfileFetchProfileEvent event,
|
||||
Emitter<ProfileState> emit,
|
||||
@@ -27,6 +49,7 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
final data = await _profileRepository.fetchUserProfile(event.id);
|
||||
emit(ProfileSuccessState(data: data));
|
||||
} on Object catch (error, stackTrace) {
|
||||
// Обработка ошибки при загрузке профиля
|
||||
emit(
|
||||
ProfileErrorState(
|
||||
message: 'Ошибка при загрузке профиля',
|
||||
@@ -34,6 +57,19 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
// Пробрасываем исключение в BlocObserver, для логирования или обработки
|
||||
addError(error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
/// Метод для выхода из профиля пользователя.
|
||||
Future<void> _logout(
|
||||
ProfileLogoutProfileEvent event,
|
||||
Emitter<ProfileState> emit,
|
||||
) async {
|
||||
// Здесь можно добавить логику выхода из профиля
|
||||
// Например, очистка токенов, данных пользователя и т.д.
|
||||
// В данном примере просто эмитим начальное состояние
|
||||
emit(ProfileInitialState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
part of 'profile_bloc.dart';
|
||||
|
||||
/// {@template profile_event}
|
||||
/// События для управления состоянием профиля пользователя.
|
||||
/// {@endtemplate}
|
||||
sealed class ProfileEvent extends Equatable {
|
||||
/// {@macro profile_event}
|
||||
const ProfileEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
/// {@template profile_event}
|
||||
/// Событие для загрузки профиля пользователя.
|
||||
/// {@endtemplate}
|
||||
final class ProfileFetchProfileEvent extends ProfileEvent {
|
||||
/// {@macro profile_event}
|
||||
const ProfileFetchProfileEvent({required this.id});
|
||||
|
||||
/// ID пользователя для загрузки профиля
|
||||
final String id;
|
||||
|
||||
@override
|
||||
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 => [];
|
||||
}
|
||||
|
||||
@@ -3,17 +3,22 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/features/profile/domain/bloc/profile_bloc.dart';
|
||||
|
||||
// Класс экрана, где мы инициализируем ProfileBloc
|
||||
// и вызываем событие ProfileFetchProfileEvent
|
||||
/// {@template profile_screen}
|
||||
/// Экран профиля пользователя.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Инициализацию ProfileBloc с репозиторием профиля
|
||||
/// - Отображение данных профиля пользователя
|
||||
/// - Обработку состояний загрузки, успеха и ошибок
|
||||
/// {@endtemplate}
|
||||
class ProfileScreen extends StatelessWidget {
|
||||
/// {@macro profile_screen}
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final profileRepository = context.di.repositories.profileRepository;
|
||||
// Здесь мы инициализируем ProfileBloc
|
||||
// и вызываем событие ProfileFetchProfileEvent
|
||||
// Или любые другие события, которые вам нужны
|
||||
// Инициализируем ProfileBloc с репозиторием и загружаем данные профиля
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
ProfileBloc(profileRepository)
|
||||
@@ -23,8 +28,16 @@ class ProfileScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Виджет, который отображает UI экрана
|
||||
/// {@template profile_screen_view}
|
||||
/// Виджет для отображения UI экрана профиля.
|
||||
///
|
||||
/// Отображает данные профиля в зависимости от состояния ProfileBloc:
|
||||
/// - Индикатор загрузки во время получения данных
|
||||
/// - Данные профиля при успешной загрузке
|
||||
/// - Сообщение об ошибке при неудачной загрузке
|
||||
/// {@endtemplate}
|
||||
class _ProfileScreenView extends StatelessWidget {
|
||||
/// {@macro profile_screen_view}
|
||||
const _ProfileScreenView();
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,39 +1,83 @@
|
||||
import 'dart:async';
|
||||
|
||||
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_env.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';
|
||||
|
||||
/// Класс для реализации корневой страницы приложения
|
||||
class RootScreen extends StatelessWidget {
|
||||
/// Создает корневую страницу приложения
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [navigationShell] - текущая ветка навигации
|
||||
/// {@template root_screen}
|
||||
/// Корневой экран приложения с навигационной структурой.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение основного навигационного интерфейса
|
||||
/// - Управление переключением между основными разделами приложения
|
||||
/// - Отображение кнопки отладки в не-продакшн окружениях
|
||||
/// - Интеграцию с GoRouter для навигации
|
||||
/// {@endtemplate}
|
||||
class RootScreen extends StatefulWidget {
|
||||
/// {@macro root_screen}
|
||||
const RootScreen({required this.navigationShell, super.key});
|
||||
|
||||
/// Текущая ветка навигации
|
||||
/// Текущая ветка навигации от GoRouter
|
||||
/// Содержит информацию о текущем состоянии навигации
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
floatingActionButton: context.di.env != AppEnv.prod
|
||||
floatingActionButton: context.di.env != .prod
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.bug_report),
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.debugScreenName);
|
||||
unawaited(context.pushNamed(DebugRoutes.debugScreenName));
|
||||
},
|
||||
)
|
||||
: null,
|
||||
body: navigationShell,
|
||||
body: widget.navigationShell,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
|
||||
],
|
||||
currentIndex: navigationShell.currentIndex,
|
||||
onTap: navigationShell.goBranch,
|
||||
currentIndex: widget.navigationShell.currentIndex,
|
||||
onTap: widget.navigationShell.goBranch,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
88
lib/features/update/README.md
Normal file
88
lib/features/update/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Модуль 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` из репозитория, если обновлений нет
|
||||
@@ -0,0 +1,42 @@
|
||||
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';
|
||||
}
|
||||
23
lib/features/update/data/repository/update_repository.dart
Normal file
23
lib/features/update/data/repository/update_repository.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
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}
|
||||
const UpdateRepository();
|
||||
|
||||
@override
|
||||
Future<UpdateEntity> checkForUpdates({
|
||||
required String versionCode,
|
||||
required String platform,
|
||||
}) {
|
||||
// TODO: Реализовать реальную логику проверки обновлений
|
||||
// Если обновления нет, возвращаем null
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'UpdateRepository';
|
||||
}
|
||||
35
lib/features/update/domain/entity/update_entity.dart
Normal file
35
lib/features/update/domain/entity/update_entity.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
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,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
38
lib/features/update/domain/state/cubit/update_cubit.dart
Normal file
38
lib/features/update/domain/state/cubit/update_cubit.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
lib/features/update/domain/state/cubit/update_state.dart
Normal file
56
lib/features/update/domain/state/cubit/update_state.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
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];
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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 ?? ''}'),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/features/update/update_routes.dart
Normal file
20
lib/features/update/update_routes.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
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(),
|
||||
);
|
||||
}
|
||||
13
lib/features/update/update_type.dart
Normal file
13
lib/features/update/update_type.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
/// {@template UpdateType}
|
||||
/// Тип обновления
|
||||
/// {@endtemplate}
|
||||
enum UpdateType {
|
||||
/// Обязательное обновление
|
||||
hard,
|
||||
|
||||
/// Мягкое обновление
|
||||
soft,
|
||||
|
||||
/// Не требуется обновление
|
||||
none,
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// dart format width=80
|
||||
|
||||
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
/// *****************************************************
|
||||
/// FlutterGen
|
||||
@@ -5,7 +7,7 @@
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -13,34 +15,6 @@ import 'package:flutter_svg/flutter_svg.dart' as _svg;
|
||||
import 'package:lottie/lottie.dart' as _lottie;
|
||||
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 {
|
||||
const $AssetsIconsGen();
|
||||
|
||||
@@ -63,25 +37,18 @@ class $AssetsLottieGen {
|
||||
}
|
||||
|
||||
class Assets {
|
||||
Assets._();
|
||||
const Assets._();
|
||||
|
||||
static const $AssetsFontsGen fonts = $AssetsFontsGen();
|
||||
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
||||
static const $AssetsLottieGen lottie = $AssetsLottieGen();
|
||||
}
|
||||
|
||||
class SvgGenImage {
|
||||
const SvgGenImage(
|
||||
this._assetName, {
|
||||
this.size,
|
||||
this.flavors = const {},
|
||||
}) : _isVecFormat = false;
|
||||
const SvgGenImage(this._assetName, {this.size, this.flavors = const {}})
|
||||
: _isVecFormat = false;
|
||||
|
||||
const SvgGenImage.vec(
|
||||
this._assetName, {
|
||||
this.size,
|
||||
this.flavors = const {},
|
||||
}) : _isVecFormat = true;
|
||||
const SvgGenImage.vec(this._assetName, {this.size, this.flavors = const {}})
|
||||
: _isVecFormat = true;
|
||||
|
||||
final String _assetName;
|
||||
final Size? size;
|
||||
@@ -102,6 +69,7 @@ class SvgGenImage {
|
||||
String? semanticsLabel,
|
||||
bool excludeFromSemantics = false,
|
||||
_svg.SvgTheme? theme,
|
||||
_svg.ColorMapper? colorMapper,
|
||||
ColorFilter? colorFilter,
|
||||
Clip clipBehavior = Clip.hardEdge,
|
||||
@deprecated Color? color,
|
||||
@@ -121,6 +89,7 @@ class SvgGenImage {
|
||||
assetBundle: bundle,
|
||||
packageName: package,
|
||||
theme: theme,
|
||||
colorMapper: colorMapper,
|
||||
);
|
||||
}
|
||||
return _svg.SvgPicture(
|
||||
@@ -135,7 +104,8 @@ class SvgGenImage {
|
||||
placeholderBuilder: placeholderBuilder,
|
||||
semanticsLabel: semanticsLabel,
|
||||
excludeFromSemantics: excludeFromSemantics,
|
||||
colorFilter: colorFilter ??
|
||||
colorFilter:
|
||||
colorFilter ??
|
||||
(color == null ? null : ColorFilter.mode(color, colorBlendMode)),
|
||||
clipBehavior: clipBehavior,
|
||||
cacheColorFilter: cacheColorFilter,
|
||||
@@ -148,10 +118,7 @@ class SvgGenImage {
|
||||
}
|
||||
|
||||
class LottieGenImage {
|
||||
const LottieGenImage(
|
||||
this._assetName, {
|
||||
this.flavors = const {},
|
||||
});
|
||||
const LottieGenImage(this._assetName, {this.flavors = const {}});
|
||||
|
||||
final String _assetName;
|
||||
final Set<String> flavors;
|
||||
@@ -168,11 +135,8 @@ class LottieGenImage {
|
||||
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
||||
Key? key,
|
||||
AssetBundle? bundle,
|
||||
Widget Function(
|
||||
BuildContext,
|
||||
Widget,
|
||||
_lottie.LottieComposition?,
|
||||
)? frameBuilder,
|
||||
Widget Function(BuildContext, Widget, _lottie.LottieComposition?)?
|
||||
frameBuilder,
|
||||
ImageErrorWidgetBuilder? errorBuilder,
|
||||
double? width,
|
||||
double? height,
|
||||
@@ -182,6 +146,9 @@ class LottieGenImage {
|
||||
bool? addRepaintBoundary,
|
||||
FilterQuality? filterQuality,
|
||||
void Function(String)? onWarning,
|
||||
_lottie.LottieDecoder? decoder,
|
||||
_lottie.RenderCache? renderCache,
|
||||
bool? backgroundLoading,
|
||||
}) {
|
||||
return _lottie.Lottie.asset(
|
||||
_assetName,
|
||||
@@ -206,6 +173,9 @@ class LottieGenImage {
|
||||
addRepaintBoundary: addRepaintBoundary,
|
||||
filterQuality: filterQuality,
|
||||
onWarning: onWarning,
|
||||
decoder: decoder,
|
||||
renderCache: renderCache,
|
||||
backgroundLoading: backgroundLoading,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/// 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';
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом локализации
|
||||
typedef LocalizationBuilder = Widget Function();
|
||||
|
||||
/// Виджет для перестройки виджета в зависимости от локализации
|
||||
/// {@template localization_consumer}
|
||||
/// Виджет для подписки на изменения локализации приложения.
|
||||
///
|
||||
/// Автоматически перестраивает дочерние виджеты при изменении языка,
|
||||
/// обеспечивая реактивность интерфейса к изменениям настроек локализации.
|
||||
/// {@endtemplate}
|
||||
class LocalizationConsumer extends StatelessWidget {
|
||||
/// {@macro localization_consumer}
|
||||
const LocalizationConsumer({required this.builder, super.key});
|
||||
|
||||
/// Функция для построения виджета с учетом текущей локализации
|
||||
final LocalizationBuilder builder;
|
||||
|
||||
@override
|
||||
@@ -19,12 +27,34 @@ class LocalizationConsumer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Класс для управления локализацией
|
||||
/// {@template localization_notifier}
|
||||
/// Класс для управления локализацией приложения.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Хранение текущей локали приложения
|
||||
/// - Уведомление подписчиков об изменениях языка
|
||||
/// - Переключение между поддерживаемыми языками
|
||||
/// {@endtemplate}
|
||||
final class LocalizationNotifier extends ChangeNotifier {
|
||||
Locale _locale = const Locale('en', 'US');
|
||||
/// {@macro localization_notifier}
|
||||
LocalizationNotifier();
|
||||
|
||||
/// Текущая локаль приложения
|
||||
/// По умолчанию используется русский язык
|
||||
Locale _locale = const Locale('ru', 'RU');
|
||||
|
||||
/// Получение текущей локали
|
||||
Locale get locale => _locale;
|
||||
|
||||
/// Получение текущего языка в виде кода языка
|
||||
String get language => _locale.languageCode;
|
||||
|
||||
/// Метод для изменения локали приложения.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [locale] - новая локаль для установки
|
||||
///
|
||||
/// Уведомляет всех подписчиков об изменении локали.
|
||||
void changeLocal(Locale locale) {
|
||||
_locale = locale;
|
||||
notifyListeners();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(AppEnv.prod).run();
|
||||
void main() => AppRunner(.prod).run();
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:friflex_starter/features/main/presentation/main_routes.dart';
|
||||
import 'package:friflex_starter/features/profile/presentation/profile_routes.dart';
|
||||
import 'package:friflex_starter/features/root/root_screen.dart';
|
||||
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||
import 'package:friflex_starter/features/update/update_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// {@template app_router}
|
||||
@@ -42,6 +43,7 @@ class AppRouter {
|
||||
path: '/splash',
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
UpdateRoutes.buildRoutes(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,14 +65,10 @@ class AppRunner {
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
|
||||
// Инициализация роутера
|
||||
router = AppRouter.createRouter(_debugService);
|
||||
|
||||
// throw Exception('Test error');
|
||||
|
||||
runApp(
|
||||
App(
|
||||
router: router,
|
||||
initDependencies: () {
|
||||
return _initDependencies(
|
||||
debugService: _debugService,
|
||||
@@ -125,9 +121,6 @@ class AppRunner {
|
||||
required AppEnv env,
|
||||
required TimerRunner timerRunner,
|
||||
}) async {
|
||||
// Имитация задержки инициализации
|
||||
// TODO(yura): Удалить после проверки
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
debugService.log(() => 'Тип сборки: ${env.name}');
|
||||
final diContainer = DiContainer(env: env, dService: debugService);
|
||||
await diContainer.init(
|
||||
@@ -137,8 +130,11 @@ class AppRunner {
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) =>
|
||||
debugService.logError(message, error: error, stackTrace: stackTrace),
|
||||
onError: (message, error, [stackTrace]) {
|
||||
timerRunner.stop();
|
||||
_debugService.logError(message, error: error, stackTrace: stackTrace);
|
||||
throw Exception('Ошибка инициализации зависимостей: $message');
|
||||
},
|
||||
);
|
||||
//throw Exception('Test error');
|
||||
return diContainer;
|
||||
|
||||
@@ -25,8 +25,8 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
|
||||
/// Метод для показа экрана ошибки
|
||||
void _showErrorScreen(Object error, StackTrace? stackTrace) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
AppRouter.rootNavigatorKey.currentState?.push(
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await AppRouter.rootNavigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(AppEnv.dev).run();
|
||||
void main() => AppRunner(.dev).run();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(AppEnv.prod).run();
|
||||
void main() => AppRunner(.prod).run();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(AppEnv.stage).run();
|
||||
void main() => AppRunner(.stage).run();
|
||||
|
||||
272
pubspec.lock
272
pubspec.lock
@@ -5,18 +5,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "82.0.0"
|
||||
version: "91.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.4.5"
|
||||
version: "8.4.1"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -76,18 +76,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
||||
sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "4.0.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.2.0"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -96,30 +96,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
version: "2.10.3"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -132,10 +116,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61"
|
||||
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.4"
|
||||
version: "8.12.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -164,10 +148,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
version: "4.11.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -220,10 +204,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.3"
|
||||
dartx:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -232,14 +216,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.8.0+1"
|
||||
version: "5.9.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -252,18 +244,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: envied
|
||||
sha256: a4e2b1d0caa479b5d61332ae516518c175a6d09328a35a0bc0a53894cc5d7e4d
|
||||
sha256: cd95ddf0982e53f0b6664e889d4a9ce678b3907a59a5047923404375ef6dcacc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.3.1"
|
||||
envied_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: envied_generator
|
||||
sha256: "894f6c5eb624c60a1ce6f642b6fd7ec68bc3440aa6f1881837aa9acbbeade0c8"
|
||||
sha256: "81ad332912f1b31afbd2b913aff9ec7b032e97f4ba7e419f52d02bb90637e77c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.3.1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -321,26 +313,26 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_gen
|
||||
sha256: "4117a3ea6b26a910c715bd58abcc5a90447e70930a5b98249e94c41da9e849bb"
|
||||
sha256: eac4863b65813aacbf16ecc07e7c271ab82fb2d95181825348f1fb7b03fd52da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.0"
|
||||
version: "5.12.0"
|
||||
flutter_gen_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_gen_core
|
||||
sha256: "3eaa2d3d8be58267ac4cd5e215ac965dd23cae0410dc073de2e82e227be32bfc"
|
||||
sha256: b6bafbbd981da2f964eb45bcb8b8a7676a281084f8922c0c75de4cfbaa849311
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.0"
|
||||
version: "5.12.0"
|
||||
flutter_gen_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_gen_runner
|
||||
sha256: e74b4ead01df3e8f02e73a26ca856759dbbe8cb3fd60941ba9f4005cd0cd19c9
|
||||
sha256: c99b10af9d404e3f46fd1927e7d90099779e935e86022674c4c2a9e6c2a93b29
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.10.0"
|
||||
version: "5.12.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -406,10 +398,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
|
||||
sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.2.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -420,14 +412,70 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
geoclue:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
name: geoclue
|
||||
sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "0.1.1"
|
||||
geolocator:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator
|
||||
sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.2"
|
||||
geolocator_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_android
|
||||
sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.1+1"
|
||||
geolocator_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_apple
|
||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.13"
|
||||
geolocator_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_linux
|
||||
sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
geolocator_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_platform_interface
|
||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.6"
|
||||
geolocator_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_web
|
||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.3"
|
||||
geolocator_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: geolocator_windows
|
||||
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -440,10 +488,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a"
|
||||
sha256: c92d18e1fe994cb06d48aa786c46b142a5633067e8297cff6b5a3ac742620104
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.1.2"
|
||||
version: "17.0.0"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -460,6 +508,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.4"
|
||||
gsettings:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gsettings
|
||||
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
hashcodes:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -499,6 +555,14 @@ packages:
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
image_size_getter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -543,26 +607,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
version: "11.0.1"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -583,10 +647,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lottie
|
||||
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
|
||||
sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "3.3.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -607,10 +671,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -635,6 +699,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -775,26 +855,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
|
||||
sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.4"
|
||||
version: "12.0.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
|
||||
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
version: "6.1.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -868,18 +948,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||
sha256: "9098ab86015c4f1d8af6486b547b11100e73b193e1899015033cb3e14ad20243"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "4.0.2"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.8"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -932,42 +1012,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: talker
|
||||
sha256: a664f5eae5284e94aa8c535e0400ab71a78fa2480ad18a7344dec928b5c56805
|
||||
sha256: "82de443cadfb6c41d457e7774c7890a91c73af3c2f17f3f7c01670bb58d5f5a1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "5.0.2"
|
||||
talker_bloc_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_bloc_logger
|
||||
sha256: "1af998d6cbafba00f66bbf49d77132275d3b113c6ac53ce0dc9d9981206f9cea"
|
||||
sha256: e631fcc9454cd86639888a9bb4654582bbc8c64c7dcc913401bfecb8892ec759
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "5.0.2"
|
||||
talker_dio_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_dio_logger
|
||||
sha256: "296a20ce600ccca7801deb5a530c28d03500c520f25f1e40c3bae727f63dd5eb"
|
||||
sha256: "5bbecc237f3d2c4af9348da5a0086321ed6dd6bf9857d723b1f54f61c810cff2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "5.0.2"
|
||||
talker_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_flutter
|
||||
sha256: "480c51bba7ac0dcab23be5e9545214a43179625bc5f787326248e94af8316aee"
|
||||
sha256: "4f7a8d739237a3a3c8ba4dddcdbc1f9d9dec143811641dbafebd6b70f947f8ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "5.0.2"
|
||||
talker_logger:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: talker_logger
|
||||
sha256: b5d0bd04229eeccdf765cabf73e964ee28528c749104b56867c8d2eb764a45ee
|
||||
sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "5.0.2"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -980,26 +1060,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.7"
|
||||
theme_tailor:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: theme_tailor
|
||||
sha256: ba98be1d04856deef932757a3ca8fa7a5e2a6f96c30466a59c48924eeb608b97
|
||||
sha256: "1ed7eeb5362e61aac4719e3ca4cd701fd6a74a507a911424eb6caef9b28a7fef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.1.1"
|
||||
theme_tailor_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: theme_tailor_annotation
|
||||
sha256: "0d5ecd13a6a52add2082aa60497179f6093acf482eb69e7fa3a9f37eb990ac34"
|
||||
sha256: "867848486f33dc4dff7649e3c6525133ecee410b5d97c77e22f1cb146baa1158"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.1.1"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1008,14 +1088,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1092,10 +1164,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1169,5 +1241,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
dart: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.38.1"
|
||||
|
||||
53
pubspec.yaml
53
pubspec.yaml
@@ -6,33 +6,33 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
version: 0.0.1+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
flutter: ">=3.32.0"
|
||||
sdk: ">=3.10.0 <4.0.0"
|
||||
flutter: ">=3.38.1"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: 1.0.8
|
||||
envied: 1.1.1
|
||||
go_router: 15.1.2
|
||||
envied: 1.3.1
|
||||
go_router: 17.0.0
|
||||
flutter_bloc: 9.1.1
|
||||
provider: 6.1.5
|
||||
dio: 5.8.0+1
|
||||
dio: 5.9.0
|
||||
intl: 0.20.2
|
||||
flutter_svg: 2.1.0
|
||||
flutter_svg: 2.2.2
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
lottie: 3.3.1
|
||||
lottie: 3.3.2
|
||||
|
||||
# Пакеты для отладки
|
||||
talker_flutter: 4.8.0
|
||||
talker_dio_logger: 4.8.0
|
||||
talker_bloc_logger: 4.8.0
|
||||
talker_logger: 4.8.0
|
||||
talker_flutter: 5.0.2
|
||||
talker_dio_logger: 5.0.2
|
||||
talker_bloc_logger: 5.0.2
|
||||
talker_logger: 5.0.2
|
||||
|
||||
equatable: 2.0.7
|
||||
|
||||
theme_tailor_annotation: 3.0.2
|
||||
theme_tailor_annotation: 3.1.1
|
||||
|
||||
### основной сервис с интерфейсами
|
||||
i_app_services:
|
||||
@@ -42,40 +42,27 @@ dependencies:
|
||||
### В зависимости от платформы ###
|
||||
app_services:
|
||||
path: app_services/base/app_services ### Базовая реализация ###
|
||||
#path: app_services/aurora/app_services ### Аврора реализация ###
|
||||
# path: app_services/aurora/app_services ### Аврора реализация ###
|
||||
# path: app_services/hms/app_services ### HarmonyOS реализация ###
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
envied_generator: 1.1.1
|
||||
build_runner: 2.4.15
|
||||
flutter_gen_runner: 5.10.0
|
||||
flutter_gen: 5.10.0
|
||||
envied_generator: 1.3.1
|
||||
build_runner: 2.10.3
|
||||
flutter_gen_runner: 5.12.0
|
||||
flutter_gen: 5.12.0
|
||||
flutter_lints: 6.0.0
|
||||
theme_tailor: 3.0.3
|
||||
theme_tailor: 3.1.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
generate: true
|
||||
assets:
|
||||
- assets/icons/
|
||||
- assets/fonts/
|
||||
- assets/lottie/
|
||||
|
||||
fonts:
|
||||
- family: Montserrat
|
||||
fonts:
|
||||
- asset: assets/fonts/Montserrat-ExtraBold.ttf
|
||||
weight: 800
|
||||
- asset: assets/fonts/Montserrat-Bold.ttf
|
||||
weight: 700
|
||||
- asset: assets/fonts/Montserrat-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: assets/fonts/Montserrat-Medium.ttf
|
||||
weight: 500
|
||||
- asset: assets/fonts/Montserrat-Regular.ttf
|
||||
weight: 400
|
||||
|
||||
flutter_gen:
|
||||
integrations:
|
||||
flutter_svg: true
|
||||
|
||||
@@ -18,9 +18,7 @@ void main() {
|
||||
|
||||
/// Создание мок-темы с правильными стилями текста
|
||||
TextTheme createMockTextTheme() {
|
||||
return const TextTheme(
|
||||
bodyMedium: TextStyle(fontSize: 14),
|
||||
);
|
||||
return const TextTheme(bodyMedium: TextStyle(fontSize: 14));
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
@@ -41,7 +39,9 @@ void main() {
|
||||
}
|
||||
|
||||
group('AppSnackBar.showInfo', () {
|
||||
testTester('показывает снекбар с информацией и правильными стилями', (tester) async {
|
||||
testTester('показывает снекбар с информацией и правильными стилями', (
|
||||
tester,
|
||||
) async {
|
||||
const infoMessage = 'Это просто сообщение';
|
||||
|
||||
AppSnackBar.showInfo(
|
||||
@@ -54,7 +54,7 @@ void main() {
|
||||
|
||||
expect(find.byType(AppSnackBar), findsOneWidget);
|
||||
expect(find.text(infoMessage), findsOneWidget);
|
||||
|
||||
|
||||
// Проверяем иконку и её цвет
|
||||
final iconFinder = find.byType(Icon);
|
||||
expect(iconFinder, findsOneWidget);
|
||||
@@ -74,7 +74,9 @@ void main() {
|
||||
});
|
||||
|
||||
group('AppSnackBar.showError', () {
|
||||
testTester('показывает снекбар с ошибкой и правильными стилями', (tester) async {
|
||||
testTester('показывает снекбар с ошибкой и правильными стилями', (
|
||||
tester,
|
||||
) async {
|
||||
const errorMessage = 'Произошла ошибка';
|
||||
|
||||
AppSnackBar.showError(
|
||||
@@ -158,7 +160,9 @@ void main() {
|
||||
});
|
||||
|
||||
group('AppSnackBar.showSuccess', () {
|
||||
testTester('показывает снекбар с успехом и правильными стилями', (tester) async {
|
||||
testTester('показывает снекбар с успехом и правильными стилями', (
|
||||
tester,
|
||||
) async {
|
||||
const successMessage = 'Операция выполнена успешно';
|
||||
|
||||
AppSnackBar.showSuccess(
|
||||
@@ -186,7 +190,10 @@ void main() {
|
||||
),
|
||||
);
|
||||
final decoration = container.decoration as BoxDecoration;
|
||||
expect(decoration.color, equals(const Color(0xFF6FB62C))); // Success фон
|
||||
expect(
|
||||
decoration.color,
|
||||
equals(const Color(0xFF6FB62C)),
|
||||
); // Success фон
|
||||
});
|
||||
|
||||
testTester('показывает снекбар с кастомной продолжительностью', (
|
||||
@@ -210,29 +217,41 @@ void main() {
|
||||
});
|
||||
|
||||
group('AppSnackBar виджет поведение', () {
|
||||
testTester('показывает анимацию появления с правильной последовательностью', (tester) async {
|
||||
const message = 'Тестовое сообщение';
|
||||
testTester(
|
||||
'показывает анимацию появления с правильной последовательностью',
|
||||
(tester) async {
|
||||
const message = 'Тестовое сообщение';
|
||||
|
||||
AppSnackBar.showError(
|
||||
tester.element(find.byType(Scaffold)),
|
||||
message: message,
|
||||
);
|
||||
AppSnackBar.showError(
|
||||
tester.element(find.byType(Scaffold)),
|
||||
message: message,
|
||||
);
|
||||
|
||||
// Проверяем начальное состояние
|
||||
await tester.pump();
|
||||
final initialPosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||
expect(initialPosition.top ?? 0, lessThan(0));
|
||||
// Проверяем начальное состояние
|
||||
await tester.pump();
|
||||
final initialPosition = tester.widget<Positioned>(
|
||||
find.byType(Positioned),
|
||||
);
|
||||
expect(initialPosition.top ?? 0, lessThan(0));
|
||||
|
||||
// Проверяем промежуточное состояние
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
final middlePosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||
expect(middlePosition.top ?? 0, greaterThan(initialPosition.top ?? 0));
|
||||
// Проверяем промежуточное состояние
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
final middlePosition = tester.widget<Positioned>(
|
||||
find.byType(Positioned),
|
||||
);
|
||||
expect(
|
||||
middlePosition.top ?? 0,
|
||||
greaterThan(initialPosition.top ?? 0),
|
||||
);
|
||||
|
||||
// Проверяем конечное состояние
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
final finalPosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||
expect(finalPosition.top ?? 0, greaterThan(0));
|
||||
});
|
||||
// Проверяем конечное состояние
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
final finalPosition = tester.widget<Positioned>(
|
||||
find.byType(Positioned),
|
||||
);
|
||||
expect(finalPosition.top ?? 0, greaterThan(0));
|
||||
},
|
||||
);
|
||||
|
||||
testTester('закрывается при тапе', (tester) async {
|
||||
const message = 'Тап для закрытия';
|
||||
@@ -323,7 +342,9 @@ void main() {
|
||||
expect(find.byType(Text), findsAtLeastNWidgets(1));
|
||||
});
|
||||
|
||||
testTester('имеет правильные отступы и размеры на разных экранах', (tester) async {
|
||||
testTester('имеет правильные отступы и размеры на разных экранах', (
|
||||
tester,
|
||||
) async {
|
||||
const message = 'Размеры';
|
||||
|
||||
// Тестируем на маленьком экране
|
||||
@@ -360,7 +381,10 @@ void main() {
|
||||
matching: find.byType(Container),
|
||||
),
|
||||
);
|
||||
expect(container.constraints?.maxWidth, equals(350)); // Максимальная ширина
|
||||
expect(
|
||||
container.constraints?.maxWidth,
|
||||
equals(350),
|
||||
); // Максимальная ширина
|
||||
});
|
||||
|
||||
testTester('имеет правильное скругление углов', (tester) async {
|
||||
@@ -465,7 +489,9 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testTester('правильно обрабатывает быстрые последовательные вызовы', (tester) async {
|
||||
testTester('правильно обрабатывает быстрые последовательные вызовы', (
|
||||
tester,
|
||||
) async {
|
||||
const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3'];
|
||||
|
||||
for (final message in messages) {
|
||||
@@ -605,12 +631,12 @@ void main() {
|
||||
|
||||
// Создаем приложение с кастомными отступами
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
const MaterialApp(
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
data: MediaQueryData(
|
||||
padding: EdgeInsets.only(top: 50), // Симулируем статус бар
|
||||
),
|
||||
child: const Scaffold(body: Center(child: Text('Test'))),
|
||||
child: Scaffold(body: Center(child: Text('Test'))),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user