diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 137613c4..dd1b63cb 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -41,6 +41,7 @@ import 'package:eatvio_app/features/settings/presentation/pages/physical_activit import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart'; import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart'; import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart'; +import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -294,6 +295,13 @@ class AppRouter { name: MyPlansScreen.name, builder: (context, state) => MyPlansScreen(), ), + GoRoute( + path: UserDetailsScreen.path, + name: UserDetailsScreen.name, + builder: (context, state) => UserDetailsScreen( + slug: state.extra as String, + ), + ), ], ); } diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 5fa33479..3dda7501 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -61,6 +61,7 @@ import 'package:eatvio_app/features/registration/services/registration_service.d import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart'; +import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart'; import 'package:get_it/get_it.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profilePlans; @@ -98,6 +99,7 @@ Future init() async { sl.registerFactory(() => CreateRecipeCubit()); sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl())); sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl())); + sl.registerFactory(() => UserDetailsCubit(profileRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/core/utils/string_extentions.dart b/lib/core/utils/string_extentions.dart index f474175e..1d366a54 100644 --- a/lib/core/utils/string_extentions.dart +++ b/lib/core/utils/string_extentions.dart @@ -2,4 +2,13 @@ extension Capitalization on String { String capitalize() { return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; } -} \ No newline at end of file + + String parseGender() { + if (this == 'male') { + return 'Мужской'; + } else if (this == 'female') { + return 'Женский'; + } + return this; + } +} diff --git a/lib/core/widgets/custom_text_form_field.dart b/lib/core/widgets/custom_text_form_field.dart index 653600b3..490255db 100644 --- a/lib/core/widgets/custom_text_form_field.dart +++ b/lib/core/widgets/custom_text_form_field.dart @@ -80,20 +80,20 @@ class CustomTextFormField extends StatelessWidget { final TextAlignVertical? textAlignVertical; final bool hasError; final bool enabled; - + // final TextAlign final bool readOnly; @override Widget build(BuildContext context) { return Align( alignment: alignment ?? Alignment.center, - child: TextFormField( + child: TextFormField( enabled: enabled, readOnly: readOnly, textAlignVertical: textAlignVertical, expands: expands, minLines: minLines, maxLength: maxLength, - onChanged: onChanged, + onChanged: onChanged, controller: controller, focusNode: focusNode ?? null, autofocus: autofocus, diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index 48b7474d..62833a86 100644 --- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -1,4 +1,5 @@ import 'package:badges/badges.dart' as badges; +import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart'; @@ -6,6 +7,8 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/noti import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; @@ -28,7 +31,7 @@ class BottomNavigationBarWidget extends StatefulWidget { } class _BottomNavigationBarWidgetState extends State { - void _onItemTapped(int index) { + void _onItemTapped(int index) async { if (currentBottomNavBarIndex != index) { setState(() { currentBottomNavBarIndex = index; diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart index b190cb27..bc8f7a96 100644 --- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart @@ -12,9 +12,12 @@ class ChatSelectCubit extends Cubit { getChatRooms({String? query}) async { emit(ChatSelectState.loading()); try { - emit(ChatSelectState.loaded( - chatRooms: (await chatRepository.getChatRoomPreviews(query: query)), - ),); + final chatRooms = await chatRepository.getChatRoomPreviews(query: query); + emit( + ChatSelectState.loaded( + chatRooms: chatRooms, + ), + ); } catch (e) { emit( ChatSelectState.error(errorMessage: e.toString()), diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart index 3c26b953..7459be0d 100644 --- a/lib/features/create_diet/presentation/diet_preview_screen.dart +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -190,7 +190,10 @@ class _DietPreviewScreenState extends State { actions: [ PortalTarget( visible: isMoreOptionsVisible, - portalFollower: MoreOptions(disableActions: true, id: widget.diet.id), + portalFollower: MoreOptions( + disableActions: true, + path: 'diet/${widget.diet.id}', + ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 24c9fa60..8cd3fd84 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -151,7 +151,7 @@ class _AboutDietScreenState extends State { BookmarkButton( isFavorite: dietState.diet.isFavorite, onTap: () { - if (dietState.diet.isFavorite) { + if (dietState.diet.isFavorite) { dietsCubit.removeFromFavorite(widget.diet.id, widget.index); } else { dietsCubit.addToFavorite(widget.diet.id, widget.index); @@ -291,7 +291,9 @@ class _AboutDietScreenState extends State { actions: [ PortalTarget( visible: isMoreOptionsVisible, - portalFollower: MoreOptions(id: widget.diet.id), + portalFollower: MoreOptions( + path: 'diet/${widget.diet.id}', + ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, diff --git a/lib/features/diets/presentation/widgets/more_options.dart b/lib/features/diets/presentation/widgets/more_options.dart index b087f9f0..8a948a7c 100644 --- a/lib/features/diets/presentation/widgets/more_options.dart +++ b/lib/features/diets/presentation/widgets/more_options.dart @@ -12,8 +12,8 @@ import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; class MoreOptions extends StatelessWidget { - const MoreOptions({super.key, required this.id, this.disableActions}); - final int id; + const MoreOptions({super.key, this.disableActions, required this.path}); + final String path; final bool? disableActions; @override Widget build(BuildContext context) { @@ -26,10 +26,12 @@ class MoreOptions extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ GestureDetector( - onTap:disableActions == true ? null : () { - Clipboard.setData(ClipboardData(text: baseUrl + '/ru/diet/' + id.toString())); - showInfoToast(s.link_was_copied); - }, + onTap: disableActions == true + ? null + : () { + Clipboard.setData(ClipboardData(text: '$baseUrl/ru/$path')); + showInfoToast(s.link_was_copied); + }, child: Row( children: [ Text( @@ -48,11 +50,13 @@ class MoreOptions extends StatelessWidget { child: CustomDivider(), ), GestureDetector( - onTap: disableActions == true ? null : () => Share.shareUri( - Uri.parse( - baseUrl + '/ru/diet/' + id.toString(), - ), - ), + onTap: disableActions == true + ? null + : () => Share.shareUri( + Uri.parse( + '$baseUrl/ru/$path', + ), + ), child: Row( children: [ Text( @@ -71,9 +75,11 @@ class MoreOptions extends StatelessWidget { child: CustomDivider(), ), GestureDetector( - onTap: disableActions == true ? null : () { - context.push('complainingScreen'); - }, + onTap: disableActions == true + ? null + : () { + context.push('complainingScreen'); + }, child: Row( children: [ Text( diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index b76cce48..3392e523 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -292,7 +292,9 @@ class _AboutPlanScreenState extends State { actions: [ PortalTarget( visible: isMoreOptionsVisible, - portalFollower: MoreOptions(id: widget.plan.id), + portalFollower: MoreOptions( + path: 'plan/${widget.plan.id}', + ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index dcf78172..7810f1a5 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -276,7 +276,10 @@ class _ProfileScreenState extends State { height: 378, child: PageView.builder( itemBuilder: (BuildContext context, int index) { - return PlanWidget(plan: state.plansList[index], index: index,); + return PlanWidget( + plan: state.plansList[index], + index: index, + ); }, onPageChanged: (bullet) { setState(() { @@ -1097,7 +1100,7 @@ class _ProfileScreenState extends State { ); } - _charts() { + _charts() { return Material( color: Colors.white, borderRadius: BorderRadius.circular(16), diff --git a/lib/features/profile/presentation/widgets/circumference_chart_widget.dart b/lib/features/profile/presentation/widgets/circumference_chart_widget.dart index 82130c54..d7673989 100644 --- a/lib/features/profile/presentation/widgets/circumference_chart_widget.dart +++ b/lib/features/profile/presentation/widgets/circumference_chart_widget.dart @@ -8,12 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart'; class CircumferenceChartWidget extends StatefulWidget { final List circumferencePoints; - final ProfileCubit profileCubit; + final ProfileCubit? profileCubit; const CircumferenceChartWidget({ super.key, required this.circumferencePoints, - required this.profileCubit, + this.profileCubit, }); @override @@ -171,117 +171,118 @@ class _CircumferenceChartWidgetState extends State { series: _getSplineSeries(), tooltipBehavior: TooltipBehavior(enable: true), ), - FloatingActionButton.small( - heroTag: 'addCircumference', - onPressed: () { - if (widget.circumferencePoints.isNotEmpty) { - chest = widget.circumferencePoints.last.chestCircumference; - waist = widget.circumferencePoints.last.waistCircumference; - hip = widget.circumferencePoints.last.hipCircumference; - chestController = FixedExtentScrollController(initialItem: chest - 20); - waistController = FixedExtentScrollController(initialItem: waist - 20); - hipController = FixedExtentScrollController(initialItem: hip - 20); - } else { - chestController = FixedExtentScrollController(); - waistController = FixedExtentScrollController(); - hipController = FixedExtentScrollController(); - } - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) { - return Column( - children: [ - Expanded( - child: Row( - children: [ - Expanded( - child: CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - scrollController: chestController, - itemExtent: 24, - onSelectedItemChanged: (int value) { - chest = value; - }, - children: [ - for (int i = 20; i < 200; i++) Text('$i'), - ], + if (widget.profileCubit != null) + FloatingActionButton.small( + heroTag: 'addCircumference', + onPressed: () { + if (widget.circumferencePoints.isNotEmpty) { + chest = widget.circumferencePoints.last.chestCircumference; + waist = widget.circumferencePoints.last.waistCircumference; + hip = widget.circumferencePoints.last.hipCircumference; + chestController = FixedExtentScrollController(initialItem: chest - 20); + waistController = FixedExtentScrollController(initialItem: waist - 20); + hipController = FixedExtentScrollController(initialItem: hip - 20); + } else { + chestController = FixedExtentScrollController(); + waistController = FixedExtentScrollController(); + hipController = FixedExtentScrollController(); + } + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) { + return Column( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + scrollController: chestController, + itemExtent: 24, + onSelectedItemChanged: (int value) { + chest = value; + }, + children: [ + for (int i = 20; i < 200; i++) Text('$i'), + ], + ), ), - ), - Expanded( - child: CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - scrollController: waistController, - itemExtent: 24, - onSelectedItemChanged: (int value) { - waist = value; - }, - children: [ - for (int i = 20; i < 200; i++) Text('$i'), - ], + Expanded( + child: CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + scrollController: waistController, + itemExtent: 24, + onSelectedItemChanged: (int value) { + waist = value; + }, + children: [ + for (int i = 20; i < 200; i++) Text('$i'), + ], + ), ), - ), - Expanded( - child: CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - scrollController: hipController, - itemExtent: 24, - onSelectedItemChanged: (int value) { - hip = value; - }, - children: [ - for (int i = 20; i < 200; i++) Text('$i'), - ], + Expanded( + child: CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + scrollController: hipController, + itemExtent: 24, + onSelectedItemChanged: (int value) { + hip = value; + }, + children: [ + for (int i = 20; i < 200; i++) Text('$i'), + ], + ), ), - ), - ], - ), - ), - InkWell( - onTap: () { - widget.profileCubit.addCircumference( - chestCircumference: chest + 20, - waistCircumference: waist + 20, - hipCircumference: hip + 20, - ); - Navigator.of(context).pop(); - }, - borderRadius: BorderRadius.circular(16), - child: Ink( - width: MediaQuery.of(context).size.width - 32, - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: appTheme.blueA400, - borderRadius: BorderRadius.circular(16), + ], ), - child: Center( - child: Text( - 'Сохранить', - style: TextStyle( - color: Colors.white, + ), + InkWell( + onTap: () { + widget.profileCubit?.addCircumference( + chestCircumference: chest + 20, + waistCircumference: waist + 20, + hipCircumference: hip + 20, + ); + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(16), + child: Ink( + width: MediaQuery.of(context).size.width - 32, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: appTheme.blueA400, + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Text( + 'Сохранить', + style: TextStyle( + color: Colors.white, + ), ), ), ), ), - ), - ], - ); - }, - ); - }, - backgroundColor: appTheme.blueA400, - shape: CircleBorder(), - child: Icon( - Icons.add, - color: Colors.white, + ], + ); + }, + ); + }, + backgroundColor: appTheme.blueA400, + shape: CircleBorder(), + child: Icon( + Icons.add, + color: Colors.white, + ), ), - ), ], ), ], diff --git a/lib/features/profile/presentation/widgets/weight_chart_widget.dart b/lib/features/profile/presentation/widgets/weight_chart_widget.dart index 082befe0..ecce8728 100644 --- a/lib/features/profile/presentation/widgets/weight_chart_widget.dart +++ b/lib/features/profile/presentation/widgets/weight_chart_widget.dart @@ -8,12 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart'; class WeightChartWidget extends StatefulWidget { final List weightPoints; - final ProfileCubit profileCubit; + final ProfileCubit? profileCubit; const WeightChartWidget({ super.key, required this.weightPoints, - required this.profileCubit, + this.profileCubit, }); @override @@ -111,99 +111,100 @@ class _WeightChartWidgetState extends State { series: _getSplineSeries(), tooltipBehavior: TooltipBehavior(enable: true), ), - FloatingActionButton.small( - heroTag: 'addWeight', - onPressed: () { - if (widget.weightPoints.isNotEmpty) { - try { - weightKilo = int.parse(widget.weightPoints.last.weight.toString().split('.')[0]); - weightGrams = int.parse(widget.weightPoints.last.weight.toString().split('.')[1][0]); - } catch (e) { - weightGrams = 0; + if (widget.profileCubit != null) + FloatingActionButton.small( + heroTag: 'addWeight', + onPressed: () { + if (widget.weightPoints.isNotEmpty) { + try { + weightKilo = int.parse(widget.weightPoints.last.weight.toString().split('.')[0]); + weightGrams = int.parse(widget.weightPoints.last.weight.toString().split('.')[1][0]); + } catch (e) { + weightGrams = 0; + } + weightKiloController = FixedExtentScrollController(initialItem: weightKilo - 20); + weightGramsController = FixedExtentScrollController(initialItem: weightGrams); + } else { + weightKiloController = FixedExtentScrollController(); + weightGramsController = FixedExtentScrollController(); } - weightKiloController = FixedExtentScrollController(initialItem: weightKilo - 20); - weightGramsController = FixedExtentScrollController(initialItem: weightGrams); - } else { - weightKiloController = FixedExtentScrollController(); - weightGramsController = FixedExtentScrollController(); - } - showModalBottomSheet( - useRootNavigator: true, - context: context, - builder: (context) { - return Column( - children: [ - Expanded( - child: Row( - children: [ - Expanded( - child: CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - scrollController: weightKiloController, - itemExtent: 24, - onSelectedItemChanged: (int value) { - weightKilo = value + 20; - }, - children: [ - for (int i = 20; i < 300; i++) Text('$i'), - ], + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) { + return Column( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + scrollController: weightKiloController, + itemExtent: 24, + onSelectedItemChanged: (int value) { + weightKilo = value + 20; + }, + children: [ + for (int i = 20; i < 300; i++) Text('$i'), + ], + ), ), - ), - Expanded( - child: CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - scrollController: weightGramsController, - itemExtent: 24, - onSelectedItemChanged: (int value) { - weightGrams = value; - }, - children: [ - for (int i = 0; i < 10; i++) Text('$i'), - ], + Expanded( + child: CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + scrollController: weightGramsController, + itemExtent: 24, + onSelectedItemChanged: (int value) { + weightGrams = value; + }, + children: [ + for (int i = 0; i < 10; i++) Text('$i'), + ], + ), ), - ), - ], - ), - ), - InkWell( - onTap: () { - widget.profileCubit.addWeight(weight: double.parse('$weightKilo.$weightGrams')); - Navigator.of(context).pop(); - }, - borderRadius: BorderRadius.circular(16), - child: Ink( - width: MediaQuery.of(context).size.width - 32, - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: appTheme.blueA400, - borderRadius: BorderRadius.circular(16), + ], ), - child: Center( - child: Text( - 'Сохранить', - style: TextStyle( - color: Colors.white, + ), + InkWell( + onTap: () { + widget.profileCubit?.addWeight(weight: double.parse('$weightKilo.$weightGrams')); + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(16), + child: Ink( + width: MediaQuery.of(context).size.width - 32, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: appTheme.blueA400, + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: Text( + 'Сохранить', + style: TextStyle( + color: Colors.white, + ), ), ), ), ), - ), - ], - ); - }, - ); - }, - backgroundColor: appTheme.blueA400, - shape: CircleBorder(), - child: Icon( - Icons.add, - color: Colors.white, + ], + ); + }, + ); + }, + backgroundColor: appTheme.blueA400, + shape: CircleBorder(), + child: Icon( + Icons.add, + color: Colors.white, + ), ), - ), ], ), ], diff --git a/lib/features/user_details/presentation/cubit/user_details_cubit.dart b/lib/features/user_details/presentation/cubit/user_details_cubit.dart new file mode 100644 index 00000000..c6eb36b7 --- /dev/null +++ b/lib/features/user_details/presentation/cubit/user_details_cubit.dart @@ -0,0 +1,41 @@ +import 'dart:developer'; + +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class UserDetailsCubit extends Cubit { + final ProfileRepository profileRepository; + + UserDetailsCubit({required this.profileRepository}) : super(UserDetailsState()); + + void getUserDetails(String slug) async { + try { + final profileDetails = await profileRepository.getProfileDetailsBySlug(slug); + emit(state.copyWith(profileDetails: profileDetails, isLoading: false)); + getChartData(slug: slug); + } catch (e) { + log(e.toString()); + emit(state.copyWith(error: e.toString(), isLoading: false)); + } + } + + void getChartData({ + String period = '1', + required String slug, + }) async { + try { + final weightPoints = await profileRepository.getWeight(period: period); + final circumferencePoints = await profileRepository.getCircumferences(period: period); + emit( + state.copyWith( + weightPoints: weightPoints, + circumferencePoints: circumferencePoints, + ), + ); + } catch (e, stackTrace) { + log(e.toString(), stackTrace: stackTrace); + emit(state.copyWith(error: e.toString(), isLoading: false)); + } + } +} diff --git a/lib/features/user_details/presentation/cubit/user_details_state.dart b/lib/features/user_details/presentation/cubit/user_details_state.dart new file mode 100644 index 00000000..354d6006 --- /dev/null +++ b/lib/features/user_details/presentation/cubit/user_details_state.dart @@ -0,0 +1,17 @@ +import 'package:eatvio_app/features/profile/models/circumference_model.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; +import 'package:eatvio_app/features/profile/models/weight_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_details_state.freezed.dart'; + +@freezed +class UserDetailsState with _$UserDetailsState { + factory UserDetailsState({ + @Default(false) bool isLoading, + String? error, + ProfileDetailsModel? profileDetails, + List? weightPoints, + List? circumferencePoints, + }) = _UserDetailsState; +} \ No newline at end of file diff --git a/lib/features/user_details/presentation/cubit/user_details_state.freezed.dart b/lib/features/user_details/presentation/cubit/user_details_state.freezed.dart new file mode 100644 index 00000000..5cad9acb --- /dev/null +++ b/lib/features/user_details/presentation/cubit/user_details_state.freezed.dart @@ -0,0 +1,278 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_details_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$UserDetailsState { + bool get isLoading => throw _privateConstructorUsedError; + String? get error => throw _privateConstructorUsedError; + ProfileDetailsModel? get profileDetails => throw _privateConstructorUsedError; + List? get weightPoints => throw _privateConstructorUsedError; + List? get circumferencePoints => + throw _privateConstructorUsedError; + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserDetailsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserDetailsStateCopyWith<$Res> { + factory $UserDetailsStateCopyWith( + UserDetailsState value, $Res Function(UserDetailsState) then) = + _$UserDetailsStateCopyWithImpl<$Res, UserDetailsState>; + @useResult + $Res call( + {bool isLoading, + String? error, + ProfileDetailsModel? profileDetails, + List? weightPoints, + List? circumferencePoints}); + + $ProfileDetailsModelCopyWith<$Res>? get profileDetails; +} + +/// @nodoc +class _$UserDetailsStateCopyWithImpl<$Res, $Val extends UserDetailsState> + implements $UserDetailsStateCopyWith<$Res> { + _$UserDetailsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? error = freezed, + Object? profileDetails = freezed, + Object? weightPoints = freezed, + Object? circumferencePoints = freezed, + }) { + return _then(_value.copyWith( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + profileDetails: freezed == profileDetails + ? _value.profileDetails + : profileDetails // ignore: cast_nullable_to_non_nullable + as ProfileDetailsModel?, + weightPoints: freezed == weightPoints + ? _value.weightPoints + : weightPoints // ignore: cast_nullable_to_non_nullable + as List?, + circumferencePoints: freezed == circumferencePoints + ? _value.circumferencePoints + : circumferencePoints // ignore: cast_nullable_to_non_nullable + as List?, + ) as $Val); + } + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProfileDetailsModelCopyWith<$Res>? get profileDetails { + if (_value.profileDetails == null) { + return null; + } + + return $ProfileDetailsModelCopyWith<$Res>(_value.profileDetails!, (value) { + return _then(_value.copyWith(profileDetails: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$UserDetailsStateImplCopyWith<$Res> + implements $UserDetailsStateCopyWith<$Res> { + factory _$$UserDetailsStateImplCopyWith(_$UserDetailsStateImpl value, + $Res Function(_$UserDetailsStateImpl) then) = + __$$UserDetailsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool isLoading, + String? error, + ProfileDetailsModel? profileDetails, + List? weightPoints, + List? circumferencePoints}); + + @override + $ProfileDetailsModelCopyWith<$Res>? get profileDetails; +} + +/// @nodoc +class __$$UserDetailsStateImplCopyWithImpl<$Res> + extends _$UserDetailsStateCopyWithImpl<$Res, _$UserDetailsStateImpl> + implements _$$UserDetailsStateImplCopyWith<$Res> { + __$$UserDetailsStateImplCopyWithImpl(_$UserDetailsStateImpl _value, + $Res Function(_$UserDetailsStateImpl) _then) + : super(_value, _then); + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? error = freezed, + Object? profileDetails = freezed, + Object? weightPoints = freezed, + Object? circumferencePoints = freezed, + }) { + return _then(_$UserDetailsStateImpl( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + profileDetails: freezed == profileDetails + ? _value.profileDetails + : profileDetails // ignore: cast_nullable_to_non_nullable + as ProfileDetailsModel?, + weightPoints: freezed == weightPoints + ? _value._weightPoints + : weightPoints // ignore: cast_nullable_to_non_nullable + as List?, + circumferencePoints: freezed == circumferencePoints + ? _value._circumferencePoints + : circumferencePoints // ignore: cast_nullable_to_non_nullable + as List?, + )); + } +} + +/// @nodoc + +class _$UserDetailsStateImpl implements _UserDetailsState { + _$UserDetailsStateImpl( + {this.isLoading = false, + this.error, + this.profileDetails, + final List? weightPoints, + final List? circumferencePoints}) + : _weightPoints = weightPoints, + _circumferencePoints = circumferencePoints; + + @override + @JsonKey() + final bool isLoading; + @override + final String? error; + @override + final ProfileDetailsModel? profileDetails; + final List? _weightPoints; + @override + List? get weightPoints { + final value = _weightPoints; + if (value == null) return null; + if (_weightPoints is EqualUnmodifiableListView) return _weightPoints; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + final List? _circumferencePoints; + @override + List? get circumferencePoints { + final value = _circumferencePoints; + if (value == null) return null; + if (_circumferencePoints is EqualUnmodifiableListView) + return _circumferencePoints; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'UserDetailsState(isLoading: $isLoading, error: $error, profileDetails: $profileDetails, weightPoints: $weightPoints, circumferencePoints: $circumferencePoints)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserDetailsStateImpl && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.error, error) || other.error == error) && + (identical(other.profileDetails, profileDetails) || + other.profileDetails == profileDetails) && + const DeepCollectionEquality() + .equals(other._weightPoints, _weightPoints) && + const DeepCollectionEquality() + .equals(other._circumferencePoints, _circumferencePoints)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + isLoading, + error, + profileDetails, + const DeepCollectionEquality().hash(_weightPoints), + const DeepCollectionEquality().hash(_circumferencePoints)); + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserDetailsStateImplCopyWith<_$UserDetailsStateImpl> get copyWith => + __$$UserDetailsStateImplCopyWithImpl<_$UserDetailsStateImpl>( + this, _$identity); +} + +abstract class _UserDetailsState implements UserDetailsState { + factory _UserDetailsState( + {final bool isLoading, + final String? error, + final ProfileDetailsModel? profileDetails, + final List? weightPoints, + final List? circumferencePoints}) = + _$UserDetailsStateImpl; + + @override + bool get isLoading; + @override + String? get error; + @override + ProfileDetailsModel? get profileDetails; + @override + List? get weightPoints; + @override + List? get circumferencePoints; + + /// Create a copy of UserDetailsState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserDetailsStateImplCopyWith<_$UserDetailsStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/user_details/presentation/user_details_screen.dart b/lib/features/user_details/presentation/user_details_screen.dart new file mode 100644 index 00000000..2aa03504 --- /dev/null +++ b/lib/features/user_details/presentation/user_details_screen.dart @@ -0,0 +1,520 @@ +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/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()..getUserDetails(slug), + child: UserDetailsContent( + slug: slug, + ), + ); + } +} + +class UserDetailsContent extends StatefulWidget { + const UserDetailsContent({super.key, required this.slug}); + final String slug; + @override + State createState() => _UserDetailsContentState(); +} + +class _UserDetailsContentState extends State { + final List chipsLabelList = ['Вес', 'Замеры', 'ИМТ']; + final List chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ']; + final List chipsIntervalList = ['1', '3', '6', '12', 'all']; + + List weightPoints = []; + List circumferencePoints = []; + int _selectedChipIndex = 0; + int _selectedIntervalChipIndex = 0; + bool isMoreOptionsVisible = false; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + 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}', + ), + anchor: 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: SingleChildScrollView( + controller: ScrollController() + ..addListener(() { + setState(() { + isMoreOptionsVisible = false; + }); + }), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 12, + children: [ + SizedBox.shrink(), + _charts(), + ExpandableBouble( + initiallyExpanded: true, + title: s.general_info, + children: [ + Text( + s.date_of_birth, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: DateTime.parse(profileDetails.birthdate).format('dd.MM.yyyy')), + ), + SizedBox(height: 16), + Text( + s.sex, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.gender.parseGender()), + ), + 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), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.height.toString()), + ), + ], + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.weight, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.weight.toStringAsFixed(0)), + ), + ], + ), + ), + ], + ), + SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.chest_circumference, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.chestCircumference.toString()), + ), + ], + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.waist_circumference, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.waistCircumference.toString()), + ), + ], + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.hip_circumference, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.hipCircumference.toString()), + ), + ], + ), + ), + ], + ), + 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), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.bloodPressureUp.toString()), + ), + ], + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.lower, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.bloodPressureDown.toStringAsFixed(0)), + ), + ], + ), + ), + ], + ), + SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.has_hypo, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.hasHypo.toString()), + ), + ], + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.has_hyper, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.hasHyper.toString()), + ), + ], + ), + ), + ], + ), + SizedBox(height: 16), + ], + ), + ExpandableBouble( + title: s.other_info, + children: [ + Text( + s.has_alergy, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.aboutAlergy), + ), + SizedBox(height: 16), + Text( + s.takes_pills, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.aboutTakingPils), + ), + SizedBox(height: 16), + Text( + s.sport_hobbies, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.aboutSportHobbies), + ), + SizedBox(height: 16), + Text( + s.liked_groceries, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.favoriteFoods), + ), + SizedBox(height: 16), + Text( + s.not_liked_groceries, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 4), + CustomTextFormField( + readOnly: true, + controller: TextEditingController(text: profileDetails.unfavoriteFoods), + ), + SizedBox(height: 16), + Text( + s.additional_information, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + 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), + ), + ), + SizedBox(height: 16), + ], + ), + ], + ), + ), + ); + } else { + return Scaffold( + body: Loader(), + ); + } + }, + ); + } + + _charts() { + return Material( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8), + SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16), + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _choiceChipWidget(0), + SizedBox(width: 8), + _choiceChipWidget(1), + 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 SizedBox.shrink(); + } + }, + ), + SizedBox(height: 8), + Row( + children: [ + SizedBox(width: 16), + _choiceIntervalChipWidget(0), + SizedBox(width: 8), + _choiceIntervalChipWidget(1), + SizedBox(width: 8), + _choiceIntervalChipWidget(2), + SizedBox(width: 8), + _choiceIntervalChipWidget(3), + SizedBox(width: 8), + _choiceIntervalChipWidget(4), + ], + ), + SizedBox(height: 8), + ], + ), + ); + } + + _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().getChartData(period: chipsIntervalList[index], slug: widget.slug); + setState(() { + _selectedIntervalChipIndex = index; + }); + }, + ); + } + + _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; + }); + }, + ); + } +} diff --git a/lib/features/user_details/presentation/widgets/expandable_bouble.dart b/lib/features/user_details/presentation/widgets/expandable_bouble.dart new file mode 100644 index 00000000..e5303a06 --- /dev/null +++ b/lib/features/user_details/presentation/widgets/expandable_bouble.dart @@ -0,0 +1,34 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:flutter/material.dart'; + +class ExpandableBouble extends StatelessWidget { + const ExpandableBouble({super.key, required this.title, required this.children, this.initiallyExpanded = false}); + final String title; + final List children; + final bool initiallyExpanded; + @override + Widget build(BuildContext context) { + return AnimatedSize( + duration: Duration(seconds: 1), + child: MainBackgroundBouble( + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + initiallyExpanded: initiallyExpanded, + expandedCrossAxisAlignment: CrossAxisAlignment.start, + expandedAlignment: Alignment.bottomLeft, + tilePadding: EdgeInsets.symmetric(horizontal: 16), + childrenPadding: EdgeInsets.symmetric(horizontal: 16), + title: Text( + title, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + children: children, + ), + ), + ), + ); + } +} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 11c0d855..a894ea49 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -150,8 +150,16 @@ class MessageLookup extends MessageLookupByLibrary { "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), + "general_info": + MessageLookupByLibrary.simpleMessage("Общая информация"), "gr": MessageLookupByLibrary.simpleMessage("гр"), "grams": MessageLookupByLibrary.simpleMessage("гр"), + "has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"), + "has_hyper": + MessageLookupByLibrary.simpleMessage("Склонность к гипертонии"), + "has_hypo": + MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"), + "height": MessageLookupByLibrary.simpleMessage("Рост"), "height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"), "hide": MessageLookupByLibrary.simpleMessage("Свернуть"), @@ -173,9 +181,12 @@ class MessageLookup extends MessageLookupByLibrary { "light_type": MessageLookupByLibrary.simpleMessage("Легкая"), "light_type_desc": MessageLookupByLibrary.simpleMessage( "С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками."), + "liked_groceries": + MessageLookupByLibrary.simpleMessage("Любимые продукты"), "link_was_copied": MessageLookupByLibrary.simpleMessage("Ссылка успешно скопирована"), "loading": MessageLookupByLibrary.simpleMessage("Загрузка..."), + "lower": MessageLookupByLibrary.simpleMessage("Нижнее"), "male": MessageLookupByLibrary.simpleMessage("Мужской"), "meal": MessageLookupByLibrary.simpleMessage("Приём пищи"), "meat": MessageLookupByLibrary.simpleMessage("Мясо"), @@ -202,6 +213,8 @@ class MessageLookup extends MessageLookupByLibrary { "no": MessageLookupByLibrary.simpleMessage("Нет"), "no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), + "not_liked_groceries": + MessageLookupByLibrary.simpleMessage("Не любимые продукты"), "one_option": MessageLookupByLibrary.simpleMessage("1 опция"), "option": MessageLookupByLibrary.simpleMessage("Опция"), "option_content": @@ -217,6 +230,7 @@ class MessageLookup extends MessageLookupByLibrary { "Опция подразумевает создание индивидуальной диеты"), "option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"), "order": MessageLookupByLibrary.simpleMessage("Порядок"), + "other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"), "password": MessageLookupByLibrary.simpleMessage("Пароль"), "password_confirmation": MessageLookupByLibrary.simpleMessage("Подтвердите пароль"), @@ -231,6 +245,8 @@ class MessageLookup extends MessageLookupByLibrary { "personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"), "photo": MessageLookupByLibrary.simpleMessage("Фото"), + "physical_fitness": + MessageLookupByLibrary.simpleMessage("Физическая форма"), "place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"), "plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"), @@ -246,6 +262,7 @@ class MessageLookup extends MessageLookupByLibrary { "popular": MessageLookupByLibrary.simpleMessage("Популярные"), "preferred_language": MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"), + "pressure": MessageLookupByLibrary.simpleMessage("Давление"), "pressure_empty": MessageLookupByLibrary.simpleMessage( "Все показатели давления обязательны к заполнению"), "pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage( @@ -263,14 +280,19 @@ class MessageLookup extends MessageLookupByLibrary { "save": MessageLookupByLibrary.simpleMessage("Сохранить"), "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), "settings": MessageLookupByLibrary.simpleMessage("Настройки"), + "sex": MessageLookupByLibrary.simpleMessage("Пол"), "share": MessageLookupByLibrary.simpleMessage("Поделиться"), "skip": MessageLookupByLibrary.simpleMessage("Пропустить"), "slim": MessageLookupByLibrary.simpleMessage("Похудение"), "slim_type": MessageLookupByLibrary.simpleMessage("Похудение"), "slim_type_desc": MessageLookupByLibrary.simpleMessage( "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), + "sport_hobbies": + MessageLookupByLibrary.simpleMessage("Спортивные увлечения"), "standard": MessageLookupByLibrary.simpleMessage("Стандарт"), "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), + "takes_pills": + MessageLookupByLibrary.simpleMessage("Принимает лекарства"), "tell_your_schedule": MessageLookupByLibrary.simpleMessage( "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"), "three_options": MessageLookupByLibrary.simpleMessage("3 опции"), @@ -281,8 +303,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"), "up_pressure": MessageLookupByLibrary.simpleMessage( "Верхние показатели давления:"), + "upper": MessageLookupByLibrary.simpleMessage("Верхнее"), "user_authorization": MessageLookupByLibrary.simpleMessage("Аутентификация"), + "user_data": + MessageLookupByLibrary.simpleMessage("Данные пользователя"), "user_registration": MessageLookupByLibrary.simpleMessage("Регистрация пользователя"), "username": MessageLookupByLibrary.simpleMessage("Имя пользователя"), diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart index bf5ff68a..328f6779 100644 --- a/lib/generated/intl/messages_ru.dart +++ b/lib/generated/intl/messages_ru.dart @@ -88,7 +88,9 @@ class MessageLookup extends MessageLookupByLibrary { "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), "day": MessageLookupByLibrary.simpleMessage("День"), "days": MessageLookupByLibrary.simpleMessage("дней"), + "deleted": MessageLookupByLibrary.simpleMessage("Удалена"), "diary": MessageLookupByLibrary.simpleMessage("Дневник"), + "diet": MessageLookupByLibrary.simpleMessage("Диета"), "diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"), "diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"), "diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"), @@ -102,6 +104,7 @@ class MessageLookup extends MessageLookupByLibrary { "down_pressure": MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"), "draft": MessageLookupByLibrary.simpleMessage("Черновик"), + "drafted": MessageLookupByLibrary.simpleMessage("Черновики"), "eating_behavior": MessageLookupByLibrary.simpleMessage("Пищевое поведение"), "eating_behavior_type": MessageLookupByLibrary.simpleMessage( @@ -109,6 +112,7 @@ class MessageLookup extends MessageLookupByLibrary { "eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage( "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"), "economy": MessageLookupByLibrary.simpleMessage("Эконом"), + "edit": MessageLookupByLibrary.simpleMessage("Редактировать"), "email": MessageLookupByLibrary.simpleMessage("Элетронная почта"), "email_empty": MessageLookupByLibrary.simpleMessage( "Необходимо ввести адрес электронной почты"), @@ -142,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary { "fit_type_desc": MessageLookupByLibrary.simpleMessage( "План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), "fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"), + "from": MessageLookupByLibrary.simpleMessage("от"), "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), @@ -187,6 +192,8 @@ class MessageLookup extends MessageLookupByLibrary { "msg_something_went_wrong": MessageLookupByLibrary.simpleMessage("Что то пошло не так!"), "my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"), + "my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"), + "my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"), "my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"), "name_empty": MessageLookupByLibrary.simpleMessage( "Имя пользователя обязательно для заполнения"), @@ -220,6 +227,7 @@ class MessageLookup extends MessageLookupByLibrary { "password_short": MessageLookupByLibrary.simpleMessage( "В пароле должно быть минимум 6 символов"), "pcs": MessageLookupByLibrary.simpleMessage("шт"), + "personal": MessageLookupByLibrary.simpleMessage("Персональные"), "personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"), "photo": MessageLookupByLibrary.simpleMessage("Фото"), @@ -242,11 +250,15 @@ class MessageLookup extends MessageLookupByLibrary { "pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage( "Все показатели давления должны быть числами"), "preview": MessageLookupByLibrary.simpleMessage("Превью"), + "price": MessageLookupByLibrary.simpleMessage("Цена"), "price_explanation": MessageLookupByLibrary.simpleMessage( "Разница в ценах, это комиссия Итвио"), "profile": MessageLookupByLibrary.simpleMessage("Профиль"), "proteins": MessageLookupByLibrary.simpleMessage("Белки"), "publish": MessageLookupByLibrary.simpleMessage("Опубликовать"), + "published": MessageLookupByLibrary.simpleMessage("Опубликованные"), + "removeFromActive": + MessageLookupByLibrary.simpleMessage("Снять с публикации"), "save": MessageLookupByLibrary.simpleMessage("Сохранить"), "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), "settings": MessageLookupByLibrary.simpleMessage("Настройки"), @@ -261,6 +273,7 @@ class MessageLookup extends MessageLookupByLibrary { "tell_your_schedule": MessageLookupByLibrary.simpleMessage( "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"), "three_options": MessageLookupByLibrary.simpleMessage("3 опции"), + "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), "unfavorite_foods": MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"), @@ -268,6 +281,8 @@ class MessageLookup extends MessageLookupByLibrary { "Верхние показатели давления:"), "user_authorization": MessageLookupByLibrary.simpleMessage("Аутентификация"), + "user_data": + MessageLookupByLibrary.simpleMessage("Данные пользователя"), "user_registration": MessageLookupByLibrary.simpleMessage("Регистрация пользователя"), "username": MessageLookupByLibrary.simpleMessage("Имя пользователя"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 52654a58..bd4faecc 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2169,6 +2169,166 @@ class S { args: [], ); } + + /// `Данные пользователя` + String get user_data { + return Intl.message( + 'Данные пользователя', + name: 'user_data', + desc: '', + args: [], + ); + } + + /// `Общая информация` + String get general_info { + return Intl.message( + 'Общая информация', + name: 'general_info', + desc: '', + args: [], + ); + } + + /// `Пол` + String get sex { + return Intl.message( + 'Пол', + name: 'sex', + desc: '', + args: [], + ); + } + + /// `Физическая форма` + String get physical_fitness { + return Intl.message( + 'Физическая форма', + name: 'physical_fitness', + desc: '', + args: [], + ); + } + + /// `Рост` + String get height { + return Intl.message( + 'Рост', + name: 'height', + desc: '', + args: [], + ); + } + + /// `Давление` + String get pressure { + return Intl.message( + 'Давление', + name: 'pressure', + desc: '', + args: [], + ); + } + + /// `Верхнее` + String get upper { + return Intl.message( + 'Верхнее', + name: 'upper', + desc: '', + args: [], + ); + } + + /// `Нижнее` + String get lower { + return Intl.message( + 'Нижнее', + name: 'lower', + desc: '', + args: [], + ); + } + + /// `Склонность к гипотонии` + String get has_hypo { + return Intl.message( + 'Склонность к гипотонии', + name: 'has_hypo', + desc: '', + args: [], + ); + } + + /// `Склонность к гипертонии` + String get has_hyper { + return Intl.message( + 'Склонность к гипертонии', + name: 'has_hyper', + desc: '', + args: [], + ); + } + + /// `Прочая информация` + String get other_info { + return Intl.message( + 'Прочая информация', + name: 'other_info', + desc: '', + args: [], + ); + } + + /// `Наличие аллергии` + String get has_alergy { + return Intl.message( + 'Наличие аллергии', + name: 'has_alergy', + desc: '', + args: [], + ); + } + + /// `Принимает лекарства` + String get takes_pills { + return Intl.message( + 'Принимает лекарства', + name: 'takes_pills', + desc: '', + args: [], + ); + } + + /// `Спортивные увлечения` + String get sport_hobbies { + return Intl.message( + 'Спортивные увлечения', + name: 'sport_hobbies', + desc: '', + args: [], + ); + } + + /// `Любимые продукты` + String get liked_groceries { + return Intl.message( + 'Любимые продукты', + name: 'liked_groceries', + desc: '', + args: [], + ); + } + + /// `Не любимые продукты` + String get not_liked_groceries { + return Intl.message( + 'Не любимые продукты', + name: 'not_liked_groceries', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1c1d0b2e..355d15b2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -210,5 +210,21 @@ "diet": "Диета", "deleted": "Удалена", "my_plans": "Мои планы", - "from": "от" + "from": "от", + "user_data": "Данные пользователя", + "general_info": "Общая информация", + "sex": "Пол", + "physical_fitness": "Физическая форма", + "height": "Рост", + "pressure": "Давление", + "upper": "Верхнее", + "lower": "Нижнее", + "has_hypo": "Склонность к гипотонии", + "has_hyper": "Склонность к гипертонии", + "other_info": "Прочая информация", + "has_alergy": "Наличие аллергии", + "takes_pills": "Принимает лекарства", + "sport_hobbies": "Спортивные увлечения", + "liked_groceries": "Любимые продукты", + "not_liked_groceries": "Не любимые продукты" } diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index b5f2d8a3..a1d3cd53 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -197,5 +197,18 @@ "wellness_type": "Улучшение самочувствия", "wellness_type_desc": "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.", "eating_behavior_type": "Формирование пищевого поведения", - "eating_behavior_type_desc": "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;" + "eating_behavior_type_desc": "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;", + "price": "Цена", + "my_diets": "Мои диеты", + "published": "Опубликованные", + "drafted": "Черновики", + "personal": "Персональные", + "edit": "Редактировать", + "toOffer": "Предложить", + "removeFromActive": "Снять с публикации", + "diet": "Диета", + "deleted": "Удалена", + "my_plans": "Мои планы", + "from": "от", + "user_data": "Данные пользователя" }