forked from eatvio/eatvio_app
402 lines
19 KiB
Dart
402 lines
19 KiB
Dart
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||
import 'package:eatvio_app/core/locator_service.dart';
|
||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||
import 'package:eatvio_app/core/utils/string_extentions.dart';
|
||
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
|
||
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
|
||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
|
||
import 'package:eatvio_app/features/profile/models/circumference_model.dart';
|
||
import 'package:eatvio_app/features/profile/models/weight_model.dart';
|
||
import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart';
|
||
import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart';
|
||
import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart';
|
||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
|
||
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_state.dart';
|
||
import 'package:eatvio_app/features/user_details/presentation/widgets/expandable_bouble.dart';
|
||
import 'package:eatvio_app/generated/l10n.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
import 'package:flutter_portal/flutter_portal.dart';
|
||
import 'package:flutter_svg/svg.dart';
|
||
|
||
class UserDetailsScreen extends StatelessWidget {
|
||
final String slug;
|
||
const UserDetailsScreen({super.key, required this.slug});
|
||
static const name = 'userDetailsScreen';
|
||
static const path = '/user-details-screen';
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return BlocProvider(
|
||
create: (context) => sl<UserDetailsCubit>()..getUserDetails(slug),
|
||
child: UserDetailsContent(slug: slug),
|
||
);
|
||
}
|
||
}
|
||
|
||
class UserDetailsContent extends StatefulWidget {
|
||
const UserDetailsContent({super.key, required this.slug});
|
||
final String slug;
|
||
@override
|
||
State<UserDetailsContent> createState() => _UserDetailsContentState();
|
||
}
|
||
|
||
class _UserDetailsContentState extends State<UserDetailsContent> {
|
||
final List<String> chipsLabelList = ['Вес', 'Замеры', 'ИМТ'];
|
||
final List<String> chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ'];
|
||
final List<String> chipsIntervalList = ['1', '3', '6', '12', 'all'];
|
||
|
||
List<WeightModel> weightPoints = [];
|
||
List<CircumferenceModel> circumferencePoints = [];
|
||
int _selectedChipIndex = 0;
|
||
int _selectedIntervalChipIndex = 0;
|
||
bool isMoreOptionsVisible = false;
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final s = S.of(context);
|
||
return BlocBuilder<UserDetailsCubit, UserDetailsState>(
|
||
builder: (context, state) {
|
||
final profileDetails = state.profileDetails;
|
||
if (profileDetails != null && state.isLoading == false) {
|
||
weightPoints = state.weightPoints ?? [];
|
||
circumferencePoints = state.circumferencePoints ?? [];
|
||
return Scaffold(
|
||
backgroundColor: appTheme.gray100,
|
||
appBar: AppBar(
|
||
centerTitle: true,
|
||
title: Text(
|
||
'${s.user_data} ${profileDetails.firstName} ${profileDetails.lastName}',
|
||
maxLines: 2,
|
||
textAlign: TextAlign.center,
|
||
overflow: TextOverflow.ellipsis,
|
||
softWrap: true,
|
||
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
|
||
),
|
||
actions: [
|
||
PortalTarget(
|
||
visible: isMoreOptionsVisible,
|
||
portalFollower: MoreOptions(path: 'profile/${widget.slug}', complainedContentType: ComplainedContent.profile, slug: widget.slug),
|
||
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
|
||
child: IconButton(
|
||
onPressed: () {
|
||
setState(() {
|
||
isMoreOptionsVisible = !isMoreOptionsVisible;
|
||
});
|
||
},
|
||
icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
body: SafeArea(
|
||
child: SingleChildScrollView(
|
||
controller: ScrollController()
|
||
..addListener(() {
|
||
if (isMoreOptionsVisible) {
|
||
setState(() {
|
||
isMoreOptionsVisible = false;
|
||
});
|
||
}
|
||
}),
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.start,
|
||
spacing: 12,
|
||
children: [
|
||
const SizedBox.shrink(),
|
||
_charts(),
|
||
ExpandableBouble(
|
||
initiallyExpanded: true,
|
||
title: s.general_info,
|
||
children: [
|
||
Text(s.date_of_birth, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(
|
||
readOnly: true,
|
||
controller: TextEditingController(text: DateTime.parse(profileDetails.birthdate).format('dd.MM.yyyy')),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(s.sex, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.gender.parseGender())),
|
||
const SizedBox(height: 16),
|
||
],
|
||
),
|
||
ExpandableBouble(
|
||
title: s.physical_fitness,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.height, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.height.toString())),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.weight, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0))),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.chest_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.chestCircumference.toString())),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.waist_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.waistCircumference.toString())),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.hip_circumference, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hipCircumference.toString())),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
),
|
||
ExpandableBouble(
|
||
title: s.pressure,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.upper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.bloodPressureUp.toString())),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.lower, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(
|
||
readOnly: true,
|
||
controller: TextEditingController(text: profileDetails.bloodPressureDown.toStringAsFixed(0)),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.has_hypo, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHypo ? 'да' : 'нет')),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(s.has_hyper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHyper ? 'да' : 'нет')),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
),
|
||
ExpandableBouble(
|
||
title: s.other_info,
|
||
children: [
|
||
Text(s.has_alergy, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutAlergy)),
|
||
const SizedBox(height: 16),
|
||
Text(s.takes_pills, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutTakingPils)),
|
||
const SizedBox(height: 16),
|
||
Text(s.sport_hobbies, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.aboutSportHobbies)),
|
||
const SizedBox(height: 16),
|
||
Text(s.liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.favoriteFoods)),
|
||
const SizedBox(height: 16),
|
||
Text(s.not_liked_groceries, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.unfavoriteFoods)),
|
||
const SizedBox(height: 16),
|
||
Text(s.additional_information, style: TextStyles.textSMedium.copyWith(color: appTheme.black)),
|
||
const SizedBox(height: 4),
|
||
SizedBox(
|
||
height: 112,
|
||
child: CustomTextFormField(
|
||
readOnly: true,
|
||
maxLength: 1000,
|
||
maxLines: null,
|
||
minLines: null,
|
||
expands: true,
|
||
textAlignVertical: TextAlignVertical.top,
|
||
controller: TextEditingController(text: profileDetails.additionalInfo),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
} else if (state.error != null) {
|
||
return Scaffold(
|
||
appBar: AppBar(),
|
||
body: DataErrorWidget(
|
||
onReload: () async {
|
||
context.read<UserDetailsCubit>().getUserDetails(widget.slug);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
return Scaffold(appBar: AppBar(), body: const Loader());
|
||
},
|
||
);
|
||
}
|
||
|
||
Material _charts() {
|
||
return Material(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const SizedBox(height: 8),
|
||
SingleChildScrollView(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
scrollDirection: Axis.horizontal,
|
||
child: Row(children: [_choiceChipWidget(0), const SizedBox(width: 8), _choiceChipWidget(1), const SizedBox(width: 8), _choiceChipWidget(2)]),
|
||
),
|
||
Builder(
|
||
builder: (context) {
|
||
switch (_selectedChipIndex) {
|
||
case 0:
|
||
return WeightChartWidget(weightPoints: weightPoints);
|
||
case 1:
|
||
return CircumferenceChartWidget(circumferencePoints: circumferencePoints);
|
||
case 2:
|
||
return ImtChartWidget(weightPoints: weightPoints);
|
||
default:
|
||
return const SizedBox.shrink();
|
||
}
|
||
},
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
const SizedBox(width: 16),
|
||
_choiceIntervalChipWidget(0),
|
||
const SizedBox(width: 8),
|
||
_choiceIntervalChipWidget(1),
|
||
const SizedBox(width: 8),
|
||
_choiceIntervalChipWidget(2),
|
||
const SizedBox(width: 8),
|
||
_choiceIntervalChipWidget(3),
|
||
const SizedBox(width: 8),
|
||
_choiceIntervalChipWidget(4),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
ChoiceChip _choiceIntervalChipWidget(int index) {
|
||
return ChoiceChip(
|
||
selectedColor: Colors.grey.withValues(alpha: 0.1),
|
||
pressElevation: 0,
|
||
showCheckmark: false,
|
||
side: BorderSide.none,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
label: Text(chipsIntervalLabelList[index]),
|
||
labelStyle: TextStyle(color: appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
|
||
selected: _selectedIntervalChipIndex == index,
|
||
onSelected: (bool selected) {
|
||
context.read<UserDetailsCubit>().getChartData(period: chipsIntervalList[index], slug: widget.slug);
|
||
setState(() {
|
||
_selectedIntervalChipIndex = index;
|
||
});
|
||
},
|
||
);
|
||
}
|
||
|
||
ChoiceChip _choiceChipWidget(int index) {
|
||
return ChoiceChip(
|
||
selectedColor: appTheme.blueA400,
|
||
pressElevation: 0,
|
||
showCheckmark: false,
|
||
side: BorderSide.none,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
label: Text(chipsLabelList[index]),
|
||
labelStyle: TextStyle(color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12),
|
||
selected: _selectedChipIndex == index,
|
||
backgroundColor: appTheme.gray100,
|
||
onSelected: (bool selected) {
|
||
setState(() {
|
||
_selectedChipIndex = index;
|
||
});
|
||
},
|
||
);
|
||
}
|
||
}
|