基本完成

This commit is contained in:
zhutao
2025-08-28 16:27:56 +08:00
commit 5d7d233d2e
132 changed files with 6390 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
import 'dart:ffi';
import 'package:flutter/material.dart';
///步骤内容卡片
class StepContentCard extends StatelessWidget {
final List<Widget> children;
const StepContentCard({super.key, required this.children});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.shadow,
blurRadius: 7,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
}
}
///卡片标题
class CardTitle extends StatelessWidget {
final String title;
const CardTitle({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 15),
child: Text(
title,
style: Theme.of(context).textTheme.titleSmall,
),
);
}
}
///配置列表
class OptionList extends StatelessWidget {
final List<String> options;
final List<String> selects;
final double widthFactor;
final Function(String) onTap;
const OptionList({
super.key,
required this.options,
required this.selects,
this.widthFactor = 0.5,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Wrap(
runSpacing: 20,
children: options.map((item) {
return FractionallySizedBox(
widthFactor: widthFactor,
child: InkWell(
onTap: () {
onTap(item);
},
child: Row(
spacing: 5,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: Checkbox(
value: selects.contains(item),
onChanged: (_) {
onTap(item);
},
),
),
Text(item),
],
),
),
);
}).toList(),
);
}
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import 'package:food_health/api/dto/profile_options_dto.dart';
import '../data/state.dart';
import '../util/common.dart';
import 'common.dart';
class DietaryPreferences extends StatefulWidget {
final List<ProfileOptionDto> options;
const DietaryPreferences({super.key, required this.options});
@override
State<DietaryPreferences> createState() => _DietaryPreferencesState();
}
class _DietaryPreferencesState extends State<DietaryPreferences> {
///切换标签
void _handToggle(String tag) {
var state = SelectionProvider.of(context);
if (getIsSelect(tag)) {
state.update((p) => p.dietaryPreferencesList.remove(tag));
} else {
state.update((p) => p.dietaryPreferencesList.add(tag));
}
}
///选中年龄
void _handAgeRange(String tag) {
var state = SelectionProvider.of(context);
state.update((p) => p.ageRange = tag);
}
///等级
void _handActivityLevel(String tag) {
var state = SelectionProvider.of(context);
state.update((p) => p.activityLevel = tag);
}
///是否标签选中
bool getIsSelect(String tag) {
var state = SelectionProvider.of(context);
return state.userProfile.dietaryPreferencesList.contains(tag);
}
@override
Widget build(BuildContext context) {
var state = SelectionProvider.of(context);
return StepContentCard(
children: [
CardTitle(title: "Dietary Restrictions & Preferences"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: OptionList(
options: getOptions(widget.options, "dietary_restrictions"),
selects: state.userProfile.dietaryPreferencesList,
onTap: _handToggle,
),
),
CardTitle(title: "Age Range"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: RadioGroup(
options: getOptions(widget.options, "age_ranges"),
value: state.userProfile.ageRange,
onChanged: _handAgeRange,
),
),
CardTitle(title: "Activity Level"),
RadioGroup(
options: getOptions(widget.options, "activity_levels"),
value: state.userProfile.activityLevel,
onChanged: _handActivityLevel,
),
],
);
}
}
///单选列表
class RadioGroup extends StatelessWidget {
final List<String> options;
final String value;
final int crossAxisCount;
final Function(String) onChanged;
const RadioGroup({
super.key,
required this.options,
required this.value,
this.crossAxisCount = 3,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
crossAxisCount: crossAxisCount,
mainAxisExtent: 40,
),
itemBuilder: (context, index) {
var data = options[index];
var isSelected = value == data;
return InkWell(
onTap: () {
onChanged(data);
},
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainer),
),
child: Text(data, style: TextStyle(color: isSelected ? Colors.white : Colors.black)),
),
);
},
itemCount: options.length,
);
}
}

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:food_health/api/dto/profile_options_dto.dart';
import 'package:food_health/config/theme/custom_colors.dart';
import 'package:remixicon/remixicon.dart';
import '../data/state.dart';
import '../util/common.dart';
import 'common.dart';
class FoodAllergies extends StatefulWidget {
final List<ProfileOptionDto> options;
const FoodAllergies({super.key, required this.options});
@override
State<FoodAllergies> createState() => _FoodAllergiesState();
}
class _FoodAllergiesState extends State<FoodAllergies> {
final TextEditingController _otherController = TextEditingController();
@override
void initState() {
super.initState();
}
///切换标签
void _handToggle(String tag) {
var state = SelectionProvider.of(context);
if (getIsSelect(tag)) {
state.update((p) => p.foodAllergiesList.remove(tag));
} else {
state.update((p) => p.foodAllergiesList.add(tag));
}
}
void _handConfirmCustom() {
var state = SelectionProvider.of(context);
if (!getIsSelect(_otherController.text)) {
state.update((p) => p.foodAllergiesList.add(_otherController.text));
_otherController.text = "";
}
}
///是否标签选中
bool getIsSelect(String tag) {
var state = SelectionProvider.of(context);
return state.userProfile.foodAllergiesList.contains(tag);
}
@override
Widget build(BuildContext context) {
var state = SelectionProvider.of(context);
return StepContentCard(
children: [
CardTitle(title: "Common Food Allergies"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: OptionList(
options: getOptions(widget.options, "common_food_allergies"),
selects: state.userProfile.foodAllergiesList,
onTap: _handToggle,
),
),
CardTitle(title: "Other Allergies"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: Row(
spacing: 15,
children: [
Expanded(
child: TextField(
controller: _otherController,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
hintText: "Add custom allergy...",
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1, color: Theme.of(context).colorScheme.surfaceContainer),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1, color: Theme.of(context).colorScheme.surfaceContainer),
),
),
),
),
InkWell(
onTap: _handConfirmCustom,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(8),
),
child: Icon(RemixIcons.add_fill),
),
),
],
),
),
CardTitle(title: "Your Allergies"),
Wrap(
runSpacing: 10,
spacing: 10,
children: state.userProfile.foodAllergiesList.map((item) {
return InkWell(
onTap: () {
_handToggle(item);
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.danger,
borderRadius: BorderRadius.circular(8),
),
child: Row(
spacing: 5,
mainAxisSize: MainAxisSize.min,
children: [
Text(
item,
style: TextStyle(color: Colors.white, fontSize: 12),
),
Icon(
RemixIcons.close_fill,
color: Colors.white,
size: 20,
),
],
),
),
);
}).toList(),
),
],
);
}
}

View File

@@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:food_health/api/dto/profile_options_dto.dart';
import 'package:food_health/config/theme/custom_colors.dart';
import 'package:remixicon/remixicon.dart';
import '../data/state.dart';
import '../util/common.dart';
import 'common.dart';
class HealthProfile extends StatefulWidget {
final List<ProfileOptionDto> options;
const HealthProfile({super.key, required this.options});
@override
State<HealthProfile> createState() => _HealthProfileState();
}
class _HealthProfileState extends State<HealthProfile> {
final TextEditingController _otherController = TextEditingController();
@override
void initState() {
super.initState();
}
///切换标签
void _handToggle(String tag) {
var state = SelectionProvider.of(context);
if (getIsSelect(tag)) {
state.update((p) => p.medicalInformationList.remove(tag));
} else {
state.update((p) => p.medicalInformationList.add(tag));
}
}
///确认搜索内容
void _handConfirmCustom() {
var state = SelectionProvider.of(context);
if (!getIsSelect(_otherController.text)) {
state.update((p) => p.currentMedicationsList.add(_otherController.text));
_otherController.text = "";
}
}
///移除搜索标签
void _handRemoveCustom(String tag) {
var state = SelectionProvider.of(context);
state.update((p) => p.currentMedicationsList.remove(tag));
}
///是否标签选中
bool getIsSelect(String tag) {
var state = SelectionProvider.of(context);
return state.userProfile.medicalInformationList.contains(tag);
}
@override
Widget build(BuildContext context) {
var state = SelectionProvider.of(context);
return StepContentCard(
children: [
CardTitle(title: "Medical Conditions"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: OptionList(
widthFactor: 1,
options: getOptions(widget.options, "medical_conditions"),
selects: state.userProfile.medicalInformationList,
onTap: _handToggle,
),
),
CardTitle(title: "Current Medications"),
Container(
margin: EdgeInsets.only(bottom: 15),
child: Row(
spacing: 15,
children: [
Expanded(
child: TextField(
controller: _otherController,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
isCollapsed: true,
contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
hintText: "Add medication...",
filled: true,
fillColor: Theme.of(context).colorScheme.surfaceContainerLow,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1, color: Theme.of(context).colorScheme.surfaceContainer),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1, color: Theme.of(context).colorScheme.surfaceContainer),
),
),
),
),
InkWell(
onTap: _handConfirmCustom,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(8),
),
child: Icon(RemixIcons.add_fill),
),
),
],
),
),
Wrap(
runSpacing: 10,
spacing: 10,
children: state.userProfile.currentMedicationsList.map((item) {
return InkWell(
onTap: () {
_handRemoveCustom(item);
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.danger,
borderRadius: BorderRadius.circular(8),
),
child: Row(
spacing: 5,
mainAxisSize: MainAxisSize.min,
children: [
Text(
item,
style: TextStyle(color: Colors.white, fontSize: 12),
),
Icon(
RemixIcons.close_fill,
color: Colors.white,
size: 20,
),
],
),
),
);
}).toList(),
),
],
);
}
}