|
@@ -0,0 +1,584 @@
|
|
|
|
|
+import 'dart:io';
|
|
|
|
|
+import 'package:flutter/material.dart';
|
|
|
|
|
+import 'package:sino_med_cloud/core/utils/logger.dart';
|
|
|
|
|
+import 'package:sino_med_cloud/core/utils/toast_utils.dart';
|
|
|
|
|
+import 'package:sino_med_cloud/l10n/app_localizations.dart';
|
|
|
|
|
+import 'package:sino_med_cloud/media/media.dart';
|
|
|
|
|
+
|
|
|
|
|
+/// 媒体功能测试页面
|
|
|
|
|
+class MediaTestPage extends StatefulWidget {
|
|
|
|
|
+ const MediaTestPage({super.key});
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ State<MediaTestPage> createState() => _MediaTestPageState();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class _MediaTestPageState extends State<MediaTestPage> {
|
|
|
|
|
+ File? _selectedImage;
|
|
|
|
|
+ File? _cameraImage;
|
|
|
|
|
+ bool _isLoading = false;
|
|
|
|
|
+
|
|
|
|
|
+ /// 获取当前选中的图片(优先使用相册/系统相机,其次使用专业相机)
|
|
|
|
|
+ File? get _currentImage => _selectedImage ?? _cameraImage;
|
|
|
|
|
+
|
|
|
|
|
+ @override
|
|
|
|
|
+ Widget build(BuildContext context) {
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ return Scaffold(
|
|
|
|
|
+ appBar: AppBar(
|
|
|
|
|
+ title: Text(l10n.mediaTest),
|
|
|
|
|
+ elevation: 0,
|
|
|
|
|
+ ),
|
|
|
|
|
+ body: SingleChildScrollView(
|
|
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
|
|
+ child: Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ // 测试按钮区域
|
|
|
|
|
+ _buildTestButtons(),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 24),
|
|
|
|
|
+
|
|
|
|
|
+ // 图片显示区域
|
|
|
|
|
+ _buildImageDisplay(),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 24),
|
|
|
|
|
+
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 构建测试按钮区域
|
|
|
|
|
+ Widget _buildTestButtons() {
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ return Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.quickTest,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 18,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 从相册选择
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading ? null : _testPickFromGallery,
|
|
|
|
|
+ icon: const Icon(Icons.photo_library),
|
|
|
|
|
+ label: Text(l10n.pickFromGallery),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 使用系统相机拍照
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading ? null : _testPickFromCamera,
|
|
|
|
|
+ icon: const Icon(Icons.camera_alt),
|
|
|
|
|
+ label: Text(l10n.pickFromCamera),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 24),
|
|
|
|
|
+
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.professionalCameraTest,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 18,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 专业相机拍照
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading ? null : _testProfessionalCamera,
|
|
|
|
|
+ icon: const Icon(Icons.camera),
|
|
|
|
|
+ label: Text(l10n.useProfessionalCamera),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 24),
|
|
|
|
|
+
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.imageProcessingTest,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 18,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 获取图片信息
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading || _currentImage == null
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : _testGetImageInfo,
|
|
|
|
|
+ icon: const Icon(Icons.info_outline),
|
|
|
|
|
+ label: Text(l10n.getImageInfo),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 去除 EXIF
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading || _currentImage == null
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : _testRemoveExif,
|
|
|
|
|
+ icon: const Icon(Icons.security),
|
|
|
|
|
+ label: Text(l10n.removeExif),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 压缩图片
|
|
|
|
|
+ ElevatedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading || _currentImage == null
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : _testCompressImage,
|
|
|
|
|
+ icon: const Icon(Icons.compress),
|
|
|
|
|
+ label: Text(l10n.compressImage),
|
|
|
|
|
+ style: ElevatedButton.styleFrom(
|
|
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 16),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ // 清除图片
|
|
|
|
|
+ OutlinedButton.icon(
|
|
|
|
|
+ onPressed: _isLoading ? null : _clearImages,
|
|
|
|
|
+ icon: const Icon(Icons.clear),
|
|
|
|
|
+ label: Text(l10n.clearAllImages),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 构建图片显示区域
|
|
|
|
|
+ Widget _buildImageDisplay() {
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ return Column(
|
|
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
+ children: [
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.selectedImage,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 18,
|
|
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 12),
|
|
|
|
|
+
|
|
|
|
|
+ if (_selectedImage == null && _cameraImage == null)
|
|
|
|
|
+ Container(
|
|
|
|
|
+ height: 200,
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ color: Colors.grey[200],
|
|
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: Center(
|
|
|
|
|
+ child: Text(
|
|
|
|
|
+ l10n.noImage,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ color: Colors.grey,
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
|
|
+ else
|
|
|
|
|
+ Column(
|
|
|
|
|
+ children: [
|
|
|
|
|
+ if (_selectedImage != null) ...[
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.galleryOrSystemCamera,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ color: Colors.grey,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 8),
|
|
|
|
|
+ _buildImagePreview(_selectedImage!),
|
|
|
|
|
+ const SizedBox(height: 16),
|
|
|
|
|
+ ],
|
|
|
|
|
+ if (_cameraImage != null) ...[
|
|
|
|
|
+ Text(
|
|
|
|
|
+ l10n.professionalCamera,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ color: Colors.grey,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ const SizedBox(height: 8),
|
|
|
|
|
+ _buildImagePreview(_cameraImage!),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 构建图片预览
|
|
|
|
|
+ Widget _buildImagePreview(File imageFile) {
|
|
|
|
|
+ return Container(
|
|
|
|
|
+ constraints: const BoxConstraints(maxHeight: 400),
|
|
|
|
|
+ decoration: BoxDecoration(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
|
|
+ border: Border.all(color: Colors.grey[300]!),
|
|
|
|
|
+ ),
|
|
|
|
|
+ child: ClipRRect(
|
|
|
|
|
+ borderRadius: BorderRadius.circular(8),
|
|
|
|
|
+ child: Image.file(
|
|
|
|
|
+ imageFile,
|
|
|
|
|
+ fit: BoxFit.contain,
|
|
|
|
|
+ errorBuilder: (context, error, stackTrace) {
|
|
|
|
|
+ return Container(
|
|
|
|
|
+ height: 200,
|
|
|
|
|
+ color: Colors.grey[200],
|
|
|
|
|
+ child: const Center(
|
|
|
|
|
+ child: Text('图片加载失败'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试从相册选择
|
|
|
|
|
+ Future<void> _testPickFromGallery() async {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始测试从相册选择图片...');
|
|
|
|
|
+ final file = await MediaService.pickFromGallery();
|
|
|
|
|
+
|
|
|
|
|
+ if (file != null) {
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _selectedImage = file;
|
|
|
|
|
+ _cameraImage = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.galleryPickSuccess);
|
|
|
|
|
+ AppLogger.d('相册选择成功: ${file.path}');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ ToastUtils.show(l10n.userCancelledSelection);
|
|
|
|
|
+ }
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('相册选择失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('相册选择异常', e);
|
|
|
|
|
+ ToastUtils.showError('相册选择失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试使用系统相机拍照
|
|
|
|
|
+ Future<void> _testPickFromCamera() async {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始测试使用系统相机拍照...');
|
|
|
|
|
+ final file = await MediaService.pickFromCamera();
|
|
|
|
|
+
|
|
|
|
|
+ if (file != null) {
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _selectedImage = file;
|
|
|
|
|
+ _cameraImage = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.cameraPhotoSuccess);
|
|
|
|
|
+ AppLogger.d('拍照成功: ${file.path}');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ ToastUtils.show(l10n.userCancelledPhoto);
|
|
|
|
|
+ }
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('拍照失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('拍照异常', e);
|
|
|
|
|
+ ToastUtils.showError('拍照失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试专业相机
|
|
|
|
|
+ Future<void> _testProfessionalCamera() async {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ CameraService? cameraService;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始测试专业相机...');
|
|
|
|
|
+ cameraService = CameraService();
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化相机
|
|
|
|
|
+ await cameraService.init();
|
|
|
|
|
+ ToastUtils.show('相机初始化成功,请拍照');
|
|
|
|
|
+
|
|
|
|
|
+ // 显示拍照对话框
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ final shouldTakePhoto = await showDialog<bool>(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ builder: (context) => AlertDialog(
|
|
|
|
|
+ title: Text(l10n.professionalCamera),
|
|
|
|
|
+ content: Text(l10n.cameraReady),
|
|
|
|
|
+ actions: [
|
|
|
|
|
+ TextButton(
|
|
|
|
|
+ onPressed: () => Navigator.of(context).pop(false),
|
|
|
|
|
+ child: Text(l10n.cancel),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ElevatedButton(
|
|
|
|
|
+ onPressed: () => Navigator.of(context).pop(true),
|
|
|
|
|
+ child: const Text('拍照'),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (shouldTakePhoto == true && mounted) {
|
|
|
|
|
+ final file = await cameraService.takePicture();
|
|
|
|
|
+
|
|
|
|
|
+ if (file != null) {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _cameraImage = file;
|
|
|
|
|
+ _selectedImage = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.professionalCameraSuccess);
|
|
|
|
|
+ AppLogger.d('专业相机拍照成功: ${file.path}');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('专业相机失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('专业相机异常', e);
|
|
|
|
|
+ ToastUtils.showError('专业相机失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ await cameraService?.dispose();
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试获取图片信息
|
|
|
|
|
+ Future<void> _testGetImageInfo() async {
|
|
|
|
|
+ final currentImage = _currentImage;
|
|
|
|
|
+ if (currentImage == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始获取图片信息...');
|
|
|
|
|
+ final info = await MediaService.getImageInfo(currentImage);
|
|
|
|
|
+
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ final sizeInBytes = info['size'] as int;
|
|
|
|
|
+ final sizeInKB = (sizeInBytes / 1024).toStringAsFixed(2);
|
|
|
|
|
+
|
|
|
|
|
+ final infoText = '${l10n.imageWidth}: ${info['width']}${l10n.pixels}\n'
|
|
|
|
|
+ '${l10n.imageHeight}: ${info['height']}${l10n.pixels}\n'
|
|
|
|
|
+ '${l10n.imageSize}: $sizeInKB ${l10n.kilobytes}\n'
|
|
|
|
|
+ '${l10n.imageFormat}: ${info['format']}';
|
|
|
|
|
+
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 使用 Alert 弹窗显示图片信息
|
|
|
|
|
+ showDialog(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ builder: (context) => AlertDialog(
|
|
|
|
|
+ title: Text(l10n.imageInfo),
|
|
|
|
|
+ content: Text(
|
|
|
|
|
+ infoText,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontFamily: 'monospace',
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ actions: [
|
|
|
|
|
+ TextButton(
|
|
|
|
|
+ onPressed: () => Navigator.of(context).pop(),
|
|
|
|
|
+ child: Text(l10n.confirm),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.getImageInfoSuccess);
|
|
|
|
|
+ AppLogger.d('图片信息: $info');
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('获取图片信息失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('获取图片信息异常', e);
|
|
|
|
|
+ ToastUtils.showError('获取图片信息失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试去除 EXIF
|
|
|
|
|
+ Future<void> _testRemoveExif() async {
|
|
|
|
|
+ final currentImage = _currentImage;
|
|
|
|
|
+ if (currentImage == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始去除 EXIF...');
|
|
|
|
|
+ final cleanedFile = await MediaService.removeExif(currentImage);
|
|
|
|
|
+
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ // 更新对应的图片变量
|
|
|
|
|
+ if (_selectedImage != null) {
|
|
|
|
|
+ _selectedImage = cleanedFile;
|
|
|
|
|
+ } else if (_cameraImage != null) {
|
|
|
|
|
+ _cameraImage = cleanedFile;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.removeExifSuccess);
|
|
|
|
|
+ AppLogger.d('EXIF 去除成功: ${cleanedFile.path}');
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('去除 EXIF 失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('去除 EXIF 异常', e);
|
|
|
|
|
+ ToastUtils.showError('去除 EXIF 失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 测试压缩图片
|
|
|
|
|
+ Future<void> _testCompressImage() async {
|
|
|
|
|
+ final currentImage = _currentImage;
|
|
|
|
|
+ if (currentImage == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ AppLogger.d('开始压缩图片...');
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ final originalSize = await currentImage.length();
|
|
|
|
|
+ final compressedFile = await MediaService.compressImage(currentImage);
|
|
|
|
|
+ final compressedSize = await compressedFile.length();
|
|
|
|
|
+
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ // 更新对应的图片变量
|
|
|
|
|
+ if (_selectedImage != null) {
|
|
|
|
|
+ _selectedImage = compressedFile;
|
|
|
|
|
+ } else if (_cameraImage != null) {
|
|
|
|
|
+ _cameraImage = compressedFile;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ final originalSizeKB = (originalSize / 1024).toStringAsFixed(2);
|
|
|
|
|
+ final compressedSizeKB = (compressedSize / 1024).toStringAsFixed(2);
|
|
|
|
|
+ final compressionRatio = ((1 - compressedSize / originalSize) * 100).toStringAsFixed(1);
|
|
|
|
|
+
|
|
|
|
|
+ final compressInfo = '${l10n.originalSize}: $originalSizeKB ${l10n.kilobytes}\n'
|
|
|
|
|
+ '${l10n.compressedSize}: $compressedSizeKB ${l10n.kilobytes}\n'
|
|
|
|
|
+ '${l10n.compressionRatio}: $compressionRatio%';
|
|
|
|
|
+
|
|
|
|
|
+ if (!mounted) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 使用 Alert 弹窗显示压缩信息
|
|
|
|
|
+ showDialog(
|
|
|
|
|
+ context: context,
|
|
|
|
|
+ builder: (context) => AlertDialog(
|
|
|
|
|
+ title: Text(l10n.compressImageSuccess),
|
|
|
|
|
+ content: Text(
|
|
|
|
|
+ compressInfo,
|
|
|
|
|
+ style: const TextStyle(
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ fontFamily: 'monospace',
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ actions: [
|
|
|
|
|
+ TextButton(
|
|
|
|
|
+ onPressed: () => Navigator.of(context).pop(),
|
|
|
|
|
+ child: Text(l10n.confirm),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ ToastUtils.showSuccess(l10n.compressImageSuccess);
|
|
|
|
|
+ AppLogger.d('图片压缩成功: ${compressedFile.path}');
|
|
|
|
|
+ } on MediaException catch (e) {
|
|
|
|
|
+ AppLogger.e('压缩图片失败', e);
|
|
|
|
|
+ ToastUtils.showError(e.message);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ AppLogger.e('压缩图片异常', e);
|
|
|
|
|
+ ToastUtils.showError('压缩图片失败: ${e.toString()}');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 清除所有图片
|
|
|
|
|
+ void _clearImages() {
|
|
|
|
|
+ final l10n = AppLocalizations.of(context)!;
|
|
|
|
|
+ setState(() {
|
|
|
|
|
+ _selectedImage = null;
|
|
|
|
|
+ _cameraImage = null;
|
|
|
|
|
+ });
|
|
|
|
|
+ ToastUtils.show(l10n.clearAllImagesSuccess);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|