feat:更新主题色适配

This commit is contained in:
2025-11-23 15:27:50 +08:00
parent 6308169013
commit 245edd9d27
15 changed files with 306 additions and 128 deletions

View File

@@ -56,6 +56,13 @@ class FoodProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateRecordFormItem({String? name, String? date, String? imageUrl}) {
if (name != null) recordFormItem.name = name;
if (date != null) recordFormItem.date = date;
if (imageUrl != null) recordFormItem.imageUrl = imageUrl;
notifyListeners();
}
void resetMomentForm() { void resetMomentForm() {
momentFormItem = Moment.getEmpty(); momentFormItem = Moment.getEmpty();
notifyListeners(); notifyListeners();
@@ -161,8 +168,8 @@ class FoodProvider with ChangeNotifier {
} }
} }
Future<void> handleRecord() async { Future<bool> handleRecord() async {
if (isLoading) return; if (isLoading) return true;
try { try {
isLoading = true; isLoading = true;
@@ -171,14 +178,34 @@ class FoodProvider with ChangeNotifier {
if (isEditing) { if (isEditing) {
await updateRecordApi(recordFormItem.id!, recordFormItem); await updateRecordApi(recordFormItem.id!, recordFormItem);
ToastUtil.success('更新记录成功');
} else { } else {
await addRecordApi(recordFormItem); await addRecordApi(recordFormItem);
ToastUtil.success('上传记录成功');
} }
return true;
} catch (e) { } catch (e) {
error = '处理数据失败: $e'; error = '处理数据失败: $e';
debugPrint('处理数据失败: $e'); debugPrint('处理数据失败: $e');
return false;
} finally {
isLoading = false;
notifyListeners();
}
}
Future<bool> deleteRecord() async {
if (isLoading) return true;
try {
isLoading = true;
error = null;
notifyListeners();
await deleteRecordApi(recordFormItem.id!);
return true;
} catch (e) {
error = '处理数据失败: $e';
debugPrint('处理数据失败: $e');
return false;
} finally { } finally {
isLoading = false; isLoading = false;
notifyListeners(); notifyListeners();

View File

@@ -4,6 +4,7 @@ import 'package:flutter_common/utils/toast_util.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/apis/session.dart'; import 'package:food_hub_app/apis/session.dart';
import 'package:food_hub_app/models/session.dart'; import 'package:food_hub_app/models/session.dart';
import 'package:food_hub_app/widgets/common/form.dart';
import 'package:food_hub_app/widgets/common/index.dart'; import 'package:food_hub_app/widgets/common/index.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
@@ -85,7 +86,6 @@ class _LoginPage extends State<LoginPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[100],
body: Form( body: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
@@ -145,7 +145,8 @@ class _LoginPage extends State<LoginPage> {
name: 'username', name: 'username',
initialValue: _username, initialValue: _username,
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
decoration: formInputDecoration( decoration: buildInputDecoration(
context: context,
hintText: "请输入账号", hintText: "请输入账号",
prefixIcon: Icons.person, prefixIcon: Icons.person,
), ),
@@ -169,22 +170,18 @@ class _LoginPage extends State<LoginPage> {
obscureText: _isObscure, obscureText: _isObscure,
onSaved: (v) => _password = v!, onSaved: (v) => _password = v!,
validator: FormBuilderValidators.required(), validator: FormBuilderValidators.required(),
decoration: InputDecoration( decoration: buildInputDecoration(
prefixIcon: Icon(Icons.lock), context: context,
hintText: "请输入密码", hintText: "请输入密码",
hintStyle: TextStyle(color: Colors.grey), prefixIcon: Icons.lock,
border: OutlineInputBorder(), ).copyWith(
filled: true,
fillColor: Colors.white,
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon(Icons.remove_red_eye, color: _eyeColor), icon: Icon(Icons.remove_red_eye, color: _eyeColor),
onPressed: () { onPressed: () {
final colors = Theme.of(context).colorScheme;
setState(() { setState(() {
_isObscure = !_isObscure; _isObscure = !_isObscure;
_eyeColor = _eyeColor = (_isObscure ? Colors.grey : colors.surface);
(_isObscure
? Colors.grey
: Theme.of(context).iconTheme.color)!;
}); });
}, },
), ),

View File

@@ -52,7 +52,11 @@ class _MomentFormPageState extends State<MomentFormPage> {
provider.momentFormItem.content = value!; provider.momentFormItem.content = value!;
}); });
}, },
decoration: buildInputDecoration(context: context, hintText: '请输入朋友圈内容'), decoration: buildInputDecoration(
context: context,
hintText: '请输入朋友圈内容',
prefixIcon: Icons.article,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return '请输入朋友圈内容'; return '请输入朋友圈内容';
@@ -90,7 +94,10 @@ class _MomentFormPageState extends State<MomentFormPage> {
} }
// 上传按钮(最后一个) // 上传按钮(最后一个)
else { else {
return buildImageUploadButton(onTap: () => _pickImages(provider)); return buildImageUploadButton(
context: context,
onTap: () => _pickImages(provider),
);
} }
}, },
); );
@@ -104,19 +111,21 @@ class _MomentFormPageState extends State<MomentFormPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildFormLabel('朋友圈内容', required: true), buildFormLabel(context: context, text: '朋友圈内容', isRequired: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildContentField(provider), _buildContentField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormLabel('上传图片', required: true), buildFormLabel(context: context, text: '上传图片', isRequired: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildImageField(provider), _buildImageField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormButtonGroup( buildFormButtonGroup(
context: context, context: context,
isShowDelete: provider.isEditing,
onConfirm: () => _submitForm(provider), onConfirm: () => _submitForm(provider),
onDelete: () {},
), ),
], ],
), ),

View File

@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_common/utils/minio_utils.dart'; import 'package:flutter_common/utils/minio_utils.dart';
import 'package:flutter_common/utils/toast_util.dart'; import 'package:flutter_common/utils/toast_util.dart';
import 'package:flutter_common/widget/common_widget.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:flutter_form_builder/flutter_form_builder.dart';
import 'package:food_hub_app/apis/recipe.dart'; import 'package:food_hub_app/apis/recipe.dart';
import 'package:food_hub_app/config/app_config.dart'; import 'package:food_hub_app/config/app_config.dart';
@@ -40,32 +42,32 @@ class _RecordFormPageState extends State<RecordFormPage> {
}); });
} }
Widget _buildFormBuilder() { Widget _buildFormBuilder(FoodProvider provider) {
final provider = context.watch<FoodProvider>();
return FormBuilder( return FormBuilder(
key: _formKey, key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildFormLabel('菜谱名称', required: true), buildFormLabel(context: context, text: '菜谱名称', isRequired: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildRecipeField(provider), _buildRecipeField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormLabel('完成时间', required: true), buildFormLabel(context: context, text: '完成时间', isRequired: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildDateField(provider), _buildDateField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormLabel('上传图片', required: true), buildFormLabel(context: context, text: '上传图片', isRequired: true),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildImageField(provider), _buildImageField(provider),
const SizedBox(height: 8), const SizedBox(height: 8),
buildFormButtonGroup( buildFormButtonGroup(
context: context, context: context,
isShowDelete: provider.isEditing,
onConfirm: () => _submitForm(provider), onConfirm: () => _submitForm(provider),
onDelete: () => _deleteForm(provider),
), ),
], ],
), ),
@@ -87,7 +89,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
}, },
onSelected: (String value) { onSelected: (String value) {
provider.recordFormItem.name = value; provider.updateRecordFormItem(name: value);
}, },
optionsViewBuilder: ( optionsViewBuilder: (
@@ -114,6 +116,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
elevation: 2, elevation: 2,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Container( child: Container(
color: Theme.of(context).colorScheme.surface,
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: 200, maxHeight: 200,
maxWidth: MediaQuery.of(context).size.width - 58, maxWidth: MediaQuery.of(context).size.width - 58,
@@ -146,7 +149,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
void onClearRecipeName() { void onClearRecipeName() {
controller.clear(); controller.clear();
provider.recordFormItem.name = ''; provider.updateRecordFormItem(name: '');
} }
Widget? buildSuffixIcon() { Widget? buildSuffixIcon() {
@@ -166,16 +169,12 @@ class _RecordFormPageState extends State<RecordFormPage> {
focusNode: focusNode, focusNode: focusNode,
enabled: !provider.isEditing, enabled: !provider.isEditing,
onChanged: (value) { onChanged: (value) {
provider.recordFormItem.name = value ?? ''; provider.updateRecordFormItem(name: value ?? '');
}, },
decoration: buildInputDecoration( decoration: buildInputDecoration(
context: context, context: context,
hintText: '请输入菜谱名称', hintText: '请输入菜谱名称',
prefixIcon: const Icon( prefixIcon: Icons.restaurant_menu,
Icons.restaurant_menu,
size: 20,
color: Color(0xFF86909C),
),
).copyWith(suffixIcon: buildSuffixIcon()), ).copyWith(suffixIcon: buildSuffixIcon()),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@@ -196,11 +195,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
decoration: buildInputDecoration( decoration: buildInputDecoration(
context: context, context: context,
hintText: '请选择完成时间', hintText: '请选择完成时间',
prefixIcon: const Icon( prefixIcon: Icons.calendar_month,
Icons.calendar_month,
size: 20,
color: Color(0xFF86909C),
),
), ),
onTap: () => _onSelectDate(provider), onTap: () => _onSelectDate(provider),
validator: (value) { validator: (value) {
@@ -224,7 +219,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
if (picked != null) { if (picked != null) {
final String date = DateFormat('yyyy-MM-dd').format(picked); final String date = DateFormat('yyyy-MM-dd').format(picked);
_formKey.currentState?.fields[_dateField]?.didChange(date); _formKey.currentState?.fields[_dateField]?.didChange(date);
provider.recordFormItem.date = date; provider.updateRecordFormItem(date: date);
} }
} }
@@ -233,7 +228,10 @@ class _RecordFormPageState extends State<RecordFormPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (provider.recordFormItem.imageUrl.isEmpty) if (provider.recordFormItem.imageUrl.isEmpty)
buildImageUploadButton(onTap: () => _pickImage(provider)) buildImageUploadButton(
context: context,
onTap: () => _pickImage(provider),
)
else else
ImagePreview( ImagePreview(
imageUrls: [provider.recordFormItem.imageUrl], imageUrls: [provider.recordFormItem.imageUrl],
@@ -254,22 +252,21 @@ class _RecordFormPageState extends State<RecordFormPage> {
if (result != null) { if (result != null) {
PlatformFile file = result.files.first; PlatformFile file = result.files.first;
LoadingDialog.show(context, message: '上传中');
final fileName = await MinIOHelper().uploadFile( final fileName = await MinIOHelper().uploadFile(
bucketName: AppConfig.bucketName, bucketName: AppConfig.bucketName,
file: file, file: file,
); );
setState(() { LoadingDialog.hide(context);
provider.recordFormItem.imageUrl = fileName; provider.updateRecordFormItem(imageUrl: fileName);
});
} }
} }
/// 移除图片 /// 移除图片
void _removeImage(FoodProvider provider) { void _removeImage(FoodProvider provider) {
setState(() { provider.updateRecordFormItem(imageUrl: '');
provider.recordFormItem.imageUrl = '';
});
} }
/// 提交表单 /// 提交表单
@@ -280,9 +277,43 @@ class _RecordFormPageState extends State<RecordFormPage> {
return; return;
} }
await provider.handleRecord(); LoadingDialog.show(context, message: '提交中');
await provider.onRecordTabChange(); final result = await provider.handleRecord();
Navigator.pop(context); if (result == true) {
await provider.onRecordTabChange();
LoadingDialog.hide(context);
if (provider.isEditing) {
showSuccessTip(context, '更新记录成功');
} else {
showSuccessTip(context, '新增记录成功');
}
Future.delayed(Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.pop(context);
}
});
}
}
}
void _deleteForm(FoodProvider provider) async {
final result = await showConfirmDialog(context, '确认删除该记录?');
if (result == true) {
LoadingDialog.show(context, message: '删除中');
final isDelete = await provider.deleteRecord();
if (isDelete) {
await provider.onRecordTabChange();
LoadingDialog.hide(context);
showSuccessTip(context, '删除记录成功');
Future.delayed(Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.pop(context);
}
});
}
} }
} }
@@ -305,7 +336,7 @@ class _RecordFormPageState extends State<RecordFormPage> {
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: CommonCard(child: _buildFormBuilder()), child: CommonCard(child: _buildFormBuilder(provider)),
), ),
), ),
); );

View File

@@ -4,50 +4,54 @@ import 'package:flutter/material.dart';
InputDecoration buildInputDecoration({ InputDecoration buildInputDecoration({
required BuildContext context, required BuildContext context,
required String hintText, required String hintText,
Widget? prefixIcon, required IconData prefixIcon,
}) { }) {
final colors = Theme.of(context).colorScheme;
return InputDecoration( return InputDecoration(
hintText: hintText, hintText: hintText,
hintStyle: const TextStyle( hintStyle: TextStyle(color: colors.onSurface.withAlpha(100)),
color: Color(0xFF999999),
fontSize: 15,
height: 1.2,
),
filled: true, filled: true,
fillColor: Colors.white, fillColor: colors.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFE5E7EB)), borderSide: const BorderSide(color: Color(0xFFE5E7EB)),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).primaryColor, width: 1.5), borderSide: BorderSide(color: colors.primary, width: 2),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.red, width: 1.5), borderSide: const BorderSide(color: Colors.red, width: 2),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14),
errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red), errorStyle: const TextStyle(fontSize: 12, height: 1, color: Colors.red),
prefixIcon: prefixIcon, prefixIcon: Icon(prefixIcon, size: 20, color: Color(0xFF86909C)),
prefixIconConstraints: const BoxConstraints(minWidth: 40), prefixIconConstraints: const BoxConstraints(minWidth: 40),
isDense: true, isDense: true,
); );
} }
Widget buildFormLabel(String text, {bool required = false}) { Widget buildFormLabel({
required BuildContext context,
required String text,
required bool isRequired,
}) {
final colors = Theme.of(context).colorScheme;
return RichText( return RichText(
text: TextSpan( text: TextSpan(
text: text, text: text,
style: const TextStyle( style: TextStyle(
color: Color(0xFF1D2129), color: colors.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontFamily: 'CustomFont', fontFamily: 'CustomFont',
height: 1.2, height: 1.2,
), ),
children: [ children: [
if (required) if (isRequired)
const TextSpan( const TextSpan(
text: ' *', text: ' *',
style: TextStyle(color: Colors.red, fontSize: 16), style: TextStyle(color: Colors.red, fontSize: 16),
@@ -60,12 +64,19 @@ Widget buildFormLabel(String text, {bool required = false}) {
/// 表单底部按钮组组件 /// 表单底部按钮组组件
Widget buildFormButtonGroup({ Widget buildFormButtonGroup({
required BuildContext context, required BuildContext context,
required bool isShowDelete,
required VoidCallback onConfirm, required VoidCallback onConfirm,
required VoidCallback onDelete,
}) { }) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded(child: _buildCancelButton(context)), Expanded(child: _buildCancelButton(context)),
const SizedBox(width: 16), if (isShowDelete) ...[
SizedBox(width: 16),
Expanded(child: _buildDeleteButton(context, onDelete)),
],
SizedBox(width: 16),
Expanded(child: _buildSubmitButton(context, onConfirm)), Expanded(child: _buildSubmitButton(context, onConfirm)),
], ],
); );
@@ -76,9 +87,8 @@ Widget _buildCancelButton(BuildContext context) {
return ElevatedButton( return ElevatedButton(
onPressed: () => Navigator.pop(context), // 直接使用传入的context返回 onPressed: () => Navigator.pop(context), // 直接使用传入的context返回
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.white, backgroundColor: Theme.of(context).colorScheme.surface,
foregroundColor: const Color(0xFF4E5969), foregroundColor: const Color(0xFF4E5969),
side: const BorderSide(color: Color(0xFFDCDFE6)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0, elevation: 0,
), ),
@@ -98,3 +108,16 @@ Widget _buildSubmitButton(BuildContext context, VoidCallback onConfirm) {
child: const Text('提交', style: TextStyle(color: Colors.white)), child: const Text('提交', style: TextStyle(color: Colors.white)),
); );
} }
/// 删除按钮
Widget _buildDeleteButton(BuildContext context, VoidCallback onDelete) {
return ElevatedButton(
onPressed: () => onDelete(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 0,
),
child: const Text('删除', style: TextStyle(color: Colors.white)),
);
}

View File

@@ -203,7 +203,12 @@ class ImagePreview extends StatelessWidget {
} }
} }
Widget buildImageUploadButton({required VoidCallback onTap}) { Widget buildImageUploadButton({
required BuildContext context,
required VoidCallback onTap,
}) {
final colors = Theme.of(context).colorScheme;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
@@ -211,16 +216,16 @@ Widget buildImageUploadButton({required VoidCallback onTap}) {
width: 96, width: 96,
height: 96, height: 96,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: colors.surface,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.black), border: Border.all(color: Colors.black),
), ),
child: const Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.add, color: Color(0xFF86909C)), Icon(Icons.add, color: colors.onSurface.withAlpha(100)),
SizedBox(height: 6), SizedBox(height: 6),
Text('添加图片', style: TextStyle(color: Color(0xFF86909C))), Text('添加图片', style: TextStyle(color: colors.onSurface.withAlpha(100))),
], ],
), ),
), ),

View File

@@ -146,42 +146,47 @@ class _RecipeCalendarState extends State<RecipeCalendar> {
bool isToday, bool isToday,
FoodProvider provider, FoodProvider provider,
) { ) {
final colors = Theme.of(context).colorScheme;
// 检查当前日期是否在需要显示红点的列表中 // 检查当前日期是否在需要显示红点的列表中
bool shouldShowRedDot = provider.recordList.any( bool shouldShowRedDot = provider.recordList.any(
(item) => item.date == formatDateTime(day), (item) => item.date == formatDateTime(day),
); );
late Color boxColor;
if (isSelected) {
boxColor = colors.secondary;
} else {
boxColor = isToday ? colors.primary : Colors.transparent;
}
late Color textColor;
if (isSelected) {
textColor = colors.surface;
} else {
textColor = isToday ? colors.surface : colors.onSurface;
}
return Container( return Container(
width: 40, width: 40,
height: 40, height: 40,
margin: EdgeInsets.all(2), decoration: BoxDecoration(color: boxColor, shape: BoxShape.circle),
decoration: BoxDecoration( child: Stack(
color: alignment: Alignment.center,
isSelected
? Theme.of(context).primaryColor
: isToday
? Colors.grey[300]
: Colors.transparent,
shape: BoxShape.circle,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
// 日期数字 // 日期数字始终居中
Text( Text(day.day.toString(), style: TextStyle(color: textColor)),
day.day.toString(),
style: TextStyle(color: isSelected ? Colors.white : Colors.black), // 红点
),
// 底部红点 - 只在指定日期显示
if (shouldShowRedDot) if (shouldShowRedDot)
Container( Align(
margin: EdgeInsets.only(top: 2), alignment: Alignment(0, 0.8),
width: 6, child: Container(
height: 6, width: 6,
decoration: BoxDecoration( height: 6,
color: Colors.red, decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.red,
shape: BoxShape.circle,
),
), ),
), ),
], ],

View File

@@ -31,7 +31,6 @@ class _RecipeListState extends State<RecipeList> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: colors.surfaceContainer, color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[200]!),
), ),
padding: EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: 16),
child: DropdownButton<String>( child: DropdownButton<String>(
@@ -64,15 +63,28 @@ class _RecipeListState extends State<RecipeList> {
); );
}).toList(), }).toList(),
onChanged: (value) { onChanged: (value) {
setState(() { provider.queryCategory = value!;
provider.queryCategory = value!; provider.refreshRecipeList();
provider.refreshRecipeList();
});
}, },
), ),
); );
} }
Widget _buildRecipeList(FoodProvider provider) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 0.9,
),
itemCount: provider.recipeSummaryList.length,
itemBuilder: (context, index) {
return RecipeCard(recipe: provider.recipeSummaryList[index]);
},
);
}
Widget _buildContent(FoodProvider provider) { Widget _buildContent(FoodProvider provider) {
if (provider.recipeSummaryList.isEmpty) { if (provider.recipeSummaryList.isEmpty) {
return buildEmptyData(); return buildEmptyData();
@@ -81,20 +93,7 @@ class _RecipeListState extends State<RecipeList> {
children: [ children: [
_buildSelectCategory(provider), _buildSelectCategory(provider),
SizedBox(height: 5), SizedBox(height: 5),
Expanded( Expanded(child: _buildRecipeList(provider)),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 每行2个
crossAxisSpacing: 8, // 水平间距
mainAxisSpacing: 8, // 垂直间距
childAspectRatio: 0.9,
),
itemCount: provider.recipeSummaryList.length,
itemBuilder: (context, index) {
return RecipeCard(recipe: provider.recipeSummaryList[index]);
},
),
),
], ],
); );
} }

View File

@@ -56,20 +56,17 @@ class _RecipeTimeline extends State<RecipeTimeline> {
if (recordList.isEmpty) { if (recordList.isEmpty) {
return buildEmptyData(); return buildEmptyData();
} else { } else {
final colors = Theme.of(context).colorScheme;
return Timeline.tileBuilder( return Timeline.tileBuilder(
theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0), theme: TimelineThemeData(nodePosition: 0, indicatorPosition: 0),
builder: TimelineTileBuilder.connected( builder: TimelineTileBuilder.connected(
itemCount: recordList.length, itemCount: recordList.length,
connectorBuilder: connectorBuilder:
(context, index, type) => Connector.solidLine( (context, index, type) =>
thickness: 2, Connector.solidLine(thickness: 2, color: colors.primary),
color: Theme.of(context).colorScheme.primary,
),
indicatorBuilder: (context, index) { indicatorBuilder: (context, index) {
return Indicator.dot( return Indicator.dot(size: 12.0, color: colors.primary);
size: 12.0,
color: Theme.of(context).colorScheme.primary,
);
}, },
contentsBuilder: (context, index) { contentsBuilder: (context, index) {
return Padding( return Padding(

View File

@@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <rive_native/rive_native_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) rive_native_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RiveNativePlugin");
rive_native_plugin_register_with_registrar(rive_native_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
rive_native
url_launcher_linux url_launcher_linux
) )

View File

@@ -7,14 +7,18 @@ import Foundation
import file_picker import file_picker
import file_selector_macos import file_selector_macos
import flutter_image_compress_macos
import path_provider_foundation import path_provider_foundation
import rive_native
import share_plus import share_plus
import shared_preferences_foundation import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RiveNativePlugin.register(with: registry.registrar(forPlugin: "RiveNativePlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@@ -33,6 +33,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.12.0" version: "2.12.0"
awesome_dialog:
dependency: transitive
description:
name: awesome_dialog
sha256: "4c5821a0a637ceee022084e78c1b8237dd4b8bfca4dd24ac2484662a56707338"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -325,6 +333,54 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "10.0.1" version: "10.0.1"
flutter_image_compress:
dependency: transitive
description:
name: flutter_image_compress
sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
flutter_image_compress_common:
dependency: transitive
description:
name: flutter_image_compress_common
sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
flutter_image_compress_macos:
dependency: transitive
description:
name: flutter_image_compress_macos
sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
flutter_image_compress_ohos:
dependency: transitive
description:
name: flutter_image_compress_ohos
sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.3"
flutter_image_compress_platform_interface:
dependency: transitive
description:
name: flutter_image_compress_platform_interface
sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
flutter_image_compress_web:
dependency: transitive
description:
name: flutter_image_compress_web
sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.5"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -780,6 +836,22 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.5.0" version: "1.5.0"
rive:
dependency: transitive
description:
name: rive
sha256: fc0abf65d03d1c9afaeb35be9e71c7cf04d2d1f76e94e69d2af1b3ba413cddf9
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.14.0-dev.14"
rive_native:
dependency: transitive
description:
name: rive_native
sha256: e9c7d36f19eb6d32f563825d4e9d5032b19a36d3ca3341641431035bca022d19
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.17"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -7,12 +7,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <rive_native/rive_native_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
RiveNativePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RiveNativePlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar( SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows file_selector_windows
rive_native
share_plus share_plus
url_launcher_windows url_launcher_windows
) )