基本完成
This commit is contained in:
25
lib/page/profile/edit/data/state.dart
Normal file
25
lib/page/profile/edit/data/state.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:food_health/api/dto/user_profile_dto.dart';
|
||||
|
||||
class SelectionState extends ChangeNotifier {
|
||||
UserProfileDto userProfile = UserProfileDto();
|
||||
|
||||
SelectionState(this.userProfile);
|
||||
|
||||
void update(void Function(UserProfileDto) updater) {
|
||||
updater(userProfile);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionProvider extends InheritedNotifier<SelectionState> {
|
||||
const SelectionProvider({
|
||||
super.key,
|
||||
required SelectionState super.notifier,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
static SelectionState of(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<SelectionProvider>()!.notifier!;
|
||||
}
|
||||
}
|
||||
292
lib/page/profile/edit/my_edit_page.dart
Normal file
292
lib/page/profile/edit/my_edit_page.dart
Normal file
@@ -0,0 +1,292 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:food_health/api/dto/profile_options_dto.dart';
|
||||
import 'package:food_health/api/dto/user_profile_dto.dart';
|
||||
import 'package:food_health/api/endpoints/profile_api.dart';
|
||||
import 'package:food_health/config/theme/custom_colors.dart';
|
||||
import 'package:food_health/page/profile/edit/widget/food_allergies.dart';
|
||||
import 'package:food_health/widgets/common/app_backend.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
import 'data/state.dart';
|
||||
import 'widget/dietary_preferences.dart';
|
||||
import 'widget/health_profile.dart';
|
||||
|
||||
class MyEditPage extends StatefulWidget {
|
||||
final UserProfileDto userProfile;
|
||||
|
||||
const MyEditPage({super.key, required this.userProfile});
|
||||
|
||||
@override
|
||||
State<MyEditPage> createState() => _MyEditPageState();
|
||||
}
|
||||
|
||||
class _MyEditPageState extends State<MyEditPage> {
|
||||
late SelectionState selectionState;
|
||||
|
||||
List<ProfileOptionDto> _options = [];
|
||||
|
||||
var _loading = true;
|
||||
|
||||
///步骤
|
||||
var _step = 0;
|
||||
|
||||
var stepList = [
|
||||
StepItem(
|
||||
title: "Food Allergies",
|
||||
icon: RemixIcons.shield_line,
|
||||
subTitle: "Help us keep you safe by telling us about your allergies",
|
||||
),
|
||||
StepItem(
|
||||
title: "Dietary Preferences",
|
||||
icon: RemixIcons.heart_line,
|
||||
subTitle: "What dietary restrictions or preferences do you follow?",
|
||||
),
|
||||
StepItem(
|
||||
title: "Health Profile",
|
||||
icon: RemixIcons.user_line,
|
||||
subTitle: "Share relevant health information for personalized recommendations",
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
selectionState = SelectionState(widget.userProfile);
|
||||
var res = await getProfileOptionsApi();
|
||||
setState(() {
|
||||
_options = res;
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
///设置步骤
|
||||
void _handStep(bool isNext) {
|
||||
if (_step == 2 && isNext) {
|
||||
_submit();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_step = (_step + (isNext ? 1 : -1)).clamp(0, 2);
|
||||
});
|
||||
}
|
||||
|
||||
void _submit() async {
|
||||
EasyLoading.show(
|
||||
status: 'Saving…',
|
||||
maskType: EasyLoadingMaskType.clear,
|
||||
);
|
||||
await updateProfileApi(selectionState.userProfile);
|
||||
EasyLoading.dismiss();
|
||||
context.pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_loading) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Scaffold(
|
||||
body: AppBackend(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.only(top: 30),
|
||||
children: [
|
||||
buildHeader(),
|
||||
buildStep(),
|
||||
buildStepInfo(),
|
||||
SelectionProvider(
|
||||
notifier: selectionState,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (_step == 0) {
|
||||
return FoodAllergies(
|
||||
options: _options,
|
||||
);
|
||||
} else if (_step == 1) {
|
||||
return DietaryPreferences(
|
||||
options: _options,
|
||||
);
|
||||
} else if (_step == 2) {
|
||||
return HealthProfile(
|
||||
options: _options,
|
||||
);
|
||||
}
|
||||
return SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 30),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: _step == 0 ? 0.4 : 1,
|
||||
child: buildItemButton(
|
||||
title: "Previous",
|
||||
color: Colors.black,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
onTap: () {
|
||||
_handStep(false);
|
||||
},
|
||||
),
|
||||
),
|
||||
buildItemButton(
|
||||
title: _step == 2 ? "Complete Setup" : "Continue",
|
||||
decoration: BoxDecoration(
|
||||
color: _step == 2 ? Theme.of(context).colorScheme.success : null,
|
||||
gradient: _step == 2
|
||||
? null
|
||||
: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.primary,
|
||||
Theme.of(context).colorScheme.primaryEnd,
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_handStep(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///构建顶部
|
||||
Widget buildHeader() {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
"Welcome to FoodSafe",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
"Let's personalize your food safety experience",
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
///步骤条
|
||||
Widget buildStep() {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: stepList.asMap().entries.map((entre) {
|
||||
//数据
|
||||
var item = entre.value;
|
||||
var index = entre.key;
|
||||
var isLast = index == stepList.length - 1;
|
||||
//颜色
|
||||
var selectColor = Theme.of(context).colorScheme.primary;
|
||||
var unselectedColor = Theme.of(context).colorScheme.surfaceContainerHigh;
|
||||
return Expanded(
|
||||
flex: isLast ? 0 : 1,
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: _step >= index ? selectColor : unselectedColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
item.icon,
|
||||
color: _step >= index ? Colors.white : Color(0xff9ca3af),
|
||||
),
|
||||
),
|
||||
|
||||
Visibility(
|
||||
visible: !isLast,
|
||||
child: Expanded(
|
||||
child: Container(
|
||||
height: 3,
|
||||
color: _step > index ? selectColor : unselectedColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///步骤条信息
|
||||
Widget buildStepInfo() {
|
||||
var stepInfo = stepList[_step];
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 20, bottom: 30),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
stepInfo.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 5),
|
||||
child: Text(
|
||||
stepInfo.subTitle,
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///item按钮
|
||||
Widget buildItemButton({
|
||||
required String title,
|
||||
Color color = Colors.white,
|
||||
required BoxDecoration decoration,
|
||||
required Function() onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
decoration: decoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StepItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
|
||||
StepItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
});
|
||||
}
|
||||
9
lib/page/profile/edit/util/common.dart
Normal file
9
lib/page/profile/edit/util/common.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:food_health/api/dto/profile_options_dto.dart';
|
||||
|
||||
List<String> getOptions(List<ProfileOptionDto> options, String key) {
|
||||
var data = options.firstWhere((item) {
|
||||
return item.key == key;
|
||||
});
|
||||
//
|
||||
return data.valuesList ?? [];
|
||||
}
|
||||
100
lib/page/profile/edit/widget/common.dart
Normal file
100
lib/page/profile/edit/widget/common.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
127
lib/page/profile/edit/widget/dietary_preferences.dart
Normal file
127
lib/page/profile/edit/widget/dietary_preferences.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
144
lib/page/profile/edit/widget/food_allergies.dart
Normal file
144
lib/page/profile/edit/widget/food_allergies.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
151
lib/page/profile/edit/widget/health_profile.dart
Normal file
151
lib/page/profile/edit/widget/health_profile.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user