feat(profile): add blogPosts of user, charts

This commit is contained in:
mso7nueh
2024-08-30 16:36:22 +03:00
parent f994589baa
commit 01d6fea790
20 changed files with 582 additions and 164 deletions

View File

@@ -381,7 +381,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
Row(
children: [
Text(
widget.blogPost.createdAt.format('dd MMMM', 'ru'),
datum.createdAt.format('dd MMMM', 'ru'),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,

View File

@@ -497,8 +497,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
Row(
children: [
Text(
'99 августа',
// datum.createdAt.format('dd MMMM', 'ru'),
datum.createdAt.format('dd MMMM', 'ru'),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,

View File

@@ -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<Datum> childrenComments,
@JsonKey(name: "created_at") required DateTime createdAt,
}) = _Datum;
factory Datum.fromJson(Map<String, Object?> json) => _$DatumFromJson(json);

View File

@@ -247,6 +247,8 @@ mixin _$Datum {
String get body => throw _privateConstructorUsedError;
@JsonKey(name: "children_comments")
List<Datum> get childrenComments => throw _privateConstructorUsedError;
@JsonKey(name: "created_at")
DateTime get createdAt => throw _privateConstructorUsedError;
Map<String, dynamic> 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<Datum> childrenComments});
@JsonKey(name: "children_comments") List<Datum> 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<Datum>,
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<Datum> childrenComments});
@JsonKey(name: "children_comments") List<Datum> 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<Datum>,
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<Datum> childrenComments})
required final List<Datum> childrenComments,
@JsonKey(name: "created_at") required this.createdAt})
: _childrenComments = childrenComments;
factory _$DatumImpl.fromJson(Map<String, dynamic> 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<Datum> 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<Datum> childrenComments,
@JsonKey(name: "created_at") required final DateTime createdAt}) =
_$DatumImpl;
factory _Datum.fromJson(Map<String, dynamic> json) = _$DatumImpl.fromJson;
@@ -476,6 +500,9 @@ abstract class _Datum implements Datum {
@JsonKey(name: "children_comments")
List<Datum> get childrenComments;
@override
@JsonKey(name: "created_at")
DateTime get createdAt;
@override
@JsonKey(ignore: true)
_$$DatumImplCopyWith<_$DatumImpl> get copyWith =>
throw _privateConstructorUsedError;

View File

@@ -30,6 +30,7 @@ _$DatumImpl _$$DatumImplFromJson(Map<String, dynamic> json) => _$DatumImpl(
childrenComments: (json['children_comments'] as List<dynamic>)
.map((e) => Datum.fromJson(e as Map<String, dynamic>))
.toList(),
createdAt: DateTime.parse(json['created_at'] as String),
);
Map<String, dynamic> _$$DatumImplToJson(_$DatumImpl instance) =>
@@ -39,6 +40,7 @@ Map<String, dynamic> _$$DatumImplToJson(_$DatumImpl instance) =>
'author_profile_icon': instance.authorProfileIcon,
'body': instance.body,
'children_comments': instance.childrenComments,
'created_at': instance.createdAt.toIso8601String(),
};
_$LinksImpl _$$LinksImplFromJson(Map<String, dynamic> json) => _$LinksImpl(

View File

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

View File

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

View File

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

View File

@@ -168,8 +168,8 @@ _$ChartPointImpl _$$ChartPointImplFromJson(Map<String, dynamic> 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),
);

View File

@@ -13,6 +13,7 @@ class ProfileCubit extends Cubit<ProfileState> {
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<ProfileState> {
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()));

View File

@@ -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<FeedBlogPostModel> blogPosts,
}) = ProfileStateLoaded;
const factory ProfileState.error({

View File

@@ -20,7 +20,9 @@ mixin _$ProfileState {
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(ProfileModel profile) loaded,
required TResult Function(
ProfileModel profile, List<FeedBlogPostModel> blogPosts)
loaded,
required TResult Function(String errorMessage) error,
}) =>
throw _privateConstructorUsedError;
@@ -28,7 +30,8 @@ mixin _$ProfileState {
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ProfileModel profile)? loaded,
TResult? Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult? Function(String errorMessage)? error,
}) =>
throw _privateConstructorUsedError;
@@ -36,7 +39,8 @@ mixin _$ProfileState {
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(ProfileModel profile)? loaded,
TResult Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) =>
@@ -126,7 +130,9 @@ class _$ProfileStateImpl implements _ProfileState {
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(ProfileModel profile) loaded,
required TResult Function(
ProfileModel profile, List<FeedBlogPostModel> blogPosts)
loaded,
required TResult Function(String errorMessage) error,
}) {
return $default();
@@ -137,7 +143,8 @@ class _$ProfileStateImpl implements _ProfileState {
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ProfileModel profile)? loaded,
TResult? Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return $default?.call();
@@ -148,7 +155,8 @@ class _$ProfileStateImpl implements _ProfileState {
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(ProfileModel profile)? loaded,
TResult Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {
@@ -241,7 +249,9 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading {
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(ProfileModel profile) loaded,
required TResult Function(
ProfileModel profile, List<FeedBlogPostModel> blogPosts)
loaded,
required TResult Function(String errorMessage) error,
}) {
return loading();
@@ -252,7 +262,8 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading {
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ProfileModel profile)? loaded,
TResult? Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return loading?.call();
@@ -263,7 +274,8 @@ class _$ProfileStateLoadingImpl implements ProfileStateLoading {
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(ProfileModel profile)? loaded,
TResult Function(ProfileModel profile, List<FeedBlogPostModel> 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<FeedBlogPostModel> 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<FeedBlogPostModel>,
));
}
@@ -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<FeedBlogPostModel> blogPosts})
: _blogPosts = blogPosts;
@override
final ProfileModel profile;
final List<FeedBlogPostModel> _blogPosts;
@override
List<FeedBlogPostModel> 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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(ProfileModel profile) loaded,
required TResult Function(
ProfileModel profile, List<FeedBlogPostModel> 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 extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ProfileModel profile)? loaded,
TResult? Function(ProfileModel profile, List<FeedBlogPostModel> 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 extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(ProfileModel profile)? loaded,
TResult Function(ProfileModel profile, List<FeedBlogPostModel> 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<FeedBlogPostModel> blogPosts}) =
_$ProfileStateLoadedImpl;
ProfileModel get profile;
List<FeedBlogPostModel> get blogPosts;
@JsonKey(ignore: true)
_$$ProfileStateLoadedImplCopyWith<_$ProfileStateLoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
@@ -540,7 +576,9 @@ class _$ProfileStateErrorImpl implements ProfileStateError {
TResult when<TResult extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(ProfileModel profile) loaded,
required TResult Function(
ProfileModel profile, List<FeedBlogPostModel> blogPosts)
loaded,
required TResult Function(String errorMessage) error,
}) {
return error(errorMessage);
@@ -551,7 +589,8 @@ class _$ProfileStateErrorImpl implements ProfileStateError {
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(ProfileModel profile)? loaded,
TResult? Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult? Function(String errorMessage)? error,
}) {
return error?.call(errorMessage);
@@ -562,7 +601,8 @@ class _$ProfileStateErrorImpl implements ProfileStateError {
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(ProfileModel profile)? loaded,
TResult Function(ProfileModel profile, List<FeedBlogPostModel> blogPosts)?
loaded,
TResult Function(String errorMessage)? error,
required TResult orElse(),
}) {

View File

@@ -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<ProfileScreen> {
List<Circumference> chartData = [];
List<ChartPoint> chartPoints = [];
final List<String> chipsLabelList = ['Вес', 'Замеры', 'ИМТ'];
final List<String> chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ'];
int _selectedChipIndex = 0;
int _selectedIntervalChipIndex = 0;
@override
Widget build(BuildContext context) {
@@ -37,23 +42,111 @@ class _ProfileScreenState extends State<ProfileScreen> {
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<ProfileScreen> {
),
);
}
_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;
});
},
);
}
}

View File

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

View File

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

View File

@@ -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<ProfileModel.ChartPoint> 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<CartesianSeries<ProfileModel.ChartPoint, DateTime>> _getSplineSeries() {
return <CartesianSeries<ProfileModel.ChartPoint, DateTime>>[
SplineSeries<ProfileModel.ChartPoint, DateTime>(
SplineAreaSeries<ProfileModel.ChartPoint, DateTime>(
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,

View File

@@ -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<List<FeedBlogPostModel>> getBlogPostsByAuthorSlug({
required String slug,
}) async {
try {
return (await profileService.getBlogPostByAuthorSlug(
slug: slug,
))
.feedBlogPostList;
} catch (e) {
rethrow;
}
}
}

View File

@@ -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<Map<String, ChartPoint>> addWeight({
@Part() required int weight,
});
@GET('/api/blogPosts/search_by_author/{slug}')
Future<FeedBlogPostSettingsModel> getBlogPostByAuthorSlug({
@Path() required String slug,
});
}

View File

@@ -175,6 +175,40 @@ class _ProfileService implements ProfileService {
return _value;
}
@override
Future<FeedBlogPostSettingsModel> getBlogPostByAuthorSlug(
{required String slug}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<FeedBlogPostSettingsModel>(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<Map<String, dynamic>>(_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<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

View File

@@ -455,7 +455,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
Row(
children: [
Text(
widget.recipes.createdAt.format('dd MMMM', 'ru'),
datum.createdAt.format('dd MMMM', 'ru'),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,