flutter_integrated_whb/lib/customWidget/photo_picker_row.dart

300 lines
9.0 KiB
Dart
Raw Permalink Normal View History

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:qhd_prevention/tools/h_colors.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:photo_manager/photo_manager.dart';
import 'ItemWidgetFactory.dart';
/// 媒体选择类型
enum MediaType { image, video }
/// 横向一行最多四个媒体的添加组件,支持拍摄和全屏相册多选
/// 使用示例:
/// MediaPickerRow(
/// maxCount: 4,
/// mediaType: MediaType.video,
/// onChanged: (List<File> medias) {
/// // medias 列表更新
/// },
/// ),
class MediaPickerRow extends StatefulWidget {
final int maxCount;
final MediaType mediaType;
final ValueChanged<List<File>> onChanged;
const MediaPickerRow({
Key? key,
this.maxCount = 4,
this.mediaType = MediaType.image,
required this.onChanged,
}) : super(key: key);
@override
_MediaPickerRowState createState() => _MediaPickerRowState();
}
class _MediaPickerRowState extends State<MediaPickerRow> {
final ImagePicker _picker = ImagePicker();
final List<File> _files = [];
Future<void> _showPickerOptions() async {
showModalBottomSheet(
context: context,
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();
},
),
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(),
),
],
),
),
);
}
Future<void> _pickCamera() async {
if (_files.length >= widget.maxCount) return;
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) {
setState(() {
_files.add(File(picked!.path));
});
widget.onChanged(_files);
}
} catch (e) {
debugPrint('拍摄失败: \$e');
}
}
Future<void> _pickGallery() async {
if (_files.length >= widget.maxCount) return;
final permission = await PhotoManager.requestPermissionExtend();
if (permission != PermissionState.authorized &&
permission != PermissionState.limited) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请到设置中开启相册访问权限')));
return;
}
try {
final remaining = widget.maxCount - _files.length;
final List<AssetEntity>? assets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
requestType:
widget.mediaType == MediaType.image
? RequestType.image
: RequestType.video,
maxAssets: remaining,
gridCount: 4,
),
);
if (assets != null) {
for (final asset in assets) {
if (_files.length >= widget.maxCount) break;
final file = await asset.file;
if (file != null) {
_files.add(file);
}
}
setState(() {});
widget.onChanged(_files);
}
} catch (e) {
debugPrint('相册选择失败: \$e');
}
}
void _removeFile(int index) {
setState(() {
_files.removeAt(index);
});
widget.onChanged(_files);
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 80,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount:
_files.length < widget.maxCount
? _files.length + 1
: widget.maxCount,
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
if (index < _files.length) {
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(5),
child:
widget.mediaType == MediaType.image
? Image.file(
_files[index],
width: 80,
height: 80,
fit: BoxFit.cover,
)
: Container(
width: 80,
height: 80,
color: Colors.black12,
child: const Center(
child: Icon(
Icons.videocam,
color: Colors.white70,
),
),
),
),
Positioned(
top: -6,
right: -6,
child: IconButton(
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
onPressed: () => _removeFile(index),
),
),
],
);
} else {
return GestureDetector(
onTap: _showPickerOptions,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(5),
),
child: Center(
child: Icon(
widget.mediaType == MediaType.image
? Icons.camera_alt
: Icons.videocam,
color: Colors.black26,
),
),
),
);
}
},
),
);
}
}
/// 整改后照片上传区域组件
class RepairedPhotoSection extends StatelessWidget {
final int maxCount;
final MediaType mediaType;
final String title;
final ValueChanged<List<File>> onChanged;
final VoidCallback onAiIdentify;
final bool isShowAI;
final double horizontalPadding;
const RepairedPhotoSection({
Key? key,
this.maxCount = 4,
this.mediaType = MediaType.image,
required this.title,
this.isShowAI = false,
required this.onChanged,
required this.onAiIdentify,
this.horizontalPadding = 10,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 5, right: 10),
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: ListItemFactory.createRowSpaceBetweenItem(
leftText: title,
rightText: '0/$maxCount',
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: MediaPickerRow(
maxCount: maxCount,
mediaType: mediaType,
onChanged: onChanged,
),
),
const SizedBox(height: 20),
if (isShowAI)
Row(
children: [
GestureDetector(
onTap: onAiIdentify,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFDFEAFF),
borderRadius: BorderRadius.circular(18),
),
child: Row(
children: [
Image.asset('assets/images/ai_img.png', width: 20),
const SizedBox(width: 5),
const Text('AI隐患识别与处理'),
],
),
),
),
],
),
],
),
);
}
}