import 'package:file_picker/file_picker.dart'; import 'package:flisp_app/models/flisp.dart'; import 'package:flisp_app/provider/flisp_provider.dart'; import 'package:flisp_app/utils/minio_utils.dart'; import 'package:flisp_app/widgets/flisp_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:provider/provider.dart'; class FlispForm extends StatefulWidget { final GlobalKey formKey; final bool isEditing; final Flisp? initialFlisp; final VoidCallback onOk; const FlispForm({ super.key, required this.formKey, required this.isEditing, required this.onOk, this.initialFlisp, }); @override State createState() => _FlispFormState(); } class _FlispFormState extends State { bool _showTagOptions = false; @override void initState() { super.initState(); } void _toggleTagOptions() { setState(() { _showTagOptions = !_showTagOptions; }); } void _selectTag(FlispTag tag) { final provider = Provider.of(context, listen: false); setState(() { provider.formItem.tag = tag; _showTagOptions = false; }); } void _selectImage(String fileName) { final provider = Provider.of(context, listen: false); setState(() { provider.formItem.imageUrl = '${MinIOHelper().rustfsFileUrl}/${MinIOHelper().bucketName}/$fileName'; }); } void _deleteImage() { final provider = Provider.of(context, listen: false); setState(() { provider.formItem.imageUrl = ''; }); } @override Widget build(BuildContext context) { final int maxContentCount = 100; final provider = Provider.of(context); final colors = Theme.of(context).colorScheme; Widget buildContentField() { return FormBuilderTextField( name: 'content', initialValue: provider.formItem.content, onChanged: (value) { setState(() { provider.formItem.content = value ?? ''; }); }, decoration: InputDecoration( hintText: '记录你的灵感瞬间...', hintStyle: TextStyle(color: Colors.grey), counterText: '', suffixText: '${provider.formItem.content.length}/$maxContentCount', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade200), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: colors.primary), ), filled: true, fillColor: colors.surface, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), minLines: 4, maxLines: 8, maxLength: maxContentCount, validator: (value) { if (value != null && value.isEmpty) { return '请输入内容'; } if (value != null && value.length > maxContentCount) { return '内容不能超过$maxContentCount个字符'; } return null; }, ); } Widget buildFormBody() { return Expanded( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildContentField(), SizedBox(height: 8), buildFlispTag(provider.formItem), SizedBox(height: 12), if (provider.formItem.imageUrl.isNotEmpty) buildFlispImage( flisp: provider.formItem, showDelete: true, onDelete: _deleteImage, ), ], ), ), ); } void selectImage() async { FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['jpg', 'jpeg', 'png'], allowMultiple: false, ); if (result != null) { PlatformFile file = result.files.first; final fileName = await MinIOHelper().uploadFile(file: file); _selectImage(fileName); } } Widget buildSelectImageButton() { return GestureDetector( onTap: () => selectImage(), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.blue.withAlpha(50), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue.withAlpha(80), width: 1), ), child: Icon( Icons.photo_library_rounded, size: 20, color: Colors.blue, ), ), ); } Widget buildSelectTagButton() { return GestureDetector( onTap: _toggleTagOptions, child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.orange.withAlpha(50), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.orange.withAlpha(80), width: 1), ), child: Icon( Icons.local_offer_rounded, size: 20, color: Colors.orange, ), ), ); } Widget buildConfirmButton() { return Container( width: 48, height: 48, decoration: BoxDecoration( color: colors.primary, borderRadius: BorderRadius.circular(24), ), child: IconButton( icon: Icon(Icons.arrow_forward_rounded, size: 24), color: Colors.white, onPressed: () => widget.onOk(), ), ); } Widget buildShowTagOptions() { // 计算弹窗位置(基于标签按钮位置) return Positioned( bottom: 50, // 调整垂直位置 left: 60, // 调整水平位置 child: Container( width: 90, padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: FlispTag.values.map((tag) { return GestureDetector( onTap: () => _selectTag(tag), child: Container( width: double.infinity, padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), child: Row( children: [ Container( width: 20, height: 20, decoration: BoxDecoration( color: tag.color, shape: BoxShape.circle, ), child: Icon( tag.icon, size: 12, color: Colors.white, ), ), SizedBox(width: 8), Text(tag.label, style: TextStyle(fontSize: 12)), ], ), ), ); }).toList(), ), ), ); } Widget buildBottomButton() { return Container( padding: EdgeInsets.only(top: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ buildSelectImageButton(), SizedBox(width: 12), buildSelectTagButton(), ], ), buildConfirmButton(), ], ), ); } // 整体使用Stack布局分离事件层级 return Stack( children: [ // 外层点击区域(用于关闭弹窗) GestureDetector( onTap: () { if (_showTagOptions) { setState(() => _showTagOptions = false); } }, // 弹窗显示时才拦截事件,隐藏时不影响其他点击 behavior: _showTagOptions ? HitTestBehavior.opaque : HitTestBehavior.translucent, ), // 主表单内容 FormBuilder( key: widget.formKey, child: Column(children: [buildFormBody(), buildBottomButton()]), ), // 标签弹窗(放在最上层,确保事件优先响应) if (_showTagOptions) buildShowTagOptions(), ], ); } }