qhdkfq_regulatory_flutter/lib/Custom/photo_picker_row.dart

299 lines
8.6 KiB
Dart

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';
/// 媒体选择类型
enum MediaType { image, video }
/// 横向最多四个、可自动换行的媒体添加组件,支持拍摄和相册多选
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 =
widget.mediaType == MediaType.image
? await _picker.pickImage(source: ImageSource.camera)
: 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) {
// 准备所有已选媒体和“添加”按钮
final children = <Widget>[
for (int i = 0; i < _files.length; i++) _buildMediaItem(i),
if (_files.length < widget.maxCount) _buildAddButton(),
];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Wrap(
spacing: 8, // 横向间距
runSpacing: 8, // 纵向行距
children: children,
),
);
}
Widget _buildMediaItem(int index) {
return Stack(
clipBehavior: Clip.none, // 允许子项溢出到父级之外
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: 0, // 负值让图标一半溢出
right: 0,
child: GestureDetector(
onTap: () => _removeFile(index),
child: Image.asset(
"assets/images/close.png",
width: 24,
height: 24,
),
),
),
],
);
}
Widget _buildAddButton() {
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: Text(
title,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 8),
// 媒体行
MediaPickerRow(
maxCount: maxCount,
mediaType: mediaType,
onChanged: onChanged,
),
if (isShowAI)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Stack(
children: [
GestureDetector(
onTap: onAiIdentify,
child: Container(
width: double.infinity,
// 让它撑满宽度,以便定位
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFDFEAFF),
borderRadius: BorderRadius.circular(18),
),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 15),
child: const Text('AI隐患识别与处理'),
),
),
// 这个 Positioned 会把图紧贴右上角
Positioned(
top: 0,
right: 0,
child: Image.asset(
'assets/images/ai_img.png',
width: 20,
height: 20,
),
),
],
),
),
],
),
);
}
}