user details created

This commit is contained in:
Username1548
2024-12-25 17:51:50 +07:00
parent 0c981b0068
commit 3521a58c2f
23 changed files with 1382 additions and 220 deletions

View File

@@ -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<BottomNavigationBarWidget> {
void _onItemTapped(int index) {
void _onItemTapped(int index) async {
if (currentBottomNavBarIndex != index) {
setState(() {
currentBottomNavBarIndex = index;

View File

@@ -12,9 +12,12 @@ class ChatSelectCubit extends Cubit<ChatSelectState> {
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()),

View File

@@ -190,7 +190,10 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
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,

View File

@@ -151,7 +151,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
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<AboutDietScreen> {
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,

View File

@@ -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(

View File

@@ -292,7 +292,9 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
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,

View File

@@ -276,7 +276,10 @@ class _ProfileScreenState extends State<ProfileScreen> {
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<ProfileScreen> {
);
}
_charts() {
_charts() {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(16),

View File

@@ -8,12 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart';
class CircumferenceChartWidget extends StatefulWidget {
final List<CircumferenceModel> 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<CircumferenceChartWidget> {
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,
),
),
),
],
),
],

View File

@@ -8,12 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart';
class WeightChartWidget extends StatefulWidget {
final List<WeightModel> 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<WeightChartWidget> {
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,
),
),
),
],
),
],

View File

@@ -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<UserDetailsState> {
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));
}
}
}

View File

@@ -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<WeightModel>? weightPoints,
List<CircumferenceModel>? circumferencePoints,
}) = _UserDetailsState;
}

View File

@@ -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>(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<WeightModel>? get weightPoints => throw _privateConstructorUsedError;
List<CircumferenceModel>? 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<UserDetailsState> 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<WeightModel>? weightPoints,
List<CircumferenceModel>? 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<WeightModel>?,
circumferencePoints: freezed == circumferencePoints
? _value.circumferencePoints
: circumferencePoints // ignore: cast_nullable_to_non_nullable
as List<CircumferenceModel>?,
) 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<WeightModel>? weightPoints,
List<CircumferenceModel>? 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<WeightModel>?,
circumferencePoints: freezed == circumferencePoints
? _value._circumferencePoints
: circumferencePoints // ignore: cast_nullable_to_non_nullable
as List<CircumferenceModel>?,
));
}
}
/// @nodoc
class _$UserDetailsStateImpl implements _UserDetailsState {
_$UserDetailsStateImpl(
{this.isLoading = false,
this.error,
this.profileDetails,
final List<WeightModel>? weightPoints,
final List<CircumferenceModel>? circumferencePoints})
: _weightPoints = weightPoints,
_circumferencePoints = circumferencePoints;
@override
@JsonKey()
final bool isLoading;
@override
final String? error;
@override
final ProfileDetailsModel? profileDetails;
final List<WeightModel>? _weightPoints;
@override
List<WeightModel>? 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<CircumferenceModel>? _circumferencePoints;
@override
List<CircumferenceModel>? 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<WeightModel>? weightPoints,
final List<CircumferenceModel>? circumferencePoints}) =
_$UserDetailsStateImpl;
@override
bool get isLoading;
@override
String? get error;
@override
ProfileDetailsModel? get profileDetails;
@override
List<WeightModel>? get weightPoints;
@override
List<CircumferenceModel>? 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;
}

View File

@@ -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<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}',
),
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<UserDetailsCubit>().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;
});
},
);
}
}

View File

@@ -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<Widget> 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,
),
),
),
);
}
}