import 'dart:io'; import 'package:flutter/material.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; /// 图片网格预览组件 - 用于批量模式下显示多张图片的网格布局 /// 支持图片预览、选择状态和错误处理 class ImageGridPreview extends StatelessWidget { /// 分享的图片文件列表 final List images; /// 选中的图片索引列表 final List selectedIndices; /// 图片选择状态变更回调 final Function(int index, bool selected) onSelectionChanged; /// 是否显示选择框 final bool showSelection; /// 网格列数 final int crossAxisCount; /// 图片间距 final double spacing; /// 边距 final EdgeInsets padding; const ImageGridPreview({ Key? key, required this.images, required this.selectedIndices, required this.onSelectionChanged, this.showSelection = true, this.crossAxisCount = 2, this.spacing = 8.0, this.padding = const EdgeInsets.all(16), }) : super(key: key); @override Widget build(BuildContext context) { if (images.isEmpty) { return _buildEmptyState(context); } return Padding( padding: padding, child: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: spacing, mainAxisSpacing: spacing, childAspectRatio: 1.0, ), itemCount: images.length, itemBuilder: (context, index) => _buildImageItem(context, index), ), ); } /// 构建空状态 Widget _buildEmptyState(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.photo_library_outlined, size: 64, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 16), Text( '暂无图片', style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.outline, ), ), ], ), ); } /// 构建单个图片项 Widget _buildImageItem(BuildContext context, int index) { final imageFile = images[index]; final isSelected = selectedIndices.contains(index); return GestureDetector( onTap: () { if (showSelection) { onSelectionChanged(index, !isSelected); } else { _showImagePreview(context, index); } }, onLongPress: () => _showImagePreview(context, index), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: isSelected ? Border.all( color: Theme.of(context).colorScheme.primary, width: 3, ) : null, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(5), // 稍小以避免边框溢出 child: Stack( fit: StackFit.expand, children: [ // 图片显示 Image.file( File(imageFile.path), fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => _buildErrorPlaceholder(context), frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { if (frame == null) return _buildLoadingPlaceholder(context); return child; }, ), // 选择标记 if (showSelection) Positioned( top: 8, right: 8, child: Container( width: 24, height: 24, decoration: BoxDecoration( color: isSelected ? Theme.of(context).colorScheme.primary : Colors.white.withOpacity(0.7), shape: BoxShape.circle, border: Border.all( color: isSelected ? Theme.of(context).colorScheme.primary : Colors.grey.withOpacity(0.5), width: 2, ), ), child: isSelected ? const Icon( Icons.check, color: Colors.white, size: 16, ) : null, ), ), // 文件类型标记 if (_isGifImage(imageFile.path)) Positioned( bottom: 8, left: 8, child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), borderRadius: BorderRadius.circular(4), ), child: const Text( 'GIF', style: TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ); } /// 构建加载中的占位符 Widget _buildLoadingPlaceholder(BuildContext context) { return Container( color: Colors.grey[200], child: Center( child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.primary, ), ), ), ); } /// 构建错误占位符 Widget _buildErrorPlaceholder(BuildContext context) { return Container( color: Colors.grey[200], child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.broken_image, size: 32, color: Colors.grey[400], ), const SizedBox(height: 4), Text( '加载失败', style: TextStyle( color: Colors.grey[600], fontSize: 12, ), ), ], ), ); } /// 显示图片预览 void _showImagePreview(BuildContext context, int index) { Navigator.of(context).push( PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => ImagePreviewPage( images: images, initialIndex: index, ), transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ), ); } /// 检查是否为GIF图片 bool _isGifImage(String path) { return path.toLowerCase().endsWith('.gif'); } } /// 图片预览页面 - 全屏显示单张图片 class ImagePreviewPage extends StatefulWidget { /// 图片文件列表 final List images; /// 初始显示的图片索引 final int initialIndex; const ImagePreviewPage({ Key? key, required this.images, required this.initialIndex, }) : super(key: key); @override State createState() => _ImagePreviewPageState(); } class _ImagePreviewPageState extends State with SingleTickerProviderStateMixin { late PageController _pageController; late AnimationController _animationController; late Animation _fadeAnimation; /// 当前显示的图片索引 int currentIndex = 0; @override void initState() { super.initState(); currentIndex = widget.initialIndex; _pageController = PageController(initialPage: currentIndex); // 动画控制器 _animationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); // 延迟显示UI控件 Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { _animationController.forward(); } }); } @override void dispose() { _pageController.dispose(); _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // 图片显示区域 PageView.builder( controller: _pageController, itemCount: widget.images.length, onPageChanged: (index) { setState(() { currentIndex = index; }); }, itemBuilder: (context, index) => _buildFullImage(context, index), ), // 顶部工具栏 FadeTransition( opacity: _fadeAnimation, child: _buildTopToolbar(), ), // 底部指示器 FadeTransition( opacity: _fadeAnimation, child: _buildBottomIndicator(), ), ], ), ); } /// 构建全屏图片 Widget _buildFullImage(BuildContext context, int index) { final imageFile = widget.images[index]; return InteractiveViewer( panEnabled: true, boundaryMargin: const EdgeInsets.all(20), minScale: 0.5, maxScale: 4.0, child: Image.file( File(imageFile.path), fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) => _buildErrorView(), ), ); } /// 构建错误视图 Widget _buildErrorView() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.broken_image, size: 64, color: Colors.white54, ), const SizedBox(height: 16), Text( '图片加载失败', style: TextStyle( color: Colors.white54, fontSize: 16, ), ), ], ), ); } /// 构建顶部工具栏 Widget _buildTopToolbar() { return Positioned( top: 0, left: 0, right: 0, child: Container( height: MediaQuery.of(context).padding.top + 56, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.7), Colors.transparent, ], ), ), padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top, left: 16, right: 16, ), child: Row( children: [ // 返回按钮 IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon( Icons.arrow_back, color: Colors.white, ), ), const Spacer(), // 图片信息 if (_isGifImage(widget.images[currentIndex].path)) Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: const Text( 'GIF', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), ), ); } /// 构建底部指示器 Widget _buildBottomIndicator() { return Positioned( bottom: MediaQuery.of(context).padding.bottom + 16, left: 0, right: 0, child: Center( child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(16), ), child: Text( '${currentIndex + 1} / ${widget.images.length}', style: const TextStyle( color: Colors.white, fontSize: 14, ), ), ), ), ); } /// 检查是否为GIF图片 bool _isGifImage(String path) { return path.toLowerCase().endsWith('.gif'); } }