Files
eatvio_app/lib/widgets/custom_expandable_list.dart
2024-10-09 11:39:01 +03:00

467 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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; // Возвращаемое значение по умолчанию, если уровень активности не распознан
}
}
}