Files
eatvio_app/lib/features/user_details/presentation/user_details_screen.dart
2025-09-22 17:08:41 +03:00

402 lines
19 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 '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;
});
},
);
}
}