import 'dart:io'; import 'package:flutter/material.dart'; import 'package:maibu_satabot_v2/core/localization/app_localizations.dart'; import 'package:maibu_satabot_v2/core/theme/AppTheme.dart'; import '../constants/report_constants.dart'; /// 媒体上传模块 class MediaUploader extends StatelessWidget { final List mediaFiles; final VoidCallback onCameraTap; final VoidCallback onVideoTap; final VoidCallback onAddFromGallery; final Function(int) onRemove; final Function(int)? onTap; const MediaUploader({ super.key, required this.mediaFiles, required this.onCameraTap, required this.onVideoTap, required this.onAddFromGallery, required this.onRemove, this.onTap, }); @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Container( margin: const EdgeInsets.symmetric( horizontal: AppDimensions.horizontalPadding, ), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: context.appColors.cardBackground, borderRadius: BorderRadius.circular(AppDimensions.borderRadius), boxShadow: [ BoxShadow( color: context.appColors.cardShadow, blurRadius: AppDimensions.cardShadowBlur, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( loc.translate('report.upload_media'), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: context.appColors.textPrimary, ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: SingleChildScrollView( scrollDirection: Axis.horizontal, physics: const ClampingScrollPhysics(), child: Row( children: [ // 拍照按钮 _buildUploadButton( context, icon: Icons.camera_alt, label: loc.translate('report.take_photo'), onTap: onCameraTap, ), const SizedBox(width: 8), // 录像按钮 _buildUploadButton( context, icon: Icons.videocam, label: loc.translate('report.record_video'), onTap: onVideoTap, ), const SizedBox(width: 8), // 已上传的媒体预览 ...mediaFiles.asMap().entries.map((entry) { final String filePath = entry.value; final bool isNetworkImage = filePath.startsWith('http'); final bool isVideo = _isVideoFile(filePath); return GestureDetector( onTap: () => onTap?.call(entry.key), child: Stack( children: [ Container( width: 80, height: 80, margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular( AppDimensions.borderRadiusSmall, ), color: context.appColors.fillBackground, ), child: ClipRRect( borderRadius: BorderRadius.circular( AppDimensions.borderRadiusSmall, ), child: isVideo ? _buildVideoThumbnail(filePath) : (isNetworkImage ? Image.network( filePath, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return _buildPlaceholderIcon( Icons.broken_image, ); }, ) : Image.file( File(filePath), fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return _buildPlaceholderIcon( Icons.broken_image, ); }, )), ), ), Positioned( top: 4, right: 12, child: GestureDetector( onTap: () => onRemove(entry.key), child: Container( width: 20, height: 20, decoration: const BoxDecoration( color: Colors.black54, shape: BoxShape.circle, ), child: const Icon( Icons.close, size: 14, color: Colors.white, ), ), ), ), // 视频播放图标 if (isVideo) Positioned( child: Container( width: 80, height: 80, margin: const EdgeInsets.only(right: 8), alignment: Alignment.center, child: Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( color: Colors.black38, shape: BoxShape.circle, ), child: const Icon( Icons.play_arrow, color: Colors.white, size: 24, ), ), ), ), ], ), ); }).toList(), // 添加按钮 _buildUploadButton( context, icon: Icons.add, label: '', onTap: onAddFromGallery, isAddButton: true, ), ], ), ), ), ], ), ); } Widget _buildUploadButton( BuildContext context, { required IconData icon, required String label, required VoidCallback onTap, bool isAddButton = false, }) { return GestureDetector( onTap: onTap, child: Container( width: 80, height: 80, decoration: BoxDecoration( color: isAddButton ? context.appColors.fillBackground : context.appColors.cardBackground, borderRadius: BorderRadius.circular(AppDimensions.borderRadiusMedium), border: Border.all( color: isAddButton ? context.appColors.divider : context.appColors.primary, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, color: isAddButton ? context.appColors.textTertiary : context.appColors.primary, size: 24, ), if (label.isNotEmpty) ...[ const SizedBox(height: 4), Text( label, style: TextStyle( fontSize: 12, color: isAddButton ? context.appColors.textTertiary : context.appColors.primary, ), ), ], ], ), ), ); } /// 判断是否为视频文件 bool _isVideoFile(String filePath) { final lowerPath = filePath.toLowerCase(); return lowerPath.endsWith('.mp4') || lowerPath.endsWith('.mov') || lowerPath.endsWith('.avi') || lowerPath.endsWith('.mkv') || lowerPath.endsWith('.wmv') || lowerPath.endsWith('.flv'); } /// 构建视频缩略图(显示占位符) Widget _buildVideoThumbnail(String filePath) { return Stack( fit: StackFit.expand, children: [ // 灰色背景 Container(color: Colors.grey[300]), // 视频图标 const Center(child: Icon(Icons.videocam, color: Colors.grey, size: 32)), ], ); } /// 构建占位图标 Widget _buildPlaceholderIcon(IconData icon) { return Container( color: Colors.grey[300], child: Center(child: Icon(icon, color: Colors.grey, size: 32)), ); } }