mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-22 09:30:45 +00:00
Compare commits
1 Commits
main
...
feat/add-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b5f80e7d9 |
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/app_path_provider.dart';
|
export 'src/app_path_provider.dart';
|
||||||
export 'src/app_secure_storage.dart';
|
export 'src/app_secure_storage.dart';
|
||||||
|
export 'src/app_url_launcher.dart';
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class AppPathProvider implements IPathProvider {
|
|||||||
/// Наименование сервиса
|
/// Наименование сервиса
|
||||||
static const name = 'AuroraAppPathProvider';
|
static const name = 'AuroraAppPathProvider';
|
||||||
|
|
||||||
|
String get nameImpl => AppPathProvider.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAppDocumentsDirectoryPath() async {
|
Future<String> getAppDocumentsDirectoryPath() async {
|
||||||
return (await getApplicationDocumentsDirectory()).path;
|
return (await getApplicationDocumentsDirectory()).path;
|
||||||
|
|||||||
@@ -15,11 +15,15 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
FlutterSecureStorageAurora.setSecret(secretKey);
|
FlutterSecureStorageAurora.setSecret(secretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const name = 'AuroraAppSecureStorage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppSecureStorage.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String secretKey;
|
final String secretKey;
|
||||||
|
|
||||||
static const name = 'AuroraAppSecureStorage';
|
|
||||||
|
|
||||||
/// Экземпляр хранилища данных
|
/// Экземпляр хранилища данных
|
||||||
final _box = const FlutterSecureStorage();
|
final _box = const FlutterSecureStorage();
|
||||||
|
|
||||||
@@ -47,7 +51,4 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
await _box.write(key: key, value: value);
|
await _box.write(key: key, value: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get nameImpl => AppSecureStorage.name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
|
|
||||||
|
/// {@template app_url_launcher}
|
||||||
|
/// Класс для Аврора реализации сервиса работы с URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AppUrlLauncher implements IUrlLauncher {
|
||||||
|
/// {@macro app_url_launcher}
|
||||||
|
AppUrlLauncher();
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const String name = 'AuroraAppUrlLauncher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppUrlLauncher.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLaunchUrl(Uri url) async {
|
||||||
|
return url_launcher.canLaunchUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launchUrl(Uri url) async {
|
||||||
|
return url_launcher.launchUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,12 @@ 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.1)
|
# Зависимости для работы с путями (плагин встроен в sdk flutter 3.27.3)
|
||||||
path_provider: 2.1.5
|
path_provider: 2.1.5
|
||||||
|
|
||||||
|
# Зависимости для работы с открытием ссылок (плагин встроен в sdk flutter 3.27.3)
|
||||||
|
url_launcher: 6.3.1
|
||||||
|
|
||||||
# Обязательные интерфейсы
|
# Обязательные интерфейсы
|
||||||
i_app_services:
|
i_app_services:
|
||||||
path: ../../i_app_services
|
path: ../../i_app_services
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/app_path_provider.dart';
|
export 'src/app_path_provider.dart';
|
||||||
export 'src/app_secure_storage.dart';
|
export 'src/app_secure_storage.dart';
|
||||||
|
export 'src/app_url_launcher.dart';
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ class AppPathProvider implements IPathProvider {
|
|||||||
/// Наименование сервиса
|
/// Наименование сервиса
|
||||||
static const name = 'BaseAppPathProvider';
|
static const name = 'BaseAppPathProvider';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppPathProvider.name;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAppDocumentsDirectoryPath() async {
|
Future<String> getAppDocumentsDirectoryPath() async {
|
||||||
return (await getApplicationDocumentsDirectory()).path;
|
return (await getApplicationDocumentsDirectory()).path;
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
/// {@macro app_secure_storage}
|
/// {@macro app_secure_storage}
|
||||||
AppSecureStorage({this.secretKey});
|
AppSecureStorage({this.secretKey});
|
||||||
|
|
||||||
@override
|
|
||||||
final String? secretKey;
|
|
||||||
|
|
||||||
/// Наименование сервиса
|
/// Наименование сервиса
|
||||||
static const name = 'BaseAppSecureStorage';
|
static const name = 'BaseAppSecureStorage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppSecureStorage.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? secretKey;
|
||||||
|
|
||||||
/// Экземпляр хранилища данных
|
/// Экземпляр хранилища данных
|
||||||
final _box = const FlutterSecureStorage();
|
final _box = const FlutterSecureStorage();
|
||||||
|
|
||||||
@@ -44,7 +47,4 @@ final class AppSecureStorage implements ISecureStorage {
|
|||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
await _box.write(key: key, value: value);
|
await _box.write(key: key, value: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get nameImpl => AppSecureStorage.name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal file
26
app_services/base/app_services/lib/src/app_url_launcher.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart' as url_launcher;
|
||||||
|
|
||||||
|
/// {@template app_url_launcher}
|
||||||
|
/// Класс для базовой реализации сервиса работы с URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class AppUrlLauncher implements IUrlLauncher {
|
||||||
|
/// {@macro app_url_launcher}
|
||||||
|
AppUrlLauncher();
|
||||||
|
|
||||||
|
/// Наименование сервиса
|
||||||
|
static const String name = 'BaseAppUrlLauncher';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get nameImpl => AppUrlLauncher.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canLaunchUrl(Uri url) async {
|
||||||
|
return url_launcher.canLaunchUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> launchUrl(Uri url) async {
|
||||||
|
return url_launcher.launchUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ dependencies:
|
|||||||
# для работы с путями в хранилища
|
# для работы с путями в хранилища
|
||||||
path_provider: 2.1.5
|
path_provider: 2.1.5
|
||||||
|
|
||||||
|
# Зависимости для сервиса внешних ссылок
|
||||||
|
url_launcher: 6.3.1
|
||||||
|
|
||||||
# Обязательные интерфейсы
|
# Обязательные интерфейсы
|
||||||
i_app_services:
|
i_app_services:
|
||||||
path: ../../i_app_services
|
path: ../../i_app_services
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ library;
|
|||||||
|
|
||||||
export 'src/i_path_provider.dart';
|
export 'src/i_path_provider.dart';
|
||||||
export 'src/i_secure_storage.dart';
|
export 'src/i_secure_storage.dart';
|
||||||
|
export 'src/i_url_launcher.dart';
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/// Класс для описания интерфейса сервиса
|
/// Класс для описания интерфейса сервиса для получения пути хранения файлов
|
||||||
/// для получения пути хранения файлов
|
|
||||||
abstract interface class IPathProvider {
|
abstract interface class IPathProvider {
|
||||||
/// Наименования интерфейса
|
/// Наименования интерфейса
|
||||||
static const name = 'IPathProvider';
|
static const name = 'IPathProvider';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
/// Получение path на внутренне хранилище приложения
|
/// Получение path на внутренне хранилище приложения
|
||||||
Future<String?> getAppDocumentsDirectoryPath();
|
Future<String?> getAppDocumentsDirectoryPath();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/// Класс интерфейса для работы с защищенным хранилищем
|
/// Класс для описания интерфейса для работы с защищенным хранилищем
|
||||||
abstract interface class ISecureStorage {
|
abstract interface class ISecureStorage {
|
||||||
/// Описывает обязательные параметры имплементаций
|
/// Описывает обязательные параметры имплементаций
|
||||||
///
|
///
|
||||||
@@ -6,14 +6,17 @@ abstract interface class ISecureStorage {
|
|||||||
/// - [secretKey] - секретный ключ для шифрования данных
|
/// - [secretKey] - секретный ключ для шифрования данных
|
||||||
const ISecureStorage._({required this.secretKey});
|
const ISecureStorage._({required this.secretKey});
|
||||||
|
|
||||||
|
/// Наименования интерфейса
|
||||||
|
static const name = 'ISecureStorage';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
/// Секретный ключ для шифрования данных
|
/// Секретный ключ для шифрования данных
|
||||||
/// Нужен, если надо передать ключ в реализацию
|
/// Нужен, если надо передать ключ в реализацию
|
||||||
/// например, в Aurora
|
/// например, в Aurora
|
||||||
final String? secretKey;
|
final String? secretKey;
|
||||||
|
|
||||||
/// Наименования интерфейса
|
|
||||||
static const name = 'ISecureStorage';
|
|
||||||
|
|
||||||
/// Метод для получения значения из защищенного хранилища
|
/// Метод для получения значения из защищенного хранилища
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
@@ -41,6 +44,4 @@ abstract interface class ISecureStorage {
|
|||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [key] - ключ
|
/// - [key] - ключ
|
||||||
Future<bool> containsKey(String key);
|
Future<bool> containsKey(String key);
|
||||||
|
|
||||||
String get nameImpl;
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal file
18
app_services/i_app_services/lib/src/i_url_launcher.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// Класс для описания интерфейса сервиса для запуска URL
|
||||||
|
abstract interface class IUrlLauncher {
|
||||||
|
/// Наименования интерфейса
|
||||||
|
static const name = 'IUrlLauncher';
|
||||||
|
|
||||||
|
/// Получение имени имплементации
|
||||||
|
String get nameImpl;
|
||||||
|
|
||||||
|
/// Метод для проверки возможности запуска ссылки
|
||||||
|
///
|
||||||
|
/// - [url] - ссылка для проверки
|
||||||
|
Future<bool> canLaunchUrl(Uri url);
|
||||||
|
|
||||||
|
/// Метод для запуска ссылки
|
||||||
|
///
|
||||||
|
/// - [url] - ссылка для запуска
|
||||||
|
Future<bool> launchUrl(Uri url);
|
||||||
|
}
|
||||||
@@ -22,6 +22,9 @@ final class DiServices {
|
|||||||
/// Сервис для работы с защищенным локальным хранилищем
|
/// Сервис для работы с защищенным локальным хранилищем
|
||||||
late final ISecureStorage secureStorage;
|
late final ISecureStorage secureStorage;
|
||||||
|
|
||||||
|
/// Сервис для работы с URL
|
||||||
|
late final IUrlLauncher urlLauncher;
|
||||||
|
|
||||||
/// Метод для инициализации сервисов в приложении.
|
/// Метод для инициализации сервисов в приложении.
|
||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
@@ -32,6 +35,7 @@ final class DiServices {
|
|||||||
/// Последовательность инициализации:
|
/// Последовательность инициализации:
|
||||||
/// 1. Инициализация сервиса путей (AppPathProvider)
|
/// 1. Инициализация сервиса путей (AppPathProvider)
|
||||||
/// 2. Инициализация защищенного хранилища (AppSecureStorage)
|
/// 2. Инициализация защищенного хранилища (AppSecureStorage)
|
||||||
|
/// 3. Инициализация сервиса URL (AppUrlLauncherService)
|
||||||
void init({
|
void init({
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
required OnError onError,
|
required OnError onError,
|
||||||
@@ -44,14 +48,19 @@ final class DiServices {
|
|||||||
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
secureStorage = AppSecureStorage(
|
secureStorage = AppSecureStorage(secretKey: diContainer.appConfig.secretKey);
|
||||||
secretKey: diContainer.appConfig.secretKey,
|
|
||||||
);
|
|
||||||
onProgress(AppSecureStorage.name);
|
onProgress(AppSecureStorage.name);
|
||||||
} on Object catch (error, stackTrace) {
|
} on Object catch (error, stackTrace) {
|
||||||
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
urlLauncher = AppUrlLauncher();
|
||||||
|
onProgress(AppUrlLauncher.name);
|
||||||
|
} on Object catch (error, stackTrace) {
|
||||||
|
onError('Ошибка инициализации ${IUrlLauncher.name}', error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
onProgress('Инициализация сервисов завершена!');
|
onProgress('Инициализация сервисов завершена!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import 'package:friflex_starter/features/debug/screens/components_screen.dart';
|
|||||||
import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/path_provider_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/secure_storage_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
|
||||||
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
|
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/debug/screens/url_launcher_screen.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// {@template debug_routes}
|
/// {@template debug_routes}
|
||||||
@@ -20,6 +23,9 @@ abstract final class DebugRoutes {
|
|||||||
static const String themeScreenName = 'theme_screen';
|
static const String themeScreenName = 'theme_screen';
|
||||||
static const String langScreenName = 'lang_screen';
|
static const String langScreenName = 'lang_screen';
|
||||||
static const String componentsScreenName = 'components_screen';
|
static const String componentsScreenName = 'components_screen';
|
||||||
|
static const String pathProviderScreenName = 'path_provider_screen';
|
||||||
|
static const String secureStorageScreenName = 'secure_storage_screen';
|
||||||
|
static const String urlLauncherScreenName = 'url_launcher_screen';
|
||||||
|
|
||||||
/// Пути к экранам
|
/// Пути к экранам
|
||||||
static const String debugScreenPath = '/debug';
|
static const String debugScreenPath = '/debug';
|
||||||
@@ -29,6 +35,9 @@ abstract final class DebugRoutes {
|
|||||||
static const String themeScreenPath = 'debug/theme';
|
static const String themeScreenPath = 'debug/theme';
|
||||||
static const String langScreenPath = 'debug/lang';
|
static const String langScreenPath = 'debug/lang';
|
||||||
static const String componentsScreenPath = 'debug/components';
|
static const String componentsScreenPath = 'debug/components';
|
||||||
|
static const String pathProviderScreenPath = 'debug/path_provider';
|
||||||
|
static const String secureStorageScreenPath = 'debug/secure_storage';
|
||||||
|
static const String urlLauncherScreenPath = 'debug/url_launcher';
|
||||||
|
|
||||||
/// Метод для создания роутов для отладки
|
/// Метод для создания роутов для отладки
|
||||||
///
|
///
|
||||||
@@ -70,6 +79,21 @@ abstract final class DebugRoutes {
|
|||||||
name: componentsScreenName,
|
name: componentsScreenName,
|
||||||
builder: (context, state) => const ComponentsScreen(),
|
builder: (context, state) => const ComponentsScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: pathProviderScreenPath,
|
||||||
|
name: pathProviderScreenName,
|
||||||
|
builder: (context, state) => const PathProviderScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: secureStorageScreenPath,
|
||||||
|
name: secureStorageScreenName,
|
||||||
|
builder: (context, state) => const SecureStorageScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: urlLauncherScreenPath,
|
||||||
|
name: urlLauncherScreenName,
|
||||||
|
builder: (context, state) => const UrlLauncherScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
|
||||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
@@ -20,9 +20,7 @@ class DebugScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text('Окружение: ${context.di.appConfig.env.name}'),
|
Text('Окружение: ${context.di.appConfig.env.name}'),
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
Text(
|
Text('Реализация AppServices: ${context.di.services.secureStorage.nameImpl}'),
|
||||||
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
|
|
||||||
),
|
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@@ -74,14 +72,33 @@ class DebugScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: const Text('Экран компонентов'),
|
child: const Text('Экран компонентов'),
|
||||||
),
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.pathProviderScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Path Provider'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.secureStorageScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pushNamed(DebugRoutes.urlLauncherScreenName);
|
||||||
|
},
|
||||||
|
child: const Text('Экран Url Launcher'),
|
||||||
|
),
|
||||||
const HBox(22),
|
const HBox(22),
|
||||||
const Text('Имитирование ошибок:'),
|
const Text('Имитирование ошибок:'),
|
||||||
const HBox(16),
|
const HBox(16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
throw Exception(
|
throw Exception('Тестовая ошибка Exception для отладки FlutterError');
|
||||||
'Тестовая ошибка Exception для отладки FlutterError',
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Text('Вызывать ошибку FlutterError'),
|
child: const Text('Вызывать ошибку FlutterError'),
|
||||||
),
|
),
|
||||||
|
|||||||
147
lib/features/debug/screens/path_provider_screen.dart
Normal file
147
lib/features/debug/screens/path_provider_screen.dart
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template path_provider_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина path_provider.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для получения путей к директориям приложения
|
||||||
|
/// - Демонстрацию содержимого директории файлов приложения
|
||||||
|
/// {@endtemplate}
|
||||||
|
class PathProviderScreen extends StatefulWidget {
|
||||||
|
/// {@macro path_provider_screen}
|
||||||
|
const PathProviderScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PathProviderScreen> createState() => _PathProviderScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PathProviderScreenState extends State<PathProviderScreen> {
|
||||||
|
/// Плагин для работы с путями в приложении
|
||||||
|
late final IPathProvider _pathProvider;
|
||||||
|
|
||||||
|
/// Корневой путь к директории файлов приложения
|
||||||
|
String? _rootPath;
|
||||||
|
|
||||||
|
/// Текущий путь к директории, отображаемой в списке
|
||||||
|
String? _currentPath;
|
||||||
|
|
||||||
|
/// Загрузка файлов
|
||||||
|
Future<List<FileSystemEntity>?>? _loadFilesFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pathProvider = context.di.services.pathProvider;
|
||||||
|
_loadFilesFuture = _initRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Path Provider')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Path Provider: ${context.di.services.pathProvider.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
Text('Содержимое папки документов приложения:'),
|
||||||
|
const HBox(8),
|
||||||
|
Text('Текущий путь:'),
|
||||||
|
const HBox(8),
|
||||||
|
Text(_currentPath ?? ''),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _currentPath != null && _rootPath != null && _currentPath != _rootPath
|
||||||
|
? _goBack
|
||||||
|
: null,
|
||||||
|
child: const Text('Назад'),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder<List<FileSystemEntity>?>(
|
||||||
|
future: _loadFilesFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Ошибка: \\${snapshot.error}'));
|
||||||
|
}
|
||||||
|
final files = snapshot.data;
|
||||||
|
if (files == null) {
|
||||||
|
return const Center(child: Text('Недоступно'));
|
||||||
|
}
|
||||||
|
if (files.isEmpty) {
|
||||||
|
return const Center(child: Text('Папка пуста'));
|
||||||
|
}
|
||||||
|
return ListView(
|
||||||
|
children: files
|
||||||
|
.map(
|
||||||
|
(item) => ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
item is Directory ? Icons.folder : Icons.insert_drive_file,
|
||||||
|
),
|
||||||
|
title: Text(item.path.split(Platform.pathSeparator).last),
|
||||||
|
onTap: item is Directory ? () => _openDir(item.path) : null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для инициализации корневой директории и загрузки её содержимого
|
||||||
|
Future<List<FileSystemEntity>?> _initRoot() async {
|
||||||
|
final dirPath = await _pathProvider.getAppDocumentsDirectoryPath();
|
||||||
|
if (dirPath == null) {
|
||||||
|
setState(() {
|
||||||
|
_rootPath = null;
|
||||||
|
_currentPath = null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final files = Directory(dirPath).listSync();
|
||||||
|
setState(() {
|
||||||
|
_rootPath = dirPath;
|
||||||
|
_currentPath = dirPath;
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для загрузки файлов в указанной директории
|
||||||
|
List<FileSystemEntity>? _loadFiles(String path) {
|
||||||
|
return Directory(path).listSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для открытия директории и загрузки её содержимого
|
||||||
|
void _openDir(String path) async {
|
||||||
|
setState(() {
|
||||||
|
_currentPath = path;
|
||||||
|
_loadFilesFuture = Future.value(_loadFiles(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для перехода к родительской директории
|
||||||
|
void _goBack() async {
|
||||||
|
if (_currentPath == null || _rootPath == null || _currentPath == _rootPath) return;
|
||||||
|
final parent = Directory(_currentPath!).parent.path;
|
||||||
|
if (parent.length < _rootPath!.length) return;
|
||||||
|
final files = _loadFiles(parent);
|
||||||
|
setState(() {
|
||||||
|
_currentPath = parent;
|
||||||
|
_loadFilesFuture = Future.value(files);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
125
lib/features/debug/screens/secure_storage_screen.dart
Normal file
125
lib/features/debug/screens/secure_storage_screen.dart
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template secure_storage_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина flutter_secure_storage.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для провеки записи и чтения защищенных данных
|
||||||
|
/// {@endtemplate}
|
||||||
|
class SecureStorageScreen extends StatefulWidget {
|
||||||
|
/// {@macro secure_storage_screen}
|
||||||
|
const SecureStorageScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SecureStorageScreen> createState() => _SecureStorageScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SecureStorageScreenState extends State<SecureStorageScreen> {
|
||||||
|
/// Плагин для работы с защищенным хранилищем
|
||||||
|
late final ISecureStorage _secureStorage;
|
||||||
|
|
||||||
|
/// Контроллер для ввода ключа
|
||||||
|
final TextEditingController _keyController = TextEditingController();
|
||||||
|
|
||||||
|
/// Контроллер для ввода значения
|
||||||
|
final TextEditingController _valueController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_secureStorage = context.di.services.secureStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_keyController.dispose();
|
||||||
|
_valueController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Secure Storage')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Secure Storage: ${context.di.services.secureStorage.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _keyController,
|
||||||
|
onChanged: (value) {
|
||||||
|
_valueController.clear();
|
||||||
|
},
|
||||||
|
decoration: const InputDecoration(labelText: 'Ключ'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _valueController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Значение'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleWrite(context),
|
||||||
|
child: const Text('Записать в Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleRead(context),
|
||||||
|
child: const Text('Прочитать из Secure Storage'),
|
||||||
|
),
|
||||||
|
const HBox(8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _handleDelete(context),
|
||||||
|
child: const Text('Удалить из Secure Storage'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для записи значения в Secure Storage
|
||||||
|
Future<void> _handleWrite(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
final value = _valueController.text;
|
||||||
|
try {
|
||||||
|
await _secureStorage.write(key, value);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Значение записано в Secure Storage');
|
||||||
|
} on Object catch (e) {
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка записи: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для чтения значения из Secure Storage
|
||||||
|
Future<void> _handleRead(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
try {
|
||||||
|
final value = await _secureStorage.read(key) ?? 'Значение не найдено';
|
||||||
|
_valueController.value = TextEditingValue(text: value);
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка чтения: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Обработчик для удаления значения из Secure Storage
|
||||||
|
Future<void> _handleDelete(BuildContext context) async {
|
||||||
|
final key = _keyController.text;
|
||||||
|
try {
|
||||||
|
await _secureStorage.delete(key);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
_valueController.clear();
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Значение удалено из Secure Storage');
|
||||||
|
} on Object catch (e) {
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка удаления: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
lib/features/debug/screens/url_launcher_screen.dart
Normal file
133
lib/features/debug/screens/url_launcher_screen.dart
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_box.dart';
|
||||||
|
import 'package:friflex_starter/app/ui_kit/app_snackbar.dart';
|
||||||
|
import 'package:i_app_services/i_app_services.dart';
|
||||||
|
|
||||||
|
/// {@template url_launcher_screen}
|
||||||
|
/// Экран для отладки и тестирования плагина url_launcher.
|
||||||
|
///
|
||||||
|
/// Отвечает за:
|
||||||
|
/// - Тестирование работы реализаций плагина для проверки открытия URL
|
||||||
|
/// {@endtemplate}
|
||||||
|
class UrlLauncherScreen extends StatefulWidget {
|
||||||
|
/// {@macro url_launcher_screen}
|
||||||
|
const UrlLauncherScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UrlLauncherScreen> createState() => _UrlLauncherScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UrlLauncherScreenState extends State<UrlLauncherScreen> {
|
||||||
|
/// Плагин для работы с URL
|
||||||
|
late final IUrlLauncher _urlLauncher;
|
||||||
|
|
||||||
|
/// Контроллер для ввода URL для открытия
|
||||||
|
final TextEditingController _urlController = TextEditingController();
|
||||||
|
|
||||||
|
/// Контроллер для ввода URL для проверки возможности открытия
|
||||||
|
final TextEditingController _canOpenUrlController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_urlLauncher = context.di.services.urlLauncher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_urlController.dispose();
|
||||||
|
_canOpenUrlController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('URL Launcher')),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Реализация Url Launcher: ${context.di.services.urlLauncher.nameImpl}'),
|
||||||
|
const HBox(8),
|
||||||
|
TextField(
|
||||||
|
controller: _urlController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Введите ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _launchUrl(context),
|
||||||
|
child: const Text('Открыть ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
TextField(
|
||||||
|
controller: _canOpenUrlController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Введите ссылку'),
|
||||||
|
),
|
||||||
|
const HBox(16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _checkCanOpenUrl(context),
|
||||||
|
child: const Text('Проверить возможность открытия'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для открытия URL
|
||||||
|
Future<void> _launchUrl(BuildContext context) async {
|
||||||
|
final url = _urlController.text.trim();
|
||||||
|
if (url.isEmpty) {
|
||||||
|
AppSnackBar.showInfo(context, message: 'Введите ссылку для открытия');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final success = await _urlLauncher.launchUrl(uri);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (!success) {
|
||||||
|
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
|
||||||
|
}
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка при открытии ссылки: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Метод для проверки возможности открытия URL
|
||||||
|
Future<void> _checkCanOpenUrl(BuildContext context) async {
|
||||||
|
final url = _canOpenUrlController.text.trim();
|
||||||
|
if (url.isEmpty) {
|
||||||
|
AppSnackBar.showInfo(context, message: 'Введите ссылку для проверки');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(url);
|
||||||
|
if (uri == null) {
|
||||||
|
AppSnackBar.showError(context, message: 'Некорректная ссылка: $url');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final canOpen = await _urlLauncher.canLaunchUrl(uri);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (canOpen) {
|
||||||
|
AppSnackBar.showSuccess(context: context, message: 'Возможно открыть ссылку: $url');
|
||||||
|
} else {
|
||||||
|
AppSnackBar.showError(context, message: 'Не удалось открыть ссылку: $url');
|
||||||
|
}
|
||||||
|
} on Object catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
AppSnackBar.showError(context, message: 'Ошибка при проверке ссылки: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
pubspec.lock
32
pubspec.lock
@@ -1024,6 +1024,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.1"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.16"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1032,6 +1056,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user