forked from eatvio/eatvio_app
467 lines
16 KiB
Dart
467 lines
16 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:eatvio_app/core/const.dart';
|
||
import 'package:eatvio_app/core/token_storage.dart';
|
||
import 'package:eatvio_app/generated/l10n.dart';
|
||
import 'package:eatvio_app/old/profile/my_profile_screen/bloc/my_profile_bloc.dart';
|
||
import 'package:eatvio_app/theme/custom_button_style.dart';
|
||
import 'package:eatvio_app/widgets/custom_hyper_switch.dart';
|
||
import 'package:eatvio_app/widgets/custom_hypo_switch.dart';
|
||
import 'package:eatvio_app/widgets/next_step_button.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import 'package:intl/intl.dart';
|
||
import 'package:rive/rive.dart' as rive;
|
||
|
||
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 SizedBox(
|
||
height: 32,
|
||
width: 32,
|
||
child: rive.RiveAnimation.asset(
|
||
'assets/rive_animations/loading.riv',
|
||
),
|
||
);
|
||
} 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) {
|
||
final s = S.of(context);
|
||
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: s.first_name),
|
||
),
|
||
TextField(
|
||
controller: state.lastNameFormController,
|
||
decoration: InputDecoration(labelText: s.last_name),
|
||
),
|
||
TextField(
|
||
decoration: InputDecoration(labelText: s.date_of_birth),
|
||
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: s.country),
|
||
),
|
||
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: s.city_of_living),
|
||
),
|
||
TextField(
|
||
controller: state.weightFormController,
|
||
decoration: InputDecoration(labelText: s.weight_measurements),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.heightFormController,
|
||
decoration: InputDecoration(labelText: s.height_measurements),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.chestCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: s.chest_circumference),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.waistCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: s.waist_circumference),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.hipCircumferenceFormController,
|
||
decoration: InputDecoration(labelText: s.hip_circumference),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.upPressureFormController,
|
||
decoration: InputDecoration(labelText: s.up_pressure),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.downPressureFormController,
|
||
decoration: InputDecoration(labelText: s.down_pressure),
|
||
keyboardType: TextInputType.number,
|
||
inputFormatters: <TextInputFormatter>[digitsOnly],
|
||
),
|
||
TextField(
|
||
controller: state.allergiesFormController,
|
||
decoration: InputDecoration(labelText: s.allergies_presence),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.pillsFormController,
|
||
decoration: InputDecoration(labelText: s.do_you_take_pills),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.sportsFormController,
|
||
decoration: InputDecoration(labelText: s.your_favorite_sports),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.favoriteFormController,
|
||
decoration: InputDecoration(labelText: s.favorite_foods),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.unfavoriteFormController,
|
||
decoration: InputDecoration(labelText: s.unfavorite_foods),
|
||
maxLines: null,
|
||
),
|
||
TextField(
|
||
controller: state.additionalFormController,
|
||
decoration: InputDecoration(labelText: s.additional_information),
|
||
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(s.do_you_have_hypotension_tendency, state),
|
||
],
|
||
),
|
||
Row(
|
||
children: [
|
||
HyperSwitch(s.do_you_have_hypertension_tendency, state),
|
||
],
|
||
),
|
||
NextStepButton(
|
||
onPressed: () => sendProfileData(state),
|
||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||
text: s.save.toUpperCase(),
|
||
margin: EdgeInsets.only(top: 20, bottom: 20),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
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 {
|
||
try {
|
||
final profileData = _createProfileData(state);
|
||
final token = await _getTokenFromBox();
|
||
final apiUrl = _buildApiUrl();
|
||
final response = await _sendHttpRequest(profileData, apiUrl, token);
|
||
|
||
if (response.statusCode == 200) {
|
||
_navigateToProfilePage();
|
||
} else {
|
||
final errorMessage = _getErrorMessage(response);
|
||
_showErrorDialog(errorMessage);
|
||
throw Exception('Ошибка при отправке данных профиля');
|
||
}
|
||
} catch (error) {
|
||
throw Exception('Ошибка при отправке данных профиля: $error');
|
||
}
|
||
}
|
||
|
||
Map<String, dynamic> _createProfileData(MyProfileLoadedState? state) {
|
||
return {
|
||
'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 ?? 'сидячий'),
|
||
};
|
||
}
|
||
|
||
Future<String?> _getTokenFromBox() async {
|
||
return await TokenStorage.getTokenFromStorage();
|
||
}
|
||
|
||
String _buildApiUrl() {
|
||
return baseUrl + '/api/profile/update';
|
||
}
|
||
|
||
Future<http.Response> _sendHttpRequest(
|
||
Map<String, dynamic> profileData,
|
||
String apiUrl,
|
||
String? token,
|
||
) {
|
||
return http.post(
|
||
Uri.parse(apiUrl),
|
||
body: json.encode(profileData),
|
||
encoding: utf8,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
'Authorization': 'Bearer $token',
|
||
},
|
||
);
|
||
}
|
||
|
||
void _navigateToProfilePage() {
|
||
GoRouter.of(context).go('/my-profile-screen');
|
||
}
|
||
|
||
String _getErrorMessage(http.Response response) {
|
||
final res = jsonDecode(response.body);
|
||
return res['message'];
|
||
}
|
||
|
||
void _showErrorDialog(String errorMessage) {
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: Text('Ошибка'),
|
||
content: Text(errorMessage),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: onTapOk(context),
|
||
child: Text('OK'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
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; // Возвращаемое значение по умолчанию, если уровень активности не распознан
|
||
}
|
||
}
|
||
}
|