feat(debug): Добавить экраны отладки для плагинов

This commit is contained in:
Artem Barkalov
2025-06-23 01:03:38 +03:00
parent fb92795b67
commit 9b5f80e7d9
21 changed files with 605 additions and 30 deletions

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;
} }

View 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 = '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);
}
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;
} }

View 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);
}
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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();
} }

View File

@@ -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;
} }

View 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);
}

View File

@@ -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('Инициализация сервисов завершена!');
} }
} }

View File

@@ -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(),
),
], ],
); );
} }

View File

@@ -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'),
), ),

View 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);
});
}
}

View 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');
}
}
}

View 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');
}
}
}

View File

@@ -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: