diff --git a/app_services/aurora/app_services/lib/app_services.dart b/app_services/aurora/app_services/lib/app_services.dart index a2dadb6..459cad9 100644 --- a/app_services/aurora/app_services/lib/app_services.dart +++ b/app_services/aurora/app_services/lib/app_services.dart @@ -2,3 +2,4 @@ library; export 'src/app_path_provider.dart'; export 'src/app_secure_storage.dart'; +export 'src/app_url_launcher.dart'; diff --git a/app_services/aurora/app_services/lib/src/app_path_provider.dart b/app_services/aurora/app_services/lib/src/app_path_provider.dart index ed1bb27..96e8afe 100644 --- a/app_services/aurora/app_services/lib/src/app_path_provider.dart +++ b/app_services/aurora/app_services/lib/src/app_path_provider.dart @@ -11,6 +11,8 @@ class AppPathProvider implements IPathProvider { /// Наименование сервиса static const name = 'AuroraAppPathProvider'; + String get nameImpl => AppPathProvider.name; + @override Future getAppDocumentsDirectoryPath() async { return (await getApplicationDocumentsDirectory()).path; diff --git a/app_services/aurora/app_services/lib/src/app_secure_storage.dart b/app_services/aurora/app_services/lib/src/app_secure_storage.dart index 5af0c1d..ac78027 100644 --- a/app_services/aurora/app_services/lib/src/app_secure_storage.dart +++ b/app_services/aurora/app_services/lib/src/app_secure_storage.dart @@ -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 write(String key, String value) async { await _box.write(key: key, value: value); } - - @override - String get nameImpl => AppSecureStorage.name; } diff --git a/app_services/aurora/app_services/lib/src/app_url_launcher.dart b/app_services/aurora/app_services/lib/src/app_url_launcher.dart new file mode 100644 index 0000000..0d6a29e --- /dev/null +++ b/app_services/aurora/app_services/lib/src/app_url_launcher.dart @@ -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 canLaunchUrl(Uri url) async { + return url_launcher.canLaunchUrl(url); + } + + @override + Future launchUrl(Uri url) async { + return url_launcher.launchUrl(url); + } +} diff --git a/app_services/aurora/app_services/pubspec.yaml b/app_services/aurora/app_services/pubspec.yaml index e26c212..1696092 100644 --- a/app_services/aurora/app_services/pubspec.yaml +++ b/app_services/aurora/app_services/pubspec.yaml @@ -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 diff --git a/app_services/base/app_services/lib/app_services.dart b/app_services/base/app_services/lib/app_services.dart index a2dadb6..459cad9 100644 --- a/app_services/base/app_services/lib/app_services.dart +++ b/app_services/base/app_services/lib/app_services.dart @@ -2,3 +2,4 @@ library; export 'src/app_path_provider.dart'; export 'src/app_secure_storage.dart'; +export 'src/app_url_launcher.dart'; diff --git a/app_services/base/app_services/lib/src/app_path_provider.dart b/app_services/base/app_services/lib/src/app_path_provider.dart index 238cade..7052ef9 100644 --- a/app_services/base/app_services/lib/src/app_path_provider.dart +++ b/app_services/base/app_services/lib/src/app_path_provider.dart @@ -11,6 +11,9 @@ class AppPathProvider implements IPathProvider { /// Наименование сервиса static const name = 'BaseAppPathProvider'; + @override + String get nameImpl => AppPathProvider.name; + @override Future getAppDocumentsDirectoryPath() async { return (await getApplicationDocumentsDirectory()).path; diff --git a/app_services/base/app_services/lib/src/app_secure_storage.dart b/app_services/base/app_services/lib/src/app_secure_storage.dart index 54d4855..8ce6753 100644 --- a/app_services/base/app_services/lib/src/app_secure_storage.dart +++ b/app_services/base/app_services/lib/src/app_secure_storage.dart @@ -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 write(String key, String value) async { await _box.write(key: key, value: value); } - - @override - String get nameImpl => AppSecureStorage.name; } diff --git a/app_services/base/app_services/lib/src/app_url_launcher.dart b/app_services/base/app_services/lib/src/app_url_launcher.dart new file mode 100644 index 0000000..11930ce --- /dev/null +++ b/app_services/base/app_services/lib/src/app_url_launcher.dart @@ -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 canLaunchUrl(Uri url) async { + return url_launcher.canLaunchUrl(url); + } + + @override + Future launchUrl(Uri url) async { + return url_launcher.launchUrl(url); + } +} diff --git a/app_services/base/app_services/pubspec.yaml b/app_services/base/app_services/pubspec.yaml index 6e7ccc9..30a1d3f 100644 --- a/app_services/base/app_services/pubspec.yaml +++ b/app_services/base/app_services/pubspec.yaml @@ -19,6 +19,9 @@ dependencies: # для работы с путями в хранилища path_provider: 2.1.5 + # Зависимости для сервиса внешних ссылок + url_launcher: 6.3.1 + # Обязательные интерфейсы i_app_services: path: ../../i_app_services diff --git a/app_services/i_app_services/lib/i_app_services.dart b/app_services/i_app_services/lib/i_app_services.dart index 036bc78..2867da5 100644 --- a/app_services/i_app_services/lib/i_app_services.dart +++ b/app_services/i_app_services/lib/i_app_services.dart @@ -2,3 +2,4 @@ library; export 'src/i_path_provider.dart'; export 'src/i_secure_storage.dart'; +export 'src/i_url_launcher.dart'; diff --git a/app_services/i_app_services/lib/src/i_path_provider.dart b/app_services/i_app_services/lib/src/i_path_provider.dart index ead04b5..84bad92 100644 --- a/app_services/i_app_services/lib/src/i_path_provider.dart +++ b/app_services/i_app_services/lib/src/i_path_provider.dart @@ -1,9 +1,11 @@ -/// Класс для описания интерфейса сервиса -/// для получения пути хранения файлов +/// Класс для описания интерфейса сервиса для получения пути хранения файлов abstract interface class IPathProvider { /// Наименования интерфейса static const name = 'IPathProvider'; + /// Получение имени имплементации + String get nameImpl; + /// Получение path на внутренне хранилище приложения Future getAppDocumentsDirectoryPath(); } diff --git a/app_services/i_app_services/lib/src/i_secure_storage.dart b/app_services/i_app_services/lib/src/i_secure_storage.dart index 9e7bb03..a2cd444 100644 --- a/app_services/i_app_services/lib/src/i_secure_storage.dart +++ b/app_services/i_app_services/lib/src/i_secure_storage.dart @@ -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 containsKey(String key); - - String get nameImpl; } diff --git a/app_services/i_app_services/lib/src/i_url_launcher.dart b/app_services/i_app_services/lib/src/i_url_launcher.dart new file mode 100644 index 0000000..4cfb6fa --- /dev/null +++ b/app_services/i_app_services/lib/src/i_url_launcher.dart @@ -0,0 +1,18 @@ +/// Класс для описания интерфейса сервиса для запуска URL +abstract interface class IUrlLauncher { + /// Наименования интерфейса + static const name = 'IUrlLauncher'; + + /// Получение имени имплементации + String get nameImpl; + + /// Метод для проверки возможности запуска ссылки + /// + /// - [url] - ссылка для проверки + Future canLaunchUrl(Uri url); + + /// Метод для запуска ссылки + /// + /// - [url] - ссылка для запуска + Future launchUrl(Uri url); +} diff --git a/lib/di/di_services.dart b/lib/di/di_services.dart index 9c6eec2..07b331a 100644 --- a/lib/di/di_services.dart +++ b/lib/di/di_services.dart @@ -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('Инициализация сервисов завершена!'); } } diff --git a/lib/features/debug/debug_routes.dart b/lib/features/debug/debug_routes.dart index 439681e..40804f5 100644 --- a/lib/features/debug/debug_routes.dart +++ b/lib/features/debug/debug_routes.dart @@ -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(), + ), ], ); } diff --git a/lib/features/debug/screens/debug_screen.dart b/lib/features/debug/screens/debug_screen.dart index ec070fb..16b5fe5 100644 --- a/lib/features/debug/screens/debug_screen.dart +++ b/lib/features/debug/screens/debug_screen.dart @@ -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'), ), diff --git a/lib/features/debug/screens/path_provider_screen.dart b/lib/features/debug/screens/path_provider_screen.dart new file mode 100644 index 0000000..a34fb3e --- /dev/null +++ b/lib/features/debug/screens/path_provider_screen.dart @@ -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 createState() => _PathProviderScreenState(); +} + +class _PathProviderScreenState extends State { + /// Плагин для работы с путями в приложении + late final IPathProvider _pathProvider; + + /// Корневой путь к директории файлов приложения + String? _rootPath; + + /// Текущий путь к директории, отображаемой в списке + String? _currentPath; + + /// Загрузка файлов + Future?>? _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?>( + 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?> _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? _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); + }); + } +} diff --git a/lib/features/debug/screens/secure_storage_screen.dart b/lib/features/debug/screens/secure_storage_screen.dart new file mode 100644 index 0000000..0fd535f --- /dev/null +++ b/lib/features/debug/screens/secure_storage_screen.dart @@ -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 createState() => _SecureStorageScreenState(); +} + +class _SecureStorageScreenState extends State { + /// Плагин для работы с защищенным хранилищем + 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 _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 _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 _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'); + } + } +} diff --git a/lib/features/debug/screens/url_launcher_screen.dart b/lib/features/debug/screens/url_launcher_screen.dart new file mode 100644 index 0000000..492944f --- /dev/null +++ b/lib/features/debug/screens/url_launcher_screen.dart @@ -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 createState() => _UrlLauncherScreenState(); +} + +class _UrlLauncherScreenState extends State { + /// Плагин для работы с 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 _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 _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'); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index b0767e6..7c2e2ea 100644 --- a/pubspec.lock +++ b/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: