diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 27d8901a..b2e827cf 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -381,7 +381,7 @@ class _BlogPostScreenState extends State { Row( children: [ Text( - widget.blogPost.createdAt.format('dd MMMM', 'ru'), + datum.createdAt.format('dd MMMM', 'ru'), style: TextStyle( color: Colors.grey.shade600, fontSize: 12, diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 42aa3bf4..643f6111 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -497,8 +497,7 @@ class _AboutDietScreenState extends State { Row( children: [ Text( - '99 августа', - // datum.createdAt.format('dd MMMM', 'ru'), + datum.createdAt.format('dd MMMM', 'ru'), style: TextStyle( color: Colors.grey.shade600, fontSize: 12, diff --git a/lib/features/feed/models/comment_model.dart b/lib/features/feed/models/comment_model.dart index 4c8aa268..52f163e4 100644 --- a/lib/features/feed/models/comment_model.dart +++ b/lib/features/feed/models/comment_model.dart @@ -22,6 +22,7 @@ class Datum with _$Datum { @JsonKey(name: "author_profile_icon") required String authorProfileIcon, @JsonKey(name: "body") required String body, @JsonKey(name: "children_comments") required List childrenComments, + @JsonKey(name: "created_at") required DateTime createdAt, }) = _Datum; factory Datum.fromJson(Map json) => _$DatumFromJson(json); diff --git a/lib/features/feed/models/comment_model.freezed.dart b/lib/features/feed/models/comment_model.freezed.dart index 3f088135..8e33c3bd 100644 --- a/lib/features/feed/models/comment_model.freezed.dart +++ b/lib/features/feed/models/comment_model.freezed.dart @@ -247,6 +247,8 @@ mixin _$Datum { String get body => throw _privateConstructorUsedError; @JsonKey(name: "children_comments") List get childrenComments => throw _privateConstructorUsedError; + @JsonKey(name: "created_at") + DateTime get createdAt => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -263,7 +265,8 @@ abstract class $DatumCopyWith<$Res> { @JsonKey(name: "author_name") String authorName, @JsonKey(name: "author_profile_icon") String authorProfileIcon, @JsonKey(name: "body") String body, - @JsonKey(name: "children_comments") List childrenComments}); + @JsonKey(name: "children_comments") List childrenComments, + @JsonKey(name: "created_at") DateTime createdAt}); } /// @nodoc @@ -284,6 +287,7 @@ class _$DatumCopyWithImpl<$Res, $Val extends Datum> Object? authorProfileIcon = null, Object? body = null, Object? childrenComments = null, + Object? createdAt = null, }) { return _then(_value.copyWith( id: null == id @@ -306,6 +310,10 @@ class _$DatumCopyWithImpl<$Res, $Val extends Datum> ? _value.childrenComments : childrenComments // ignore: cast_nullable_to_non_nullable as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, ) as $Val); } } @@ -322,7 +330,8 @@ abstract class _$$DatumImplCopyWith<$Res> implements $DatumCopyWith<$Res> { @JsonKey(name: "author_name") String authorName, @JsonKey(name: "author_profile_icon") String authorProfileIcon, @JsonKey(name: "body") String body, - @JsonKey(name: "children_comments") List childrenComments}); + @JsonKey(name: "children_comments") List childrenComments, + @JsonKey(name: "created_at") DateTime createdAt}); } /// @nodoc @@ -341,6 +350,7 @@ class __$$DatumImplCopyWithImpl<$Res> Object? authorProfileIcon = null, Object? body = null, Object? childrenComments = null, + Object? createdAt = null, }) { return _then(_$DatumImpl( id: null == id @@ -363,6 +373,10 @@ class __$$DatumImplCopyWithImpl<$Res> ? _value._childrenComments : childrenComments // ignore: cast_nullable_to_non_nullable as List, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, )); } } @@ -376,7 +390,8 @@ class _$DatumImpl implements _Datum { @JsonKey(name: "author_profile_icon") required this.authorProfileIcon, @JsonKey(name: "body") required this.body, @JsonKey(name: "children_comments") - required final List childrenComments}) + required final List childrenComments, + @JsonKey(name: "created_at") required this.createdAt}) : _childrenComments = childrenComments; factory _$DatumImpl.fromJson(Map json) => @@ -404,9 +419,13 @@ class _$DatumImpl implements _Datum { return EqualUnmodifiableListView(_childrenComments); } + @override + @JsonKey(name: "created_at") + final DateTime createdAt; + @override String toString() { - return 'Datum(id: $id, authorName: $authorName, authorProfileIcon: $authorProfileIcon, body: $body, childrenComments: $childrenComments)'; + return 'Datum(id: $id, authorName: $authorName, authorProfileIcon: $authorProfileIcon, body: $body, childrenComments: $childrenComments, createdAt: $createdAt)'; } @override @@ -421,7 +440,9 @@ class _$DatumImpl implements _Datum { other.authorProfileIcon == authorProfileIcon) && (identical(other.body, body) || other.body == body) && const DeepCollectionEquality() - .equals(other._childrenComments, _childrenComments)); + .equals(other._childrenComments, _childrenComments) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt)); } @JsonKey(ignore: true) @@ -432,7 +453,8 @@ class _$DatumImpl implements _Datum { authorName, authorProfileIcon, body, - const DeepCollectionEquality().hash(_childrenComments)); + const DeepCollectionEquality().hash(_childrenComments), + createdAt); @JsonKey(ignore: true) @override @@ -450,13 +472,15 @@ class _$DatumImpl implements _Datum { abstract class _Datum implements Datum { const factory _Datum( - {@JsonKey(name: "id") required final int id, - @JsonKey(name: "author_name") required final String authorName, - @JsonKey(name: "author_profile_icon") - required final String authorProfileIcon, - @JsonKey(name: "body") required final String body, - @JsonKey(name: "children_comments") - required final List childrenComments}) = _$DatumImpl; + {@JsonKey(name: "id") required final int id, + @JsonKey(name: "author_name") required final String authorName, + @JsonKey(name: "author_profile_icon") + required final String authorProfileIcon, + @JsonKey(name: "body") required final String body, + @JsonKey(name: "children_comments") + required final List childrenComments, + @JsonKey(name: "created_at") required final DateTime createdAt}) = + _$DatumImpl; factory _Datum.fromJson(Map json) = _$DatumImpl.fromJson; @@ -476,6 +500,9 @@ abstract class _Datum implements Datum { @JsonKey(name: "children_comments") List get childrenComments; @override + @JsonKey(name: "created_at") + DateTime get createdAt; + @override @JsonKey(ignore: true) _$$DatumImplCopyWith<_$DatumImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/features/feed/models/comment_model.g.dart b/lib/features/feed/models/comment_model.g.dart index ef471dc6..0c92d582 100644 --- a/lib/features/feed/models/comment_model.g.dart +++ b/lib/features/feed/models/comment_model.g.dart @@ -30,6 +30,7 @@ _$DatumImpl _$$DatumImplFromJson(Map json) => _$DatumImpl( childrenComments: (json['children_comments'] as List) .map((e) => Datum.fromJson(e as Map)) .toList(), + createdAt: DateTime.parse(json['created_at'] as String), ); Map _$$DatumImplToJson(_$DatumImpl instance) => @@ -39,6 +40,7 @@ Map _$$DatumImplToJson(_$DatumImpl instance) => 'author_profile_icon': instance.authorProfileIcon, 'body': instance.body, 'children_comments': instance.childrenComments, + 'created_at': instance.createdAt.toIso8601String(), }; _$LinksImpl _$$LinksImplFromJson(Map json) => _$LinksImpl( diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 010bf6d3..6f46ac9c 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; -import 'package:eatvio_app/core/constants/constants.dart'; +import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/locator_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -14,6 +13,7 @@ class MenuScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + centerTitle: false, title: Text('Меню'), ), body: SingleChildScrollView( @@ -83,6 +83,7 @@ class MenuScreen extends StatelessWidget { leading, height: 24, width: 24, + colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn), ), title: Text(title), trailing: Icon( diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index 6c72a1fa..62dfc065 100644 --- a/lib/features/profile/models/profile_model.dart +++ b/lib/features/profile/models/profile_model.dart @@ -100,8 +100,8 @@ class ChartPoint with _$ChartPoint { const factory ChartPoint({ @JsonKey(name: "id") required int id, @JsonKey(name: "user_id") required int userId, - @JsonKey(name: "weight") required int weight, - @JsonKey(name: "bmi") required String bmi, + @JsonKey(name: "weight") required double weight, + @JsonKey(name: "bmi") required double bmi, @JsonKey(name: "created_at") required DateTime createdAt, @JsonKey(name: "updated_at") required DateTime updatedAt, }) = _ChartPoint; diff --git a/lib/features/profile/models/profile_model.freezed.dart b/lib/features/profile/models/profile_model.freezed.dart index 0e5607b4..21954c15 100644 --- a/lib/features/profile/models/profile_model.freezed.dart +++ b/lib/features/profile/models/profile_model.freezed.dart @@ -2121,9 +2121,9 @@ mixin _$ChartPoint { @JsonKey(name: "user_id") int get userId => throw _privateConstructorUsedError; @JsonKey(name: "weight") - int get weight => throw _privateConstructorUsedError; + double get weight => throw _privateConstructorUsedError; @JsonKey(name: "bmi") - String get bmi => throw _privateConstructorUsedError; + double get bmi => throw _privateConstructorUsedError; @JsonKey(name: "created_at") DateTime get createdAt => throw _privateConstructorUsedError; @JsonKey(name: "updated_at") @@ -2144,8 +2144,8 @@ abstract class $ChartPointCopyWith<$Res> { $Res call( {@JsonKey(name: "id") int id, @JsonKey(name: "user_id") int userId, - @JsonKey(name: "weight") int weight, - @JsonKey(name: "bmi") String bmi, + @JsonKey(name: "weight") double weight, + @JsonKey(name: "bmi") double bmi, @JsonKey(name: "created_at") DateTime createdAt, @JsonKey(name: "updated_at") DateTime updatedAt}); } @@ -2182,11 +2182,11 @@ class _$ChartPointCopyWithImpl<$Res, $Val extends ChartPoint> weight: null == weight ? _value.weight : weight // ignore: cast_nullable_to_non_nullable - as int, + as double, bmi: null == bmi ? _value.bmi : bmi // ignore: cast_nullable_to_non_nullable - as String, + as double, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -2210,8 +2210,8 @@ abstract class _$$ChartPointImplCopyWith<$Res> $Res call( {@JsonKey(name: "id") int id, @JsonKey(name: "user_id") int userId, - @JsonKey(name: "weight") int weight, - @JsonKey(name: "bmi") String bmi, + @JsonKey(name: "weight") double weight, + @JsonKey(name: "bmi") double bmi, @JsonKey(name: "created_at") DateTime createdAt, @JsonKey(name: "updated_at") DateTime updatedAt}); } @@ -2246,11 +2246,11 @@ class __$$ChartPointImplCopyWithImpl<$Res> weight: null == weight ? _value.weight : weight // ignore: cast_nullable_to_non_nullable - as int, + as double, bmi: null == bmi ? _value.bmi : bmi // ignore: cast_nullable_to_non_nullable - as String, + as double, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -2285,10 +2285,10 @@ class _$ChartPointImpl implements _ChartPoint { final int userId; @override @JsonKey(name: "weight") - final int weight; + final double weight; @override @JsonKey(name: "bmi") - final String bmi; + final double bmi; @override @JsonKey(name: "created_at") final DateTime createdAt; @@ -2339,8 +2339,8 @@ abstract class _ChartPoint implements ChartPoint { const factory _ChartPoint( {@JsonKey(name: "id") required final int id, @JsonKey(name: "user_id") required final int userId, - @JsonKey(name: "weight") required final int weight, - @JsonKey(name: "bmi") required final String bmi, + @JsonKey(name: "weight") required final double weight, + @JsonKey(name: "bmi") required final double bmi, @JsonKey(name: "created_at") required final DateTime createdAt, @JsonKey(name: "updated_at") required final DateTime updatedAt}) = _$ChartPointImpl; @@ -2356,10 +2356,10 @@ abstract class _ChartPoint implements ChartPoint { int get userId; @override @JsonKey(name: "weight") - int get weight; + double get weight; @override @JsonKey(name: "bmi") - String get bmi; + double get bmi; @override @JsonKey(name: "created_at") DateTime get createdAt; diff --git a/lib/features/profile/models/profile_model.g.dart b/lib/features/profile/models/profile_model.g.dart index af1c1c4d..020ffca1 100644 --- a/lib/features/profile/models/profile_model.g.dart +++ b/lib/features/profile/models/profile_model.g.dart @@ -168,8 +168,8 @@ _$ChartPointImpl _$$ChartPointImplFromJson(Map json) => _$ChartPointImpl( id: (json['id'] as num).toInt(), userId: (json['user_id'] as num).toInt(), - weight: (json['weight'] as num).toInt(), - bmi: json['bmi'] as String, + weight: (json['weight'] as num).toDouble(), + bmi: (json['bmi'] as num).toDouble(), createdAt: DateTime.parse(json['created_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String), ); diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index 0a08e8de..d36ff4d5 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -13,6 +13,7 @@ class ProfileCubit extends Cubit { try { emit(ProfileState.loaded( profile: await profileRepository.getProfileBySlug(slug), + blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: slug), )); } catch (e) { emit(ProfileState.error(errorMessage: e.toString())); @@ -22,8 +23,10 @@ class ProfileCubit extends Cubit { getMyProfile() async { try { + final myProfile = await profileRepository.getMyProfile(); emit(ProfileState.loaded( - profile: await profileRepository.getMyProfile(), + profile: myProfile, + blogPosts: await profileRepository.getBlogPostsByAuthorSlug(slug: myProfile.user.slug), )); } catch (e) { emit(ProfileState.error(errorMessage: e.toString())); diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart index 1f06bfe4..f9f4eed1 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -11,6 +12,7 @@ abstract class ProfileState with _$ProfileState { const factory ProfileState.loaded({ required ProfileModel profile, + required List blogPosts, }) = ProfileStateLoaded; const factory ProfileState.error({ diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.freezed.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.freezed.dart index 2658d2e2..dbc10dfd 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_state.freezed.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_state.freezed.dart @@ -20,7 +20,9 @@ mixin _$ProfileState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(ProfileModel profile) loaded, + required TResult Function( + ProfileModel profile, List blogPosts) + loaded, required TResult Function(String errorMessage) error, }) => throw _privateConstructorUsedError; @@ -28,7 +30,8 @@ mixin _$ProfileState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ProfileModel profile)? loaded, + TResult? Function(ProfileModel profile, List blogPosts)? + loaded, TResult? Function(String errorMessage)? error, }) => throw _privateConstructorUsedError; @@ -36,7 +39,8 @@ mixin _$ProfileState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ProfileModel profile)? loaded, + TResult Function(ProfileModel profile, List blogPosts)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) => @@ -126,7 +130,9 @@ class _$ProfileStateImpl implements _ProfileState { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(ProfileModel profile) loaded, + required TResult Function( + ProfileModel profile, List blogPosts) + loaded, required TResult Function(String errorMessage) error, }) { return $default(); @@ -137,7 +143,8 @@ class _$ProfileStateImpl implements _ProfileState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ProfileModel profile)? loaded, + TResult? Function(ProfileModel profile, List blogPosts)? + loaded, TResult? Function(String errorMessage)? error, }) { return $default?.call(); @@ -148,7 +155,8 @@ class _$ProfileStateImpl implements _ProfileState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ProfileModel profile)? loaded, + TResult Function(ProfileModel profile, List blogPosts)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -241,7 +249,9 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(ProfileModel profile) loaded, + required TResult Function( + ProfileModel profile, List blogPosts) + loaded, required TResult Function(String errorMessage) error, }) { return loading(); @@ -252,7 +262,8 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ProfileModel profile)? loaded, + TResult? Function(ProfileModel profile, List blogPosts)? + loaded, TResult? Function(String errorMessage)? error, }) { return loading?.call(); @@ -263,7 +274,8 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ProfileModel profile)? loaded, + TResult Function(ProfileModel profile, List blogPosts)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { @@ -321,7 +333,7 @@ abstract class _$$ProfileStateLoadedImplCopyWith<$Res> { $Res Function(_$ProfileStateLoadedImpl) then) = __$$ProfileStateLoadedImplCopyWithImpl<$Res>; @useResult - $Res call({ProfileModel profile}); + $Res call({ProfileModel profile, List blogPosts}); $ProfileModelCopyWith<$Res> get profile; } @@ -338,12 +350,17 @@ class __$$ProfileStateLoadedImplCopyWithImpl<$Res> @override $Res call({ Object? profile = null, + Object? blogPosts = null, }) { return _then(_$ProfileStateLoadedImpl( profile: null == profile ? _value.profile : profile // ignore: cast_nullable_to_non_nullable as ProfileModel, + blogPosts: null == blogPosts + ? _value._blogPosts + : blogPosts // ignore: cast_nullable_to_non_nullable + as List, )); } @@ -359,14 +376,23 @@ class __$$ProfileStateLoadedImplCopyWithImpl<$Res> /// @nodoc class _$ProfileStateLoadedImpl implements ProfileStateLoaded { - const _$ProfileStateLoadedImpl({required this.profile}); + const _$ProfileStateLoadedImpl( + {required this.profile, required final List blogPosts}) + : _blogPosts = blogPosts; @override final ProfileModel profile; + final List _blogPosts; + @override + List get blogPosts { + if (_blogPosts is EqualUnmodifiableListView) return _blogPosts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_blogPosts); + } @override String toString() { - return 'ProfileState.loaded(profile: $profile)'; + return 'ProfileState.loaded(profile: $profile, blogPosts: $blogPosts)'; } @override @@ -374,11 +400,14 @@ class _$ProfileStateLoadedImpl implements ProfileStateLoaded { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProfileStateLoadedImpl && - (identical(other.profile, profile) || other.profile == profile)); + (identical(other.profile, profile) || other.profile == profile) && + const DeepCollectionEquality() + .equals(other._blogPosts, _blogPosts)); } @override - int get hashCode => Object.hash(runtimeType, profile); + int get hashCode => Object.hash( + runtimeType, profile, const DeepCollectionEquality().hash(_blogPosts)); @JsonKey(ignore: true) @override @@ -392,10 +421,12 @@ class _$ProfileStateLoadedImpl implements ProfileStateLoaded { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(ProfileModel profile) loaded, + required TResult Function( + ProfileModel profile, List blogPosts) + loaded, required TResult Function(String errorMessage) error, }) { - return loaded(profile); + return loaded(profile, blogPosts); } @override @@ -403,10 +434,11 @@ class _$ProfileStateLoadedImpl implements ProfileStateLoaded { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ProfileModel profile)? loaded, + TResult? Function(ProfileModel profile, List blogPosts)? + loaded, TResult? Function(String errorMessage)? error, }) { - return loaded?.call(profile); + return loaded?.call(profile, blogPosts); } @override @@ -414,12 +446,13 @@ class _$ProfileStateLoadedImpl implements ProfileStateLoaded { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ProfileModel profile)? loaded, + TResult Function(ProfileModel profile, List blogPosts)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { if (loaded != null) { - return loaded(profile); + return loaded(profile, blogPosts); } return orElse(); } @@ -463,10 +496,13 @@ class _$ProfileStateLoadedImpl implements ProfileStateLoaded { } abstract class ProfileStateLoaded implements ProfileState { - const factory ProfileStateLoaded({required final ProfileModel profile}) = + const factory ProfileStateLoaded( + {required final ProfileModel profile, + required final List blogPosts}) = _$ProfileStateLoadedImpl; ProfileModel get profile; + List get blogPosts; @JsonKey(ignore: true) _$$ProfileStateLoadedImplCopyWith<_$ProfileStateLoadedImpl> get copyWith => throw _privateConstructorUsedError; @@ -540,7 +576,9 @@ class _$ProfileStateErrorImpl implements ProfileStateError { TResult when( TResult Function() $default, { required TResult Function() loading, - required TResult Function(ProfileModel profile) loaded, + required TResult Function( + ProfileModel profile, List blogPosts) + loaded, required TResult Function(String errorMessage) error, }) { return error(errorMessage); @@ -551,7 +589,8 @@ class _$ProfileStateErrorImpl implements ProfileStateError { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ProfileModel profile)? loaded, + TResult? Function(ProfileModel profile, List blogPosts)? + loaded, TResult? Function(String errorMessage)? error, }) { return error?.call(errorMessage); @@ -562,7 +601,8 @@ class _$ProfileStateErrorImpl implements ProfileStateError { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ProfileModel profile)? loaded, + TResult Function(ProfileModel profile, List blogPosts)? + loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index e9e37c56..f85417fa 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -1,5 +1,6 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; @@ -21,6 +22,10 @@ class ProfileScreen extends StatefulWidget { class _ProfileScreenState extends State { List chartData = []; List chartPoints = []; + final List chipsLabelList = ['Вес', 'Замеры', 'ИМТ']; + final List chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ']; + int _selectedChipIndex = 0; + int _selectedIntervalChipIndex = 0; @override Widget build(BuildContext context) { @@ -37,23 +42,111 @@ class _ProfileScreenState extends State { chartData = state.profile.circumferences; chartPoints = state.profile.chartPoints; return Scaffold( - appBar: AppBar( - toolbarHeight: 0, - ), + backgroundColor: Colors.grey.shade200, + appBar: widget.author == null ? AppBar(toolbarHeight: 0) : AppBar(), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(left: 16, bottom: 32), - child: CircleAvatar( - radius: 32, - backgroundImage: NetworkImage(state.profile.user.profilePhotoUrl), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)), + color: Colors.white, + ), + child: Row( + children: [ + CircleAvatar( + radius: 32, + backgroundImage: NetworkImage(state.profile.user.profilePhotoUrl), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + state.profile.user.name, + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + if (state.profile.userDetails.additionalInfo.isNotEmpty) + Text( + state.profile.userDetails.additionalInfo, + style: TextStyle( + color: Colors.black.withOpacity(0.4), + height: 1, + ), + ), + ], + ), + ], ), ), - CircumferenceChartWidget(chartData: chartData), - WeightChartWidget(chartPoints: chartPoints), - ImtChartWidget(chartPoints: chartPoints), + if (widget.author == null) SizedBox(height: 8), + if (widget.author == null) + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + 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) { + if (_selectedChipIndex == 0) return WeightChartWidget(chartPoints: chartPoints); + if (_selectedChipIndex == 1) return CircumferenceChartWidget(chartData: chartData); + if (_selectedChipIndex == 2) + return ImtChartWidget(chartPoints: chartPoints); + else + return Container(); + }, + ), + 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), + ], + ), + ), + SizedBox(height: 16), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => BlogPostWidget(feedBlogPostModel: state.blogPosts[index]), + separatorBuilder: (context, index) => SizedBox(height: 8), + itemCount: state.blogPosts.length, + ), + SizedBox(height: 16), ], ), ), @@ -80,4 +173,55 @@ class _ProfileScreenState extends State { ), ); } + + _choiceIntervalChipWidget(int index) { + return ChoiceChip( + selectedColor: Colors.grey.withOpacity(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) { + 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/profile/presentation/widgets/circumference_chart_widget.dart b/lib/features/profile/presentation/widgets/circumference_chart_widget.dart index a4a6af7a..f4849df1 100644 --- a/lib/features/profile/presentation/widgets/circumference_chart_widget.dart +++ b/lib/features/profile/presentation/widgets/circumference_chart_widget.dart @@ -1,7 +1,7 @@ import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; class CircumferenceChartWidget extends StatelessWidget { final List chartData; @@ -13,35 +13,103 @@ class CircumferenceChartWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return SfCartesianChart( - margin: EdgeInsets.zero, - enableAxisAnimation: true, - enableSideBySideSeriesPlacement: true, - legend: const Legend( - isVisible: true, - opacity: 0.7, - position: LegendPosition.bottom, - ), - plotAreaBorderWidth: 0, - trackballBehavior: TrackballBehavior( - enable: true, - activationMode: ActivationMode.singleTap, - ), - primaryXAxis: DateTimeAxis( - dateFormat: DateFormat.MMMd('ru'), - axisLine: AxisLine( - width: 1, + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Text( + 'Грудь ${chartData.last.chestCircumference} см', + style: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + chartData.last.chestCircumference > chartData[chartData.length - 2].chestCircumference + ? Icon( + Icons.arrow_drop_up, + size: 18, + color: Colors.grey, + ) + : Icon( + Icons.arrow_drop_down, + size: 18, + color: Colors.grey, + ), + Text( + 'Талия ${chartData.last.waistCircumference} см', + style: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + chartData.last.waistCircumference > chartData[chartData.length - 2].waistCircumference + ? Icon( + Icons.arrow_drop_up, + size: 18, + color: Colors.grey, + ) + : Icon( + Icons.arrow_drop_down, + size: 18, + color: Colors.grey, + ), + Text( + 'Бёдра ${chartData.last.hipCircumference} см', + style: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + chartData.last.hipCircumference > chartData[chartData.length - 2].hipCircumference + ? Icon( + Icons.arrow_drop_up, + size: 18, + color: Colors.grey, + ) + : Icon( + Icons.arrow_drop_down, + size: 18, + color: Colors.grey, + ), + ], + ), ), - ), - primaryYAxis: NumericAxis( - labelFormat: '{value} см', - axisLine: AxisLine( - width: 1, + SfCartesianChart( + margin: EdgeInsets.zero, + enableAxisAnimation: true, + enableSideBySideSeriesPlacement: true, + legend: const Legend( + isVisible: true, + opacity: 0.7, + position: LegendPosition.bottom, + ), + plotAreaBorderWidth: 0, + trackballBehavior: TrackballBehavior( + enable: true, + activationMode: ActivationMode.singleTap, + ), + primaryXAxis: DateTimeCategoryAxis( + dateFormat: DateFormat.MMMd('ru'), + axisLine: AxisLine( + width: 1, + ), + ), + primaryYAxis: NumericAxis( + labelFormat: '{value} см', + axisLine: AxisLine( + width: 1, + ), + majorTickLines: MajorTickLines(size: 0), + ), + series: _getSplineSeries(), + tooltipBehavior: TooltipBehavior(enable: true), ), - majorTickLines: MajorTickLines(size: 0), - ), - series: _getSplineSeries(), - tooltipBehavior: TooltipBehavior(enable: true), + ], ); } diff --git a/lib/features/profile/presentation/widgets/imt_chart_widget.dart b/lib/features/profile/presentation/widgets/imt_chart_widget.dart index 275a5810..0dc06899 100644 --- a/lib/features/profile/presentation/widgets/imt_chart_widget.dart +++ b/lib/features/profile/presentation/widgets/imt_chart_widget.dart @@ -13,35 +13,61 @@ class ImtChartWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return SfCartesianChart( - margin: EdgeInsets.zero, - enableAxisAnimation: true, - enableSideBySideSeriesPlacement: true, - legend: const Legend( - isVisible: true, - opacity: 0.7, - position: LegendPosition.bottom, - ), - plotAreaBorderWidth: 0, - trackballBehavior: TrackballBehavior( - enable: true, - activationMode: ActivationMode.singleTap, - ), - primaryXAxis: DateTimeAxis( - dateFormat: DateFormat.MMMd('ru'), - axisLine: AxisLine( - width: 1, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Text( + '${chartPoints.last.bmi.toStringAsFixed(2)} кг/м²', + style: TextStyle( + color: Colors.black, + fontSize: 48, + fontWeight: FontWeight.w500, + ), + ), + chartPoints.last.bmi > chartPoints[chartPoints.length - 2].bmi + ? Icon( + Icons.arrow_drop_up, + size: 48, + color: Colors.grey, + ) + : Icon( + Icons.arrow_drop_down, + size: 48, + color: Colors.grey, + ), + ], + ), ), - ), - primaryYAxis: NumericAxis( - labelFormat: '{value} кг', - axisLine: AxisLine( - width: 1, + SfCartesianChart( + margin: EdgeInsets.zero, + enableAxisAnimation: true, + enableSideBySideSeriesPlacement: true, + plotAreaBorderWidth: 0, + trackballBehavior: TrackballBehavior( + enable: true, + activationMode: ActivationMode.singleTap, + ), + primaryXAxis: DateTimeCategoryAxis( + dateFormat: DateFormat.MMMd('ru'), + axisLine: AxisLine( + width: 1, + ), + ), + primaryYAxis: NumericAxis( + labelFormat: '{value} кг/м²', + axisLine: AxisLine( + width: 1, + ), + majorTickLines: MajorTickLines(size: 0), + ), + series: _getSplineSeries(), + tooltipBehavior: TooltipBehavior(enable: true), ), - majorTickLines: MajorTickLines(size: 0), - ), - series: _getSplineSeries(), - tooltipBehavior: TooltipBehavior(enable: true), + ], ); } @@ -62,7 +88,7 @@ class ImtChartWidget extends StatelessWidget { color: Colors.blue, name: 'ИМТ', xValueMapper: (ProfileModel.ChartPoint value, int index) => value.createdAt, - yValueMapper: (ProfileModel.ChartPoint value, int index) => double.parse(value.bmi), + yValueMapper: (ProfileModel.ChartPoint value, int index) => value.bmi, ), ]; } diff --git a/lib/features/profile/presentation/widgets/weight_chart_widget.dart b/lib/features/profile/presentation/widgets/weight_chart_widget.dart index eabad3ec..f799cc0d 100644 --- a/lib/features/profile/presentation/widgets/weight_chart_widget.dart +++ b/lib/features/profile/presentation/widgets/weight_chart_widget.dart @@ -1,7 +1,8 @@ +import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart' as ProfileModel; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; class WeightChartWidget extends StatelessWidget { final List chartPoints; @@ -13,53 +14,103 @@ class WeightChartWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return SfCartesianChart( - margin: EdgeInsets.zero, - enableAxisAnimation: true, - enableSideBySideSeriesPlacement: true, - legend: const Legend( - isVisible: true, - opacity: 0.7, - position: LegendPosition.bottom, - ), - plotAreaBorderWidth: 0, - trackballBehavior: TrackballBehavior( - enable: true, - activationMode: ActivationMode.singleTap, - ), - primaryXAxis: DateTimeAxis( - dateFormat: DateFormat.MMMd('ru'), - axisLine: AxisLine( - width: 1, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Text( + '${chartPoints.last.weight} кг', + style: TextStyle( + color: Colors.black, + fontSize: 48, + fontWeight: FontWeight.w500, + ), + ), + chartPoints.last.weight > chartPoints[chartPoints.length - 2].weight + ? Icon( + Icons.arrow_drop_up, + size: 48, + color: Colors.grey.withOpacity(0.4), + ) + : Icon( + Icons.arrow_drop_down, + size: 48, + color: Colors.grey.withOpacity(0.4), + ), + ], + ), ), - ), - primaryYAxis: NumericAxis( - labelFormat: '{value} кг', - axisLine: AxisLine( - width: 1, + SfCartesianChart( + margin: EdgeInsets.symmetric(horizontal: 8), + enableAxisAnimation: true, + plotAreaBorderWidth: 0, + trackballBehavior: TrackballBehavior( + enable: true, + activationMode: ActivationMode.singleTap, + tooltipDisplayMode: TrackballDisplayMode.groupAllPoints, + tooltipSettings: InteractiveTooltip(), + lineType: TrackballLineType.vertical, + ), + primaryXAxis: DateTimeCategoryAxis( + initialVisibleMinimum: chartPoints.first.createdAt, + initialVisibleMaximum: chartPoints.last.createdAt, + tickPosition: TickPosition.inside, + majorTickLines: MajorTickLines( + size: 0, + ), + edgeLabelPlacement: EdgeLabelPlacement.shift, + dateFormat: DateFormat.MMMd('ru'), + labelStyle: TextStyle( + color: Colors.grey, + ), + majorGridLines: MajorGridLines(dashArray: [1], color: Colors.grey.withOpacity(0.3)), + axisLine: AxisLine( + color: Colors.grey.withOpacity(0.3), + width: 0.4, + ), + ), + primaryYAxis: NumericAxis( + isVisible: false, + labelFormat: '{value} кг', + majorGridLines: MajorGridLines(dashArray: [1], color: Colors.grey), + axisLine: AxisLine( + width: 1, + ), + majorTickLines: MajorTickLines(size: 0), + ), + series: _getSplineSeries(), + tooltipBehavior: TooltipBehavior(enable: true), ), - majorTickLines: MajorTickLines(size: 0), - ), - series: _getSplineSeries(), - tooltipBehavior: TooltipBehavior(enable: true), + ], ); } List> _getSplineSeries() { return >[ - SplineSeries( + SplineAreaSeries( markerSettings: MarkerSettings( isVisible: true, - color: Colors.blue, - height: 12, - width: 12, + color: appTheme.blueA400, + height: 8, + width: 8, ), dataLabelSettings: DataLabelSettings( labelAlignment: ChartDataLabelAlignment.top, isVisible: true, ), dataSource: chartPoints, - color: Colors.blue, + borderColor: appTheme.blueA400, + gradient: LinearGradient( + colors: [ + appTheme.blueA400.withOpacity(0.14), + Colors.transparent, + ], + begin: Alignment.topCenter, + end: Alignment(0, 0.6), + ), name: 'Вес', xValueMapper: (ProfileModel.ChartPoint value, int index) => value.createdAt, yValueMapper: (ProfileModel.ChartPoint value, int index) => value.weight, diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index e859c8d6..d03e39d8 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/services/profile_service.dart'; @@ -53,4 +54,17 @@ class ProfileRepository { rethrow; } } + + Future> getBlogPostsByAuthorSlug({ + required String slug, + }) async { + try { + return (await profileService.getBlogPostByAuthorSlug( + slug: slug, + )) + .feedBlogPostList; + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/profile/services/profile_service.dart b/lib/features/profile/services/profile_service.dart index 9966a82d..2ec6cee0 100644 --- a/lib/features/profile/services/profile_service.dart +++ b/lib/features/profile/services/profile_service.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/feed/models/feed_blog_post_settings_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:retrofit/retrofit.dart'; @@ -27,4 +28,9 @@ abstract class ProfileService { Future> addWeight({ @Part() required int weight, }); + + @GET('/api/blogPosts/search_by_author/{slug}') + Future getBlogPostByAuthorSlug({ + @Path() required String slug, + }); } diff --git a/lib/features/profile/services/profile_service.g.dart b/lib/features/profile/services/profile_service.g.dart index 96b65b4d..ab9461bf 100644 --- a/lib/features/profile/services/profile_service.g.dart +++ b/lib/features/profile/services/profile_service.g.dart @@ -175,6 +175,40 @@ class _ProfileService implements ProfileService { return _value; } + @override + Future getBlogPostByAuthorSlug( + {required String slug}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/blogPosts/search_by_author/${slug}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late FeedBlogPostSettingsModel _value; + try { + _value = FeedBlogPostSettingsModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/features/recipes/presentation/pages/about_recipes_screen.dart b/lib/features/recipes/presentation/pages/about_recipes_screen.dart index 6955aa72..fdb23673 100644 --- a/lib/features/recipes/presentation/pages/about_recipes_screen.dart +++ b/lib/features/recipes/presentation/pages/about_recipes_screen.dart @@ -455,7 +455,7 @@ class _AboutRecipesScreenState extends State { Row( children: [ Text( - widget.recipes.createdAt.format('dd MMMM', 'ru'), + datum.createdAt.format('dd MMMM', 'ru'), style: TextStyle( color: Colors.grey.shade600, fontSize: 12,