mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-22 17:40:45 +00:00
feat(debug): Добавить экраны отладки для плагинов
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user