qhd-prevention-flutter/lib/customWidget/photo_picker_row.dart

300 lines
9.0 KiB
Dart
Raw Normal View History

2025-07-03 09:45:15 +08:00
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
2025-07-07 16:49:05 +08:00
import 'package:qhd_prevention/tools/h_colors.dart';
2025-07-03 09:45:15 +08:00
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
2025-07-07 16:49:05 +08:00
import 'package:photo_manager/photo_manager.dart';
2025-07-03 09:45:15 +08:00
2025-07-07 16:49:05 +08:00
import 'ItemWidgetFactory.dart';
/// 媒体选择类型
enum MediaType { image, video }
/// 横向一行最多四个媒体的添加组件,支持拍摄和全屏相册多选
2025-07-03 09:45:15 +08:00
/// 使用示例:
2025-07-07 16:49:05 +08:00
/// MediaPickerRow(
2025-07-03 09:45:15 +08:00
/// maxCount: 4,
2025-07-07 16:49:05 +08:00
/// mediaType: MediaType.video,
/// onChanged: (List<File> medias) {
/// // medias 列表更新
2025-07-03 09:45:15 +08:00
/// },
/// ),
2025-07-07 16:49:05 +08:00
class MediaPickerRow extends StatefulWidget {
2025-07-03 09:45:15 +08:00
final int maxCount;
2025-07-07 16:49:05 +08:00
final MediaType mediaType;
2025-07-03 09:45:15 +08:00
final ValueChanged<List<File>> onChanged;
2025-07-07 16:49:05 +08:00
const MediaPickerRow({
2025-07-03 09:45:15 +08:00
Key? key,
this.maxCount = 4,
2025-07-07 16:49:05 +08:00
this.mediaType = MediaType.image,
2025-07-03 09:45:15 +08:00
required this.onChanged,
}) : super(key: key);
@override
2025-07-07 16:49:05 +08:00
_MediaPickerRowState createState() => _MediaPickerRowState();
2025-07-03 09:45:15 +08:00
}
2025-07-07 16:49:05 +08:00
class _MediaPickerRowState extends State<MediaPickerRow> {
2025-07-03 09:45:15 +08:00
final ImagePicker _picker = ImagePicker();
2025-07-07 16:49:05 +08:00
final List<File> _files = [];
2025-07-03 09:45:15 +08:00
Future<void> _showPickerOptions() async {
showModalBottomSheet(
context: context,
2025-07-07 16:49:05 +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();
},
),
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-03 09:45:15 +08:00
),
2025-07-07 16:49:05 +08:00
),
2025-07-03 09:45:15 +08:00
);
}
Future<void> _pickCamera() async {
2025-07-07 16:49:05 +08:00
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');
2025-07-03 09:45:15 +08:00
}
}
Future<void> _pickGallery() async {
2025-07-07 16:49:05 +08:00
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);
}
2025-07-03 09:45:15 +08:00
}
2025-07-07 16:49:05 +08:00
setState(() {});
widget.onChanged(_files);
2025-07-03 09:45:15 +08:00
}
2025-07-07 16:49:05 +08:00
} catch (e) {
debugPrint('相册选择失败: \$e');
2025-07-03 09:45:15 +08:00
}
}
2025-07-07 16:49:05 +08:00
void _removeFile(int index) {
2025-07-03 09:45:15 +08:00
setState(() {
2025-07-07 16:49:05 +08:00
_files.removeAt(index);
2025-07-03 09:45:15 +08:00
});
2025-07-07 16:49:05 +08:00
widget.onChanged(_files);
2025-07-03 09:45:15 +08:00
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 80,
child: ListView.separated(
scrollDirection: Axis.horizontal,
2025-07-07 16:49:05 +08:00
itemCount:
_files.length < widget.maxCount
? _files.length + 1
: widget.maxCount,
2025-07-03 09:45:15 +08:00
separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
2025-07-07 16:49:05 +08:00
if (index < _files.length) {
2025-07-03 09:45:15 +08:00
return Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(5),
2025-07-07 16:49:05 +08:00
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,
),
),
),
2025-07-03 09:45:15 +08:00
),
Positioned(
top: -6,
right: -6,
child: IconButton(
icon: const Icon(Icons.cancel, size: 20, color: Colors.red),
2025-07-07 16:49:05 +08:00
onPressed: () => _removeFile(index),
2025-07-03 09:45:15 +08:00
),
),
],
);
} else {
return GestureDetector(
onTap: _showPickerOptions,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
2025-07-07 16:49:05 +08:00
border: Border.all(color: Colors.black12),
2025-07-03 09:45:15 +08:00
borderRadius: BorderRadius.circular(5),
),
2025-07-07 16:49:05 +08:00
child: Center(
child: Icon(
widget.mediaType == MediaType.image
? Icons.camera_alt
: Icons.videocam,
color: Colors.black26,
),
2025-07-03 09:45:15 +08:00
),
),
);
}
},
),
);
}
}
2025-07-07 16:49:05 +08:00
/// 整改后照片上传区域组件
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隐患识别与处理'),
],
),
),
),
],
),
],
),
);
}
}