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_secure_storage.dart';
|
||||
export 'src/app_url_launcher.dart';
|
||||
|
||||
@@ -11,6 +11,8 @@ class AppPathProvider implements IPathProvider {
|
||||
/// Наименование сервиса
|
||||
static const name = 'AuroraAppPathProvider';
|
||||
|
||||
String get nameImpl => AppPathProvider.name;
|
||||
|
||||
@override
|
||||
Future<String> getAppDocumentsDirectoryPath() async {
|
||||
return (await getApplicationDocumentsDirectory()).path;
|
||||
|
||||
@@ -15,11 +15,15 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
FlutterSecureStorageAurora.setSecret(secretKey);
|
||||
}
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'AuroraAppSecureStorage';
|
||||
|
||||
@override
|
||||
String get nameImpl => AppSecureStorage.name;
|
||||
|
||||
@override
|
||||
final String secretKey;
|
||||
|
||||
static const name = 'AuroraAppSecureStorage';
|
||||
|
||||
/// Экземпляр хранилища данных
|
||||
final _box = const FlutterSecureStorage();
|
||||
|
||||
@@ -47,7 +51,4 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
Future<void> write(String key, String value) async {
|
||||
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
|
||||
ref: aurora-0.5.3
|
||||
|
||||
# для работы с путями (плагин встроен в sdk flutter 3.27.1)
|
||||
# Зависимости для работы с путями (плагин встроен в sdk flutter 3.27.3)
|
||||
path_provider: 2.1.5
|
||||
|
||||
# Зависимости для работы с открытием ссылок (плагин встроен в sdk flutter 3.27.3)
|
||||
url_launcher: 6.3.1
|
||||
|
||||
# Обязательные интерфейсы
|
||||
i_app_services:
|
||||
path: ../../i_app_services
|
||||
|
||||
@@ -2,3 +2,4 @@ library;
|
||||
|
||||
export 'src/app_path_provider.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';
|
||||
|
||||
@override
|
||||
String get nameImpl => AppPathProvider.name;
|
||||
|
||||
@override
|
||||
Future<String> getAppDocumentsDirectoryPath() async {
|
||||
return (await getApplicationDocumentsDirectory()).path;
|
||||
|
||||
@@ -11,12 +11,15 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
/// {@macro app_secure_storage}
|
||||
AppSecureStorage({this.secretKey});
|
||||
|
||||
@override
|
||||
final String? secretKey;
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'BaseAppSecureStorage';
|
||||
|
||||
@override
|
||||
String get nameImpl => AppSecureStorage.name;
|
||||
|
||||
@override
|
||||
final String? secretKey;
|
||||
|
||||
/// Экземпляр хранилища данных
|
||||
final _box = const FlutterSecureStorage();
|
||||
|
||||
@@ -44,7 +47,4 @@ final class AppSecureStorage implements ISecureStorage {
|
||||
Future<void> write(String key, String value) async {
|
||||
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
|
||||
|
||||
# Зависимости для сервиса внешних ссылок
|
||||
url_launcher: 6.3.1
|
||||
|
||||
# Обязательные интерфейсы
|
||||
i_app_services:
|
||||
path: ../../i_app_services
|
||||
|
||||
@@ -2,3 +2,4 @@ library;
|
||||
|
||||
export 'src/i_path_provider.dart';
|
||||
export 'src/i_secure_storage.dart';
|
||||
export 'src/i_url_launcher.dart';
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/// Класс для описания интерфейса сервиса
|
||||
/// для получения пути хранения файлов
|
||||
/// Класс для описания интерфейса сервиса для получения пути хранения файлов
|
||||
abstract interface class IPathProvider {
|
||||
/// Наименования интерфейса
|
||||
static const name = 'IPathProvider';
|
||||
|
||||
/// Получение имени имплементации
|
||||
String get nameImpl;
|
||||
|
||||
/// Получение path на внутренне хранилище приложения
|
||||
Future<String?> getAppDocumentsDirectoryPath();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Класс интерфейса для работы с защищенным хранилищем
|
||||
/// Класс для описания интерфейса для работы с защищенным хранилищем
|
||||
abstract interface class ISecureStorage {
|
||||
/// Описывает обязательные параметры имплементаций
|
||||
///
|
||||
@@ -6,14 +6,17 @@ abstract interface class ISecureStorage {
|
||||
/// - [secretKey] - секретный ключ для шифрования данных
|
||||
const ISecureStorage._({required this.secretKey});
|
||||
|
||||
/// Наименования интерфейса
|
||||
static const name = 'ISecureStorage';
|
||||
|
||||
/// Получение имени имплементации
|
||||
String get nameImpl;
|
||||
|
||||
/// Секретный ключ для шифрования данных
|
||||
/// Нужен, если надо передать ключ в реализацию
|
||||
/// например, в Aurora
|
||||
final String? secretKey;
|
||||
|
||||
/// Наименования интерфейса
|
||||
static const name = 'ISecureStorage';
|
||||
|
||||
/// Метод для получения значения из защищенного хранилища
|
||||
///
|
||||
/// Принимает:
|
||||
@@ -41,6 +44,4 @@ abstract interface class ISecureStorage {
|
||||
/// Принимает:
|
||||
/// - [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;
|
||||
|
||||
/// Сервис для работы с URL
|
||||
late final IUrlLauncher urlLauncher;
|
||||
|
||||
/// Метод для инициализации сервисов в приложении.
|
||||
///
|
||||
/// Принимает:
|
||||
@@ -32,6 +35,7 @@ final class DiServices {
|
||||
/// Последовательность инициализации:
|
||||
/// 1. Инициализация сервиса путей (AppPathProvider)
|
||||
/// 2. Инициализация защищенного хранилища (AppSecureStorage)
|
||||
/// 3. Инициализация сервиса URL (AppUrlLauncherService)
|
||||
void init({
|
||||
required OnProgress onProgress,
|
||||
required OnError onError,
|
||||
@@ -44,14 +48,19 @@ final class DiServices {
|
||||
onError('Ошибка инициализации ${IPathProvider.name}', error, stackTrace);
|
||||
}
|
||||
try {
|
||||
secureStorage = AppSecureStorage(
|
||||
secretKey: diContainer.appConfig.secretKey,
|
||||
);
|
||||
secureStorage = AppSecureStorage(secretKey: diContainer.appConfig.secretKey);
|
||||
onProgress(AppSecureStorage.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError('Ошибка инициализации ${ISecureStorage.name}', error, stackTrace);
|
||||
}
|
||||
|
||||
try {
|
||||
urlLauncher = AppUrlLauncher();
|
||||
onProgress(AppUrlLauncher.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError('Ошибка инициализации ${IUrlLauncher.name}', error, stackTrace);
|
||||
}
|
||||
|
||||
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/icons_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/tokens_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';
|
||||
|
||||
/// {@template debug_routes}
|
||||
@@ -20,6 +23,9 @@ abstract final class DebugRoutes {
|
||||
static const String themeScreenName = 'theme_screen';
|
||||
static const String langScreenName = 'lang_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';
|
||||
@@ -29,6 +35,9 @@ abstract final class DebugRoutes {
|
||||
static const String themeScreenPath = 'debug/theme';
|
||||
static const String langScreenPath = 'debug/lang';
|
||||
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,
|
||||
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:friflex_starter/app/ui_kit/app_box.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:go_router/go_router.dart';
|
||||
|
||||
@@ -20,9 +20,7 @@ class DebugScreen extends StatelessWidget {
|
||||
children: [
|
||||
Text('Окружение: ${context.di.appConfig.env.name}'),
|
||||
const HBox(22),
|
||||
Text(
|
||||
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
|
||||
),
|
||||
Text('Реализация AppServices: ${context.di.services.secureStorage.nameImpl}'),
|
||||
const HBox(22),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
@@ -74,14 +72,33 @@ class DebugScreen extends StatelessWidget {
|
||||
},
|
||||
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 Text('Имитирование ошибок:'),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
throw Exception(
|
||||
'Тестовая ошибка Exception для отладки FlutterError',
|
||||
);
|
||||
throw Exception('Тестовая ошибка Exception для отладки 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"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1032,6 +1056,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user