| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | import 'dart:io'; | 
					
						
							|  |  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  |  | import 'package:image_picker/image_picker.dart'; | 
					
						
							|  |  |  |  | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; | 
					
						
							|  |  |  |  | import 'package:photo_manager/photo_manager.dart'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import 'ItemWidgetFactory.dart'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /// 媒体选择类型
 | 
					
						
							|  |  |  |  | enum MediaType { image, video } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | /// 纵向滚动四列的媒体添加组件,支持拍摄、全屏相册多选,以及初始地址列表展示
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | /// 使用示例:
 | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | /// MediaPickerGrid(
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | ///   maxCount: 4,
 | 
					
						
							|  |  |  |  | ///   mediaType: MediaType.video,
 | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  | ///   initialMediaPaths: ['https://...', '/local/path.png'],
 | 
					
						
							|  |  |  |  | ///   onChanged: (List<File> medias) {},
 | 
					
						
							|  |  |  |  | ///   onMediaAdded: (String path) {},
 | 
					
						
							|  |  |  |  | ///   onMediaRemoved: (String path) {},
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | /// ),
 | 
					
						
							|  |  |  |  | class MediaPickerRow extends StatefulWidget { | 
					
						
							|  |  |  |  |   final int maxCount; | 
					
						
							|  |  |  |  |   final MediaType mediaType; | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   final List<String>? initialMediaPaths; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final ValueChanged<List<File>> onChanged; | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   final ValueChanged<String>? onMediaAdded; | 
					
						
							|  |  |  |  |   final ValueChanged<String>? onMediaRemoved; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const MediaPickerRow({ | 
					
						
							|  |  |  |  |     Key? key, | 
					
						
							|  |  |  |  |     this.maxCount = 4, | 
					
						
							|  |  |  |  |     this.mediaType = MediaType.image, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     this.initialMediaPaths, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     required this.onChanged, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     this.onMediaAdded, | 
					
						
							|  |  |  |  |     this.onMediaRemoved, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   }) : super(key: key); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |   _MediaPickerGridState createState() => _MediaPickerGridState(); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | class _MediaPickerGridState extends State<MediaPickerRow> { | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final ImagePicker _picker = ImagePicker(); | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   late List<String> _mediaPaths; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   void initState() { | 
					
						
							|  |  |  |  |     super.initState(); | 
					
						
							|  |  |  |  |     _mediaPaths = widget.initialMediaPaths != null | 
					
						
							|  |  |  |  |         ? widget.initialMediaPaths!.take(widget.maxCount).toList() | 
					
						
							|  |  |  |  |         : []; | 
					
						
							|  |  |  |  |     WidgetsBinding.instance.addPostFrameCallback((_) { | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |       widget.onChanged( | 
					
						
							|  |  |  |  |         _mediaPaths.map((p) => p.startsWith('http') ? File('') : File(p)).toList(), | 
					
						
							|  |  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   Future<void> _showPickerOptions() async { | 
					
						
							|  |  |  |  |     showModalBottomSheet( | 
					
						
							|  |  |  |  |       context: context, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |       builder: (_) => SafeArea( | 
					
						
							|  |  |  |  |         child: Wrap( | 
					
						
							|  |  |  |  |           children: [ | 
					
						
							|  |  |  |  |             ListTile( | 
					
						
							|  |  |  |  |               leading: Icon( | 
					
						
							|  |  |  |  |                 widget.mediaType == MediaType.image | 
					
						
							|  |  |  |  |                     ? Icons.camera_alt | 
					
						
							|  |  |  |  |                     : Icons.videocam, | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |               title: Text( | 
					
						
							|  |  |  |  |                 widget.mediaType == MediaType.image ? '拍照' : '拍摄视频', | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |               onTap: () { | 
					
						
							|  |  |  |  |                 Navigator.of(context).pop(); | 
					
						
							|  |  |  |  |                 _pickCamera(); | 
					
						
							|  |  |  |  |               }, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |             ListTile( | 
					
						
							|  |  |  |  |               leading: Icon( | 
					
						
							|  |  |  |  |                 widget.mediaType == MediaType.image | 
					
						
							|  |  |  |  |                     ? Icons.photo_library | 
					
						
							|  |  |  |  |                     : Icons.video_library, | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |               title: Text( | 
					
						
							|  |  |  |  |                 widget.mediaType == MediaType.image | 
					
						
							|  |  |  |  |                     ? '从相册选择' | 
					
						
							|  |  |  |  |                     : '从相册选择视频', | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |               onTap: () { | 
					
						
							|  |  |  |  |                 Navigator.of(context).pop(); | 
					
						
							|  |  |  |  |                 _pickGallery(); | 
					
						
							|  |  |  |  |               }, | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |             ListTile( | 
					
						
							|  |  |  |  |               leading: const Icon(Icons.close), | 
					
						
							|  |  |  |  |               title: const Text('取消'), | 
					
						
							|  |  |  |  |               onTap: () => Navigator.of(context).pop(), | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |           ], | 
					
						
							|  |  |  |  |         ), | 
					
						
							|  |  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   Future<void> _pickCamera() async { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     if (_mediaPaths.length >= widget.maxCount) return; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     try { | 
					
						
							|  |  |  |  |       XFile? picked; | 
					
						
							|  |  |  |  |       if (widget.mediaType == MediaType.image) { | 
					
						
							|  |  |  |  |         picked = await _picker.pickImage(source: ImageSource.camera); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         picked = await _picker.pickVideo(source: ImageSource.camera); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       if (picked != null) { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |         final path = picked.path; | 
					
						
							|  |  |  |  |         setState(() => _mediaPaths.add(path)); | 
					
						
							|  |  |  |  |         widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); | 
					
						
							|  |  |  |  |         widget.onMediaAdded?.call(path); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  |       debugPrint('拍摄失败: $e'); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   Future<void> _pickGallery() async { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     if (_mediaPaths.length >= widget.maxCount) return; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     final permission = await PhotoManager.requestPermissionExtend(); | 
					
						
							|  |  |  |  |     if (permission != PermissionState.authorized && | 
					
						
							|  |  |  |  |         permission != PermissionState.limited) { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |       ScaffoldMessenger.of(context).showSnackBar( | 
					
						
							|  |  |  |  |         const SnackBar(content: Text('请到设置中开启相册访问权限')), | 
					
						
							|  |  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |       return; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |       final remaining = widget.maxCount - _mediaPaths.length; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |       final List<AssetEntity>? assets = await AssetPicker.pickAssets( | 
					
						
							|  |  |  |  |         context, | 
					
						
							|  |  |  |  |         pickerConfig: AssetPickerConfig( | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |           requestType: widget.mediaType == MediaType.image | 
					
						
							|  |  |  |  |               ? RequestType.image | 
					
						
							|  |  |  |  |               : RequestType.video, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |           maxAssets: remaining, | 
					
						
							|  |  |  |  |           gridCount: 4, | 
					
						
							|  |  |  |  |         ), | 
					
						
							|  |  |  |  |       ); | 
					
						
							|  |  |  |  |       if (assets != null) { | 
					
						
							|  |  |  |  |         for (final asset in assets) { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |           if (_mediaPaths.length >= widget.maxCount) break; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |           final file = await asset.file; | 
					
						
							|  |  |  |  |           if (file != null) { | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |             final path = file.path; | 
					
						
							|  |  |  |  |             _mediaPaths.add(path); | 
					
						
							|  |  |  |  |             widget.onMediaAdded?.call(path); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |           } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |         setState(() {}); | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |         widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:22:07 +08:00
										 |  |  |  |       debugPrint('相册选择失败: $e'); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   void _removeMedia(int index) { | 
					
						
							|  |  |  |  |     final removed = _mediaPaths[index]; | 
					
						
							|  |  |  |  |     setState(() => _mediaPaths.removeAt(index)); | 
					
						
							|  |  |  |  |     widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); | 
					
						
							|  |  |  |  |     widget.onMediaRemoved?.call(removed); | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |     return GridView.builder( | 
					
						
							|  |  |  |  |       shrinkWrap: true, | 
					
						
							|  |  |  |  |       physics: const NeverScrollableScrollPhysics(), | 
					
						
							|  |  |  |  |       gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | 
					
						
							|  |  |  |  |         crossAxisCount: 4, | 
					
						
							|  |  |  |  |         crossAxisSpacing: 8, | 
					
						
							|  |  |  |  |         mainAxisSpacing: 8, | 
					
						
							|  |  |  |  |         childAspectRatio: 1, | 
					
						
							|  |  |  |  |         mainAxisExtent: 80, | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |       itemCount: _mediaPaths.length < widget.maxCount | 
					
						
							|  |  |  |  |           ? _mediaPaths.length + 1 | 
					
						
							|  |  |  |  |           : widget.maxCount, | 
					
						
							|  |  |  |  |       itemBuilder: (context, index) { | 
					
						
							|  |  |  |  |         if (index < _mediaPaths.length) { | 
					
						
							|  |  |  |  |           final path = _mediaPaths[index]; | 
					
						
							|  |  |  |  |           final isNetwork = path.startsWith('http'); | 
					
						
							|  |  |  |  |           return Stack( | 
					
						
							|  |  |  |  |             children: [ | 
					
						
							|  |  |  |  |               ClipRRect( | 
					
						
							|  |  |  |  |                 borderRadius: BorderRadius.circular(5), | 
					
						
							|  |  |  |  |                 child: widget.mediaType == MediaType.image | 
					
						
							|  |  |  |  |                     ? (isNetwork | 
					
						
							|  |  |  |  |                     ? Image.network(path, fit: BoxFit.cover,width: 80, height: 80,) | 
					
						
							|  |  |  |  |                     : Image.file(File(path), width: 80, height: 80, fit: BoxFit.cover)) | 
					
						
							|  |  |  |  |                     : Container( | 
					
						
							|  |  |  |  |                   color: Colors.black12, | 
					
						
							|  |  |  |  |                   child: const Center( | 
					
						
							|  |  |  |  |                     child: Icon( | 
					
						
							|  |  |  |  |                       Icons.videocam, | 
					
						
							|  |  |  |  |                       color: Colors.white70, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |                     ), | 
					
						
							|  |  |  |  |                   ), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |               ), | 
					
						
							|  |  |  |  |               Positioned( | 
					
						
							|  |  |  |  |                 top: -15, | 
					
						
							|  |  |  |  |                 right: -10, | 
					
						
							|  |  |  |  |                 child: IconButton( | 
					
						
							|  |  |  |  |                   icon: const Icon(Icons.cancel, size: 20, color: Colors.red), | 
					
						
							|  |  |  |  |                   onPressed: () => _removeMedia(index), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |                 ), | 
					
						
							|  |  |  |  |               ), | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |             ], | 
					
						
							|  |  |  |  |           ); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |           return GestureDetector( | 
					
						
							|  |  |  |  |             onTap: _showPickerOptions, | 
					
						
							|  |  |  |  |             child: Container( | 
					
						
							|  |  |  |  |               decoration: BoxDecoration( | 
					
						
							|  |  |  |  |                 border: Border.all(color: Colors.black12), | 
					
						
							|  |  |  |  |                 borderRadius: BorderRadius.circular(5), | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |               child: const Center( | 
					
						
							|  |  |  |  |                 child: Icon(Icons.camera_alt, color: Colors.black26), | 
					
						
							|  |  |  |  |               ), | 
					
						
							|  |  |  |  |             ), | 
					
						
							|  |  |  |  |           ); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | /// 照片上传区域组件,使用纵向四列Grid展示
 | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  | class RepairedPhotoSection extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final int maxCount; | 
					
						
							|  |  |  |  |   final MediaType mediaType; | 
					
						
							|  |  |  |  |   final String title; | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   final List<String>? initialMediaPaths; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final ValueChanged<List<File>> onChanged; | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |   final ValueChanged<String>? onMediaAdded; | 
					
						
							|  |  |  |  |   final ValueChanged<String>? onMediaRemoved; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   final VoidCallback onAiIdentify; | 
					
						
							|  |  |  |  |   final bool isShowAI; | 
					
						
							|  |  |  |  |   final double horizontalPadding; | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |   final bool isRequired; | 
					
						
							|  |  |  |  |   final bool isShowNum; | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const RepairedPhotoSection({ | 
					
						
							|  |  |  |  |     Key? key, | 
					
						
							|  |  |  |  |     this.maxCount = 4, | 
					
						
							|  |  |  |  |     this.mediaType = MediaType.image, | 
					
						
							|  |  |  |  |     required this.title, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     this.initialMediaPaths, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |     this.isShowAI = false, | 
					
						
							|  |  |  |  |     required this.onChanged, | 
					
						
							|  |  |  |  |     required this.onAiIdentify, | 
					
						
							|  |  |  |  |     this.horizontalPadding = 10, | 
					
						
							| 
									
										
										
										
											2025-07-30 17:08:46 +08:00
										 |  |  |  |     this.onMediaAdded, | 
					
						
							|  |  |  |  |     this.onMediaRemoved, | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |     this.isRequired = false, | 
					
						
							|  |  |  |  |     this.isShowNum = true, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   }) : super(key: key); | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |   @override | 
					
						
							|  |  |  |  |   _RepairedPhotoSectionState createState() => _RepairedPhotoSectionState(); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class _RepairedPhotoSectionState extends State<RepairedPhotoSection> { | 
					
						
							|  |  |  |  |   late List<String> _mediaPaths; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   @override | 
					
						
							|  |  |  |  |   void initState() { | 
					
						
							|  |  |  |  |     super.initState(); | 
					
						
							|  |  |  |  |     _mediaPaths = widget.initialMediaPaths?.take(widget.maxCount).toList() ?? []; | 
					
						
							|  |  |  |  |     WidgetsBinding.instance.addPostFrameCallback((_) { | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |       widget.onChanged(_mediaPaths.map((p) => File(p)).toList()); | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |     }); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |   @override | 
					
						
							|  |  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |  |     return Container( | 
					
						
							|  |  |  |  |       color: Colors.white, | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |       padding: const EdgeInsets.only(left: 0, right: 10), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |       child: Column( | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |         crossAxisAlignment: CrossAxisAlignment.start, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |         children: [ | 
					
						
							|  |  |  |  |           Padding( | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |             padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             child: ListItemFactory.createRowSpaceBetweenItem( | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |               leftText: widget.title, | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  |               rightText: widget.isShowNum ? '${_mediaPaths.length}/${widget.maxCount}' : '', | 
					
						
							|  |  |  |  |               isRequired: widget.isRequired, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             ), | 
					
						
							|  |  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |           const SizedBox(height: 8), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |           Padding( | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |             padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             child: MediaPickerRow( | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |               maxCount: widget.maxCount, | 
					
						
							|  |  |  |  |               mediaType: widget.mediaType, | 
					
						
							|  |  |  |  |               initialMediaPaths: _mediaPaths, | 
					
						
							|  |  |  |  |               onChanged: (files) { | 
					
						
							|  |  |  |  |                 final newPaths = files.map((f) => f.path).toList(); | 
					
						
							|  |  |  |  |                 setState(() { | 
					
						
							|  |  |  |  |                   _mediaPaths = newPaths; | 
					
						
							|  |  |  |  |                 }); | 
					
						
							|  |  |  |  |                 widget.onChanged(files); | 
					
						
							|  |  |  |  |               }, | 
					
						
							|  |  |  |  |               onMediaAdded: widget.onMediaAdded, | 
					
						
							|  |  |  |  |               onMediaRemoved: widget.onMediaRemoved, | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             ), | 
					
						
							|  |  |  |  |           ), | 
					
						
							|  |  |  |  |           const SizedBox(height: 20), | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |           if (widget.isShowAI) | 
					
						
							|  |  |  |  |             Padding( | 
					
						
							|  |  |  |  |               padding: EdgeInsets.symmetric(horizontal: widget.horizontalPadding), | 
					
						
							|  |  |  |  |               child: GestureDetector( | 
					
						
							|  |  |  |  |                 onTap: widget.onAiIdentify, | 
					
						
							|  |  |  |  |                 child: Container( | 
					
						
							|  |  |  |  |                   padding: const EdgeInsets.symmetric(horizontal: 15), | 
					
						
							|  |  |  |  |                   height: 36, | 
					
						
							|  |  |  |  |                   decoration: BoxDecoration( | 
					
						
							|  |  |  |  |                     color: const Color(0xFFDFEAFF), | 
					
						
							|  |  |  |  |                     borderRadius: BorderRadius.circular(18), | 
					
						
							|  |  |  |  |                   ), | 
					
						
							|  |  |  |  |                   child: Row( | 
					
						
							|  |  |  |  |                     mainAxisSize: MainAxisSize.min, | 
					
						
							|  |  |  |  |                     children: [ | 
					
						
							|  |  |  |  |                       Image.asset('assets/images/ai_img.png', width: 20), | 
					
						
							|  |  |  |  |                       const SizedBox(width: 5), | 
					
						
							|  |  |  |  |                       const Text('AI隐患识别与处理'), | 
					
						
							|  |  |  |  |                     ], | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |                   ), | 
					
						
							|  |  |  |  |                 ), | 
					
						
							| 
									
										
										
										
											2025-07-31 17:33:26 +08:00
										 |  |  |  |               ), | 
					
						
							| 
									
										
										
										
											2025-07-11 11:03:21 +08:00
										 |  |  |  |             ), | 
					
						
							|  |  |  |  |         ], | 
					
						
							|  |  |  |  |       ), | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-08-07 17:33:16 +08:00
										 |  |  |  | } |