import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_view/photo_view.dart'; // 新增:图片查看器 import '../../../core/theme/app_colors.dart'; import '../controllers/picture_translation_controller.dart'; import '../models/picture_translation_models.dart'; import 'picture_translation_history_detail_view.dart'; import '../../translation/views/dialogs/language_selection_dialog.dart'; import '../../common/widgets/language_selector_bar.dart'; class PictureTranslationView extends GetView { const PictureTranslationView({super.key}); @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], appBar: _buildAppBar(isDarkMode), body: SafeArea( child: Obx(() { return CustomScrollView( slivers: [ SliverPadding( padding: EdgeInsets.all(16.w), sliver: SliverList( delegate: SliverChildListDelegate([ _buildQuickActions(), SizedBox(height: 16.h), _buildImageSection(), SizedBox(height: 16.h), if (controller.currentStep.value.status != ProcessingStatus.idle) _buildProcessingIndicator(), if (controller.sourceText.value.isNotEmpty || controller.translatedText.value.isNotEmpty) ...[ SizedBox(height: 16.h), _buildResultsSection(), ], SizedBox(height: 16.h), _buildHistorySection(), ]), ), ), ], ); }), ), ); } /// 构建应用栏 PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, elevation: 1, leading: IconButton( icon: Icon( Icons.arrow_back_ios, size: 20.sp, color: isDarkMode ? Colors.white : Colors.black87, ), onPressed: () => Get.back(), ), centerTitle: true, title: Text( 'pictureTranslation'.tr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), foregroundColor: isDarkMode ? Colors.white : Colors.black87, actions: [ IconButton( icon: const Icon(Icons.history), onPressed: _showHistoryBottomSheet, tooltip: 'historyRecords'.tr, // 历史记录 ), PopupMenuButton( onSelected: _handleMenuAction, itemBuilder: (context) => [ PopupMenuItem( value: 'clear', child: Row( children: [ const Icon(Icons.clear_all, size: 20), const SizedBox(width: 8), Text('clearResults'.tr), //清空结果 ], ), ), PopupMenuItem( value: 'settings', child: Row( children: [ const Icon(Icons.settings, size: 20), const SizedBox(width: 8), Text('settings'.tr), //设置 ], ), ), ], ), ], bottom: PreferredSize( preferredSize: Size.fromHeight(50.h), child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), child: _buildLanguageSelector(isDarkMode), ), ), ); } /// 构建语言选择栏 Widget _buildLanguageSelector(bool isDarkMode) { return LanguageSelectorBar( sourceText: () => controller.sourceLanguageChineseName.value, targetText: () => controller.targetLanguageChineseName.value, onTapSource: () => _showLanguageDialog(true), onTapTarget: () => _showLanguageDialog(false), onSwap: controller.swapLanguages, translateLanguageNames: true, // 翻译键代表的中文文本内容 fontSizeSp: 15, ); } /// 构建快速操作区域 Widget _buildQuickActions() { return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Get.isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'selectImageSource'.tr, // 选择图片来源 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), SizedBox(height: 12.h), Row( children: [ Expanded( child: _buildActionButton( icon: Icons.photo_library, label: 'photoAlbum'.tr, onTap: controller.pickFromGallery, color: Colors.blue, ), ), SizedBox(width: 12.w), Expanded( child: _buildActionButton( icon: Icons.camera_alt, label: 'takePhoto'.tr, onTap: controller.pickFromCamera, color: Colors.green, ), ), ], ), ], ), ); } /// 构建操作按钮 Widget _buildActionButton({ required IconData icon, required String label, required VoidCallback onTap, required Color color, }) { final isDarkMode = Get.isDarkMode; return Material( color: color.withOpacity(isDarkMode ? 0.2 : 0.1), borderRadius: BorderRadius.circular(8.r), child: InkWell( onTap: controller.isProcessing.value ? null : onTap, borderRadius: BorderRadius.circular(8.r), child: Container( padding: EdgeInsets.symmetric(vertical: 16.h), child: Column( children: [ Icon( icon, color: color, size: 24.sp, ), SizedBox(height: 4.h), Text( label, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, color: color, ), ), ], ), ), ), ); } Future _showLanguageDialog(bool isSource) async { final isDarkMode = Get.isDarkMode; // 使用Get.isDarkMode替代Theme.of(context) final langs = controller.languageManager.getAllChineseNames(); final currentLanguage = isSource ? controller.sourceLanguageChineseName.value : controller.targetLanguageChineseName.value; // 复用统一语言选择弹窗组件(保持中文名直接显示) LanguageSelectionDialog.show( isSource, isDarkMode, languages: langs, currentLanguage: currentLanguage, excludeLanguage: isSource ? controller.targetLanguageChineseName.value : controller.sourceLanguageChineseName.value, includeAutoDetect: isSource, translateLanguageNames: true, // 开启多语言显示,语言中文名作为翻译键 onSelected: (name) { if (isSource) { controller.setSourceLanguage(name); } else { controller.setTargetLanguage(name); } }, ); } Widget _buildImageSection() { return Container( decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Get.isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.w), child: Text( 'imagePreview'.tr, // 图片预览 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), ), _buildImagePreview(), ], ), ); } Widget _buildProcessingIndicator() { final step = controller.currentStep.value; return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Get.isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ _buildStatusIcon(step.status), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( step.title, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), if (step.description.isNotEmpty) ...[ SizedBox(height: 4.h), Text( step.description, style: TextStyle( fontSize: 14.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600], ), ), ], ], ), ), ], ), if (step.progress > 0 && step.status != ProcessingStatus.error) ...[ SizedBox(height: 12.h), LinearProgressIndicator( value: step.progress, backgroundColor: Get.isDarkMode ? Colors.white12 : Colors.grey[200], valueColor: AlwaysStoppedAnimation(_getStatusColor(step.status)), ), ], ], ), ); } Widget _buildStatusIcon(ProcessingStatus status) { switch (status) { case ProcessingStatus.picking: case ProcessingStatus.recognizing: case ProcessingStatus.translating: case ProcessingStatus.saving: return SizedBox( width: 20.w, height: 20.w, child: CircularProgressIndicator( strokeWidth: 2, color: _getStatusColor(status), ), ); case ProcessingStatus.error: return Icon(Icons.error_outline, color: AppColors.error, size: 20.sp); case ProcessingStatus.idle: return Icon(Icons.check_circle_outline, color: AppColors.success, size: 20.sp); } } Color _getStatusColor(ProcessingStatus status) { switch (status) { case ProcessingStatus.picking: return AppColors.primary; case ProcessingStatus.recognizing: return AppColors.warning; case ProcessingStatus.translating: return AppColors.accent; case ProcessingStatus.saving: return AppColors.success; case ProcessingStatus.error: return AppColors.error; case ProcessingStatus.idle: return AppColors.disabled; } } Widget _buildResultsSection() { return Container( decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Get.isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.w), child: Text( 'translationResult'.tr, // 翻译结果 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), ), if (controller.sourceText.value.isNotEmpty) _buildTextCard( 'recognitionResult'.tr, // 识别结果 controller.sourceText.value, controller.sourceLanguageChineseName.value, Colors.blue, ), if (controller.translatedText.value.isNotEmpty) _buildTextCard( 'translationResult'.tr, // 翻译结果 controller.translatedText.value, controller.targetLanguageChineseName.value, Colors.green, ), if (controller.translatedImageBytes.value != null) ...[ Padding( padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( 'translatedImagePreview'.tr, // 翻译后的图片预览 style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), ), SizedBox(height: 8.h), Container( height: 200.h, margin: EdgeInsets.symmetric(horizontal: 16.w).copyWith(bottom: 16.h), decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.grey[100], borderRadius: BorderRadius.circular(8.r), border: Border.all( color: Get.isDarkMode ? Colors.white12 : Colors.grey[300]!), ), child: GestureDetector( onTap: () => _openImageViewer( MemoryImage(controller.translatedImageBytes.value!), ), child: ClipRRect( borderRadius: BorderRadius.circular(8.r), child: Image.memory( controller.translatedImageBytes.value!, fit: BoxFit.cover, width: double.infinity, errorBuilder: (context, error, stackTrace) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48.sp, color: Colors.red[400]), SizedBox(height: 8.h), Text( 'translatedImageLoadFailed'.tr, // 翻译图片加载失败 style: TextStyle( fontSize: 14.sp, color: Colors.red[600]), ), ], ), ); }, ), ), ), ), ], ], ), ); } Widget _buildTextCard( String title, String content, String language, Color color) { return Container( margin: EdgeInsets.symmetric(horizontal: 16.w).copyWith(bottom: 16.h), padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: color.withOpacity(Get.isDarkMode ? 0.15 : 0.05), borderRadius: BorderRadius.circular(8.r), border: Border.all(color: color.withOpacity(Get.isDarkMode ? 0.3 : 0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '$title (${language.tr})', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: color, ), ), IconButton( icon: Icon(Icons.copy, size: 18.sp, color: color), onPressed: () { Clipboard.setData(ClipboardData(text: content)); Get.snackbar('success'.tr, 'copied'.tr); // 对应中文:复制成功 }, padding: EdgeInsets.zero, constraints: const BoxConstraints(), tooltip: 'copy'.tr, // 复制 ), ], ), SizedBox(height: 8.h), SelectableText( content, style: TextStyle( fontSize: 15.sp, color: Get.isDarkMode ? Colors.white70 : Colors.black87, height: 1.4), ), ], ), ); } Widget _buildHistorySection() { return Container( decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: Get.isDarkMode ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'translationHistory'.tr, // 翻译历史 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Get.isDarkMode ? AppColors.textPrimary : Colors.black87, ), ), TextButton( onPressed: _showHistoryBottomSheet, child: Text('viewAll'.tr), // 查看全部 ), ], ), ), if (controller.historyList.isEmpty) Padding( padding: EdgeInsets.symmetric(horizontal: 16.w).copyWith(bottom: 16.h), child: Text( 'noTranslationHistory'.tr, // 暂无历史记录 style: TextStyle( fontSize: 14.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600]), ), ) else ...controller.historyList .take(3) .map((item) => _buildHistoryItem(item)) .toList(), ], ), ); } Widget _buildHistoryItem(PictureTranslationHistory item) { return ListTile( leading: ClipRRect( borderRadius: BorderRadius.circular(4.r), child: Container( width: 40.w, height: 40.w, color: Get.isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[200], child: Icon(Icons.image, color: Get.isDarkMode ? Colors.white70 : Colors.grey[400]), ), ), title: Text( item.sourceText, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 14.sp), ), subtitle: Text( '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', // 源语言名/目标语言名 style: TextStyle( fontSize: 12.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600]), ), trailing: Text( _formatTime(item.timestamp), style: TextStyle( fontSize: 12.sp, color: Get.isDarkMode ? Colors.white54 : Colors.grey[500]), ), onTap: () => _showHistoryDetail(item), ); } void _showHistoryBottomSheet() { Get.bottomSheet( Container( height: 600.h, decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)), ), child: Column( children: [ Container( padding: EdgeInsets.all(16.w), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'translationHistory'.tr, // 翻译历史 style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, color: Get.isDarkMode ? AppColors.textPrimary : null), ), IconButton( onPressed: () => Get.back(), icon: Icon(Icons.close, color: Get.isDarkMode ? AppColors.textPrimary : null), ), ], ), ), const Divider(height: 1), Expanded( child: Obx(() { if (controller.historyList.isEmpty) { return Center( child: Text( 'noTranslationHistory'.tr, // 暂无历史记录 style: TextStyle( fontSize: 16.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600]), ), ); } return ListView.separated( itemCount: controller.historyList.length, separatorBuilder: (context, index) => const Divider(height: 1), itemBuilder: (context, index) { final item = controller.historyList[index]; return _buildHistoryItem(item); }, ); }), ), ], ), ), isScrollControlled: true, ); } void _handleMenuAction(String action) { switch (action) { case 'clear': controller.reset(); break; case 'settings': _showSettingsDialog(); break; } } void _showHistoryDetail(PictureTranslationHistory item) { Get.to(() => PictureTranslationHistoryDetailView(item: item)); } String _formatTime(DateTime timestamp) { final now = DateTime.now(); final difference = now.difference(timestamp); if (difference.inDays > 0) { return 'daysAgo' .tr .replaceAll('@days', difference.inDays.toString()); // @days天前 } else if (difference.inHours > 0) { return 'hoursAgo' .tr .replaceAll('@hours', difference.inHours.toString()); // @hours小时前 } else if (difference.inMinutes > 0) { return 'minutesAgo'.tr.replaceAll( '@minutes', difference.inMinutes.toString()); // @minutes分钟前 } else { return 'justNow'.tr; // 刚刚 } } void _showSettingsDialog() { Get.dialog( AlertDialog( backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white, title: Text('settings'.tr, // 设置 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: Icon(Icons.photo_size_select_actual, color: Get.isDarkMode ? Colors.white : null), title: Text('imageQuality'.tr, // 图片质量 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), subtitle: Obx(() => Text( 'currentQuality'.tr.replaceAll( '@value', controller.imageQuality.value .toString()), // 当前质量: @value% style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54))), onTap: () { Get.back(); _showImageQualityDialog(); }, ), ListTile( leading: Icon(Icons.photo_size_select_large, color: Get.isDarkMode ? Colors.white : null), title: Text('maxSize'.tr, // 最大尺寸 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), subtitle: Obx(() => Text( 'currentMaxSize'.tr.replaceAll('@px', controller.maxImageSize.value.toString()), // 当前: @pxpx style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54))), onTap: () { Get.back(); _showImageSizeDialog(); }, ), ], ), actions: [ TextButton( onPressed: () => Get.back(), child: Text('close'.tr, // 关闭 style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), ), ], ), ); } void _showImageQualityDialog() { Get.dialog( AlertDialog( backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white, title: Text('setImageQuality'.tr, // 设置图片质量 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), content: Obx(() => Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'currentQuality'.tr.replaceAll( '@value', controller.imageQuality.value .toString()), // 当前质量: @value% style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), Slider( value: controller.imageQuality.value.toDouble(), min: 50, max: 100, divisions: 10, onChanged: (value) { controller.imageQuality.value = value.toInt(); }, ), ], )), actions: [ TextButton( onPressed: () => Get.back(), child: Text('confirm'.tr, // 确认 style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), ), ], ), ); } void _showImageSizeDialog() { final sizes = [1280, 1920, 2560, 3840]; Get.dialog( AlertDialog( backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white, title: Text('setMaxSize'.tr, // 设置最大尺寸 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), content: Column( mainAxisSize: MainAxisSize.min, children: sizes .map((size) => RadioListTile( title: Text('${size}px'), value: size, groupValue: controller.maxImageSize.value, onChanged: (value) { if (value != null) { controller.maxImageSize.value = value; Get.back(); } }, )) .toList(), ), actions: [ TextButton( onPressed: () => Get.back(), child: Text('cancel'.tr, // 取消 style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), ), ], ), ); } void _showClearHistoryDialog() { Get.dialog( AlertDialog( backgroundColor: Get.isDarkMode ? Colors.grey[900] : Colors.white, title: Text('clearHistory'.tr, // 清除历史记录 style: TextStyle( color: Get.isDarkMode ? Colors.white : Colors.black87)), content: Text('clearHistoryConfirm'.tr, // 确定要清除所有翻译历史记录吗?此操作无法撤销。 style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), actions: [ TextButton( onPressed: () => Get.back(), child: Text('cancel'.tr, // 取消 style: TextStyle( color: Get.isDarkMode ? Colors.white70 : Colors.black54)), ), TextButton( onPressed: () { controller.clearHistory(); Get.back(); }, child: Text('confirm'.tr, // 确认 style: TextStyle(color: AppColors.error)), ), ], ), ); } Widget _buildImagePreview() { final file = controller.pickedImageFile.value; return Container( height: 200.h, margin: EdgeInsets.symmetric(horizontal: 16.w).copyWith(bottom: 16.h), decoration: BoxDecoration( color: Get.isDarkMode ? AppColors.cardBackground : Colors.grey[100], borderRadius: BorderRadius.circular(8.r), border: Border.all( color: Get.isDarkMode ? Colors.white12 : Colors.grey[300]!), ), child: file == null ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.image_outlined, size: 48.sp, color: Get.isDarkMode ? Colors.white54 : Colors.grey[400]), SizedBox(height: 8.h), Text( 'pleaseSelectImage'.tr, // 请选择图片 style: TextStyle( fontSize: 14.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600]), ), ], ), ) : GestureDetector( // 点击原图预览打开查看器(与翻译后的图片预览一致) onTap: () => _openImageViewer(FileImage(file)), child: ClipRRect( borderRadius: BorderRadius.circular(8.r), child: Image.file( file, fit: BoxFit.cover, width: double.infinity, errorBuilder: (context, error, stackTrace) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48.sp, color: Colors.red[400]), SizedBox(height: 8.h), Text( 'imageLoadFailed'.tr, // 图片加载失败 style: TextStyle( fontSize: 14.sp, color: Get.isDarkMode ? AppColors.textSecondary : Colors.grey[600]), ), ], ), ); }, ), ), ), ); } Widget _buildSourceTextBox() { return _buildTextBox( 'recognitionResult'.tr, // 识别结果 controller.sourceText.value); } Widget _buildTranslatedTextBox() { return _buildTextBox( 'translationResult'.tr, // 翻译结果 controller.translatedText.value); } Widget _buildTextBox(String title, String content) { return Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.r), border: Border.all( color: Get.isDarkMode ? Colors.white12 : Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8.h), Text(content.isEmpty ? '—' : content), ], ), ); } } void _openImageViewer(ImageProvider provider) { Get.to(() => Scaffold( body: Container( color: Colors.black, child: GestureDetector( onTap: () => Get.back(), // 点击退出 child: PhotoView( imageProvider: provider, minScale: PhotoViewComputedScale.contained, maxScale: PhotoViewComputedScale.covered * 2, backgroundDecoration: const BoxDecoration(color: Colors.black), ), ), ), )); }