forked from eatvio/eatvio_app
421 lines
15 KiB
Dart
421 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:eatvio_app/core/app_export.dart';
|
||
import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart';
|
||
import 'package:eatvio_app/widgets/custom_hyper_switch.dart';
|
||
import 'package:eatvio_app/widgets/custom_hypo_switch.dart';
|
||
import 'package:intl/intl.dart';
|
||
import 'package:eatvio_app/widgets/next_step_button.dart';
|
||
import 'dart:convert';
|
||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||
import 'package:hive/hive.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
class ExpandableList extends StatefulWidget {
|
||
final String title;
|
||
|
||
ExpandableList(this.title);
|
||
|
||
@override
|
||
_ExpandableListState createState() => _ExpandableListState();
|
||
}
|
||
|
||
class _ExpandableListState extends State<ExpandableList> {
|
||
bool isExpanded = false;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
GestureDetector(
|
||
onTap: toggleExpand(),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
widget.title,
|
||
style: TextStyle(
|
||
color: Colors.black,
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.bold),
|
||
),
|
||
Icon(isExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
|
||
],
|
||
),
|
||
),
|
||
if (isExpanded)
|
||
Padding(
|
||
padding: const EdgeInsets.only(left: 16.0),
|
||
child: BlocBuilder<MyProfileBloc, MyProfileState>(
|
||
builder: (context, state) {
|
||
if (state is MyProfileInitialState) {
|
||
// Это начальное состояние, данные ещё не загружены.
|
||
return CircularProgressIndicator(
|
||
color: Colors.white,
|
||
backgroundColor: Colors.white,
|
||
);
|
||
} else if (state is MyProfileLoadedState) {
|
||
// Данные загружены, можно строить форму.
|
||
return _buildForm(context, state);
|
||
} else if (state is MyProfileErrorState) {
|
||
// Произошла ошибка при загрузке данных.
|
||
return Center(child: Text(state.errorMessage));
|
||
} else {
|
||
// Другие состояния, если они есть.
|
||
return Container(); // Пустой контейнер для необработанных состояний.
|
||
}
|
||
}),
|
||
),
|
||
Divider(),
|
||
],
|
||
);
|
||
}
|
||
|
||
toggleExpand() {
|
||
return () {
|
||
setState(() {
|
||
isExpanded = !isExpanded;
|
||
});
|
||
};
|
||
}
|
||
|
||
_buildForm(BuildContext context, MyProfileLoadedState state) {
|
||
List<String> cities = [
|
||
'Москва',
|
||
'Санкт-Петербург',
|
||
'Новосибирск',
|
||
'Екатеринбург',
|
||
'Нижний Новгород',
|
||
'Казань',
|
||
'Челябинск',
|
||
'Омск',
|
||
'Самара',
|
||
'Ростов-на-Дону',
|
||
'Уфа',
|
||
'Красноярск',
|
||
'Воронеж',
|
||
'Пермь',
|
||
'Волгоград',
|
||
'Краснодар',
|
||
'Саратов',
|
||
'Тюмень',
|
||
'Тольятти',
|
||
'Ижевск',
|
||
'Барнаул',
|
||
'Ульяновск',
|
||
'Иркутск',
|
||
'Хабаровск',
|
||
'Ярославль',
|
||
'Владивосток',
|
||
'Махачкала',
|
||
'Томск',
|
||
'Оренбург',
|
||
'Кемерово',
|
||
];
|
||
List<String> countries = [
|
||
'Россия',
|
||
];
|
||
List<String> activities = [
|
||
'сидячий',
|
||
'низкий (1-3 тренировки в неделю по 20 мин)',
|
||
'умеренный (3-4 тренировки в неделю по 30-60 мин)',
|
||
'высокий (5-7 тренировок по 30-60 мин)',
|
||
'экстремальный (несколько интенсивных тренировок в день)'
|
||
];
|
||
final digitsOnly = FilteringTextInputFormatter.digitsOnly;
|
||
return Column(children: [
|
||
TextField(
|
||
controller: state.firstNameFormController,
|
||
decoration: InputDecoration(labelText: 'first_name'.tr),
|
||
),
|
||
TextField(
|
||
controller: state.lastNameFormController,
|
||
decoration: InputDecoration(labelText: 'last_name'.tr),
|
||
),
|
||
TextField(
|
||
decoration: InputDecoration(labelText: 'date_of_birth'.tr),
|
||
controller: state.dateFormController,
|
||
onTap: inputTime(context, state)),
|
||
DropdownButtonFormField<String>(
|
||
value: state.selectedCountry,
|
||
hint: Text(state.selectedCountry ?? "Не выбрано"),
|
||
onChanged: setCountry(state),
|
||
items: countries.map<DropdownMenuItem<String>>((String value) {
|
||
return DropdownMenuItem<String>(
|
||
value: value,
|
||
child: Text(value),
|
||
);
|
||
}).toList(),
|
||
decoration: InputDecoration(labelText: 'country'.tr),
|
||
),
|
||
DropdownButtonFormField<String>(
|
||
hint: Text(state.selectedCity ?? "Не выбрано"),
|
||
value: state.selectedCity,
|
||
onChanged: setCity(state),
|
||
items: cities.map<DropdownMenuItem<String>>((String value) {
|
||
return DropdownMenuItem<String>(
|
||
value: value,
|
||
child: Text(value),
|
||
);
|
||
}).toList(),
|
||
decoration: InputDecoration(labelText: 'city_of_living'.tr),
|
||
),
|
||
TextField(
|
||
controller: state.weightFormController,
|
||
decoration: InputDecoration(labelText: 'weight_measurements'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.heightFormController,
|
||
decoration: InputDecoration(labelText: 'height_measurements'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.chestCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: 'chest_circumference'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.waistCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: 'waist_circumference'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.hipCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: 'hip_circumference'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.upPressureFormController,
|
||
decoration: InputDecoration(labelText: 'up_pressure'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.downPressureFormController,
|
||
decoration: InputDecoration(labelText: 'down_pressure'.tr),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly]),
|
||
TextField(
|
||
controller: state.allergiesFormController,
|
||
decoration: InputDecoration(labelText: 'allergies_presence'.tr),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.pillsFormController,
|
||
decoration: InputDecoration(labelText: 'do_you_take_pills'.tr),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.sportsFormController,
|
||
decoration: InputDecoration(labelText: 'your_favorite_sports'.tr),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.favoriteFormController,
|
||
decoration: InputDecoration(labelText: 'favorite_foods'.tr),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.unfavoriteFormController,
|
||
decoration: InputDecoration(labelText: 'unfavorite_foods'.tr),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.additionalFormController,
|
||
decoration: InputDecoration(labelText: 'additional_information'.tr),
|
||
maxLines: null,
|
||
),
|
||
DropdownButtonFormField<String>(
|
||
hint: Text(state.selectedActivity ?? "Не выбрано"),
|
||
value: state.selectedActivity,
|
||
onChanged: setActivity(state),
|
||
items: activities.map<DropdownMenuItem<String>>((String value) {
|
||
return DropdownMenuItem<String>(
|
||
value: value,
|
||
child: SizedBox(
|
||
// Используем SizedBox для ограничения ширины элемента списка
|
||
width: MediaQuery.of(context).size.width *
|
||
0.8, // Устанавливаем желаемую ширину элемента списка
|
||
child: Text(value),
|
||
),
|
||
);
|
||
}).toList(),
|
||
isExpanded: true, // Расширяем список на всю доступную ширину
|
||
decoration: InputDecoration(labelText: 'Активность'),
|
||
),
|
||
Row(
|
||
children: [
|
||
HypoSwitch('do_you_have_hypotension_tendency'.tr, state),
|
||
],
|
||
),
|
||
Row(
|
||
children: [
|
||
HyperSwitch('do_you_have_hypertension_tendency'.tr, state),
|
||
],
|
||
),
|
||
NextStepButton(
|
||
onPressed: () => sendProfileData(state),
|
||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||
text: "save".tr.toUpperCase(),
|
||
margin: EdgeInsets.only(top: 20.h, bottom: 20.h)),
|
||
]);
|
||
}
|
||
|
||
sendData(MyProfileLoadedState state) {
|
||
return () {
|
||
sendProfileData(state);
|
||
};
|
||
}
|
||
|
||
setCity(MyProfileLoadedState state) {
|
||
return (newValue) {
|
||
// Обработчик изменения выбранного города
|
||
setSelectedCity(state, newValue);
|
||
};
|
||
}
|
||
|
||
setCountry(MyProfileLoadedState state) {
|
||
return (newValue) {
|
||
// Обработчик изменения выбранной страны
|
||
setSelectedCountry(state, newValue);
|
||
};
|
||
}
|
||
|
||
setActivity(MyProfileLoadedState state) {
|
||
return (newValue) {
|
||
// Обработчик изменения выбранной страны
|
||
setSelectedActivity(state, newValue);
|
||
};
|
||
}
|
||
|
||
inputTime(BuildContext context, MyProfileLoadedState state) {
|
||
return () {
|
||
showDatePicker(
|
||
context: context,
|
||
initialDate: DateTime.now(),
|
||
firstDate: DateTime(1900),
|
||
lastDate: DateTime.now(),
|
||
).then((selectedDate) {
|
||
if (selectedDate != null) {
|
||
setState(() {
|
||
state.dateFormController!.text =
|
||
DateFormat('dd-MM-yyyy').format(selectedDate);
|
||
});
|
||
}
|
||
});
|
||
};
|
||
}
|
||
|
||
void setSelectedCity(MyProfileLoadedState? state, String? newValue) {
|
||
setState(() {
|
||
state!.selectedCity = newValue;
|
||
});
|
||
}
|
||
|
||
void setSelectedCountry(MyProfileLoadedState? state, String? newValue) {
|
||
setState(() {
|
||
state!.selectedCountry = newValue;
|
||
});
|
||
}
|
||
|
||
void setSelectedActivity(MyProfileLoadedState? state, String? newValue) {
|
||
setState(() {
|
||
state!.selectedActivity = newValue;
|
||
});
|
||
}
|
||
|
||
Future<void> sendProfileData(MyProfileLoadedState? state) async {
|
||
print("asd");
|
||
try {
|
||
// Создаем JSON объект с данными профиля
|
||
Map<String, dynamic> profileData = {
|
||
'first_name': state?.firstNameFormController?.text,
|
||
'last_name': state?.lastNameFormController?.text,
|
||
'chest_circumference': state?.chestCircumferenceFormController?.text,
|
||
'waist_circumference': state?.waistCircumferenceFormController?.text,
|
||
'hip_circumference': state?.hipCircumferenceFormController?.text,
|
||
'birthdate': state?.dateFormController?.text,
|
||
'height': state?.heightFormController?.text,
|
||
'weight': state?.weightFormController?.text,
|
||
'blood_pressure_up': state?.upPressureFormController?.text,
|
||
'blood_pressure_down': state?.downPressureFormController?.text,
|
||
'about_alergy': state?.allergiesFormController?.text ?? 'Нет аллергий',
|
||
'about_taking_pils': state?.pillsFormController?.text,
|
||
'about_sport_hobbies': state?.sportsFormController?.text,
|
||
'favorite_foods': state?.favoriteFormController?.text,
|
||
'unfavorite_foods': state?.unfavoriteFormController?.text,
|
||
'additional_info': state?.additionalFormController?.text,
|
||
'country': state?.selectedCountry ?? 'Россия',
|
||
'city': state?.selectedCity ?? 'Москва',
|
||
'has_hypo': state?.hypoFormController,
|
||
'has_hyper': state?.hyperFormController,
|
||
'activity_coefficient':
|
||
determineActivityFactor(state?.selectedActivity ?? 'сидячий'),
|
||
};
|
||
final box = await Hive.openBox('token');
|
||
final token = box.get('token');
|
||
String apiUrl = dotenv.env['SERVER_ROOT']! + '/profile/update';
|
||
http.Response response = await http.post(
|
||
Uri.parse(apiUrl),
|
||
body: json.encode(profileData),
|
||
encoding: utf8,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
|
||
print(response.body);
|
||
if (response.statusCode == 200) {
|
||
Navigator.pushNamed(
|
||
context,
|
||
AppRoutes.myProfile,
|
||
);
|
||
} else {
|
||
var res = await jsonDecode(response.body);
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: Text('Ошибка'),
|
||
content: Text(res['message']),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: onTapOk(context),
|
||
child: Text('OK'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
throw Exception('Ошибка при отправке данных профиля');
|
||
}
|
||
} catch (error) {
|
||
throw Exception('Ошибка при отправке данных профиля: $error');
|
||
}
|
||
}
|
||
|
||
onTapOk(BuildContext context) {
|
||
return () {
|
||
Navigator.pop(context);
|
||
};
|
||
}
|
||
|
||
double determineActivityFactor(String activityLevel) {
|
||
switch (activityLevel) {
|
||
case "сидячий":
|
||
return 1.2;
|
||
case "низкий (1-3 тренировки в неделю по 20 мин)":
|
||
return 1.375;
|
||
case "умеренный (3-4 тренировки в неделю по 30-60 мин)":
|
||
return 1.55;
|
||
case "высокий (5-7 тренировок по 30-60 мин)":
|
||
return 1.7;
|
||
case "экстремальный (несколько интенсивных тренировок в день)":
|
||
return 1.9;
|
||
default:
|
||
return 1.2; // Возвращаемое значение по умолчанию, если уровень активности не распознан
|
||
}
|
||
}
|
||
}
|