mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-22 09:30:45 +00:00
refactor(app): Обновить описание и структуру файлов конфигурации, улучшить документацию (#14)
Co-authored-by: PetrovY <y.petrov@friflex.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
/// Экземпляр хранилища данных
|
||||
|
||||
@@ -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<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
|
||||
@@ -83,6 +104,8 @@ class _AppState extends State<App> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Метод для перезапуска инициализации зависимостей
|
||||
/// Вызывается при ошибках инициализации для повторной попытки
|
||||
void _retryInit() {
|
||||
setState(() {
|
||||
_initFuture = widget.initDependencies();
|
||||
@@ -90,9 +113,17 @@ class _AppState extends State<App> {
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template app_internal}
|
||||
/// Внутренний виджет приложения, отображающий основной интерфейс
|
||||
/// после успешной инициализации зависимостей.
|
||||
///
|
||||
/// Настраивает MaterialApp с роутером, темами и локализацией.
|
||||
/// {@endtemplate}
|
||||
class _App extends StatelessWidget {
|
||||
/// {@macro app_internal}
|
||||
const _App({required this.router});
|
||||
|
||||
/// Роутер приложения для навигации
|
||||
final GoRouter router;
|
||||
|
||||
@override
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -24,11 +24,13 @@ final class _Dev {
|
||||
4081271699,
|
||||
];
|
||||
|
||||
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
|
||||
@@ -65,11 +67,13 @@ final class _Prod {
|
||||
655048645,
|
||||
];
|
||||
|
||||
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,
|
||||
@@ -85,11 +89,13 @@ final class _Prod {
|
||||
3044498279,
|
||||
];
|
||||
|
||||
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
|
||||
@@ -128,11 +134,13 @@ final class _Stage {
|
||||
568662398,
|
||||
];
|
||||
|
||||
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,
|
||||
@@ -150,9 +158,11 @@ final class _Stage {
|
||||
1192880631,
|
||||
];
|
||||
|
||||
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 +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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<String> _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<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
@@ -125,9 +143,13 @@ final class DiRepositories {
|
||||
/// В зависимости от окружения инициализируется моковый или сетевой репозиторий.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
||||
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [mockFactory] - функция-фабрика для инициализации мокового репозитория
|
||||
/// - [mainFactory] - функция-фабрика для инициализации основного репозитория
|
||||
/// - [onProgress] - обратный вызов для уведомления о прогрессе
|
||||
/// - [environment] - окружение приложения для определения стратегии инициализации
|
||||
///
|
||||
/// Возвращает:
|
||||
/// - Экземпляр репозитория в зависимости от окружения
|
||||
T _lazyInitRepo<T extends DiBaseRepo>({
|
||||
required AppEnv environment,
|
||||
required T Function() mainFactory,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ProfileEvent, ProfileState> {
|
||||
/// {@macro profile_bloc}
|
||||
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
|
||||
// Вам необходимо добавлять только
|
||||
// один обработчик событий в конструкторе
|
||||
// Регистрируем обработчики событий в конструкторе
|
||||
on<ProfileEvent>((event, emit) async {
|
||||
if (event is ProfileFetchProfileEvent) {
|
||||
await _fetchProfile(event, emit);
|
||||
@@ -16,8 +22,19 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Репозиторий для работы с данными профиля
|
||||
final IProfileRepository _profileRepository;
|
||||
|
||||
/// Метод для загрузки данных профиля пользователя.
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [event] - событие с ID пользователя для загрузки
|
||||
/// - [emit] - функция для эмиссии состояний
|
||||
///
|
||||
/// Последовательность состояний:
|
||||
/// 1. ProfileWaitingState - начало загрузки
|
||||
/// 2. ProfileSuccessState - успешная загрузка с данными
|
||||
/// 3. ProfileErrorState - ошибка загрузки с сообщением
|
||||
Future<void> _fetchProfile(
|
||||
ProfileFetchProfileEvent event,
|
||||
Emitter<ProfileState> emit,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>[
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
|
||||
],
|
||||
currentIndex: navigationShell.currentIndex,
|
||||
|
||||
@@ -33,12 +33,12 @@ class $AssetsFontsGen {
|
||||
|
||||
/// List of all assets
|
||||
List<String> 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<String> 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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user