mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-06 04:02:17 +00:00
Compare commits
6 Commits
feat/add-d
...
feat/add-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
550a1de182 | ||
|
|
7291a5e514 | ||
|
|
2129e16b85 | ||
|
|
a4d61d4453 | ||
|
|
9179344358 | ||
|
|
f97632e0cd |
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
# 🚀 Friflex Flutter Starter - Корпоративный шаблон
|
# 🚀 Friflex Starter - Корпоративный шаблон
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ 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,8 +11,6 @@ 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,15 +15,11 @@ 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();
|
||||||
|
|
||||||
@@ -51,4 +47,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
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,10 +1,11 @@
|
|||||||
name: app_services
|
name: app_services
|
||||||
description: "Аврора ОС сервисы для приложения"
|
description: "Google сервисы для приложения"
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.16.2 <4.0.0'
|
sdk: ^3.8.0
|
||||||
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
@@ -17,11 +18,13 @@ 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.5
|
path_provider: 2.1.4
|
||||||
|
path_provider_aurora:
|
||||||
# Зависимости для работы с открытием ссылок (плагин встроен в sdk flutter 3.27.3)
|
git:
|
||||||
url_launcher: 6.3.1
|
url: https://gitlab.com/omprussia/flutter/packages.git
|
||||||
|
ref: aurora-path_provider_aurora-0.6.0
|
||||||
|
path: packages/path_provider_aurora
|
||||||
|
|
||||||
# Обязательные интерфейсы
|
# Обязательные интерфейсы
|
||||||
i_app_services:
|
i_app_services:
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ 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,9 +11,6 @@ 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,24 +2,17 @@ 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();
|
||||||
|
|
||||||
@@ -47,4 +40,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
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,9 +19,6 @@ 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,4 +2,3 @@ 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,11 +1,9 @@
|
|||||||
/// Класс для описания интерфейса сервиса для получения пути хранения файлов
|
/// Класс для описания интерфейса сервиса
|
||||||
|
/// для получения пути хранения файлов
|
||||||
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,17 +6,14 @@ 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';
|
||||||
|
|
||||||
/// Метод для получения значения из защищенного хранилища
|
/// Метод для получения значения из защищенного хранилища
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
@@ -44,4 +41,6 @@ abstract interface class ISecureStorage {
|
|||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [key] - ключ
|
/// - [key] - ключ
|
||||||
Future<bool> containsKey(String key);
|
Future<bool> containsKey(String key);
|
||||||
|
|
||||||
|
String get nameImpl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
/// Класс для описания интерфейса сервиса для запуска 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,43 +12,22 @@ 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
|
||||||
@@ -104,8 +83,6 @@ class _AppState extends State<App> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Метод для перезапуска инициализации зависимостей
|
|
||||||
/// Вызывается при ошибках инициализации для повторной попытки
|
|
||||||
void _retryInit() {
|
void _retryInit() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_initFuture = widget.initDependencies();
|
_initFuture = widget.initDependencies();
|
||||||
@@ -113,17 +90,9 @@ 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,45 +1,12 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -55,17 +22,9 @@ 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;
|
||||||
|
|
||||||
@@ -81,17 +40,9 @@ 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,13 +24,11 @@ final class _Dev {
|
|||||||
4081271699,
|
4081271699,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(
|
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||||
List<int>.generate(
|
_envieddatasecretKey.length,
|
||||||
_envieddatasecretKey.length,
|
(int i) => i,
|
||||||
(int i) => i,
|
growable: false,
|
||||||
growable: false,
|
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
@@ -67,13 +65,11 @@ final class _Prod {
|
|||||||
655048645,
|
655048645,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String baseUrl = String.fromCharCodes(
|
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
||||||
List<int>.generate(
|
_envieddatabaseUrl.length,
|
||||||
_envieddatabaseUrl.length,
|
(int i) => i,
|
||||||
(int i) => i,
|
growable: false,
|
||||||
growable: false,
|
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
||||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
|
||||||
);
|
|
||||||
|
|
||||||
static const List<int> _enviedkeysecretKey = <int>[
|
static const List<int> _enviedkeysecretKey = <int>[
|
||||||
359753139,
|
359753139,
|
||||||
@@ -89,13 +85,11 @@ final class _Prod {
|
|||||||
3044498279,
|
3044498279,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(
|
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||||
List<int>.generate(
|
_envieddatasecretKey.length,
|
||||||
_envieddatasecretKey.length,
|
(int i) => i,
|
||||||
(int i) => i,
|
growable: false,
|
||||||
growable: false,
|
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
@@ -134,13 +128,11 @@ final class _Stage {
|
|||||||
568662398,
|
568662398,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String baseUrl = String.fromCharCodes(
|
static final String baseUrl = String.fromCharCodes(List<int>.generate(
|
||||||
List<int>.generate(
|
_envieddatabaseUrl.length,
|
||||||
_envieddatabaseUrl.length,
|
(int i) => i,
|
||||||
(int i) => i,
|
growable: false,
|
||||||
growable: false,
|
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]));
|
||||||
).map((int i) => _envieddatabaseUrl[i] ^ _enviedkeybaseUrl[i]),
|
|
||||||
);
|
|
||||||
|
|
||||||
static const List<int> _enviedkeysecretKey = <int>[
|
static const List<int> _enviedkeysecretKey = <int>[
|
||||||
2132342089,
|
2132342089,
|
||||||
@@ -158,11 +150,9 @@ final class _Stage {
|
|||||||
1192880631,
|
1192880631,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final String secretKey = String.fromCharCodes(
|
static final String secretKey = String.fromCharCodes(List<int>.generate(
|
||||||
List<int>.generate(
|
_envieddatasecretKey.length,
|
||||||
_envieddatasecretKey.length,
|
(int i) => i,
|
||||||
(int i) => i,
|
growable: false,
|
||||||
growable: false,
|
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]));
|
||||||
).map((int i) => _envieddatasecretKey[i] ^ _enviedkeysecretKey[i]),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
lib/app/app_config/i_app_config.dart
Normal file
16
lib/app/app_config/i_app_config.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:friflex_starter/app/app_env.dart';
|
||||||
|
|
||||||
|
/// Класс для описания интерфейса конфигурации
|
||||||
|
abstract interface class IAppConfig {
|
||||||
|
/// Наименование сервиса
|
||||||
|
String get name => 'IAppConfig';
|
||||||
|
|
||||||
|
/// Основной адрес для запросов к API
|
||||||
|
String get baseUrl;
|
||||||
|
|
||||||
|
/// Тип окружения
|
||||||
|
AppEnv get env;
|
||||||
|
|
||||||
|
/// Секретный ключ для шифрования данных
|
||||||
|
String get secretKey;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.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/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,20 +1,12 @@
|
|||||||
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
|
||||||
@@ -27,29 +19,12 @@ 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,10 +1,7 @@
|
|||||||
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}
|
||||||
@@ -12,10 +9,7 @@ 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,17 +1,6 @@
|
|||||||
/// {@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,4 +1,5 @@
|
|||||||
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,24 +25,12 @@ 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;
|
||||||
|
|
||||||
@@ -52,24 +40,18 @@ 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(
|
||||||
@@ -143,13 +125,9 @@ 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,39 +3,20 @@ 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,
|
||||||
@@ -48,19 +29,14 @@ final class DiServices {
|
|||||||
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
secureStorage = AppSecureStorage(secretKey: diContainer.appConfig.secretKey);
|
secureStorage = AppSecureStorage(
|
||||||
|
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,17 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template auth_screen}
|
/// {@template AuthScreen}
|
||||||
/// Экран авторизации пользователя.
|
|
||||||
///
|
///
|
||||||
/// Отвечает за:
|
|
||||||
/// - Отображение формы входа в приложение
|
|
||||||
/// - Обработку процесса аутентификации
|
|
||||||
/// - Навигацию после успешной авторизации
|
|
||||||
///
|
|
||||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class AuthScreen extends StatelessWidget {
|
class AuthScreen extends StatelessWidget {
|
||||||
/// {@macro auth_screen}
|
/// {@macro AuthScreen}
|
||||||
const AuthScreen({super.key});
|
const AuthScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ 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}
|
||||||
@@ -23,9 +20,6 @@ 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';
|
||||||
@@ -35,9 +29,6 @@ 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';
|
|
||||||
|
|
||||||
/// Метод для создания роутов для отладки
|
/// Метод для создания роутов для отладки
|
||||||
///
|
///
|
||||||
@@ -79,21 +70,6 @@ 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,33 +2,18 @@ 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 components_screen}
|
/// {@template ComponentsScreen}
|
||||||
/// Экран для демонстрации и тестирования компонентов приложения.
|
/// Экран для демонстрации компонентов приложения.
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Демонстрацию различных типов снекбаров (ошибка, успех, информация)
|
|
||||||
/// - Тестирование кастомных UI компонентов
|
|
||||||
/// - Предоставление примеров использования компонентов
|
|
||||||
/// - Валидацию корректности работы компонентов
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ComponentsScreen extends StatefulWidget {
|
class ComponentsScreen extends StatefulWidget {
|
||||||
/// {@macro components_screen}
|
/// {@macro ComponentsScreen}
|
||||||
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/app_context_ext.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/app_context_ext.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,7 +20,9 @@ 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('Реализация AppServices: ${context.di.services.secureStorage.nameImpl}'),
|
Text(
|
||||||
|
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
|
||||||
|
),
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -72,33 +74,14 @@ 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('Тестовая ошибка Exception для отладки FlutterError');
|
throw Exception(
|
||||||
|
'Тестовая ошибка Exception для отладки FlutterError',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const Text('Вызывать ошибку FlutterError'),
|
child: const Text('Вызывать ошибку FlutterError'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,16 +2,11 @@ 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 icons_screen}
|
/// {@template IconsScreen}
|
||||||
/// Экран для отображения всех доступных иконок приложения.
|
/// Экран для отрисовки иконок
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Отображение списка всех SVG иконок из assets/icons/
|
|
||||||
/// - Предоставление возможности просмотра иконок для разработчиков
|
|
||||||
/// - Демонстрацию использования системы генерации ресурсов
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class IconsScreen extends StatelessWidget {
|
class IconsScreen extends StatelessWidget {
|
||||||
/// {@macro icons_screen}
|
/// {@macro IconsScreen}
|
||||||
const IconsScreen({super.key});
|
const IconsScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -35,20 +30,19 @@ 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
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ 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 lang_screen}
|
/// {@template LangScreen}
|
||||||
/// Экран для отладки и тестирования локализации приложения.
|
/// Экран для отладки языков приложения
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Демонстрацию переключения между поддерживаемыми языками
|
|
||||||
/// - Отображение локализованных строк с разными шрифтами
|
|
||||||
/// - Тестирование системы локализации и шрифтов
|
|
||||||
/// - Показ текущего языка приложения
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class LangScreen extends StatelessWidget {
|
class LangScreen extends StatelessWidget {
|
||||||
/// {@macro lang_screen}
|
/// {@macro LangScreen}
|
||||||
const LangScreen({super.key});
|
const LangScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||||
|
|
||||||
/// {@template theme_screen}
|
/// {@template ThemeScreen}
|
||||||
/// Экран для отладки и тестирования темы приложения.
|
/// Экран для отладки темы приложения
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Демонстрацию переключения между светлой и темной темами
|
|
||||||
/// - Отображение тестовых цветов из цветовой схемы
|
|
||||||
/// - Показ текущего режима темы
|
|
||||||
/// - Тестирование системы управления темами
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ThemeScreen extends StatelessWidget {
|
class ThemeScreen extends StatelessWidget {
|
||||||
/// {@macro theme_screen}
|
/// {@macro ThemeScreen}
|
||||||
const ThemeScreen({super.key});
|
const ThemeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template tokens_screen}
|
/// {@template TokensScreen}
|
||||||
/// Экран для отображения и управления токенами аутентификации.
|
/// Экран для отображения токенов
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Отображение текущих токенов доступа и обновления
|
|
||||||
/// - Демонстрацию работы с токенами в приложении
|
|
||||||
/// - Тестирование функциональности аутентификации
|
|
||||||
///
|
|
||||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class TokensScreen extends StatelessWidget {
|
class TokensScreen extends StatelessWidget {
|
||||||
/// {@macro tokens_screen}
|
/// {@macro TokensScreen}
|
||||||
const TokensScreen({super.key});
|
const TokensScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// {@template ui_kit_screen}
|
/// {@template UiKitScreen}
|
||||||
/// Экран для демонстрации и тестирования компонентов UI Kit.
|
/// Экран для отрисовки UI Kit
|
||||||
///
|
/// и тестирования его компонентов.
|
||||||
/// Отвечает за:
|
|
||||||
/// - Отображение всех доступных компонентов UI Kit
|
|
||||||
/// - Демонстрацию использования кастомных виджетов
|
|
||||||
/// - Тестирование стилей и тем оформления
|
|
||||||
/// - Предоставление примера использования UI компонентов
|
|
||||||
///
|
|
||||||
/// В текущей реализации является заглушкой для будущих компонентов.
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class UiKitScreen extends StatelessWidget {
|
class UiKitScreen extends StatelessWidget {
|
||||||
/// {@macro ui_kit_screen}
|
/// {@macro UiKitScreen}
|
||||||
const UiKitScreen({super.key});
|
const UiKitScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
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,16 +5,10 @@ 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);
|
||||||
@@ -22,19 +16,8 @@ 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,22 +3,17 @@ 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';
|
||||||
|
|
||||||
/// {@template profile_screen}
|
// Класс экрана, где мы инициализируем ProfileBloc
|
||||||
/// Экран профиля пользователя.
|
// и вызываем событие 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)
|
||||||
@@ -28,16 +23,8 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@template profile_screen_view}
|
/// Виджет, который отображает UI экрана
|
||||||
/// Виджет для отображения UI экрана профиля.
|
|
||||||
///
|
|
||||||
/// Отображает данные профиля в зависимости от состояния ProfileBloc:
|
|
||||||
/// - Индикатор загрузки во время получения данных
|
|
||||||
/// - Данные профиля при успешной загрузке
|
|
||||||
/// - Сообщение об ошибке при неудачной загрузке
|
|
||||||
/// {@endtemplate}
|
|
||||||
class _ProfileScreenView extends StatelessWidget {
|
class _ProfileScreenView extends StatelessWidget {
|
||||||
/// {@macro profile_screen_view}
|
|
||||||
const _ProfileScreenView();
|
const _ProfileScreenView();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -4,21 +4,15 @@ 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
|
||||||
@@ -35,7 +29,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: 'Главная'),
|
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
||||||
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,11 +71,17 @@ class Assets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SvgGenImage {
|
class SvgGenImage {
|
||||||
const SvgGenImage(this._assetName, {this.size, this.flavors = const {}})
|
const SvgGenImage(
|
||||||
: _isVecFormat = false;
|
this._assetName, {
|
||||||
|
this.size,
|
||||||
|
this.flavors = const {},
|
||||||
|
}) : _isVecFormat = false;
|
||||||
|
|
||||||
const SvgGenImage.vec(this._assetName, {this.size, this.flavors = const {}})
|
const SvgGenImage.vec(
|
||||||
: _isVecFormat = true;
|
this._assetName, {
|
||||||
|
this.size,
|
||||||
|
this.flavors = const {},
|
||||||
|
}) : _isVecFormat = true;
|
||||||
|
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
final Size? size;
|
final Size? size;
|
||||||
@@ -129,8 +135,7 @@ 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,
|
||||||
@@ -143,7 +148,10 @@ class SvgGenImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LottieGenImage {
|
class LottieGenImage {
|
||||||
const LottieGenImage(this._assetName, {this.flavors = const {}});
|
const LottieGenImage(
|
||||||
|
this._assetName, {
|
||||||
|
this.flavors = const {},
|
||||||
|
});
|
||||||
|
|
||||||
final String _assetName;
|
final String _assetName;
|
||||||
final Set<String> flavors;
|
final Set<String> flavors;
|
||||||
@@ -160,8 +168,11 @@ class LottieGenImage {
|
|||||||
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
||||||
Key? key,
|
Key? key,
|
||||||
AssetBundle? bundle,
|
AssetBundle? bundle,
|
||||||
Widget Function(BuildContext, Widget, _lottie.LottieComposition?)?
|
Widget Function(
|
||||||
frameBuilder,
|
BuildContext,
|
||||||
|
Widget,
|
||||||
|
_lottie.LottieComposition?,
|
||||||
|
)? frameBuilder,
|
||||||
ImageErrorWidgetBuilder? errorBuilder,
|
ImageErrorWidgetBuilder? errorBuilder,
|
||||||
double? width,
|
double? width,
|
||||||
double? height,
|
double? height,
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
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
|
||||||
@@ -27,34 +19,12 @@ class LocalizationConsumer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@template localization_notifier}
|
/// Класс для управления локализацией
|
||||||
/// Класс для управления локализацией приложения.
|
|
||||||
///
|
|
||||||
/// Отвечает за:
|
|
||||||
/// - Хранение текущей локали приложения
|
|
||||||
/// - Уведомление подписчиков об изменениях языка
|
|
||||||
/// - Переключение между поддерживаемыми языками
|
|
||||||
/// {@endtemplate}
|
|
||||||
final class LocalizationNotifier extends ChangeNotifier {
|
final class LocalizationNotifier extends ChangeNotifier {
|
||||||
/// {@macro localization_notifier}
|
Locale _locale = const Locale('en', 'US');
|
||||||
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();
|
||||||
|
|||||||
32
pubspec.lock
32
pubspec.lock
@@ -1024,30 +1024,6 @@ 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:
|
||||||
@@ -1056,14 +1032,6 @@ 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:
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ void main() {
|
|||||||
|
|
||||||
/// Создание мок-темы с правильными стилями текста
|
/// Создание мок-темы с правильными стилями текста
|
||||||
TextTheme createMockTextTheme() {
|
TextTheme createMockTextTheme() {
|
||||||
return const TextTheme(bodyMedium: TextStyle(fontSize: 14));
|
return const TextTheme(
|
||||||
|
bodyMedium: TextStyle(fontSize: 14),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
@@ -39,9 +41,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group('AppSnackBar.showInfo', () {
|
group('AppSnackBar.showInfo', () {
|
||||||
testTester('показывает снекбар с информацией и правильными стилями', (
|
testTester('показывает снекбар с информацией и правильными стилями', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
const infoMessage = 'Это просто сообщение';
|
const infoMessage = 'Это просто сообщение';
|
||||||
|
|
||||||
AppSnackBar.showInfo(
|
AppSnackBar.showInfo(
|
||||||
@@ -74,9 +74,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar.showError', () {
|
group('AppSnackBar.showError', () {
|
||||||
testTester('показывает снекбар с ошибкой и правильными стилями', (
|
testTester('показывает снекбар с ошибкой и правильными стилями', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
const errorMessage = 'Произошла ошибка';
|
const errorMessage = 'Произошла ошибка';
|
||||||
|
|
||||||
AppSnackBar.showError(
|
AppSnackBar.showError(
|
||||||
@@ -160,9 +158,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar.showSuccess', () {
|
group('AppSnackBar.showSuccess', () {
|
||||||
testTester('показывает снекбар с успехом и правильными стилями', (
|
testTester('показывает снекбар с успехом и правильными стилями', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
const successMessage = 'Операция выполнена успешно';
|
const successMessage = 'Операция выполнена успешно';
|
||||||
|
|
||||||
AppSnackBar.showSuccess(
|
AppSnackBar.showSuccess(
|
||||||
@@ -190,10 +186,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
final decoration = container.decoration as BoxDecoration;
|
final decoration = container.decoration as BoxDecoration;
|
||||||
expect(
|
expect(decoration.color, equals(const Color(0xFF6FB62C))); // Success фон
|
||||||
decoration.color,
|
|
||||||
equals(const Color(0xFF6FB62C)),
|
|
||||||
); // Success фон
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('показывает снекбар с кастомной продолжительностью', (
|
testTester('показывает снекбар с кастомной продолжительностью', (
|
||||||
@@ -217,41 +210,29 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('AppSnackBar виджет поведение', () {
|
group('AppSnackBar виджет поведение', () {
|
||||||
testTester(
|
testTester('показывает анимацию появления с правильной последовательностью', (tester) async {
|
||||||
'показывает анимацию появления с правильной последовательностью',
|
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>(
|
final initialPosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||||
find.byType(Positioned),
|
expect(initialPosition.top ?? 0, lessThan(0));
|
||||||
);
|
|
||||||
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>(
|
final middlePosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||||
find.byType(Positioned),
|
expect(middlePosition.top ?? 0, greaterThan(initialPosition.top ?? 0));
|
||||||
);
|
|
||||||
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>(
|
final finalPosition = tester.widget<Positioned>(find.byType(Positioned));
|
||||||
find.byType(Positioned),
|
expect(finalPosition.top ?? 0, greaterThan(0));
|
||||||
);
|
});
|
||||||
expect(finalPosition.top ?? 0, greaterThan(0));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
testTester('закрывается при тапе', (tester) async {
|
testTester('закрывается при тапе', (tester) async {
|
||||||
const message = 'Тап для закрытия';
|
const message = 'Тап для закрытия';
|
||||||
@@ -342,9 +323,7 @@ void main() {
|
|||||||
expect(find.byType(Text), findsAtLeastNWidgets(1));
|
expect(find.byType(Text), findsAtLeastNWidgets(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('имеет правильные отступы и размеры на разных экранах', (
|
testTester('имеет правильные отступы и размеры на разных экранах', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
const message = 'Размеры';
|
const message = 'Размеры';
|
||||||
|
|
||||||
// Тестируем на маленьком экране
|
// Тестируем на маленьком экране
|
||||||
@@ -381,10 +360,7 @@ void main() {
|
|||||||
matching: find.byType(Container),
|
matching: find.byType(Container),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(
|
expect(container.constraints?.maxWidth, equals(350)); // Максимальная ширина
|
||||||
container.constraints?.maxWidth,
|
|
||||||
equals(350),
|
|
||||||
); // Максимальная ширина
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('имеет правильное скругление углов', (tester) async {
|
testTester('имеет правильное скругление углов', (tester) async {
|
||||||
@@ -489,9 +465,7 @@ void main() {
|
|||||||
expect(tester.takeException(), isNull);
|
expect(tester.takeException(), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
testTester('правильно обрабатывает быстрые последовательные вызовы', (
|
testTester('правильно обрабатывает быстрые последовательные вызовы', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3'];
|
const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3'];
|
||||||
|
|
||||||
for (final message in messages) {
|
for (final message in messages) {
|
||||||
|
|||||||
3
tools/.sh
Normal file
3
tools/.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
format code
|
||||||
|
|
||||||
|
dart format --output=none --set-exit-if-changed .
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/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