mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 19:52:17 +00:00
Compare commits
6 Commits
feat/add-t
...
feat/add-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5f80e7d9 | ||
|
|
fb92795b67 | ||
|
|
24bf652319 | ||
|
|
98630f744f | ||
|
|
ba5fdba9be | ||
|
|
427a821e5d |
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
# 🚀 Friflex Starter - Корпоративный шаблон
|
# 🚀 Friflex Flutter Starter - Корпоративный шаблон
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/app_path_provider.dart';
|
export 'src/app_path_provider.dart';
|
||||||
export 'src/app_secure_storage.dart';
|
export 'src/app_secure_storage.dart';
|
||||||
|
export 'src/app_url_launcher.dart';
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class AppPathProvider implements IPathProvider {
|
|||||||
/// Наименование сервиса
|
/// Наименование сервиса
|
||||||
static const name = 'AuroraAppPathProvider';
|
static const name = 'AuroraAppPathProvider';
|
||||||
|
|
||||||
|
String get nameImpl => AppPathProvider.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAppDocumentsDirectoryPath() async {
|
Future<String> getAppDocumentsDirectoryPath() async {
|
||||||
return (await getApplicationDocumentsDirectory()).path;
|
return (await getApplicationDocumentsDirectory()).path;
|
||||||
|
|||||||
@@ -15,11 +15,15 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
FlutterSecureStorageAurora.setSecret(secretKey);
|
FlutterSecureStorageAurora.setSecret(secretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const name = 'AuroraAppSecureStorage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppSecureStorage.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String secretKey;
|
final String secretKey;
|
||||||
|
|
||||||
static const name = 'AuroraAppSecureStorage';
|
|
||||||
|
|
||||||
/// Экземпляр хранилища данных
|
/// Экземпляр хранилища данных
|
||||||
final _box = const FlutterSecureStorage();
|
final _box = const FlutterSecureStorage();
|
||||||
|
|
||||||
@@ -47,7 +51,4 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
await _box.write(key: key, value: value);
|
await _box.write(key: key, value: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get nameImpl => AppSecureStorage.name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
|
|
||||||
|
/// {@template app_url_launcher}
|
||||||
|
/// Класс для Аврора реализации сервиса работы с URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AppUrlLauncher implements IUrlLauncher {
|
||||||
|
/// {@macro app_url_launcher}
|
||||||
|
AppUrlLauncher();
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const String name = 'AuroraAppUrlLauncher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppUrlLauncher.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLaunchUrl(Uri url) async {
|
||||||
|
return url_launcher.canLaunchUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launchUrl(Uri url) async {
|
||||||
|
return url_launcher.launchUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
name: app_services
|
name: app_services
|
||||||
description: "Google сервисы для приложения"
|
description: "Аврора ОС сервисы для приложения"
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.0
|
sdk: '>=3.16.2 <4.0.0'
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -18,13 +17,11 @@ dependencies:
|
|||||||
url: https://gitlab.com/omprussia/flutter/flutter-community-plugins/flutter_secure_storage_aurora.git
|
url: https://gitlab.com/omprussia/flutter/flutter-community-plugins/flutter_secure_storage_aurora.git
|
||||||
ref: aurora-0.5.3
|
ref: aurora-0.5.3
|
||||||
|
|
||||||
# для работы с путями в хранилища
|
# Зависимости для работы с путями (плагин встроен в sdk flutter 3.27.3)
|
||||||
path_provider: 2.1.4
|
path_provider: 2.1.5
|
||||||
path_provider_aurora:
|
|
||||||
git:
|
# Зависимости для работы с открытием ссылок (плагин встроен в sdk flutter 3.27.3)
|
||||||
url: https://gitlab.com/omprussia/flutter/packages.git
|
url_launcher: 6.3.1
|
||||||
ref: aurora-path_provider_aurora-0.6.0
|
|
||||||
path: packages/path_provider_aurora
|
|
||||||
|
|
||||||
# Обязательные интерфейсы
|
# Обязательные интерфейсы
|
||||||
i_app_services:
|
i_app_services:
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/app_path_provider.dart';
|
export 'src/app_path_provider.dart';
|
||||||
export 'src/app_secure_storage.dart';
|
export 'src/app_secure_storage.dart';
|
||||||
|
export 'src/app_url_launcher.dart';
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ class AppPathProvider implements IPathProvider {
|
|||||||
/// Наименование сервиса
|
/// Наименование сервиса
|
||||||
static const name = 'BaseAppPathProvider';
|
static const name = 'BaseAppPathProvider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppPathProvider.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAppDocumentsDirectoryPath() async {
|
Future<String> getAppDocumentsDirectoryPath() async {
|
||||||
return (await getApplicationDocumentsDirectory()).path;
|
return (await getApplicationDocumentsDirectory()).path;
|
||||||
|
|||||||
@@ -2,17 +2,24 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||||||
import 'package:i_app_services/i_app_services.dart';
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
/// {@template app_secure_storage}
|
/// {@template app_secure_storage}
|
||||||
/// Класс для Aurora реализации сервис по работе с защищенным хранилищем
|
/// Класс для базовой реализации сервиса работы с защищенным хранилищем.
|
||||||
/// [secretKey] - ключ для шифрования данных, если нужен
|
///
|
||||||
|
/// Использует flutter_secure_storage для безопасного хранения данных.
|
||||||
|
/// Поддерживает все основные операции с защищенным хранилищем.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
final class AppSecureStorage implements ISecureStorage {
|
final class AppSecureStorage implements ISecureStorage {
|
||||||
|
/// {@macro app_secure_storage}
|
||||||
AppSecureStorage({this.secretKey});
|
AppSecureStorage({this.secretKey});
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const name = 'BaseAppSecureStorage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppSecureStorage.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String? secretKey;
|
final String? secretKey;
|
||||||
|
|
||||||
static const name = 'BaseAppSecureStorage';
|
|
||||||
|
|
||||||
/// Экземпляр хранилища данных
|
/// Экземпляр хранилища данных
|
||||||
final _box = const FlutterSecureStorage();
|
final _box = const FlutterSecureStorage();
|
||||||
|
|
||||||
@@ -40,7 +47,4 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
await _box.write(key: key, value: value);
|
await _box.write(key: key, value: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get nameImpl => AppSecureStorage.name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal file
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
|
|
||||||
|
/// {@template app_url_launcher}
|
||||||
|
/// Класс для базовой реализации сервиса работы с URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AppUrlLauncher implements IUrlLauncher {
|
||||||
|
/// {@macro app_url_launcher}
|
||||||
|
AppUrlLauncher();
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const String name = 'BaseAppUrlLauncher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppUrlLauncher.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLaunchUrl(Uri url) async {
|
||||||
|
return url_launcher.canLaunchUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launchUrl(Uri url) async {
|
||||||
|
return url_launcher.launchUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ dependencies:
|
|||||||
# для работы с путями в хранилища
|
# для работы с путями в хранилища
|
||||||
path_provider: 2.1.5
|
path_provider: 2.1.5
|
||||||
|
|
||||||
|
# Зависимости для сервиса внешних ссылок
|
||||||
|
url_launcher: 6.3.1
|
||||||
|
|
||||||
# Обязательные интерфейсы
|
# Обязательные интерфейсы
|
||||||
i_app_services:
|
i_app_services:
|
||||||
path: ../../i_app_services
|
path: ../../i_app_services
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/i_path_provider.dart';
|
export 'src/i_path_provider.dart';
|
||||||
export 'src/i_secure_storage.dart';
|
export 'src/i_secure_storage.dart';
|
||||||
|
export 'src/i_url_launcher.dart';
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/// Класс для описания интерфейса сервиса
|
/// Класс для описания интерфейса сервиса для получения пути хранения файлов
|
||||||
/// для получения пути хранения файлов
|
|
||||||
abstract interface class IPathProvider {
|
abstract interface class IPathProvider {
|
||||||
/// Наименования интерфейса
|
/// Наименования интерфейса
|
||||||
static const name = 'IPathProvider';
|
static const name = 'IPathProvider';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
/// Получение path на внутренне хранилище приложения
|
/// Получение path на внутренне хранилище приложения
|
||||||
Future<String?> getAppDocumentsDirectoryPath();
|
Future<String?> getAppDocumentsDirectoryPath();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/// Класс интерфейса для работы с защищенным хранилищем
|
/// Класс для описания интерфейса для работы с защищенным хранилищем
|
||||||
abstract interface class ISecureStorage {
|
abstract interface class ISecureStorage {
|
||||||
/// Описывает обязательные параметры имплементаций
|
/// Описывает обязательные параметры имплементаций
|
||||||
///
|
///
|
||||||
@@ -6,14 +6,17 @@ abstract interface class ISecureStorage {
|
|||||||
/// - [secretKey] - секретный ключ для шифрования данных
|
/// - [secretKey] - секретный ключ для шифрования данных
|
||||||
const ISecureStorage._({required this.secretKey});
|
const ISecureStorage._({required this.secretKey});
|
||||||
|
|
||||||
|
/// Наименования интерфейса
|
||||||
|
static const name = 'ISecureStorage';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
/// Секретный ключ для шифрования данных
|
/// Секретный ключ для шифрования данных
|
||||||
/// Нужен, если надо передать ключ в реализацию
|
/// Нужен, если надо передать ключ в реализацию
|
||||||
/// например, в Aurora
|
/// например, в Aurora
|
||||||
final String? secretKey;
|
final String? secretKey;
|
||||||
|
|
||||||
/// Наименования интерфейса
|
|
||||||
static const name = 'ISecureStorage';
|
|
||||||
|
|
||||||
/// Метод для получения значения из защищенного хранилища
|
/// Метод для получения значения из защищенного хранилища
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
@@ -41,6 +44,4 @@ abstract interface class ISecureStorage {
|
|||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [key] - ключ
|
/// - [key] - ключ
|
||||||
Future<bool> containsKey(String key);
|
Future<bool> containsKey(String key);
|
||||||
|
|
||||||
String get nameImpl;
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal file
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// Класс для описания интерфейса сервиса для запуска URL
|
||||||
|
abstract interface class IUrlLauncher {
|
||||||
|
/// Наименования интерфейса
|
||||||
|
static const name = 'IUrlLauncher';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
|
/// Метод для проверки возможности запуска ссылки
|
||||||
|
///
|
||||||
|
/// - [url] - ссылка для проверки
|
||||||
|
Future<bool> canLaunchUrl(Uri url);
|
||||||
|
|
||||||
|
/// Метод для запуска ссылки
|
||||||
|
///
|
||||||
|
/// - [url] - ссылка для запуска
|
||||||
|
Future<bool> launchUrl(Uri url);
|
||||||
|
}
|
||||||
@@ -12,22 +12,43 @@ import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
|||||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Класс приложения
|
/// {@template app}
|
||||||
|
/// Главный виджет приложения, управляющий инициализацией зависимостей
|
||||||
|
/// и отображением основного интерфейса приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Инициализацию зависимостей приложения
|
||||||
|
/// - Отображение экрана загрузки во время инициализации
|
||||||
|
/// - Обработку ошибок инициализации
|
||||||
|
/// - Настройку провайдеров для темы и локализации
|
||||||
|
/// {@endtemplate}
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
|
/// {@macro app}
|
||||||
const App({required this.router, required this.initDependencies, super.key});
|
const App({required this.router, required this.initDependencies, super.key});
|
||||||
|
|
||||||
/// Роутер приложения
|
/// Роутер приложения для навигации между экранами
|
||||||
final GoRouter router;
|
final GoRouter router;
|
||||||
|
|
||||||
/// Функция для инициализации зависимостей
|
/// Функция для инициализации зависимостей приложения
|
||||||
|
/// Возвращает Future с контейнером зависимостей
|
||||||
final Future<DiContainer> Function() initDependencies;
|
final Future<DiContainer> Function() initDependencies;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<App> createState() => _AppState();
|
State<App> createState() => _AppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// {@template app_state}
|
||||||
|
/// Состояние главного виджета приложения.
|
||||||
|
///
|
||||||
|
/// Управляет процессом инициализации зависимостей и отображением
|
||||||
|
/// соответствующих экранов в зависимости от состояния инициализации.
|
||||||
|
/// {@endtemplate}
|
||||||
class _AppState extends State<App> {
|
class _AppState extends State<App> {
|
||||||
|
/// {@macro app_state}
|
||||||
|
_AppState();
|
||||||
|
|
||||||
/// Мутабельная Future для инициализации зависимостей
|
/// Мутабельная Future для инициализации зависимостей
|
||||||
|
/// Позволяет перезапускать инициализацию при ошибках
|
||||||
late Future<DiContainer> _initFuture;
|
late Future<DiContainer> _initFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -83,6 +104,8 @@ class _AppState extends State<App> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Метод для перезапуска инициализации зависимостей
|
||||||
|
/// Вызывается при ошибках инициализации для повторной попытки
|
||||||
void _retryInit() {
|
void _retryInit() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_initFuture = widget.initDependencies();
|
_initFuture = widget.initDependencies();
|
||||||
@@ -90,9 +113,17 @@ class _AppState extends State<App> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// {@template app_internal}
|
||||||
|
/// Внутренний виджет приложения, отображающий основной интерфейс
|
||||||
|
/// после успешной инициализации зависимостей.
|
||||||
|
///
|
||||||
|
/// Настраивает MaterialApp с роутером, темами и локализацией.
|
||||||
|
/// {@endtemplate}
|
||||||
class _App extends StatelessWidget {
|
class _App extends StatelessWidget {
|
||||||
|
/// {@macro app_internal}
|
||||||
const _App({required this.router});
|
const _App({required this.router});
|
||||||
|
|
||||||
|
/// Роутер приложения для навигации
|
||||||
final GoRouter router;
|
final GoRouter router;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,12 +1,45 @@
|
|||||||
import 'package:envied/envied.dart';
|
import 'package:envied/envied.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/app_env.dart';
|
||||||
|
|
||||||
part 'app_config.g.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')
|
@Envied(name: 'Dev', path: 'env/dev.env')
|
||||||
class AppConfigDev implements IAppConfig {
|
class AppConfigDev implements IAppConfig {
|
||||||
|
/// {@macro app_config_dev}
|
||||||
|
AppConfigDev();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppEnv get env => AppEnv.dev;
|
AppEnv get env => AppEnv.dev;
|
||||||
|
|
||||||
@@ -22,9 +55,17 @@ class AppConfigDev implements IAppConfig {
|
|||||||
final String secretKey = _Dev.secretKey;
|
final String secretKey = _Dev.secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Класс для реализации конфигурации с продакшн данными
|
/// {@template app_config_prod}
|
||||||
|
/// Класс для реализации конфигурации приложения в продакшн режиме.
|
||||||
|
///
|
||||||
|
/// Использует переменные окружения из файла env/prod.env.
|
||||||
|
/// Предназначен для финальной сборки приложения.
|
||||||
|
/// {@endtemplate}
|
||||||
@Envied(name: 'Prod', path: 'env/prod.env')
|
@Envied(name: 'Prod', path: 'env/prod.env')
|
||||||
class AppConfigProd implements IAppConfig {
|
class AppConfigProd implements IAppConfig {
|
||||||
|
/// {@macro app_config_prod}
|
||||||
|
AppConfigProd();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppEnv get env => AppEnv.prod;
|
AppEnv get env => AppEnv.prod;
|
||||||
|
|
||||||
@@ -40,9 +81,17 @@ class AppConfigProd implements IAppConfig {
|
|||||||
final String secretKey = _Prod.secretKey;
|
final String secretKey = _Prod.secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Класс для реализации конфигурации с стейдж данными
|
/// {@template app_config_stage}
|
||||||
|
/// Класс для реализации конфигурации приложения в стейдж режиме.
|
||||||
|
///
|
||||||
|
/// Использует переменные окружения из файла env/stage.env.
|
||||||
|
/// Предназначен для тестирования в среде, близкой к продакшн.
|
||||||
|
/// {@endtemplate}
|
||||||
@Envied(name: 'Stage', path: 'env/stage.env')
|
@Envied(name: 'Stage', path: 'env/stage.env')
|
||||||
class AppConfigStage implements IAppConfig {
|
class AppConfigStage implements IAppConfig {
|
||||||
|
/// {@macro app_config_stage}
|
||||||
|
AppConfigStage();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppEnv get env => AppEnv.stage;
|
AppEnv get env => AppEnv.stage;
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ final class _Dev {
|
|||||||
4081271699,
|
4081271699,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
static final String secretKey = String.fromCharCodes(
|
||||||
_envieddatasecretKey.length,
|
List<int>.generate(
|
||||||
(int i) => i,
|
_envieddatasecretKey.length,
|
||||||
growable: false,
|
(int i) => i,
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
growable: false,
|
||||||
|
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
@@ -65,11 +67,13 @@ final class _Prod {
|
|||||||
655048645,
|
655048645,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
static final String baseUrl = String.fromCharCodes(
|
||||||
_envieddatabaseUrl.length,
|
List<int>.generate(
|
||||||
(int i) => i,
|
_envieddatabaseUrl.length,
|
||||||
growable: false,
|
(int i) => i,
|
||||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
growable: false,
|
||||||
|
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
||||||
|
);
|
||||||
|
|
||||||
static const List<int> _enviedkeysecretKey = <int>[
|
static const List<int> _enviedkeysecretKey = <int>[
|
||||||
359753139,
|
359753139,
|
||||||
@@ -85,11 +89,13 @@ final class _Prod {
|
|||||||
3044498279,
|
3044498279,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
static final String secretKey = String.fromCharCodes(
|
||||||
_envieddatasecretKey.length,
|
List<int>.generate(
|
||||||
(int i) => i,
|
_envieddatasecretKey.length,
|
||||||
growable: false,
|
(int i) => i,
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
growable: false,
|
||||||
|
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
@@ -128,11 +134,13 @@ final class _Stage {
|
|||||||
568662398,
|
568662398,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
static final String baseUrl = String.fromCharCodes(
|
||||||
_envieddatabaseUrl.length,
|
List<int>.generate(
|
||||||
(int i) => i,
|
_envieddatabaseUrl.length,
|
||||||
growable: false,
|
(int i) => i,
|
||||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
growable: false,
|
||||||
|
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
||||||
|
);
|
||||||
|
|
||||||
static const List<int> _enviedkeysecretKey = <int>[
|
static const List<int> _enviedkeysecretKey = <int>[
|
||||||
2132342089,
|
2132342089,
|
||||||
@@ -150,9 +158,11 @@ final class _Stage {
|
|||||||
1192880631,
|
1192880631,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
static final String secretKey = String.fromCharCodes(
|
||||||
_envieddatasecretKey.length,
|
List<int>.generate(
|
||||||
(int i) => i,
|
_envieddatasecretKey.length,
|
||||||
growable: false,
|
(int i) => i,
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[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,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
|
||||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||||
import 'package:friflex_starter/di/di_container.dart';
|
import 'package:friflex_starter/di/di_container.dart';
|
||||||
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
import 'package:friflex_starter/l10n/gen/app_localizations.dart';
|
||||||
@@ -11,9 +10,6 @@ extension AppContextExt on BuildContext {
|
|||||||
/// Метод для получения экземпляра DIContainer
|
/// Метод для получения экземпляра DIContainer
|
||||||
DiContainer get di => read<DiContainer>();
|
DiContainer get di => read<DiContainer>();
|
||||||
|
|
||||||
/// Геттер для получения цветовой схемы
|
|
||||||
AppColors get colors => Theme.of(this).extension<AppColors>()!;
|
|
||||||
|
|
||||||
/// Геттер для получения темы
|
/// Геттер для получения темы
|
||||||
ThemeNotifier get theme => read<ThemeNotifier>();
|
ThemeNotifier get theme => read<ThemeNotifier>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
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/app/http/i_http_client.dart';
|
||||||
|
|
||||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';
|
||||||
|
|
||||||
|
part 'app_colors_scheme.tailor.dart';
|
||||||
|
|
||||||
/// {@template app_colors}
|
/// {@template app_colors}
|
||||||
/// Класс, реализующий расширение для добавления токенов в цветовую схему
|
/// Класс, реализующий расширение для добавления токенов в цветовую схему
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class AppColors extends ThemeExtension<AppColors> with DiagnosticableTreeMixin {
|
@TailorMixin(themeGetter: ThemeGetter.onBuildContext)
|
||||||
|
class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
|
||||||
/// {@macro app_colors}
|
/// {@macro app_colors}
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
@@ -23,18 +26,23 @@ class AppColors extends ThemeExtension<AppColors> with DiagnosticableTreeMixin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// Цвет тестовый
|
/// Цвет тестовый
|
||||||
|
@override
|
||||||
final Color testColor;
|
final Color testColor;
|
||||||
|
|
||||||
/// Цвет элемента текста
|
/// Цвет элемента текста
|
||||||
|
@override
|
||||||
final Color itemTextColor;
|
final Color itemTextColor;
|
||||||
|
|
||||||
/// Цвет фона снекбара ошибки
|
/// Цвет фона снекбара ошибки
|
||||||
|
@override
|
||||||
final Color errorSnackbarBackground;
|
final Color errorSnackbarBackground;
|
||||||
|
|
||||||
/// Цвет фона снекбара успеха
|
/// Цвет фона снекбара успеха
|
||||||
|
@override
|
||||||
final Color successSnackbarBackground;
|
final Color successSnackbarBackground;
|
||||||
|
|
||||||
/// Цвет фона снекбара информации
|
/// Цвет фона снекбара информации
|
||||||
|
@override
|
||||||
final Color infoSnackbarBackground;
|
final Color infoSnackbarBackground;
|
||||||
|
|
||||||
/// Цвета светлой темы
|
/// Цвета светлой темы
|
||||||
@@ -54,50 +62,4 @@ class AppColors extends ThemeExtension<AppColors> with DiagnosticableTreeMixin {
|
|||||||
infoSnackbarBackground: const Color.fromARGB(255, 35, 147, 178),
|
infoSnackbarBackground: const Color.fromARGB(255, 35, 147, 178),
|
||||||
itemTextColor: Colors.white,
|
itemTextColor: Colors.white,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
|
||||||
ThemeExtension<AppColors> copyWith({
|
|
||||||
Color? testColor,
|
|
||||||
Color? errorSnackbarBackground,
|
|
||||||
Color? successSnackbarBackground,
|
|
||||||
Color? infoSnackbarBackground,
|
|
||||||
Color? itemTextColor,
|
|
||||||
}) => AppColors(
|
|
||||||
testColor: testColor ?? this.testColor,
|
|
||||||
errorSnackbarBackground:
|
|
||||||
errorSnackbarBackground ?? this.errorSnackbarBackground,
|
|
||||||
successSnackbarBackground:
|
|
||||||
successSnackbarBackground ?? this.successSnackbarBackground,
|
|
||||||
infoSnackbarBackground:
|
|
||||||
infoSnackbarBackground ?? this.infoSnackbarBackground,
|
|
||||||
itemTextColor: itemTextColor ?? this.itemTextColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ThemeExtension<AppColors> lerp(
|
|
||||||
covariant ThemeExtension<AppColors>? other,
|
|
||||||
double t,
|
|
||||||
) {
|
|
||||||
if (other is! AppColors) return this;
|
|
||||||
|
|
||||||
return AppColors(
|
|
||||||
testColor: Color.lerp(testColor, other.testColor, t)!,
|
|
||||||
errorSnackbarBackground: Color.lerp(
|
|
||||||
errorSnackbarBackground,
|
|
||||||
other.errorSnackbarBackground,
|
|
||||||
t,
|
|
||||||
)!,
|
|
||||||
successSnackbarBackground: Color.lerp(
|
|
||||||
successSnackbarBackground,
|
|
||||||
other.successSnackbarBackground,
|
|
||||||
t,
|
|
||||||
)!,
|
|
||||||
infoSnackbarBackground: Color.lerp(
|
|
||||||
infoSnackbarBackground,
|
|
||||||
other.infoSnackbarBackground,
|
|
||||||
t,
|
|
||||||
)!,
|
|
||||||
itemTextColor: Color.lerp(itemTextColor, other.itemTextColor, t)!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
102
lib/app/theme/app_colors_scheme.tailor.dart
Normal file
102
lib/app/theme/app_colors_scheme.tailor.dart
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'app_colors_scheme.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TailorAnnotationsGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
mixin _$AppColorsTailorMixin on ThemeExtension<AppColors> {
|
||||||
|
Color get testColor;
|
||||||
|
Color get itemTextColor;
|
||||||
|
Color get errorSnackbarBackground;
|
||||||
|
Color get successSnackbarBackground;
|
||||||
|
Color get infoSnackbarBackground;
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppColors copyWith({
|
||||||
|
Color? testColor,
|
||||||
|
Color? itemTextColor,
|
||||||
|
Color? errorSnackbarBackground,
|
||||||
|
Color? successSnackbarBackground,
|
||||||
|
Color? infoSnackbarBackground,
|
||||||
|
}) {
|
||||||
|
return AppColors(
|
||||||
|
testColor: testColor ?? this.testColor,
|
||||||
|
itemTextColor: itemTextColor ?? this.itemTextColor,
|
||||||
|
errorSnackbarBackground:
|
||||||
|
errorSnackbarBackground ?? this.errorSnackbarBackground,
|
||||||
|
successSnackbarBackground:
|
||||||
|
successSnackbarBackground ?? this.successSnackbarBackground,
|
||||||
|
infoSnackbarBackground:
|
||||||
|
infoSnackbarBackground ?? this.infoSnackbarBackground,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppColors lerp(covariant ThemeExtension<AppColors>? other, double t) {
|
||||||
|
if (other is! AppColors) return this as AppColors;
|
||||||
|
return AppColors(
|
||||||
|
testColor: Color.lerp(testColor, other.testColor, t)!,
|
||||||
|
itemTextColor: Color.lerp(itemTextColor, other.itemTextColor, t)!,
|
||||||
|
errorSnackbarBackground: Color.lerp(
|
||||||
|
errorSnackbarBackground,
|
||||||
|
other.errorSnackbarBackground,
|
||||||
|
t,
|
||||||
|
)!,
|
||||||
|
successSnackbarBackground: Color.lerp(
|
||||||
|
successSnackbarBackground,
|
||||||
|
other.successSnackbarBackground,
|
||||||
|
t,
|
||||||
|
)!,
|
||||||
|
infoSnackbarBackground: Color.lerp(
|
||||||
|
infoSnackbarBackground,
|
||||||
|
other.infoSnackbarBackground,
|
||||||
|
t,
|
||||||
|
)!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is AppColors &&
|
||||||
|
const DeepCollectionEquality().equals(testColor, other.testColor) &&
|
||||||
|
const DeepCollectionEquality().equals(
|
||||||
|
itemTextColor,
|
||||||
|
other.itemTextColor,
|
||||||
|
) &&
|
||||||
|
const DeepCollectionEquality().equals(
|
||||||
|
errorSnackbarBackground,
|
||||||
|
other.errorSnackbarBackground,
|
||||||
|
) &&
|
||||||
|
const DeepCollectionEquality().equals(
|
||||||
|
successSnackbarBackground,
|
||||||
|
other.successSnackbarBackground,
|
||||||
|
) &&
|
||||||
|
const DeepCollectionEquality().equals(
|
||||||
|
infoSnackbarBackground,
|
||||||
|
other.infoSnackbarBackground,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return Object.hash(
|
||||||
|
runtimeType.hashCode,
|
||||||
|
const DeepCollectionEquality().hash(testColor),
|
||||||
|
const DeepCollectionEquality().hash(itemTextColor),
|
||||||
|
const DeepCollectionEquality().hash(errorSnackbarBackground),
|
||||||
|
const DeepCollectionEquality().hash(successSnackbarBackground),
|
||||||
|
const DeepCollectionEquality().hash(infoSnackbarBackground),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppColorsBuildContext on BuildContext {
|
||||||
|
AppColors get appColors => Theme.of(this).extension<AppColors>()!;
|
||||||
|
}
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
/// Тип функции для построения виджета с учетом темы
|
||||||
typedef ThemeBuilder = Widget Function();
|
typedef ThemeBuilder = Widget Function();
|
||||||
|
|
||||||
/// Виджет для подписки на изменение темы приложения
|
/// {@template theme_consumer}
|
||||||
|
/// Виджет для подписки на изменения темы приложения.
|
||||||
|
///
|
||||||
|
/// Автоматически перестраивает дочерние виджеты при изменении темы,
|
||||||
|
/// обеспечивая реактивность интерфейса к изменениям настроек темы.
|
||||||
|
/// {@endtemplate}
|
||||||
class ThemeConsumer extends StatelessWidget {
|
class ThemeConsumer extends StatelessWidget {
|
||||||
|
/// {@macro theme_consumer}
|
||||||
const ThemeConsumer({required this.builder, super.key});
|
const ThemeConsumer({required this.builder, super.key});
|
||||||
|
|
||||||
|
/// Функция для построения виджета с учетом текущей темы
|
||||||
final ThemeBuilder builder;
|
final ThemeBuilder builder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -19,12 +27,29 @@ class ThemeConsumer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Класс для управления темой приложения
|
/// {@template theme_notifier}
|
||||||
|
/// Класс для управления темой приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Хранение текущего режима темы (светлая/темная/системная)
|
||||||
|
/// - Уведомление подписчиков об изменениях темы
|
||||||
|
/// - Переключение между режимами темы
|
||||||
|
/// {@endtemplate}
|
||||||
final class ThemeNotifier extends ChangeNotifier {
|
final class ThemeNotifier extends ChangeNotifier {
|
||||||
|
/// {@macro theme_notifier}
|
||||||
|
ThemeNotifier();
|
||||||
|
|
||||||
|
/// Текущий режим темы приложения
|
||||||
|
/// По умолчанию используется системная тема
|
||||||
ThemeMode _themeMode = ThemeMode.system;
|
ThemeMode _themeMode = ThemeMode.system;
|
||||||
|
|
||||||
|
/// Получение текущего режима темы
|
||||||
ThemeMode get themeMode => _themeMode;
|
ThemeMode get themeMode => _themeMode;
|
||||||
|
|
||||||
|
/// Метод для переключения темы приложения.
|
||||||
|
///
|
||||||
|
/// Переключает между светлой и темной темой.
|
||||||
|
/// Если текущая тема светлая, переключает на темную и наоборот.
|
||||||
void changeTheme() {
|
void changeTheme() {
|
||||||
_themeMode = _themeMode == ThemeMode.light
|
_themeMode = _themeMode == ThemeMode.light
|
||||||
? ThemeMode.dark
|
? ThemeMode.dark
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
/// {@template h_box}
|
/// {@template h_box}
|
||||||
/// HBox виджет для вертикального отступа (Надстройка над SizedBox)
|
/// Виджет для создания вертикального отступа.
|
||||||
|
///
|
||||||
|
/// Надстройка над SizedBox, предназначенная для создания
|
||||||
|
/// отступов по вертикали с более понятным названием.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class HBox extends SizedBox {
|
class HBox extends SizedBox {
|
||||||
/// {@macro h_box}
|
/// {@macro h_box}
|
||||||
@@ -9,7 +12,10 @@ class HBox extends SizedBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// {@template w_box}
|
/// {@template w_box}
|
||||||
/// WBox виджет для вертикального отступа (Надстройка над SizedBox)
|
/// Виджет для создания горизонтального отступа.
|
||||||
|
///
|
||||||
|
/// Надстройка над SizedBox, предназначенная для создания
|
||||||
|
/// отступов по горизонтали с более понятным названием.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class WBox extends SizedBox {
|
class WBox extends SizedBox {
|
||||||
/// {@macro w_box}
|
/// {@macro w_box}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
|
||||||
/// {@template app_snackbar}
|
/// {@template app_snackbar}
|
||||||
@@ -264,9 +264,9 @@ class _AppSnackBarState extends State<AppSnackBar>
|
|||||||
/// [TypeSnackBar.error] - цвет ошибки
|
/// [TypeSnackBar.error] - цвет ошибки
|
||||||
Color _getBackgroundColor(TypeSnackBar type) {
|
Color _getBackgroundColor(TypeSnackBar type) {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
TypeSnackBar.success => context.colors.successSnackbarBackground,
|
TypeSnackBar.success => context.appColors.successSnackbarBackground,
|
||||||
TypeSnackBar.error => context.colors.errorSnackbarBackground,
|
TypeSnackBar.error => context.appColors.errorSnackbarBackground,
|
||||||
TypeSnackBar.info => context.colors.infoSnackbarBackground,
|
TypeSnackBar.info => context.appColors.infoSnackbarBackground,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
/// Миксин репозитория в приложении.
|
/// {@template di_base_repo}
|
||||||
/// Каждый интерфейс репозитория в приложении должен подмешивать текущий класс
|
/// Базовый миксин для всех репозиториев в приложении.
|
||||||
|
///
|
||||||
|
/// Предоставляет общую функциональность для всех репозиториев:
|
||||||
|
/// - Уникальное наименование репозитория
|
||||||
|
/// - Базовую структуру для DI контейнера
|
||||||
|
///
|
||||||
|
/// Каждый репозиторий в приложении должен использовать этот миксин
|
||||||
|
/// для обеспечения совместимости с системой зависимостей.
|
||||||
|
/// {@endtemplate}
|
||||||
mixin class DiBaseRepo {
|
mixin class DiBaseRepo {
|
||||||
/// Наименование репозитория
|
/// {@macro di_base_repo}
|
||||||
|
DiBaseRepo();
|
||||||
|
|
||||||
|
/// Наименование репозитория для идентификации в DI контейнере
|
||||||
String get name => 'DiBaseRepo';
|
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/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/app_env.dart';
|
||||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
import 'package:friflex_starter/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';
|
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||||
|
|
||||||
/// Список названий моковых репозиториев, которые должны быть подменены
|
/// Список названий моковых репозиториев, которые должны быть подменены
|
||||||
/// для использования в сборке stage окружения
|
/// для использования в сборке stage окружения.
|
||||||
///
|
///
|
||||||
/// Для того, чтобы репозиторий был автоматически подменен на моковый в stage
|
/// Для того, чтобы репозиторий был автоматически подменен на моковый в stage
|
||||||
/// сборке, необходимо в этом списке указать название мокового репозитория,
|
/// сборке, необходимо в этом списке указать название мокового репозитория,
|
||||||
@@ -25,12 +25,24 @@ import 'package:friflex_starter/features/profile/domain/repository/i_profile_rep
|
|||||||
/// ```
|
/// ```
|
||||||
final List<String> _mockReposToSwitch = [];
|
final List<String> _mockReposToSwitch = [];
|
||||||
|
|
||||||
/// Класс для инициализации репозиториев в приложении
|
/// {@template di_repositories}
|
||||||
|
/// Класс для инициализации и управления репозиториями приложения.
|
||||||
///
|
///
|
||||||
/// По умолчанию репозиторию присваивается моковая реализация.
|
/// Отвечает за:
|
||||||
/// В зависимости от окружения либо выполняется подмена репозиторий,
|
/// - Инициализацию репозиториев для работы с данными
|
||||||
/// либо используется моковый.
|
/// - Автоматическое переключение между моковыми и реальными репозиториями
|
||||||
|
/// - Уведомление о прогрессе инициализации
|
||||||
|
/// - Обработку ошибок инициализации репозиториев
|
||||||
|
///
|
||||||
|
/// Стратегия инициализации по окружениям:
|
||||||
|
/// - dev: всегда используются моковые репозитории
|
||||||
|
/// - prod: всегда используются реальные репозитории
|
||||||
|
/// - stage: используются моковые репозитории из списка _mockReposToSwitch
|
||||||
|
/// {@endtemplate}
|
||||||
final class DiRepositories {
|
final class DiRepositories {
|
||||||
|
/// {@macro di_repositories}
|
||||||
|
DiRepositories();
|
||||||
|
|
||||||
/// Интерфейс для работы с репозиторием авторизации
|
/// Интерфейс для работы с репозиторием авторизации
|
||||||
late final IAuthRepository authRepository;
|
late final IAuthRepository authRepository;
|
||||||
|
|
||||||
@@ -40,18 +52,24 @@ final class DiRepositories {
|
|||||||
/// Интерфейс для работы с репозиторием профиля
|
/// Интерфейс для работы с репозиторием профиля
|
||||||
late final IProfileRepository profileRepository;
|
late final IProfileRepository profileRepository;
|
||||||
|
|
||||||
/// Метод для инициализации репозиториев в приложении
|
/// Метод для инициализации репозиториев в приложении.
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [onProgress] - обратный вызов при прогрессе
|
/// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации
|
||||||
/// - [diContainer] - контейнер зависимостей
|
/// - [diContainer] - контейнер зависимостей с конфигурацией приложения
|
||||||
|
/// - [onError] - обратный вызов для обработки ошибок инициализации
|
||||||
|
///
|
||||||
|
/// Последовательность инициализации:
|
||||||
|
/// 1. Инициализация репозитория авторизации
|
||||||
|
/// 2. Инициализация репозитория главного сервиса
|
||||||
|
/// 3. Инициализация репозитория профиля
|
||||||
void init({
|
void init({
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
required OnError onError,
|
required OnError onError,
|
||||||
required DiContainer diContainer,
|
required DiContainer diContainer,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
//Инициализация репозитория авторизации
|
// Инициализация репозитория авторизации
|
||||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||||
mockFactory: AuthMockRepository.new,
|
mockFactory: AuthMockRepository.new,
|
||||||
mainFactory: () => AuthRepository(
|
mainFactory: () => AuthRepository(
|
||||||
@@ -125,9 +143,13 @@ final class DiRepositories {
|
|||||||
/// В зависимости от окружения инициализируется моковый или сетевой репозиторий.
|
/// В зависимости от окружения инициализируется моковый или сетевой репозиторий.
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
/// - [mockFactory] - функция-фабрика для инициализации мокового репозитория
|
||||||
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
/// - [mainFactory] - функция-фабрика для инициализации основного репозитория
|
||||||
/// - [onProgress] - обратный вызов при прогрессе
|
/// - [onProgress] - обратный вызов для уведомления о прогрессе
|
||||||
|
/// - [environment] - окружение приложения для определения стратегии инициализации
|
||||||
|
///
|
||||||
|
/// Возвращает:
|
||||||
|
/// - Экземпляр репозитория в зависимости от окружения
|
||||||
T _lazyInitRepo<T extends DiBaseRepo>({
|
T _lazyInitRepo<T extends DiBaseRepo>({
|
||||||
required AppEnv environment,
|
required AppEnv environment,
|
||||||
required T Function() mainFactory,
|
required T Function() mainFactory,
|
||||||
|
|||||||
@@ -3,20 +3,39 @@ import 'package:friflex_starter/di/di_container.dart';
|
|||||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||||
import 'package:i_app_services/i_app_services.dart';
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
/// Класс для инициализации сервисов
|
/// {@template di_services}
|
||||||
|
/// Класс для инициализации и управления сервисами приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Инициализацию сервисов для работы с путями
|
||||||
|
/// - Инициализацию сервисов для работы с защищенным хранилищем
|
||||||
|
/// - Уведомление о прогрессе инициализации
|
||||||
|
/// - Обработку ошибок инициализации сервисов
|
||||||
|
/// {@endtemplate}
|
||||||
final class DiServices {
|
final class DiServices {
|
||||||
/// Сервис для работы с путями
|
/// {@macro di_services}
|
||||||
|
DiServices();
|
||||||
|
|
||||||
|
/// Сервис для работы с путями файловой системы
|
||||||
late final IPathProvider pathProvider;
|
late final IPathProvider pathProvider;
|
||||||
|
|
||||||
/// Сервис для работы с локальным хранилищем
|
/// Сервис для работы с защищенным локальным хранилищем
|
||||||
late final ISecureStorage secureStorage;
|
late final ISecureStorage secureStorage;
|
||||||
|
|
||||||
/// Метод для инициализации репозиториев в приложении
|
/// Сервис для работы с URL
|
||||||
|
late final IUrlLauncher urlLauncher;
|
||||||
|
|
||||||
|
/// Метод для инициализации сервисов в приложении.
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [onProgress] - обратный вызов при прогрессе
|
/// - [onProgress] - обратный вызов для уведомления о прогрессе инициализации
|
||||||
/// - [diContainer] - контейнер зависимостей
|
/// - [diContainer] - контейнер зависимостей с конфигурацией приложения
|
||||||
/// - [onError] - обратный вызов при ошибке
|
/// - [onError] - обратный вызов для обработки ошибок инициализации
|
||||||
|
///
|
||||||
|
/// Последовательность инициализации:
|
||||||
|
/// 1. Инициализация сервиса путей (AppPathProvider)
|
||||||
|
/// 2. Инициализация защищенного хранилища (AppSecureStorage)
|
||||||
|
/// 3. Инициализация сервиса URL (AppUrlLauncherService)
|
||||||
void init({
|
void init({
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
required OnError onError,
|
required OnError onError,
|
||||||
@@ -29,14 +48,19 @@ final class DiServices {
|
|||||||
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
secureStorage = AppSecureStorage(
|
secureStorage = AppSecureStorage(secretKey: diContainer.appConfig.secretKey);
|
||||||
secretKey: diContainer.appConfig.secretKey,
|
|
||||||
);
|
|
||||||
onProgress(AppSecureStorage.name);
|
onProgress(AppSecureStorage.name);
|
||||||
} on Object catch (error, stackTrace) {
|
} on Object catch (error, stackTrace) {
|
||||||
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
urlLauncher = AppUrlLauncher();
|
||||||
|
onProgress(AppUrlLauncher.name);
|
||||||
|
} on Object catch (error, stackTrace) {
|
||||||
|
onError('Ошибка инициализации ${IUrlLauncher.name}', error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
onProgress('Инициализация сервисов завершена!');
|
onProgress('Инициализация сервисов завершена!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template AuthScreen}
|
/// {@template auth_screen}
|
||||||
|
/// Экран авторизации пользователя.
|
||||||
///
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Отображение формы входа в приложение
|
||||||
|
/// - Обработку процесса аутентификации
|
||||||
|
/// - Навигацию после успешной авторизации
|
||||||
|
///
|
||||||
|
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class AuthScreen extends StatelessWidget {
|
class AuthScreen extends StatelessWidget {
|
||||||
/// {@macro AuthScreen}
|
/// {@macro auth_screen}
|
||||||
const AuthScreen({super.key});
|
const AuthScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import 'package:friflex_starter/features/debug/screens/components_screen.dart';
|
|||||||
import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/path_provider_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/secure_storage_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/url_launcher_screen.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// {@template debug_routes}
|
/// {@template debug_routes}
|
||||||
@@ -20,6 +23,9 @@ abstract final class DebugRoutes {
|
|||||||
static const String themeScreenName = 'theme_screen';
|
static const String themeScreenName = 'theme_screen';
|
||||||
static const String langScreenName = 'lang_screen';
|
static const String langScreenName = 'lang_screen';
|
||||||
static const String componentsScreenName = 'components_screen';
|
static const String componentsScreenName = 'components_screen';
|
||||||
|
static const String pathProviderScreenName = 'path_provider_screen';
|
||||||
|
static const String secureStorageScreenName = 'secure_storage_screen';
|
||||||
|
static const String urlLauncherScreenName = 'url_launcher_screen';
|
||||||
|
|
||||||
/// Пути к экранам
|
/// Пути к экранам
|
||||||
static const String debugScreenPath = '/debug';
|
static const String debugScreenPath = '/debug';
|
||||||
@@ -29,6 +35,9 @@ abstract final class DebugRoutes {
|
|||||||
static const String themeScreenPath = 'debug/theme';
|
static const String themeScreenPath = 'debug/theme';
|
||||||
static const String langScreenPath = 'debug/lang';
|
static const String langScreenPath = 'debug/lang';
|
||||||
static const String componentsScreenPath = 'debug/components';
|
static const String componentsScreenPath = 'debug/components';
|
||||||
|
static const String pathProviderScreenPath = 'debug/path_provider';
|
||||||
|
static const String secureStorageScreenPath = 'debug/secure_storage';
|
||||||
|
static const String urlLauncherScreenPath = 'debug/url_launcher';
|
||||||
|
|
||||||
/// Метод для создания роутов для отладки
|
/// Метод для создания роутов для отладки
|
||||||
///
|
///
|
||||||
@@ -70,6 +79,21 @@ abstract final class DebugRoutes {
|
|||||||
name: componentsScreenName,
|
name: componentsScreenName,
|
||||||
builder: (context, state) => const ComponentsScreen(),
|
builder: (context, state) => const ComponentsScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: pathProviderScreenPath,
|
||||||
|
name: pathProviderScreenName,
|
||||||
|
builder: (context, state) => const PathProviderScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: secureStorageScreenPath,
|
||||||
|
name: secureStorageScreenName,
|
||||||
|
builder: (context, state) => const SecureStorageScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: urlLauncherScreenPath,
|
||||||
|
name: urlLauncherScreenName,
|
||||||
|
builder: (context, state) => const UrlLauncherScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_box.dart';
|
||||||
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
||||||
|
|
||||||
/// {@template ComponentsScreen}
|
/// {@template components_screen}
|
||||||
/// Экран для демонстрации компонентов приложения.
|
/// Экран для демонстрации и тестирования компонентов приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Демонстрацию различных типов снекбаров (ошибка, успех, информация)
|
||||||
|
/// - Тестирование кастомных UI компонентов
|
||||||
|
/// - Предоставление примеров использования компонентов
|
||||||
|
/// - Валидацию корректности работы компонентов
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ComponentsScreen extends StatefulWidget {
|
class ComponentsScreen extends StatefulWidget {
|
||||||
/// {@macro ComponentsScreen}
|
/// {@macro components_screen}
|
||||||
const ComponentsScreen({super.key});
|
const ComponentsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ComponentsScreen> createState() => _ComponentsScreenState();
|
State<ComponentsScreen> createState() => _ComponentsScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// {@template components_screen_state}
|
||||||
|
/// Состояние экрана компонентов.
|
||||||
|
///
|
||||||
|
/// Управляет отображением различных типов снекбаров
|
||||||
|
/// и демонстрирует их функциональность.
|
||||||
|
/// {@endtemplate}
|
||||||
class _ComponentsScreenState extends State<ComponentsScreen> {
|
class _ComponentsScreenState extends State<ComponentsScreen> {
|
||||||
|
/// {@macro components_screen_state}
|
||||||
|
_ComponentsScreenState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
|
||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -20,9 +20,7 @@ class DebugScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('Окружение: ${context.di.appConfig.env.name}'),
|
Text('Окружение: ${context.di.appConfig.env.name}'),
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
Text(
|
Text('Реализация AppServices: ${context.di.services.secureStorage.nameImpl}'),
|
||||||
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
|
|
||||||
),
|
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -74,14 +72,33 @@ class DebugScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: const Text('Экран компонентов'),
|
child: const Text('Экран компонентов'),
|
||||||
),
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.pathProviderScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Path Provider'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.secureStorageScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.urlLauncherScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Url Launcher'),
|
||||||
|
),
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
const Text('Имитирование ошибок:'),
|
const Text('Имитирование ошибок:'),
|
||||||
const HBox(16),
|
const HBox(16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
throw Exception(
|
throw Exception('Тестовая ошибка Exception для отладки FlutterError');
|
||||||
'Тестовая ошибка Exception для отладки FlutterError',
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Text('Вызывать ошибку FlutterError'),
|
child: const Text('Вызывать ошибку FlutterError'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||||
|
|
||||||
/// {@template IconsScreen}
|
/// {@template icons_screen}
|
||||||
/// Экран для отрисовки иконок
|
/// Экран для отображения всех доступных иконок приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Отображение списка всех SVG иконок из assets/icons/
|
||||||
|
/// - Предоставление возможности просмотра иконок для разработчиков
|
||||||
|
/// - Демонстрацию использования системы генерации ресурсов
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class IconsScreen extends StatelessWidget {
|
class IconsScreen extends StatelessWidget {
|
||||||
/// {@macro IconsScreen}
|
/// {@macro icons_screen}
|
||||||
const IconsScreen({super.key});
|
const IconsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,19 +35,20 @@ class IconsScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Приватный класс для реализации элемента списка иконок
|
/// {@template item_icon}
|
||||||
|
/// Виджет для отображения отдельной иконки в списке.
|
||||||
|
///
|
||||||
|
/// Отображает SVG иконку вместе с её названием файла
|
||||||
|
/// для удобства идентификации в процессе разработки.
|
||||||
|
/// {@endtemplate}
|
||||||
class _ItemIcon extends StatelessWidget {
|
class _ItemIcon extends StatelessWidget {
|
||||||
/// Создает экземпляр элемента списка иконок
|
/// {@macro item_icon}
|
||||||
///
|
|
||||||
/// Принимает:
|
|
||||||
/// - [icon] - иконка
|
|
||||||
/// - [name] - название иконки
|
|
||||||
const _ItemIcon({required this.icon, required this.name});
|
const _ItemIcon({required this.icon, required this.name});
|
||||||
|
|
||||||
/// Иконка
|
/// SVG иконка для отображения
|
||||||
final Widget icon;
|
final Widget icon;
|
||||||
|
|
||||||
/// Название иконки
|
/// Название файла иконки для идентификации
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||||
import 'package:friflex_starter/gen/fonts.gen.dart';
|
import 'package:friflex_starter/gen/fonts.gen.dart';
|
||||||
|
|
||||||
/// {@template LangScreen}
|
/// {@template lang_screen}
|
||||||
/// Экран для отладки языков приложения
|
/// Экран для отладки и тестирования локализации приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Демонстрацию переключения между поддерживаемыми языками
|
||||||
|
/// - Отображение локализованных строк с разными шрифтами
|
||||||
|
/// - Тестирование системы локализации и шрифтов
|
||||||
|
/// - Показ текущего языка приложения
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class LangScreen extends StatelessWidget {
|
class LangScreen extends StatelessWidget {
|
||||||
/// {@macro LangScreen}
|
/// {@macro lang_screen}
|
||||||
const LangScreen({super.key});
|
const LangScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,7 +43,7 @@ class LangScreen extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Тестовое слово bold: ${context.l10n.helloWorld}',
|
'Тестовое слово bold: ${context.l10n.helloWorld}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.colors.testColor,
|
color: context.appColors.testColor,
|
||||||
fontFamily: Assets.fonts.montserratBold,
|
fontFamily: Assets.fonts.montserratBold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -44,7 +51,7 @@ class LangScreen extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Тестовое слово medium: ${context.l10n.helloWorld}',
|
'Тестовое слово medium: ${context.l10n.helloWorld}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.colors.testColor,
|
color: context.appColors.testColor,
|
||||||
fontFamily: FontFamily.montserrat,
|
fontFamily: FontFamily.montserrat,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
147
lib/features/debug/screens/path_provider_screen.dart
Normal file
147
lib/features/debug/screens/path_provider_screen.dart
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template path_provider_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина path_provider.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для получения путей к директориям приложения
|
||||||
|
/// - Демонстрацию содержимого директории файлов приложения
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PathProviderScreen extends StatefulWidget {
|
||||||
|
/// {@macro path_provider_screen}
|
||||||
|
const PathProviderScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PathProviderScreen> createState() => _PathProviderScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PathProviderScreenState extends State<PathProviderScreen> {
|
||||||
|
/// Плагин для работы с путями в приложении
|
||||||
|
late final IPathProvider _pathProvider;
|
||||||
|
|
||||||
|
/// Корневой путь к директории файлов приложения
|
||||||
|
String? _rootPath;
|
||||||
|
|
||||||
|
/// Текущий путь к директории, отображаемой в списке
|
||||||
|
String? _currentPath;
|
||||||
|
|
||||||
|
/// Загрузка файлов
|
||||||
|
Future<List<FileSystemEntity>?>? _loadFilesFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pathProvider = context.di.services.pathProvider;
|
||||||
|
_loadFilesFuture = _initRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Path Provider')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Path Provider: ${context.di.services.pathProvider.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
Text('Содержимое папки документов приложения:'),
|
||||||
|
const HBox(8),
|
||||||
|
Text('Текущий путь:'),
|
||||||
|
const HBox(8),
|
||||||
|
Text(_currentPath ?? ''),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _currentPath != null && _rootPath != null && _currentPath != _rootPath
|
||||||
|
? _goBack
|
||||||
|
: null,
|
||||||
|
child: const Text('Назад'),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder<List<FileSystemEntity>?>(
|
||||||
|
future: _loadFilesFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Ошибка: \\${snapshot.error}'));
|
||||||
|
}
|
||||||
|
final files = snapshot.data;
|
||||||
|
if (files == null) {
|
||||||
|
return const Center(child: Text('Недоступно'));
|
||||||
|
}
|
||||||
|
if (files.isEmpty) {
|
||||||
|
return const Center(child: Text('Папка пуста'));
|
||||||
|
}
|
||||||
|
return ListView(
|
||||||
|
children: files
|
||||||
|
.map(
|
||||||
|
(item) => ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
item is Directory ? Icons.folder : Icons.insert_drive_file,
|
||||||
|
),
|
||||||
|
title: Text(item.path.split(Platform.pathSeparator).last),
|
||||||
|
onTap: item is Directory ? () => _openDir(item.path) : null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для инициализации корневой директории и загрузки её содержимого
|
||||||
|
Future<List<FileSystemEntity>?> _initRoot() async {
|
||||||
|
final dirPath = await _pathProvider.getAppDocumentsDirectoryPath();
|
||||||
|
if (dirPath == null) {
|
||||||
|
setState(() {
|
||||||
|
_rootPath = null;
|
||||||
|
_currentPath = null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final files = Directory(dirPath).listSync();
|
||||||
|
setState(() {
|
||||||
|
_rootPath = dirPath;
|
||||||
|
_currentPath = dirPath;
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для загрузки файлов в указанной директории
|
||||||
|
List<FileSystemEntity>? _loadFiles(String path) {
|
||||||
|
return Directory(path).listSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для открытия директории и загрузки её содержимого
|
||||||
|
void _openDir(String path) async {
|
||||||
|
setState(() {
|
||||||
|
_currentPath = path;
|
||||||
|
_loadFilesFuture = Future.value(_loadFiles(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для перехода к родительской директории
|
||||||
|
void _goBack() async {
|
||||||
|
if (_currentPath == null || _rootPath == null || _currentPath == _rootPath) return;
|
||||||
|
final parent = Directory(_currentPath!).parent.path;
|
||||||
|
if (parent.length < _rootPath!.length) return;
|
||||||
|
final files = _loadFiles(parent);
|
||||||
|
setState(() {
|
||||||
|
_currentPath = parent;
|
||||||
|
_loadFilesFuture = Future.value(files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
125
lib/features/debug/screens/secure_storage_screen.dart
Normal file
125
lib/features/debug/screens/secure_storage_screen.dart
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template secure_storage_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина flutter_secure_storage.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для провеки записи и чтения защищенных данных
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SecureStorageScreen extends StatefulWidget {
|
||||||
|
/// {@macro secure_storage_screen}
|
||||||
|
const SecureStorageScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SecureStorageScreen> createState() => _SecureStorageScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SecureStorageScreenState extends State<SecureStorageScreen> {
|
||||||
|
/// Плагин для работы с защищенным хранилищем
|
||||||
|
late final ISecureStorage _secureStorage;
|
||||||
|
|
||||||
|
/// Контроллер для ввода ключа
|
||||||
|
final TextEditingController _keyController = TextEditingController();
|
||||||
|
|
||||||
|
/// Контроллер для ввода значения
|
||||||
|
final TextEditingController _valueController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_secureStorage = context.di.services.secureStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_keyController.dispose();
|
||||||
|
_valueController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Secure Storage')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Secure Storage: ${context.di.services.secureStorage.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _keyController,
|
||||||
|
onChanged: (value) {
|
||||||
|
_valueController.clear();
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(labelText: 'Ключ'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _valueController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Значение'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleWrite(context),
|
||||||
|
child: const Text('Записать в Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleRead(context),
|
||||||
|
child: const Text('Прочитать из Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleDelete(context),
|
||||||
|
child: const Text('Удалить из Secure Storage'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для записи значения в Secure Storage
|
||||||
|
Future<void> _handleWrite(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
final value = _valueController.text;
|
||||||
|
try {
|
||||||
|
await _secureStorage.write(key, value);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Значение записано в Secure Storage');
|
||||||
|
} on Object catch (e) {
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка записи: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для чтения значения из Secure Storage
|
||||||
|
Future<void> _handleRead(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
try {
|
||||||
|
final value = await _secureStorage.read(key) ?? 'Значение не найдено';
|
||||||
|
_valueController.value = TextEditingValue(text: value);
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка чтения: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для удаления значения из Secure Storage
|
||||||
|
Future<void> _handleDelete(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
try {
|
||||||
|
await _secureStorage.delete(key);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
_valueController.clear();
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Значение удалено из Secure Storage');
|
||||||
|
} on Object catch (e) {
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка удаления: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||||
|
|
||||||
/// {@template ThemeScreen}
|
/// {@template theme_screen}
|
||||||
/// Экран для отладки темы приложения
|
/// Экран для отладки и тестирования темы приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Демонстрацию переключения между светлой и темной темами
|
||||||
|
/// - Отображение тестовых цветов из цветовой схемы
|
||||||
|
/// - Показ текущего режима темы
|
||||||
|
/// - Тестирование системы управления темами
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ThemeScreen extends StatelessWidget {
|
class ThemeScreen extends StatelessWidget {
|
||||||
/// {@macro ThemeScreen}
|
/// {@macro theme_screen}
|
||||||
const ThemeScreen({super.key});
|
const ThemeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colors = context.colors;
|
final colors = context.appColors;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Theme')),
|
appBar: AppBar(title: const Text('Theme')),
|
||||||
body: Center(
|
body: Center(
|
||||||
@@ -25,7 +32,7 @@ class ThemeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ColoredBox(
|
ColoredBox(
|
||||||
color: context.colors.testColor,
|
color: context.appColors.testColor,
|
||||||
child: const SizedBox(height: 100, width: 100),
|
child: const SizedBox(height: 100, width: 100),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template TokensScreen}
|
/// {@template tokens_screen}
|
||||||
/// Экран для отображения токенов
|
/// Экран для отображения и управления токенами аутентификации.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Отображение текущих токенов доступа и обновления
|
||||||
|
/// - Демонстрацию работы с токенами в приложении
|
||||||
|
/// - Тестирование функциональности аутентификации
|
||||||
|
///
|
||||||
|
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class TokensScreen extends StatelessWidget {
|
class TokensScreen extends StatelessWidget {
|
||||||
/// {@macro TokensScreen}
|
/// {@macro tokens_screen}
|
||||||
const TokensScreen({super.key});
|
const TokensScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template UiKitScreen}
|
/// {@template ui_kit_screen}
|
||||||
/// Экран для отрисовки UI Kit
|
/// Экран для демонстрации и тестирования компонентов UI Kit.
|
||||||
/// и тестирования его компонентов.
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Отображение всех доступных компонентов UI Kit
|
||||||
|
/// - Демонстрацию использования кастомных виджетов
|
||||||
|
/// - Тестирование стилей и тем оформления
|
||||||
|
/// - Предоставление примера использования UI компонентов
|
||||||
|
///
|
||||||
|
/// В текущей реализации является заглушкой для будущих компонентов.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class UiKitScreen extends StatelessWidget {
|
class UiKitScreen extends StatelessWidget {
|
||||||
/// {@macro UiKitScreen}
|
/// {@macro ui_kit_screen}
|
||||||
const UiKitScreen({super.key});
|
const UiKitScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
133
lib/features/debug/screens/url_launcher_screen.dart
Normal file
133
lib/features/debug/screens/url_launcher_screen.dart
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template url_launcher_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина url_launcher.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для проверки открытия URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class UrlLauncherScreen extends StatefulWidget {
|
||||||
|
/// {@macro url_launcher_screen}
|
||||||
|
const UrlLauncherScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UrlLauncherScreen> createState() => _UrlLauncherScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UrlLauncherScreenState extends State<UrlLauncherScreen> {
|
||||||
|
/// Плагин для работы с URL
|
||||||
|
late final IUrlLauncher _urlLauncher;
|
||||||
|
|
||||||
|
/// Контроллер для ввода URL для открытия
|
||||||
|
final TextEditingController _urlController = TextEditingController();
|
||||||
|
|
||||||
|
/// Контроллер для ввода URL для проверки возможности открытия
|
||||||
|
final TextEditingController _canOpenUrlController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_urlLauncher = context.di.services.urlLauncher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_urlController.dispose();
|
||||||
|
_canOpenUrlController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('URL Launcher')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Url Launcher: ${context.di.services.urlLauncher.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _urlController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Введите ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _launchUrl(context),
|
||||||
|
child: const Text('Открыть ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
TextField(
|
||||||
|
controller: _canOpenUrlController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Введите ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _checkCanOpenUrl(context),
|
||||||
|
child: const Text('Проверить возможность открытия'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для открытия URL
|
||||||
|
Future<void> _launchUrl(BuildContext context) async {
|
||||||
|
final url = _urlController.text.trim();
|
||||||
|
if (url.isEmpty) {
|
||||||
|
AppSnackBar.showInfo(context, message: 'Введите ссылку для открытия');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final success = await _urlLauncher.launchUrl(uri);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (!success) {
|
||||||
|
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
|
||||||
|
}
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка при открытии ссылки: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для проверки возможности открытия URL
|
||||||
|
Future<void> _checkCanOpenUrl(BuildContext context) async {
|
||||||
|
final url = _canOpenUrlController.text.trim();
|
||||||
|
if (url.isEmpty) {
|
||||||
|
AppSnackBar.showInfo(context, message: 'Введите ссылку для проверки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final canOpen = await _urlLauncher.canLaunchUrl(uri);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (canOpen) {
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Возможно открыть ссылку: $url');
|
||||||
|
} else {
|
||||||
|
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
|
||||||
|
}
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка при проверке ссылки: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,16 @@ import 'package:friflex_starter/features/profile/domain/repository/i_profile_rep
|
|||||||
part 'profile_event.dart';
|
part 'profile_event.dart';
|
||||||
part 'profile_state.dart';
|
part 'profile_state.dart';
|
||||||
|
|
||||||
|
/// {@template profile_bloc}
|
||||||
|
/// Bloc для управления состоянием профиля пользователя.
|
||||||
|
///
|
||||||
|
/// Обрабатывает события загрузки данных профиля и управляет
|
||||||
|
/// соответствующими состояниями (загрузка, успех, ошибка).
|
||||||
|
/// {@endtemplate}
|
||||||
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||||
|
/// {@macro profile_bloc}
|
||||||
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
|
ProfileBloc(this._profileRepository) : super(ProfileInitialState()) {
|
||||||
// Вам необходимо добавлять только
|
// Регистрируем обработчики событий в конструкторе
|
||||||
// один обработчик событий в конструкторе
|
|
||||||
on<ProfileEvent>((event, emit) async {
|
on<ProfileEvent>((event, emit) async {
|
||||||
if (event is ProfileFetchProfileEvent) {
|
if (event is ProfileFetchProfileEvent) {
|
||||||
await _fetchProfile(event, emit);
|
await _fetchProfile(event, emit);
|
||||||
@@ -16,8 +22,19 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Репозиторий для работы с данными профиля
|
||||||
final IProfileRepository _profileRepository;
|
final IProfileRepository _profileRepository;
|
||||||
|
|
||||||
|
/// Метод для загрузки данных профиля пользователя.
|
||||||
|
///
|
||||||
|
/// Принимает:
|
||||||
|
/// - [event] - событие с ID пользователя для загрузки
|
||||||
|
/// - [emit] - функция для эмиссии состояний
|
||||||
|
///
|
||||||
|
/// Последовательность состояний:
|
||||||
|
/// 1. ProfileWaitingState - начало загрузки
|
||||||
|
/// 2. ProfileSuccessState - успешная загрузка с данными
|
||||||
|
/// 3. ProfileErrorState - ошибка загрузки с сообщением
|
||||||
Future<void> _fetchProfile(
|
Future<void> _fetchProfile(
|
||||||
ProfileFetchProfileEvent event,
|
ProfileFetchProfileEvent event,
|
||||||
Emitter<ProfileState> emit,
|
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/app/app_context_ext.dart';
|
||||||
import 'package:friflex_starter/features/profile/domain/bloc/profile_bloc.dart';
|
import 'package:friflex_starter/features/profile/domain/bloc/profile_bloc.dart';
|
||||||
|
|
||||||
// Класс экрана, где мы инициализируем ProfileBloc
|
/// {@template profile_screen}
|
||||||
// и вызываем событие ProfileFetchProfileEvent
|
/// Экран профиля пользователя.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Инициализацию ProfileBloc с репозиторием профиля
|
||||||
|
/// - Отображение данных профиля пользователя
|
||||||
|
/// - Обработку состояний загрузки, успеха и ошибок
|
||||||
|
/// {@endtemplate}
|
||||||
class ProfileScreen extends StatelessWidget {
|
class ProfileScreen extends StatelessWidget {
|
||||||
|
/// {@macro profile_screen}
|
||||||
const ProfileScreen({super.key});
|
const ProfileScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final profileRepository = context.di.repositories.profileRepository;
|
final profileRepository = context.di.repositories.profileRepository;
|
||||||
// Здесь мы инициализируем ProfileBloc
|
// Инициализируем ProfileBloc с репозиторием и загружаем данные профиля
|
||||||
// и вызываем событие ProfileFetchProfileEvent
|
|
||||||
// Или любые другие события, которые вам нужны
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
ProfileBloc(profileRepository)
|
ProfileBloc(profileRepository)
|
||||||
@@ -23,8 +28,16 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Виджет, который отображает UI экрана
|
/// {@template profile_screen_view}
|
||||||
|
/// Виджет для отображения UI экрана профиля.
|
||||||
|
///
|
||||||
|
/// Отображает данные профиля в зависимости от состояния ProfileBloc:
|
||||||
|
/// - Индикатор загрузки во время получения данных
|
||||||
|
/// - Данные профиля при успешной загрузке
|
||||||
|
/// - Сообщение об ошибке при неудачной загрузке
|
||||||
|
/// {@endtemplate}
|
||||||
class _ProfileScreenView extends StatelessWidget {
|
class _ProfileScreenView extends StatelessWidget {
|
||||||
|
/// {@macro profile_screen_view}
|
||||||
const _ProfileScreenView();
|
const _ProfileScreenView();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -4,15 +4,21 @@ import 'package:friflex_starter/app/app_env.dart';
|
|||||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Класс для реализации корневой страницы приложения
|
/// {@template root_screen}
|
||||||
|
/// Корневой экран приложения с навигационной структурой.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Отображение основного навигационного интерфейса
|
||||||
|
/// - Управление переключением между основными разделами приложения
|
||||||
|
/// - Отображение кнопки отладки в не-продакшн окружениях
|
||||||
|
/// - Интеграцию с GoRouter для навигации
|
||||||
|
/// {@endtemplate}
|
||||||
class RootScreen extends StatelessWidget {
|
class RootScreen extends StatelessWidget {
|
||||||
/// Создает корневую страницу приложения
|
/// {@macro root_screen}
|
||||||
///
|
|
||||||
/// Принимает:
|
|
||||||
/// - [navigationShell] - текущая ветка навигации
|
|
||||||
const RootScreen({required this.navigationShell, super.key});
|
const RootScreen({required this.navigationShell, super.key});
|
||||||
|
|
||||||
/// Текущая ветка навигации
|
/// Текущая ветка навигации от GoRouter
|
||||||
|
/// Содержит информацию о текущем состоянии навигации
|
||||||
final StatefulNavigationShell navigationShell;
|
final StatefulNavigationShell navigationShell;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -29,7 +35,7 @@ class RootScreen extends StatelessWidget {
|
|||||||
body: navigationShell,
|
body: navigationShell,
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
items: const <BottomNavigationBarItem>[
|
items: const <BottomNavigationBarItem>[
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Главная'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
|
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
|
||||||
],
|
],
|
||||||
currentIndex: navigationShell.currentIndex,
|
currentIndex: navigationShell.currentIndex,
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ class $AssetsFontsGen {
|
|||||||
|
|
||||||
/// List of all assets
|
/// List of all assets
|
||||||
List<String> get values => [
|
List<String> get values => [
|
||||||
montserratBold,
|
montserratBold,
|
||||||
montserratExtraBold,
|
montserratExtraBold,
|
||||||
montserratMedium,
|
montserratMedium,
|
||||||
montserratRegular,
|
montserratRegular,
|
||||||
montserratSemiBold
|
montserratSemiBold,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class $AssetsIconsGen {
|
class $AssetsIconsGen {
|
||||||
@@ -71,17 +71,11 @@ class Assets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SvgGenImage {
|
class SvgGenImage {
|
||||||
const SvgGenImage(
|
const SvgGenImage(this._assetName, {this.size, this.flavors = const {}})
|
||||||
this._assetName, {
|
: _isVecFormat = false;
|
||||||
this.size,
|
|
||||||
this.flavors = const {},
|
|
||||||
}) : _isVecFormat = false;
|
|
||||||
|
|
||||||
const SvgGenImage.vec(
|
const SvgGenImage.vec(this._assetName, {this.size, this.flavors = const {}})
|
||||||
this._assetName, {
|
: _isVecFormat = true;
|
||||||
this.size,
|
|
||||||
this.flavors = const {},
|
|
||||||
}) : _isVecFormat = true;
|
|
||||||
|
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
final Size? size;
|
final Size? size;
|
||||||
@@ -135,7 +129,8 @@ class SvgGenImage {
|
|||||||
placeholderBuilder: placeholderBuilder,
|
placeholderBuilder: placeholderBuilder,
|
||||||
semanticsLabel: semanticsLabel,
|
semanticsLabel: semanticsLabel,
|
||||||
excludeFromSemantics: excludeFromSemantics,
|
excludeFromSemantics: excludeFromSemantics,
|
||||||
colorFilter: colorFilter ??
|
colorFilter:
|
||||||
|
colorFilter ??
|
||||||
(color == null ? null : ColorFilter.mode(color, colorBlendMode)),
|
(color == null ? null : ColorFilter.mode(color, colorBlendMode)),
|
||||||
clipBehavior: clipBehavior,
|
clipBehavior: clipBehavior,
|
||||||
cacheColorFilter: cacheColorFilter,
|
cacheColorFilter: cacheColorFilter,
|
||||||
@@ -148,10 +143,7 @@ class SvgGenImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LottieGenImage {
|
class LottieGenImage {
|
||||||
const LottieGenImage(
|
const LottieGenImage(this._assetName, {this.flavors = const {}});
|
||||||
this._assetName, {
|
|
||||||
this.flavors = const {},
|
|
||||||
});
|
|
||||||
|
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
final Set<String> flavors;
|
final Set<String> flavors;
|
||||||
@@ -168,11 +160,8 @@ class LottieGenImage {
|
|||||||
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
||||||
Key? key,
|
Key? key,
|
||||||
AssetBundle? bundle,
|
AssetBundle? bundle,
|
||||||
Widget Function(
|
Widget Function(BuildContext, Widget, _lottie.LottieComposition?)?
|
||||||
BuildContext,
|
frameBuilder,
|
||||||
Widget,
|
|
||||||
_lottie.LottieComposition?,
|
|
||||||
)? frameBuilder,
|
|
||||||
ImageErrorWidgetBuilder? errorBuilder,
|
ImageErrorWidgetBuilder? errorBuilder,
|
||||||
double? width,
|
double? width,
|
||||||
double? height,
|
double? height,
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
/// Тип функции для построения виджета с учетом локализации
|
||||||
typedef LocalizationBuilder = Widget Function();
|
typedef LocalizationBuilder = Widget Function();
|
||||||
|
|
||||||
/// Виджет для перестройки виджета в зависимости от локализации
|
/// {@template localization_consumer}
|
||||||
|
/// Виджет для подписки на изменения локализации приложения.
|
||||||
|
///
|
||||||
|
/// Автоматически перестраивает дочерние виджеты при изменении языка,
|
||||||
|
/// обеспечивая реактивность интерфейса к изменениям настроек локализации.
|
||||||
|
/// {@endtemplate}
|
||||||
class LocalizationConsumer extends StatelessWidget {
|
class LocalizationConsumer extends StatelessWidget {
|
||||||
|
/// {@macro localization_consumer}
|
||||||
const LocalizationConsumer({required this.builder, super.key});
|
const LocalizationConsumer({required this.builder, super.key});
|
||||||
|
|
||||||
|
/// Функция для построения виджета с учетом текущей локализации
|
||||||
final LocalizationBuilder builder;
|
final LocalizationBuilder builder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -19,12 +27,34 @@ class LocalizationConsumer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Класс для управления локализацией
|
/// {@template localization_notifier}
|
||||||
|
/// Класс для управления локализацией приложения.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Хранение текущей локали приложения
|
||||||
|
/// - Уведомление подписчиков об изменениях языка
|
||||||
|
/// - Переключение между поддерживаемыми языками
|
||||||
|
/// {@endtemplate}
|
||||||
final class LocalizationNotifier extends ChangeNotifier {
|
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;
|
Locale get locale => _locale;
|
||||||
|
|
||||||
|
/// Получение текущего языка в виде кода языка
|
||||||
|
String get language => _locale.languageCode;
|
||||||
|
|
||||||
|
/// Метод для изменения локали приложения.
|
||||||
|
///
|
||||||
|
/// Принимает:
|
||||||
|
/// - [locale] - новая локаль для установки
|
||||||
|
///
|
||||||
|
/// Уведомляет всех подписчиков об изменении локали.
|
||||||
void changeLocal(Locale locale) {
|
void changeLocal(Locale locale) {
|
||||||
_locale = locale;
|
_locale = locale;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|||||||
85
pubspec.lock
85
pubspec.lock
@@ -5,23 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "82.0.0"
|
||||||
_macros:
|
|
||||||
dependency: transitive
|
|
||||||
description: dart
|
|
||||||
source: sdk
|
|
||||||
version: "0.3.3"
|
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "7.4.5"
|
||||||
ansicolor:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -225,10 +220,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
|
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "3.1.0"
|
||||||
dartx:
|
dartx:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -592,14 +587,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
version: "3.3.1"
|
||||||
macros:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: macros
|
|
||||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.3-main.0"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -881,10 +868,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "2.0.0"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.5"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -989,6 +984,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
version: "0.7.4"
|
||||||
|
theme_tailor:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: theme_tailor
|
||||||
|
sha256: ba98be1d04856deef932757a3ca8fa7a5e2a6f96c30466a59c48924eeb608b97
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
theme_tailor_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: theme_tailor_annotation
|
||||||
|
sha256: "0d5ecd13a6a52add2082aa60497179f6093acf482eb69e7fa3a9f37eb990ac34"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
time:
|
time:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1013,6 +1024,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.1"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.16"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1021,6 +1056,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ dependencies:
|
|||||||
|
|
||||||
equatable: 2.0.7
|
equatable: 2.0.7
|
||||||
|
|
||||||
|
theme_tailor_annotation: 3.0.2
|
||||||
|
|
||||||
### основной сервис с интерфейсами
|
### основной сервис с интерфейсами
|
||||||
i_app_services:
|
i_app_services:
|
||||||
path: ./app_services/i_app_services
|
path: ./app_services/i_app_services
|
||||||
@@ -50,6 +52,7 @@ dev_dependencies:
|
|||||||
flutter_gen_runner: 5.10.0
|
flutter_gen_runner: 5.10.0
|
||||||
flutter_gen: 5.10.0
|
flutter_gen: 5.10.0
|
||||||
flutter_lints: 6.0.0
|
flutter_lints: 6.0.0
|
||||||
|
theme_tailor: 3.0.3
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ void main() {
|
|||||||
|
|
||||||
/// Создание мок-темы с правильными стилями текста
|
/// Создание мок-темы с правильными стилями текста
|
||||||
TextTheme createMockTextTheme() {
|
TextTheme createMockTextTheme() {
|
||||||
return const TextTheme(
|
return const TextTheme(bodyMedium: TextStyle(fontSize: 14));
|
||||||
bodyMedium: TextStyle(fontSize: 14),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
@@ -41,7 +39,9 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group('AppSnackBar.showInfo', () {
|
group('AppSnackBar.showInfo', () {
|
||||||
testTester('показывает снекбар с информацией и правильными стилями', (tester) async {
|
testTester('показывает снекбар с информацией и правильными стилями', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
const infoMessage = 'Это просто сообщение';
|
const infoMessage = 'Это просто сообщение';
|
||||||
|
|
||||||
AppSnackBar.showInfo(
|
AppSnackBar.showInfo(
|
||||||
@@ -54,7 +54,7 @@ void main() {
|
|||||||
|
|
||||||
expect(find.byType(AppSnackBar), findsOneWidget);
|
expect(find.byType(AppSnackBar), findsOneWidget);
|
||||||
expect(find.text(infoMessage), findsOneWidget);
|
expect(find.text(infoMessage), findsOneWidget);
|
||||||
|
|
||||||
// Проверяем иконку и её цвет
|
// Проверяем иконку и её цвет
|
||||||
final iconFinder = find.byType(Icon);
|
final iconFinder = find.byType(Icon);
|
||||||
expect(iconFinder, findsOneWidget);
|
expect(iconFinder, findsOneWidget);
|
||||||
@@ -74,7 +74,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar.showError', () {
|
group('AppSnackBar.showError', () {
|
||||||
testTester('показывает снекбар с ошибкой и правильными стилями', (tester) async {
|
testTester('показывает снекбар с ошибкой и правильными стилями', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
const errorMessage = 'Произошла ошибка';
|
const errorMessage = 'Произошла ошибка';
|
||||||
|
|
||||||
AppSnackBar.showError(
|
AppSnackBar.showError(
|
||||||
@@ -158,7 +160,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar.showSuccess', () {
|
group('AppSnackBar.showSuccess', () {
|
||||||
testTester('показывает снекбар с успехом и правильными стилями', (tester) async {
|
testTester('показывает снекбар с успехом и правильными стилями', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
const successMessage = 'Операция выполнена успешно';
|
const successMessage = 'Операция выполнена успешно';
|
||||||
|
|
||||||
AppSnackBar.showSuccess(
|
AppSnackBar.showSuccess(
|
||||||
@@ -186,7 +190,10 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
final decoration = container.decoration as BoxDecoration;
|
final decoration = container.decoration as BoxDecoration;
|
||||||
expect(decoration.color, equals(const Color(0xFF6FB62C))); // Success фон
|
expect(
|
||||||
|
decoration.color,
|
||||||
|
equals(const Color(0xFF6FB62C)),
|
||||||
|
); // Success фон
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('показывает снекбар с кастомной продолжительностью', (
|
testTester('показывает снекбар с кастомной продолжительностью', (
|
||||||
@@ -210,29 +217,41 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar виджет поведение', () {
|
group('AppSnackBar виджет поведение', () {
|
||||||
testTester('показывает анимацию появления с правильной последовательностью', (tester) async {
|
testTester(
|
||||||
const message = 'Тестовое сообщение';
|
'показывает анимацию появления с правильной последовательностью',
|
||||||
|
(tester) async {
|
||||||
|
const message = 'Тестовое сообщение';
|
||||||
|
|
||||||
AppSnackBar.showError(
|
AppSnackBar.showError(
|
||||||
tester.element(find.byType(Scaffold)),
|
tester.element(find.byType(Scaffold)),
|
||||||
message: message,
|
message: message,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Проверяем начальное состояние
|
// Проверяем начальное состояние
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
final initialPosition = tester.widget<Positioned>(find.byType(Positioned));
|
final initialPosition = tester.widget<Positioned>(
|
||||||
expect(initialPosition.top ?? 0, lessThan(0));
|
find.byType(Positioned),
|
||||||
|
);
|
||||||
|
expect(initialPosition.top ?? 0, lessThan(0));
|
||||||
|
|
||||||
// Проверяем промежуточное состояние
|
// Проверяем промежуточное состояние
|
||||||
await tester.pump(const Duration(milliseconds: 150));
|
await tester.pump(const Duration(milliseconds: 150));
|
||||||
final middlePosition = tester.widget<Positioned>(find.byType(Positioned));
|
final middlePosition = tester.widget<Positioned>(
|
||||||
expect(middlePosition.top ?? 0, greaterThan(initialPosition.top ?? 0));
|
find.byType(Positioned),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
middlePosition.top ?? 0,
|
||||||
|
greaterThan(initialPosition.top ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
// Проверяем конечное состояние
|
// Проверяем конечное состояние
|
||||||
await tester.pump(const Duration(milliseconds: 150));
|
await tester.pump(const Duration(milliseconds: 150));
|
||||||
final finalPosition = tester.widget<Positioned>(find.byType(Positioned));
|
final finalPosition = tester.widget<Positioned>(
|
||||||
expect(finalPosition.top ?? 0, greaterThan(0));
|
find.byType(Positioned),
|
||||||
});
|
);
|
||||||
|
expect(finalPosition.top ?? 0, greaterThan(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testTester('закрывается при тапе', (tester) async {
|
testTester('закрывается при тапе', (tester) async {
|
||||||
const message = 'Тап для закрытия';
|
const message = 'Тап для закрытия';
|
||||||
@@ -323,7 +342,9 @@ void main() {
|
|||||||
expect(find.byType(Text), findsAtLeastNWidgets(1));
|
expect(find.byType(Text), findsAtLeastNWidgets(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('имеет правильные отступы и размеры на разных экранах', (tester) async {
|
testTester('имеет правильные отступы и размеры на разных экранах', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
const message = 'Размеры';
|
const message = 'Размеры';
|
||||||
|
|
||||||
// Тестируем на маленьком экране
|
// Тестируем на маленьком экране
|
||||||
@@ -360,7 +381,10 @@ void main() {
|
|||||||
matching: find.byType(Container),
|
matching: find.byType(Container),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(container.constraints?.maxWidth, equals(350)); // Максимальная ширина
|
expect(
|
||||||
|
container.constraints?.maxWidth,
|
||||||
|
equals(350),
|
||||||
|
); // Максимальная ширина
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('имеет правильное скругление углов', (tester) async {
|
testTester('имеет правильное скругление углов', (tester) async {
|
||||||
@@ -465,7 +489,9 @@ void main() {
|
|||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('правильно обрабатывает быстрые последовательные вызовы', (tester) async {
|
testTester('правильно обрабатывает быстрые последовательные вызовы', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3'];
|
const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3'];
|
||||||
|
|
||||||
for (final message in messages) {
|
for (final message in messages) {
|
||||||
|
|||||||
10
tools/switch_services.sh
Normal file
10
tools/switch_services.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
### Скрипт для переключения сервисов для CI/CD
|
||||||
|
TYPE=$1
|
||||||
|
|
||||||
|
if [ -z "$TYPE" ]; then
|
||||||
|
echo "Error: TYPE is not set. Please provide a value."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
yq -i '.dependencies.app_services.path = "app_services/'"$TYPE"'/app_services"' pubspec.yaml
|
||||||
Reference in New Issue
Block a user