574 lines
17 KiB
Dart
574 lines
17 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_common/widget/dialog_widget.dart';
|
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
|
import 'package:food_hub_app/models/recipe.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/index.dart';
|
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
|
import 'package:flutter_common/utils/toast_util.dart';
|
|
import 'package:flutter_common/widget/loading_widget.dart';
|
|
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
|
|
import 'package:liquid_glass_widgets/widgets/containers/glass_card.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class RecipeForm extends StatefulWidget {
|
|
const RecipeForm({super.key});
|
|
|
|
@override
|
|
State<RecipeForm> createState() => _RecipeFormState();
|
|
}
|
|
|
|
class _RecipeFormState extends State<RecipeForm> {
|
|
final _formKey = GlobalKey<FormBuilderState>();
|
|
int _currentStep = 0;
|
|
|
|
// 表单字段名称常量
|
|
static const String _nameField = 'name';
|
|
static const String _categoryField = 'category';
|
|
static const String _recommendRateField = 'recommendRate';
|
|
static const String _remarkField = 'remark';
|
|
static const String _isShareField = 'isShare';
|
|
|
|
// 分类选项
|
|
final List<String> _categoryOptions = [
|
|
'家常菜',
|
|
'川菜',
|
|
'粤菜',
|
|
'湘菜',
|
|
'西餐',
|
|
'甜品',
|
|
'汤羹',
|
|
'其他',
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
}
|
|
|
|
// 构建步骤内容
|
|
Widget _buildStepContent(FoodProvider provider) {
|
|
switch (_currentStep) {
|
|
case 0:
|
|
return _buildInfo(provider);
|
|
case 1:
|
|
return _buildMaterials(provider);
|
|
case 2:
|
|
return _buildSteps(provider);
|
|
default:
|
|
return _buildInfo(provider);
|
|
}
|
|
}
|
|
|
|
// 第一步:基本信息
|
|
Widget _buildInfo(FoodProvider provider) {
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildFormLabel(context: context, text: '菜谱名称', isRequired: true),
|
|
const SizedBox(height: 8),
|
|
FormBuilderTextField(
|
|
name: _nameField,
|
|
initialValue: provider.recipeFormItem.name,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请输入菜谱名称',
|
|
prefixIcon: Icons.restaurant_menu,
|
|
),
|
|
onChanged: (value) {
|
|
provider.updateRecipeFormItem(name: value ?? '');
|
|
},
|
|
validator: FormBuilderValidators.compose([
|
|
FormBuilderValidators.required(errorText: '菜谱名称不能为空'),
|
|
FormBuilderValidators.maxLength(50, errorText: '名称不能超过50个字符'),
|
|
]),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
buildFormLabel(context: context, text: '分类', isRequired: true),
|
|
const SizedBox(height: 8),
|
|
FormBuilderDropdown(
|
|
name: _categoryField,
|
|
initialValue: provider.recipeFormItem.category,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请选择分类',
|
|
prefixIcon: Icons.widgets,
|
|
),
|
|
items:
|
|
_categoryOptions
|
|
.map(
|
|
(category) => DropdownMenuItem(
|
|
value: category,
|
|
child: Text(category),
|
|
),
|
|
)
|
|
.toList(),
|
|
onChanged: (value) {
|
|
provider.updateRecipeFormItem(category: value ?? '');
|
|
},
|
|
validator: FormBuilderValidators.required(errorText: '请选择分类'),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
buildFormLabel(context: context, text: '推荐评分', isRequired: false),
|
|
const SizedBox(height: 8),
|
|
FormBuilderSlider(
|
|
name: _recommendRateField,
|
|
initialValue: provider.recipeFormItem.recommendRate,
|
|
min: 1,
|
|
max: 5,
|
|
divisions: 4,
|
|
activeColor: Theme.of(context).colorScheme.primary,
|
|
decoration: const InputDecoration(border: InputBorder.none),
|
|
onChanged: (value) {
|
|
provider.updateRecipeFormItem(recommendRate: value ?? 0);
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
buildFormLabel(context: context, text: '备注', isRequired: false),
|
|
const SizedBox(height: 8),
|
|
FormBuilderTextField(
|
|
name: _remarkField,
|
|
initialValue: provider.recipeFormItem.remark,
|
|
maxLines: 3,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请输入菜谱的特别说明或小贴士',
|
|
),
|
|
onChanged: (value) {
|
|
provider.updateRecipeFormItem(remark: value ?? '');
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
FormBuilderCheckbox(
|
|
name: _isShareField,
|
|
initialValue: provider.recipeFormItem.isShare,
|
|
title: const Text('公开分享此菜谱'),
|
|
onChanged: (value) {
|
|
provider.updateRecipeFormItem(isShare: value ?? false);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 第二步:食材清单
|
|
Widget _buildMaterials(FoodProvider provider) {
|
|
final materials = provider.recipeFormItem.materialList;
|
|
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text('食材清单', style: Theme.of(context).textTheme.titleMedium),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: Icon(
|
|
Icons.add,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
onPressed: () => provider.addRecipeMaterial(),
|
|
tooltip: '添加食材',
|
|
),
|
|
],
|
|
),
|
|
if (materials.isEmpty)
|
|
Container(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
margin: EdgeInsets.only(bottom: 10),
|
|
padding: const EdgeInsets.all(10),
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.inventory_2, size: 48, color: Colors.grey[400]),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'暂无食材,请点击添加按钮添加食材',
|
|
style: TextStyle(color: Colors.grey[600]),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
Column(
|
|
children:
|
|
materials.asMap().entries.map((entry) {
|
|
final index = entry.key;
|
|
final material = entry.value;
|
|
|
|
return Column(
|
|
children: [
|
|
_buildMaterialItem(index, material, provider),
|
|
const SizedBox(height: 8),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMaterialItem(
|
|
int index,
|
|
RecipeMaterial material,
|
|
FoodProvider provider,
|
|
) {
|
|
return Column(
|
|
key: ValueKey(material.id),
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextFormField(
|
|
initialValue: material.name,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请输入名称',
|
|
prefixIcon: Icons.content_paste,
|
|
),
|
|
onChanged: (value) {
|
|
provider.updateRecipeMaterial(index, name: value);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: TextFormField(
|
|
initialValue: material.amount,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请输入用量',
|
|
prefixIcon: Icons.scale,
|
|
),
|
|
onChanged: (value) {
|
|
provider.updateRecipeMaterial(index, amount: value);
|
|
},
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
|
|
onPressed: () => provider.removeRecipeMaterial(index),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// 第三步:制作步骤
|
|
Widget _buildSteps(FoodProvider provider) {
|
|
final steps = provider.recipeFormItem.stepList;
|
|
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text('制作步骤', style: Theme.of(context).textTheme.titleMedium),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: Icon(
|
|
Icons.add,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
onPressed: () => provider.addRecipeStep(),
|
|
tooltip: '添加步骤',
|
|
),
|
|
],
|
|
),
|
|
if (steps.isEmpty)
|
|
Container(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
margin: EdgeInsets.only(bottom: 10),
|
|
padding: const EdgeInsets.all(10),
|
|
child: Column(
|
|
children: [
|
|
Icon(Icons.list_alt, size: 48, color: Colors.grey[400]),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'暂无步骤,请点击添加按钮添加制作步骤',
|
|
style: TextStyle(color: Colors.grey[600]),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
Column(
|
|
children:
|
|
steps.asMap().entries.map((entry) {
|
|
final index = entry.key;
|
|
final step = entry.value;
|
|
|
|
return Column(
|
|
children: [
|
|
_buildStepItem(index, step, provider),
|
|
const SizedBox(height: 8),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStepItem(int index, RecipeStep step, FoodProvider provider) {
|
|
return Row(
|
|
key: ValueKey(step.id),
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// 步骤编号
|
|
Container(
|
|
width: 28,
|
|
height: 28,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Text(
|
|
'${index + 1}',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
|
|
// 步骤内容
|
|
Expanded(
|
|
child: TextFormField(
|
|
initialValue: step.content,
|
|
decoration: buildInputDecoration(
|
|
context: context,
|
|
hintText: '请输入内容',
|
|
prefixIcon: Icons.content_paste,
|
|
),
|
|
onChanged: (value) {
|
|
provider.updateRecipeStep(index, content: value);
|
|
},
|
|
),
|
|
),
|
|
// 删除按钮
|
|
IconButton(
|
|
icon: const Icon(Icons.delete, color: Colors.red),
|
|
onPressed: () => provider.removeRecipeStep(index),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// 步骤指示器
|
|
Widget _buildStepIndicator() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
_buildStepCircle(0),
|
|
SizedBox(width: 10),
|
|
_buildStepConnector(0),
|
|
SizedBox(width: 10),
|
|
_buildStepCircle(1),
|
|
SizedBox(width: 10),
|
|
_buildStepConnector(1),
|
|
SizedBox(width: 10),
|
|
_buildStepCircle(2),
|
|
],
|
|
),
|
|
Container(
|
|
margin: const EdgeInsets.only(top: 8),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
_buildStepTitle(0, '基本信息'),
|
|
SizedBox(width: 40),
|
|
_buildStepTitle(1, '食材清单'),
|
|
SizedBox(width: 40),
|
|
_buildStepTitle(2, '制作步骤'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 只构建圆圈,不包含标题文字
|
|
Widget _buildStepCircle(int stepIndex) {
|
|
final isActive = _currentStep == stepIndex;
|
|
final isCompleted = _currentStep > stepIndex;
|
|
final boxColor =
|
|
isActive || isCompleted
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.grey;
|
|
|
|
return Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(color: boxColor, shape: BoxShape.circle),
|
|
child: Center(
|
|
child:
|
|
isCompleted
|
|
? const Icon(Icons.check, color: Colors.white, size: 16)
|
|
: Text(
|
|
'${stepIndex + 1}',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 箭头连接器
|
|
Widget _buildStepConnector(int stepIndex) {
|
|
final isActive = _currentStep > stepIndex;
|
|
return SizedBox(
|
|
width: 40,
|
|
child: Icon(
|
|
Icons.arrow_forward,
|
|
size: 20,
|
|
color: isActive ? Theme.of(context).colorScheme.primary : Colors.grey,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStepTitle(int stepIndex, String title) {
|
|
final isActive = _currentStep == stepIndex;
|
|
final isCompleted = _currentStep > stepIndex;
|
|
final textColor =
|
|
isActive || isCompleted
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.grey;
|
|
|
|
return Text(
|
|
title,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
|
color: textColor,
|
|
fontSize: 12,
|
|
),
|
|
);
|
|
}
|
|
|
|
// 导航按钮
|
|
Widget _buildNavigationButtons(FoodProvider provider) {
|
|
final isLastStep = _currentStep == 2;
|
|
final isFirstStep = _currentStep == 0;
|
|
|
|
return Row(
|
|
children: [
|
|
if (!isFirstStep)
|
|
Expanded(
|
|
child: buildInfoButton(
|
|
context: context,
|
|
text: '上一步',
|
|
onPressed: _previousStep,
|
|
),
|
|
),
|
|
if (!isFirstStep) const SizedBox(width: 12),
|
|
Expanded(
|
|
child: buildPrimaryButton(
|
|
context: context,
|
|
text: isLastStep ? '提交' : '下一步',
|
|
onPressed: () => _nextStep(provider),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _nextStep(FoodProvider provider) {
|
|
if (_currentStep < 2) {
|
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
|
setState(() {
|
|
_currentStep++;
|
|
});
|
|
}
|
|
} else {
|
|
_submitForm(provider);
|
|
}
|
|
}
|
|
|
|
void _previousStep() {
|
|
if (_currentStep > 0) {
|
|
setState(() {
|
|
_currentStep--;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _submitForm(FoodProvider provider) async {
|
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
|
LoadingDialog.show(context, message: '提交中');
|
|
final result = await provider.handleRecipe();
|
|
if (result == true) {
|
|
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, '新增菜谱失败');
|
|
}
|
|
}
|
|
} else {
|
|
ToastUtil.error('请检查表单填写是否正确');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final provider = context.watch<FoodProvider>();
|
|
|
|
return SingleChildScrollView(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(10),
|
|
child: GlassCard(
|
|
useOwnLayer: true,
|
|
settings: LiquidGlassSettings(
|
|
glassColor: Theme.of(context).scaffoldBackgroundColor,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_buildStepIndicator(),
|
|
FormBuilder(key: _formKey, child: _buildStepContent(provider)),
|
|
_buildNavigationButtons(provider),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|