From ba5fdba9be8a9ceca1001a7701c4862f9e9e2090 Mon Sep 17 00:00:00 2001 From: Yuri Petrov <48598325+petrovyuri@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:50:48 +0300 Subject: [PATCH] =?UTF-8?q?refactor(app):=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=83=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: PetrovY --- app_services/aurora/app_services/pubspec.yaml | 14 +-- .../lib/src/app_secure_storage.dart | 8 +- lib/app/app.dart | 37 +++++++- lib/app/app_config/app_config.dart | 57 +++++++++++- lib/app/app_config/app_config.g.dart | 60 +++++++------ lib/app/app_config/i_app_config.dart | 14 --- lib/app/http/app_http_client.dart | 2 +- lib/app/theme/theme_notifier.dart | 29 ++++++- lib/app/ui_kit/app_box.dart | 10 ++- lib/di/di_base_repo.dart | 17 +++- lib/di/di_container.dart | 1 - lib/di/di_repositories.dart | 46 +++++++--- lib/di/di_services.dart | 29 +++++-- .../presentation/screens/auth_screen.dart | 11 ++- .../debug/screens/components_screen.dart | 21 ++++- lib/features/debug/screens/icons_screen.dart | 28 +++--- lib/features/debug/screens/lang_screen.dart | 12 ++- lib/features/debug/screens/theme_screen.dart | 12 ++- lib/features/debug/screens/tokens_screen.dart | 13 ++- lib/features/debug/screens/ui_kit_screen.dart | 15 +++- .../profile/domain/bloc/profile_bloc.dart | 21 ++++- .../presentation/screens/profile_screen.dart | 25 ++++-- lib/features/root/root_screen.dart | 20 +++-- lib/gen/assets.gen.dart | 41 ++++----- lib/l10n/localization_notifier.dart | 36 +++++++- test/components/app_snackbar_test.dart | 86 ++++++++++++------- 26 files changed, 476 insertions(+), 189 deletions(-) diff --git a/app_services/aurora/app_services/pubspec.yaml b/app_services/aurora/app_services/pubspec.yaml index 4f34f2b..e26c212 100644 --- a/app_services/aurora/app_services/pubspec.yaml +++ b/app_services/aurora/app_services/pubspec.yaml @@ -1,11 +1,10 @@ name: app_services -description: "Google сервисы для приложения" +description: "Аврора ОС сервисы для приложения" version: 0.0.1 publish_to: none environment: - sdk: ^3.8.0 - + sdk: '>=3.16.2 <4.0.0' dependencies: flutter: @@ -18,13 +17,8 @@ dependencies: 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 + # для работы с путями (плагин встроен в sdk flutter 3.27.1) + path_provider: 2.1.5 # Обязательные интерфейсы i_app_services: diff --git a/app_services/base/app_services/lib/src/app_secure_storage.dart b/app_services/base/app_services/lib/src/app_secure_storage.dart index cfb1d85..54d4855 100644 --- a/app_services/base/app_services/lib/src/app_secure_storage.dart +++ b/app_services/base/app_services/lib/src/app_secure_storage.dart @@ -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'; /// Экземпляр хранилища данных diff --git a/lib/app/app.dart b/lib/app/app.dart index 2ec31a7..9e4e18e 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -12,22 +12,43 @@ import 'package:friflex_starter/l10n/gen/app_localizations.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:go_router/go_router.dart'; -/// Класс приложения +/// {@template app} +/// Главный виджет приложения, управляющий инициализацией зависимостей +/// и отображением основного интерфейса приложения. +/// +/// Отвечает за: +/// - Инициализацию зависимостей приложения +/// - Отображение экрана загрузки во время инициализации +/// - Обработку ошибок инициализации +/// - Настройку провайдеров для темы и локализации +/// {@endtemplate} class App extends StatefulWidget { + /// {@macro app} const App({required this.router, required this.initDependencies, super.key}); - /// Роутер приложения + /// Роутер приложения для навигации между экранами final GoRouter router; - /// Функция для инициализации зависимостей + /// Функция для инициализации зависимостей приложения + /// Возвращает Future с контейнером зависимостей final Future Function() initDependencies; @override State createState() => _AppState(); } +/// {@template app_state} +/// Состояние главного виджета приложения. +/// +/// Управляет процессом инициализации зависимостей и отображением +/// соответствующих экранов в зависимости от состояния инициализации. +/// {@endtemplate} class _AppState extends State { + /// {@macro app_state} + _AppState(); + /// Мутабельная Future для инициализации зависимостей + /// Позволяет перезапускать инициализацию при ошибках late Future _initFuture; @override @@ -83,6 +104,8 @@ class _AppState extends State { ); } + /// Метод для перезапуска инициализации зависимостей + /// Вызывается при ошибках инициализации для повторной попытки void _retryInit() { setState(() { _initFuture = widget.initDependencies(); @@ -90,9 +113,17 @@ class _AppState extends State { } } +/// {@template app_internal} +/// Внутренний виджет приложения, отображающий основной интерфейс +/// после успешной инициализации зависимостей. +/// +/// Настраивает MaterialApp с роутером, темами и локализацией. +/// {@endtemplate} class _App extends StatelessWidget { + /// {@macro app_internal} const _App({required this.router}); + /// Роутер приложения для навигации final GoRouter router; @override diff --git a/lib/app/app_config/app_config.dart b/lib/app/app_config/app_config.dart index 201e316..63549bf 100644 --- a/lib/app/app_config/app_config.dart +++ b/lib/app/app_config/app_config.dart @@ -1,12 +1,45 @@ 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; @@ -22,9 +55,17 @@ 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; @@ -40,9 +81,17 @@ 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; diff --git a/lib/app/app_config/app_config.g.dart b/lib/app/app_config/app_config.g.dart index 15bcb5d..caef5d6 100644 --- a/lib/app/app_config/app_config.g.dart +++ b/lib/app/app_config/app_config.g.dart @@ -24,11 +24,13 @@ final class _Dev { 4081271699, ]; - static final String secretKey = String.fromCharCodes(List.generate( - _envieddatasecretKey.length, - (int i) => i, - growable: false, - ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i])); + static final String secretKey = String.fromCharCodes( + List.generate( + _envieddatasecretKey.length, + (int i) => i, + growable: false, + ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]), + ); } // coverage:ignore-file @@ -65,11 +67,13 @@ final class _Prod { 655048645, ]; - static final String baseUrl = String.fromCharCodes(List.generate( - _envieddatabaseUrl.length, - (int i) => i, - growable: false, - ).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i])); + static final String baseUrl = String.fromCharCodes( + List.generate( + _envieddatabaseUrl.length, + (int i) => i, + growable: false, + ).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]), + ); static const List _enviedkeysecretKey = [ 359753139, @@ -85,11 +89,13 @@ final class _Prod { 3044498279, ]; - static final String secretKey = String.fromCharCodes(List.generate( - _envieddatasecretKey.length, - (int i) => i, - growable: false, - ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i])); + static final String secretKey = String.fromCharCodes( + List.generate( + _envieddatasecretKey.length, + (int i) => i, + growable: false, + ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]), + ); } // coverage:ignore-file @@ -128,11 +134,13 @@ final class _Stage { 568662398, ]; - static final String baseUrl = String.fromCharCodes(List.generate( - _envieddatabaseUrl.length, - (int i) => i, - growable: false, - ).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i])); + static final String baseUrl = String.fromCharCodes( + List.generate( + _envieddatabaseUrl.length, + (int i) => i, + growable: false, + ).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]), + ); static const List _enviedkeysecretKey = [ 2132342089, @@ -150,9 +158,11 @@ final class _Stage { 1192880631, ]; - static final String secretKey = String.fromCharCodes(List.generate( - _envieddatasecretKey.length, - (int i) => i, - growable: false, - ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i])); + static final String secretKey = String.fromCharCodes( + List.generate( + _envieddatasecretKey.length, + (int i) => i, + growable: false, + ).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]), + ); } diff --git a/lib/app/app_config/i_app_config.dart b/lib/app/app_config/i_app_config.dart index 884b5fb..6cba6b0 100644 --- a/lib/app/app_config/i_app_config.dart +++ b/lib/app/app_config/i_app_config.dart @@ -1,16 +1,2 @@ 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; -} diff --git a/lib/app/http/app_http_client.dart b/lib/app/http/app_http_client.dart index 614aced..253a3eb 100644 --- a/lib/app/http/app_http_client.dart +++ b/lib/app/http/app_http_client.dart @@ -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'; diff --git a/lib/app/theme/theme_notifier.dart b/lib/app/theme/theme_notifier.dart index 3b09daa..fdc6f19 100644 --- a/lib/app/theme/theme_notifier.dart +++ b/lib/app/theme/theme_notifier.dart @@ -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 diff --git a/lib/app/ui_kit/app_box.dart b/lib/app/ui_kit/app_box.dart index bfd5f91..cc9482d 100644 --- a/lib/app/ui_kit/app_box.dart +++ b/lib/app/ui_kit/app_box.dart @@ -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} diff --git a/lib/di/di_base_repo.dart b/lib/di/di_base_repo.dart index f7f100f..70b6c3d 100644 --- a/lib/di/di_base_repo.dart +++ b/lib/di/di_base_repo.dart @@ -1,6 +1,17 @@ -/// Миксин репозитория в приложении. -/// Каждый интерфейс репозитория в приложении должен подмешивать текущий класс +/// {@template di_base_repo} +/// Базовый миксин для всех репозиториев в приложении. +/// +/// Предоставляет общую функциональность для всех репозиториев: +/// - Уникальное наименование репозитория +/// - Базовую структуру для DI контейнера +/// +/// Каждый репозиторий в приложении должен использовать этот миксин +/// для обеспечения совместимости с системой зависимостей. +/// {@endtemplate} mixin class DiBaseRepo { - /// Наименование репозитория + /// {@macro di_base_repo} + DiBaseRepo(); + + /// Наименование репозитория для идентификации в DI контейнере String get name => 'DiBaseRepo'; } diff --git a/lib/di/di_container.dart b/lib/di/di_container.dart index 76b6244..08c49de 100644 --- a/lib/di/di_container.dart +++ b/lib/di/di_container.dart @@ -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'; diff --git a/lib/di/di_repositories.dart b/lib/di/di_repositories.dart index 289ae17..5083e74 100644 --- a/lib/di/di_repositories.dart +++ b/lib/di/di_repositories.dart @@ -13,7 +13,7 @@ import 'package:friflex_starter/features/profile/data/repository/profile_reposit import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart'; /// Список названий моковых репозиториев, которые должны быть подменены -/// для использования в сборке stage окружения +/// для использования в сборке stage окружения. /// /// Для того, чтобы репозиторий был автоматически подменен на моковый в stage /// сборке, необходимо в этом списке указать название мокового репозитория, @@ -25,12 +25,24 @@ import 'package:friflex_starter/features/profile/domain/repository/i_profile_rep /// ``` final List _mockReposToSwitch = []; -/// Класс для инициализации репозиториев в приложении +/// {@template di_repositories} +/// Класс для инициализации и управления репозиториями приложения. /// -/// По умолчанию репозиторию присваивается моковая реализация. -/// В зависимости от окружения либо выполняется подмена репозиторий, -/// либо используется моковый. +/// Отвечает за: +/// - Инициализацию репозиториев для работы с данными +/// - Автоматическое переключение между моковыми и реальными репозиториями +/// - Уведомление о прогрессе инициализации +/// - Обработку ошибок инициализации репозиториев +/// +/// Стратегия инициализации по окружениям: +/// - dev: всегда используются моковые репозитории +/// - prod: всегда используются реальные репозитории +/// - stage: используются моковые репозитории из списка _mockReposToSwitch +/// {@endtemplate} final class DiRepositories { + /// {@macro di_repositories} + DiRepositories(); + /// Интерфейс для работы с репозиторием авторизации late final IAuthRepository authRepository; @@ -40,18 +52,24 @@ final class DiRepositories { /// Интерфейс для работы с репозиторием профиля late final IProfileRepository profileRepository; - /// Метод для инициализации репозиториев в приложении + /// Метод для инициализации репозиториев в приложении. /// /// Принимает: - /// - [onProgress] - обратный вызов при прогрессе - /// - [diContainer] - контейнер зависимостей + /// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации + /// - [diContainer] - контейнер зависимостей с конфигурацией приложения + /// - [onError] - обратный вызов для обработки ошибок инициализации + /// + /// Последовательность инициализации: + /// 1. Инициализация репозитория авторизации + /// 2. Инициализация репозитория главного сервиса + /// 3. Инициализация репозитория профиля void init({ required OnProgress onProgress, required OnError onError, required DiContainer diContainer, }) { try { - //Инициализация репозитория авторизации + // Инициализация репозитория авторизации authRepository = _lazyInitRepo( mockFactory: AuthMockRepository.new, mainFactory: () => AuthRepository( @@ -125,9 +143,13 @@ final class DiRepositories { /// В зависимости от окружения инициализируется моковый или сетевой репозиторий. /// /// Принимает: - /// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами - /// - [mainFactory] - функция - фабрика для инициализации основного репозиторий - /// - [onProgress] - обратный вызов при прогрессе + /// - [mockFactory] - функция-фабрика для инициализации мокового репозитория + /// - [mainFactory] - функция-фабрика для инициализации основного репозитория + /// - [onProgress] - обратный вызов для уведомления о прогрессе + /// - [environment] - окружение приложения для определения стратегии инициализации + /// + /// Возвращает: + /// - Экземпляр репозитория в зависимости от окружения T _lazyInitRepo({ required AppEnv environment, required T Function() mainFactory, diff --git a/lib/di/di_services.dart b/lib/di/di_services.dart index de1c714..9c6eec2 100644 --- a/lib/di/di_services.dart +++ b/lib/di/di_services.dart @@ -3,20 +3,35 @@ 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; - /// Метод для инициализации репозиториев в приложении + /// Метод для инициализации сервисов в приложении. /// /// Принимает: - /// - [onProgress] - обратный вызов при прогрессе - /// - [diContainer] - контейнер зависимостей - /// - [onError] - обратный вызов при ошибке + /// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации + /// - [diContainer] - контейнер зависимостей с конфигурацией приложения + /// - [onError] - обратный вызов для обработки ошибок инициализации + /// + /// Последовательность инициализации: + /// 1. Инициализация сервиса путей (AppPathProvider) + /// 2. Инициализация защищенного хранилища (AppSecureStorage) void init({ required OnProgress onProgress, required OnError onError, diff --git a/lib/features/auth/presentation/screens/auth_screen.dart b/lib/features/auth/presentation/screens/auth_screen.dart index 2386c4d..e9ea3c0 100644 --- a/lib/features/auth/presentation/screens/auth_screen.dart +++ b/lib/features/auth/presentation/screens/auth_screen.dart @@ -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 diff --git a/lib/features/debug/screens/components_screen.dart b/lib/features/debug/screens/components_screen.dart index 6736715..8ae0514 100644 --- a/lib/features/debug/screens/components_screen.dart +++ b/lib/features/debug/screens/components_screen.dart @@ -2,18 +2,33 @@ 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'; -/// {@template ComponentsScreen} -/// Экран для демонстрации компонентов приложения. +/// {@template components_screen} +/// Экран для демонстрации и тестирования компонентов приложения. +/// +/// Отвечает за: +/// - Демонстрацию различных типов снекбаров (ошибка, успех, информация) +/// - Тестирование кастомных UI компонентов +/// - Предоставление примеров использования компонентов +/// - Валидацию корректности работы компонентов /// {@endtemplate} class ComponentsScreen extends StatefulWidget { - /// {@macro ComponentsScreen} + /// {@macro components_screen} const ComponentsScreen({super.key}); @override State createState() => _ComponentsScreenState(); } +/// {@template components_screen_state} +/// Состояние экрана компонентов. +/// +/// Управляет отображением различных типов снекбаров +/// и демонстрирует их функциональность. +/// {@endtemplate} class _ComponentsScreenState extends State { + /// {@macro components_screen_state} + _ComponentsScreenState(); + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/features/debug/screens/icons_screen.dart b/lib/features/debug/screens/icons_screen.dart index e4e6ed0..b7951b9 100644 --- a/lib/features/debug/screens/icons_screen.dart +++ b/lib/features/debug/screens/icons_screen.dart @@ -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 diff --git a/lib/features/debug/screens/lang_screen.dart b/lib/features/debug/screens/lang_screen.dart index b8d8dc9..d255f8d 100644 --- a/lib/features/debug/screens/lang_screen.dart +++ b/lib/features/debug/screens/lang_screen.dart @@ -4,11 +4,17 @@ 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 diff --git a/lib/features/debug/screens/theme_screen.dart b/lib/features/debug/screens/theme_screen.dart index bc36220..7d52fb8 100644 --- a/lib/features/debug/screens/theme_screen.dart +++ b/lib/features/debug/screens/theme_screen.dart @@ -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 diff --git a/lib/features/debug/screens/tokens_screen.dart b/lib/features/debug/screens/tokens_screen.dart index 3c39b67..20b6ec2 100644 --- a/lib/features/debug/screens/tokens_screen.dart +++ b/lib/features/debug/screens/tokens_screen.dart @@ -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 diff --git a/lib/features/debug/screens/ui_kit_screen.dart b/lib/features/debug/screens/ui_kit_screen.dart index 929f8a9..efb8598 100644 --- a/lib/features/debug/screens/ui_kit_screen.dart +++ b/lib/features/debug/screens/ui_kit_screen.dart @@ -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 diff --git a/lib/features/profile/domain/bloc/profile_bloc.dart b/lib/features/profile/domain/bloc/profile_bloc.dart index e53ffcf..1fe03fc 100644 --- a/lib/features/profile/domain/bloc/profile_bloc.dart +++ b/lib/features/profile/domain/bloc/profile_bloc.dart @@ -5,10 +5,16 @@ 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 { + /// {@macro profile_bloc} ProfileBloc(this._profileRepository) : super(ProfileInitialState()) { - // Вам необходимо добавлять только - // один обработчик событий в конструкторе + // Регистрируем обработчики событий в конструкторе on((event, emit) async { if (event is ProfileFetchProfileEvent) { await _fetchProfile(event, emit); @@ -16,8 +22,19 @@ class ProfileBloc extends Bloc { }); } + /// Репозиторий для работы с данными профиля final IProfileRepository _profileRepository; + /// Метод для загрузки данных профиля пользователя. + /// + /// Принимает: + /// - [event] - событие с ID пользователя для загрузки + /// - [emit] - функция для эмиссии состояний + /// + /// Последовательность состояний: + /// 1. ProfileWaitingState - начало загрузки + /// 2. ProfileSuccessState - успешная загрузка с данными + /// 3. ProfileErrorState - ошибка загрузки с сообщением Future _fetchProfile( ProfileFetchProfileEvent event, Emitter emit, diff --git a/lib/features/profile/presentation/screens/profile_screen.dart b/lib/features/profile/presentation/screens/profile_screen.dart index 081e943..de29130 100644 --- a/lib/features/profile/presentation/screens/profile_screen.dart +++ b/lib/features/profile/presentation/screens/profile_screen.dart @@ -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 diff --git a/lib/features/root/root_screen.dart b/lib/features/root/root_screen.dart index f97332f..ec3497f 100644 --- a/lib/features/root/root_screen.dart +++ b/lib/features/root/root_screen.dart @@ -4,15 +4,21 @@ import 'package:friflex_starter/app/app_env.dart'; import 'package:friflex_starter/features/debug/debug_routes.dart'; import 'package:go_router/go_router.dart'; -/// Класс для реализации корневой страницы приложения +/// {@template root_screen} +/// Корневой экран приложения с навигационной структурой. +/// +/// Отвечает за: +/// - Отображение основного навигационного интерфейса +/// - Управление переключением между основными разделами приложения +/// - Отображение кнопки отладки в не-продакшн окружениях +/// - Интеграцию с GoRouter для навигации +/// {@endtemplate} class RootScreen extends StatelessWidget { - /// Создает корневую страницу приложения - /// - /// Принимает: - /// - [navigationShell] - текущая ветка навигации + /// {@macro root_screen} const RootScreen({required this.navigationShell, super.key}); - /// Текущая ветка навигации + /// Текущая ветка навигации от GoRouter + /// Содержит информацию о текущем состоянии навигации final StatefulNavigationShell navigationShell; @override @@ -29,7 +35,7 @@ class RootScreen extends StatelessWidget { body: navigationShell, bottomNavigationBar: BottomNavigationBar( items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'), BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'), ], currentIndex: navigationShell.currentIndex, diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index cd0b6e2..9f82f24 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -33,12 +33,12 @@ class $AssetsFontsGen { /// List of all assets List get values => [ - montserratBold, - montserratExtraBold, - montserratMedium, - montserratRegular, - montserratSemiBold - ]; + montserratBold, + montserratExtraBold, + montserratMedium, + montserratRegular, + montserratSemiBold, + ]; } class $AssetsIconsGen { @@ -71,17 +71,11 @@ class Assets { } 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; @@ -135,7 +129,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 +143,7 @@ class SvgGenImage { } class LottieGenImage { - const LottieGenImage( - this._assetName, { - this.flavors = const {}, - }); + const LottieGenImage(this._assetName, {this.flavors = const {}}); final String _assetName; final Set flavors; @@ -168,11 +160,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, diff --git a/lib/l10n/localization_notifier.dart b/lib/l10n/localization_notifier.dart index 51fac24..bd83fa7 100644 --- a/lib/l10n/localization_notifier.dart +++ b/lib/l10n/localization_notifier.dart @@ -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(); diff --git a/test/components/app_snackbar_test.dart b/test/components/app_snackbar_test.dart index ccdede5..6f101a9 100644 --- a/test/components/app_snackbar_test.dart +++ b/test/components/app_snackbar_test.dart @@ -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(find.byType(Positioned)); - expect(initialPosition.top ?? 0, lessThan(0)); + // Проверяем начальное состояние + await tester.pump(); + final initialPosition = tester.widget( + find.byType(Positioned), + ); + expect(initialPosition.top ?? 0, lessThan(0)); - // Проверяем промежуточное состояние - await tester.pump(const Duration(milliseconds: 150)); - final middlePosition = tester.widget(find.byType(Positioned)); - expect(middlePosition.top ?? 0, greaterThan(initialPosition.top ?? 0)); + // Проверяем промежуточное состояние + await tester.pump(const Duration(milliseconds: 150)); + final middlePosition = tester.widget( + find.byType(Positioned), + ); + expect( + middlePosition.top ?? 0, + greaterThan(initialPosition.top ?? 0), + ); - // Проверяем конечное состояние - await tester.pump(const Duration(milliseconds: 150)); - final finalPosition = tester.widget(find.byType(Positioned)); - expect(finalPosition.top ?? 0, greaterThan(0)); - }); + // Проверяем конечное состояние + await tester.pump(const Duration(milliseconds: 150)); + final finalPosition = tester.widget( + 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) {