Files
food_hub_app/lib/views/moment_form.dart
2025-11-24 19:56:13 +08:00

231 lines
7.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_common/utils/log_utils.dart';
import 'package:flutter_common/utils/minio_utils.dart';
import 'package:flutter_common/utils/toast_util.dart';
import 'package:flutter_common/widget/common_widget.dart';
import 'package:flutter_common/widget/dialog_widget.dart';
import 'package:flutter_common/widget/loading_widget.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/config/app_config.dart';
import 'package:food_hub_app/provider/food_provider.dart';
import 'package:food_hub_app/widgets/common/form.dart';
import 'package:food_hub_app/widgets/common/image.dart';
import 'package:provider/provider.dart';
class MomentFormPage extends StatefulWidget {
const MomentFormPage({super.key});
@override
State<MomentFormPage> createState() => _MomentFormPageState();
}
class _MomentFormPageState extends State<MomentFormPage> {
final _formKey = GlobalKey<FormBuilderState>();
final _focusNode = FocusNode();
static const String _contentField = 'content';
static const int maxContentLength = 200;
static const int maxImageCount = 9; // 最大图片数量
Widget _buildContentField(FoodProvider provider) {
final colors = Theme.of(context).colorScheme;
return FormBuilderTextField(
name: _contentField,
initialValue: provider.momentFormItem.content,
focusNode: _focusNode,
maxLines: 5,
minLines: 3,
maxLength: maxContentLength,
buildCounter: (
context, {
required currentLength,
required isFocused,
maxLength,
}) {
return Text(
'$currentLength/$maxContentLength',
style: TextStyle(
color: currentLength > maxLength! ? Colors.red : colors.primary,
),
);
},
onChanged: (value) {
provider.updateMomentFormItem(content: value);
},
decoration: buildInputDecoration(context: context, hintText: '请输入朋友圈内容'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入朋友圈内容';
}
if (value.length > maxContentLength) {
return '内容不能超过$maxContentLength字';
}
return null;
},
);
}
Widget _buildImageField(FoodProvider provider) {
final imageUrls = provider.momentFormItem.imageList;
final totalItems =
imageUrls.length + (imageUrls.length < maxImageCount ? 1 : 0);
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 4/3,
),
itemCount: totalItems,
itemBuilder: (context, index) {
if (index < imageUrls.length) {
return ImagePreview(
imageUrls: imageUrls,
index: index,
onRemoveImage: () => _removeImage(provider, index),
);
}
// 上传按钮(最后一个)
else {
return buildImageUploadButton(
context: context,
onTap: () => _pickImages(provider),
);
}
},
);
}
Widget _buildFormBuilder(FoodProvider provider) {
return FormBuilder(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFormLabel(context: context, text: '朋友圈内容', isRequired: true),
const SizedBox(height: 8),
_buildContentField(provider),
const SizedBox(height: 8),
buildFormLabel(context: context, text: '上传图片最多9张', isRequired: false),
const SizedBox(height: 8),
_buildImageField(provider),
const SizedBox(height: 8),
buildFormButtonGroup(
context: context,
isShowDelete: provider.isEditing,
onConfirm: () => _submitForm(provider),
onDelete: () {},
),
],
),
);
}
/// 选择多张图片
Future<void> _pickImages(FoodProvider provider) async {
final currentCount = provider.momentFormItem.imageList.length;
final remainingCount = maxImageCount - currentCount;
if (remainingCount <= 0) {
ToastUtil.error('最多只能上传$maxImageCount张图片');
return;
}
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png'],
allowMultiple: true,
);
if (result != null) {
// 限制选择数量
final filesToUpload = result.files.take(remainingCount).toList();
LoadingDialog.show(context, message: '上传中');
final newImageUrls = <String>[];
// 逐个上传图片
for (final file in filesToUpload) {
final fileName = await MinIOHelper().uploadFile(
bucketName: AppConfig.bucketName,
file: file,
);
logger.i('图片名称:$fileName');
newImageUrls.add(fileName);
}
// 更新图片列表
LoadingDialog.hide(context);
provider.addMomentFormImage(newImageUrls);
}
}
/// 移除单张图片
void _removeImage(FoodProvider provider, int index) {
provider.removeMomentFormImage(index);
}
/// 提交表单
void _submitForm(FoodProvider provider) async{
if (_formKey.currentState?.saveAndValidate() ?? false) {
LoadingDialog.show(context, message: '提交中');
final result = await provider.handleMoment();
if (result == true) {
await provider.refreshMomentList();
LoadingDialog.hide(context);
if (provider.isEditing) {
showSuccessTip(context, '更新朋友圈成功');
} else {
showSuccessTip(context, '新增朋友圈成功');
}
Future.delayed(Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.pop(context);
}
});
} else {
LoadingDialog.hide(context);
if (provider.isEditing) {
showErrorTip(context, '更新朋友圈失败');
} else {
showErrorTip(context, '新增朋友圈失败');
}
}
}
}
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme;
final provider = context.watch<FoodProvider>();
final String title = provider.isEditing ? '编辑朋友圈' : '新增朋友圈';
return Scaffold(
appBar: AppBar(
title: Text(title, style: const TextStyle(color: Colors.white)),
backgroundColor: colors.primary,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(10),
child: CommonCard(child: _buildFormBuilder(provider)),
),
),
);
}
}