From 6620ed5337bbaf1cacabb2cdbc53543150a867f1 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Thu, 8 Aug 2024 16:40:44 +0300 Subject: [PATCH 1/4] feat(feed): refactor creating post --- .../pages/blog_post_create_screen.dart | 70 +- .../bloc/diets_cubit/diets_cubit.dart | 18 + .../bloc/diets_cubit/diets_state.dart | 19 + .../bloc/diets_cubit/diets_state.freezed.dart | 620 ++++++++++++++++++ .../feed/presentation/pages/feed_screen.dart | 46 +- .../presentation/widgets/diets_widget.dart | 187 ++++++ lib/locator_service.dart | 2 + lib/main.dart | 2 + 8 files changed, 908 insertions(+), 56 deletions(-) create mode 100644 lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart create mode 100644 lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart create mode 100644 lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart create mode 100644 lib/features/feed/presentation/widgets/diets_widget.dart diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index 226bfa1e..eac8aa48 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -49,7 +49,7 @@ class _BlogPostCreateScreenState extends State { _buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) { return Scaffold( - resizeToAvoidBottomInset: false, + resizeToAvoidBottomInset: true, appBar: AppBar(), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: SizedBox( @@ -62,8 +62,12 @@ class _BlogPostCreateScreenState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), - backgroundColor: appTheme.blueA400, - onPressed: () => _submitForm(context, state.title!.text, state.description!.text, state.youtubeURL!.text), + backgroundColor: state.image != null || state.title!.text.isNotEmpty || state.description!.text.isNotEmpty || state.youtubeURL!.text.isNotEmpty + ? appTheme.blueA400 + : Colors.grey, + onPressed: state.image != null || state.title!.text.isNotEmpty || state.description!.text.isNotEmpty || state.youtubeURL!.text.isNotEmpty + ? () => _submitForm(context, state.title!.text, state.description!.text, state.youtubeURL!.text) + : null, label: Text( 'Создать', style: theme.textTheme.titleSmall, @@ -75,32 +79,29 @@ class _BlogPostCreateScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (state.youtubeURL!.text.isEmpty) Text('Изображение поста'), + if (state.youtubeURL!.text.isEmpty) SizedBox(height: 8), + if (state.youtubeURL!.text.isEmpty) _buildAddPhoto(context, state), + SizedBox(height: 16), Text('Заголовок'), SizedBox(height: 8), CustomTextFormField( controller: state.title, - validator: _validateTitle, ), SizedBox(height: 16), Text('Описание'), SizedBox(height: 8), CustomTextFormField( controller: state.description, - validator: _validateDescription, maxLines: 6, minLines: 1, ), SizedBox(height: 16), - Text('Ссылка на видео'), - SizedBox(height: 8), - CustomTextFormField( + if (state.image == null) Text('Ссылка на видео'), + if (state.image == null) SizedBox(height: 8), + if (state.image == null) CustomTextFormField( controller: state.youtubeURL, - validator: _validateYouTubeLink, ), - SizedBox(height: 16), - Text('Изображение поста'), - SizedBox(height: 8), - _buildAddPhoto(context, state), SizedBox(height: 80), ], ), @@ -110,20 +111,7 @@ class _BlogPostCreateScreenState extends State { } _submitForm(BuildContext context, String title, String description, String youtubeURL) async { - String? titleError = _validateTitle(title); - String? descriptionError = _validateDescription(description); - String? youtubeURLError = _validateYouTubeLink(youtubeURL); - - if (titleError != null || descriptionError != null || youtubeURLError != null) { - // Display error messages or handle invalid input as needed - // For example, show a snackbar with the error message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(titleError ?? descriptionError ?? youtubeURLError!)), - ); - } else { - // Proceed with form submission - context.read().add(CreateBlogPost()); - } + context.read().add(CreateBlogPost()); } _buildAddPhoto(BuildContext context, BlogPostCreateLoadedState state) { @@ -179,31 +167,3 @@ addImage(BuildContext context, bool isLoading) { }; } } - -String? _validateTitle(value) { - if (value!.isEmpty) { - return 'Пожалуйста, введите заголовок'; - } - return null; -} - -String? _validateDescription(value) { - if (value!.isEmpty) { - return 'Пожалуйста, введите описание'; - } - return null; -} - -String? _validateYouTubeLink(value) { - if (value!.isEmpty) { - return 'Пожалуйста, введите ссылку'; - } else if (!_isValidUrl(value)) { - return 'Пожалуйста, введите корректную ссылку'; - } - return null; -} - -bool _isValidUrl(String url) { - final RegExp urlRegExp = new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$"); - return urlRegExp.hasMatch(url); -} diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart new file mode 100644 index 00000000..858bbe9e --- /dev/null +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart @@ -0,0 +1,18 @@ +import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; +import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DietsCubit extends Cubit { + final FeedRepository feedRepository; + + DietsCubit({required this.feedRepository}) : super(const DietsState.loading()); + + loadDiets() async { + try { + final diets = await feedRepository.getDiets(); + emit(DietsState.loaded(diets: diets)); + } catch (e) { + emit(DietsState.error(errorMessage: e.toString())); + } + } +} diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart new file mode 100644 index 00000000..3cd342e8 --- /dev/null +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.dart @@ -0,0 +1,19 @@ +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'diets_state.freezed.dart'; + +@freezed +abstract class DietsState with _$DietsState { + const factory DietsState() = _DietsState; + + const factory DietsState.loading() = DietsStateLoading; + + const factory DietsState.loaded({ + required DietsPagination diets, + }) = DietsStateLoaded; + + const factory DietsState.error({ + required String errorMessage, + }) = DietsStateError; +} diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart new file mode 100644 index 00000000..adff30f5 --- /dev/null +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_state.freezed.dart @@ -0,0 +1,620 @@ +// 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 'diets_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$DietsState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(DietsPagination diets) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(DietsPagination diets)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(DietsPagination diets)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_DietsState value) $default, { + required TResult Function(DietsStateLoading value) loading, + required TResult Function(DietsStateLoaded value) loaded, + required TResult Function(DietsStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_DietsState value)? $default, { + TResult? Function(DietsStateLoading value)? loading, + TResult? Function(DietsStateLoaded value)? loaded, + TResult? Function(DietsStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_DietsState value)? $default, { + TResult Function(DietsStateLoading value)? loading, + TResult Function(DietsStateLoaded value)? loaded, + TResult Function(DietsStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DietsStateCopyWith<$Res> { + factory $DietsStateCopyWith( + DietsState value, $Res Function(DietsState) then) = + _$DietsStateCopyWithImpl<$Res, DietsState>; +} + +/// @nodoc +class _$DietsStateCopyWithImpl<$Res, $Val extends DietsState> + implements $DietsStateCopyWith<$Res> { + _$DietsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$DietsStateImplCopyWith<$Res> { + factory _$$DietsStateImplCopyWith( + _$DietsStateImpl value, $Res Function(_$DietsStateImpl) then) = + __$$DietsStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$DietsStateImplCopyWithImpl<$Res> + extends _$DietsStateCopyWithImpl<$Res, _$DietsStateImpl> + implements _$$DietsStateImplCopyWith<$Res> { + __$$DietsStateImplCopyWithImpl( + _$DietsStateImpl _value, $Res Function(_$DietsStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$DietsStateImpl implements _DietsState { + const _$DietsStateImpl(); + + @override + String toString() { + return 'DietsState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$DietsStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(DietsPagination diets) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(DietsPagination diets)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(DietsPagination diets)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_DietsState value) $default, { + required TResult Function(DietsStateLoading value) loading, + required TResult Function(DietsStateLoaded value) loaded, + required TResult Function(DietsStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_DietsState value)? $default, { + TResult? Function(DietsStateLoading value)? loading, + TResult? Function(DietsStateLoaded value)? loaded, + TResult? Function(DietsStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_DietsState value)? $default, { + TResult Function(DietsStateLoading value)? loading, + TResult Function(DietsStateLoaded value)? loaded, + TResult Function(DietsStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _DietsState implements DietsState { + const factory _DietsState() = _$DietsStateImpl; +} + +/// @nodoc +abstract class _$$DietsStateLoadingImplCopyWith<$Res> { + factory _$$DietsStateLoadingImplCopyWith(_$DietsStateLoadingImpl value, + $Res Function(_$DietsStateLoadingImpl) then) = + __$$DietsStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$DietsStateLoadingImplCopyWithImpl<$Res> + extends _$DietsStateCopyWithImpl<$Res, _$DietsStateLoadingImpl> + implements _$$DietsStateLoadingImplCopyWith<$Res> { + __$$DietsStateLoadingImplCopyWithImpl(_$DietsStateLoadingImpl _value, + $Res Function(_$DietsStateLoadingImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$DietsStateLoadingImpl implements DietsStateLoading { + const _$DietsStateLoadingImpl(); + + @override + String toString() { + return 'DietsState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$DietsStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(DietsPagination diets) loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(DietsPagination diets)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(DietsPagination diets)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_DietsState value) $default, { + required TResult Function(DietsStateLoading value) loading, + required TResult Function(DietsStateLoaded value) loaded, + required TResult Function(DietsStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_DietsState value)? $default, { + TResult? Function(DietsStateLoading value)? loading, + TResult? Function(DietsStateLoaded value)? loaded, + TResult? Function(DietsStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_DietsState value)? $default, { + TResult Function(DietsStateLoading value)? loading, + TResult Function(DietsStateLoaded value)? loaded, + TResult Function(DietsStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class DietsStateLoading implements DietsState { + const factory DietsStateLoading() = _$DietsStateLoadingImpl; +} + +/// @nodoc +abstract class _$$DietsStateLoadedImplCopyWith<$Res> { + factory _$$DietsStateLoadedImplCopyWith(_$DietsStateLoadedImpl value, + $Res Function(_$DietsStateLoadedImpl) then) = + __$$DietsStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({DietsPagination diets}); + + $DietsPaginationCopyWith<$Res> get diets; +} + +/// @nodoc +class __$$DietsStateLoadedImplCopyWithImpl<$Res> + extends _$DietsStateCopyWithImpl<$Res, _$DietsStateLoadedImpl> + implements _$$DietsStateLoadedImplCopyWith<$Res> { + __$$DietsStateLoadedImplCopyWithImpl(_$DietsStateLoadedImpl _value, + $Res Function(_$DietsStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? diets = null, + }) { + return _then(_$DietsStateLoadedImpl( + diets: null == diets + ? _value.diets + : diets // ignore: cast_nullable_to_non_nullable + as DietsPagination, + )); + } + + @override + @pragma('vm:prefer-inline') + $DietsPaginationCopyWith<$Res> get diets { + return $DietsPaginationCopyWith<$Res>(_value.diets, (value) { + return _then(_value.copyWith(diets: value)); + }); + } +} + +/// @nodoc + +class _$DietsStateLoadedImpl implements DietsStateLoaded { + const _$DietsStateLoadedImpl({required this.diets}); + + @override + final DietsPagination diets; + + @override + String toString() { + return 'DietsState.loaded(diets: $diets)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DietsStateLoadedImpl && + (identical(other.diets, diets) || other.diets == diets)); + } + + @override + int get hashCode => Object.hash(runtimeType, diets); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DietsStateLoadedImplCopyWith<_$DietsStateLoadedImpl> get copyWith => + __$$DietsStateLoadedImplCopyWithImpl<_$DietsStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(DietsPagination diets) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(diets); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(DietsPagination diets)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(diets); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(DietsPagination diets)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(diets); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_DietsState value) $default, { + required TResult Function(DietsStateLoading value) loading, + required TResult Function(DietsStateLoaded value) loaded, + required TResult Function(DietsStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_DietsState value)? $default, { + TResult? Function(DietsStateLoading value)? loading, + TResult? Function(DietsStateLoaded value)? loaded, + TResult? Function(DietsStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_DietsState value)? $default, { + TResult Function(DietsStateLoading value)? loading, + TResult Function(DietsStateLoaded value)? loaded, + TResult Function(DietsStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class DietsStateLoaded implements DietsState { + const factory DietsStateLoaded({required final DietsPagination diets}) = + _$DietsStateLoadedImpl; + + DietsPagination get diets; + @JsonKey(ignore: true) + _$$DietsStateLoadedImplCopyWith<_$DietsStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DietsStateErrorImplCopyWith<$Res> { + factory _$$DietsStateErrorImplCopyWith(_$DietsStateErrorImpl value, + $Res Function(_$DietsStateErrorImpl) then) = + __$$DietsStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$DietsStateErrorImplCopyWithImpl<$Res> + extends _$DietsStateCopyWithImpl<$Res, _$DietsStateErrorImpl> + implements _$$DietsStateErrorImplCopyWith<$Res> { + __$$DietsStateErrorImplCopyWithImpl( + _$DietsStateErrorImpl _value, $Res Function(_$DietsStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$DietsStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$DietsStateErrorImpl implements DietsStateError { + const _$DietsStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'DietsState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DietsStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DietsStateErrorImplCopyWith<_$DietsStateErrorImpl> get copyWith => + __$$DietsStateErrorImplCopyWithImpl<_$DietsStateErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(DietsPagination diets) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(DietsPagination diets)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(DietsPagination diets)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_DietsState value) $default, { + required TResult Function(DietsStateLoading value) loading, + required TResult Function(DietsStateLoaded value) loaded, + required TResult Function(DietsStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_DietsState value)? $default, { + TResult? Function(DietsStateLoading value)? loading, + TResult? Function(DietsStateLoaded value)? loaded, + TResult? Function(DietsStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_DietsState value)? $default, { + TResult Function(DietsStateLoading value)? loading, + TResult Function(DietsStateLoaded value)? loaded, + TResult Function(DietsStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class DietsStateError implements DietsState { + const factory DietsStateError({required final String errorMessage}) = + _$DietsStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$DietsStateErrorImplCopyWith<_$DietsStateErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index ab7bf32d..f3c32cd6 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -1,8 +1,11 @@ +import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; +import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart'; +import 'package:eatvio_app/features/feed/presentation/widgets/diets_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/recipes_widget.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/cupertino.dart'; @@ -59,14 +62,55 @@ class _FeedScreenState extends State { builder: (BuildContext context) { if (_selectedChipIndex == 0) return _buildAllBlogPosts(); - else + else if (_selectedChipIndex == 1) return _buildAllRecipes(); + else + return _buildDiets(); }, ), ), ); } + _buildDiets() { + return RefreshIndicator( + onRefresh: () => context.read().loadDiets(), + child: BlocBuilder( + builder: (BuildContext context, DietsState state) { + if (state is DietsStateLoaded) { + return ListView.separated( + padding: EdgeInsets.symmetric(vertical: 8), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return DietsWidget(datum: state.diets.data[index]); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 8); + }, + itemCount: state.diets.data.length, + ); + } else if (state is DietsStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text(state.errorMessage), + ), + ), + ); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), + ); + } + _buildAllBlogPosts() { return RefreshIndicator( onRefresh: () => context.read().loadFeedBlogPostList(), diff --git a/lib/features/feed/presentation/widgets/diets_widget.dart b/lib/features/feed/presentation/widgets/diets_widget.dart new file mode 100644 index 00000000..58dc0db0 --- /dev/null +++ b/lib/features/feed/presentation/widgets/diets_widget.dart @@ -0,0 +1,187 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dismissible_page/dismissible_page.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class DietsWidget extends StatefulWidget { + final Datum datum; + + const DietsWidget({super.key, required this.datum}); + + @override + State createState() => _DietsWidgetState(); +} + +class _DietsWidgetState extends State { + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: widget.datum), + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + child: GestureDetector( + onTap: () { + context.pushTransparentRoute( + rootNavigator: true, + PhotoViewScreen( + imageUrls: [baseUrl + '/storage/' + widget.datum.image], + index: 0, + ), + ); + }, + child: Hero( + tag: baseUrl + '/storage/' + widget.datum.image, + child: CachedNetworkImage( + imageUrl: baseUrl + '/storage/' + widget.datum.image, + ), + ), + ), + ), + Positioned( + left: 8, + bottom: 8, + child: Wrap( + runSpacing: 8, + children: [ + _iconButton(Icons.fastfood, '139.9 ккал'), + SizedBox(width: 8), + _iconButton(Icons.timer_rounded, widget.datum.state), + SizedBox(width: 8), + _iconButton(Icons.fast_forward_rounded, widget.datum.duration.toString()), + ], + ), + ), + Positioned( + left: 8, + top: 8, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + Icon( + Icons.favorite_rounded, + size: 16, + color: Colors.black, + ), + SizedBox(width: 4), + Text( + '0', + style: TextStyle( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + Positioned( + right: 8, + top: 8, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + Icon( + Icons.visibility_rounded, + size: 16, + color: Colors.black, + ), + SizedBox(width: 4), + Text( + '0', + style: TextStyle( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ], + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + widget.datum.name, + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ), + SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + widget.datum.details, + style: TextStyle( + color: Colors.black87, + ), + ), + ), + SizedBox(height: 12), + ], + ), + ), + ); + } + + _iconButton(IconData icon, [String? text]) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(16), + ), + height: 32, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 16, + color: Colors.black, + ), + if (text != null) SizedBox(width: 8), + if (text != null) + Text( + text, + style: TextStyle( + fontSize: 12, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } +} diff --git a/lib/locator_service.dart b/lib/locator_service.dart index 5ca320fe..f08807e0 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -15,6 +15,7 @@ import 'package:eatvio_app/features/chat/services/chat_service.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_bloc.dart'; import 'package:eatvio_app/features/diary/repositories/diary_repository.dart'; import 'package:eatvio_app/features/diary/services/diary_service.dart'; +import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; @@ -36,6 +37,7 @@ Future init() async { sl.registerFactory(() => ChatRoomCubit(chatRepository: sl(), initializationCubit: sl())); sl.registerFactory(() => FeedCubit(feedRepository: sl())); sl.registerFactory(() => RecipesCubit(feedRepository: sl())); + sl.registerFactory(() => DietsCubit(feedRepository: sl())); sl.registerFactory(() => BlogPostCubit(blogPostRepository: sl())); sl.registerFactory(() => DiaryBloc(diaryRepository: sl())); sl.registerFactory(() => CalendarCubit(calendarRepository: sl())); diff --git a/lib/main.dart b/lib/main.dart index fdc96e6a..d07b6ae9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.da import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_bloc.dart'; +import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/locator_service.dart' as di; @@ -51,6 +52,7 @@ class MyApp extends StatelessWidget { BlocProvider(create: (context) => ThemeBloc(ThemeState(themeType: PrefUtils().getThemeData()))), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => sl()..loadRecipesList()), + BlocProvider(create: (context) => sl()..loadDiets()), BlocProvider(create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent())), BlocProvider(create: (context) => sl()..add(LoadDiary())), BlocProvider( From 4af39985ee1123601495d5b1fab12c992d239978 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 9 Aug 2024 15:10:27 +0300 Subject: [PATCH 2/4] feat(feed): about diets screen --- lib/app_router.dart | 9 ++ .../presentation/pages/about_diet_screen.dart | 94 +++++++++++++++++++ .../feed/presentation/pages/feed_screen.dart | 4 +- .../presentation/widgets/diets_widget.dart | 4 +- .../pages/about_recipes_screen.dart | 15 ++- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 lib/features/diets/presentation/pages/about_diet_screen.dart diff --git a/lib/app_router.dart b/lib/app_router.dart index 96851119..752d0ab5 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -8,6 +8,8 @@ import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart'; +import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart'; @@ -92,6 +94,13 @@ class AppRouter { recipes: state.extra as Recipes, ), ), + GoRoute( + path: '/about-diet-screen', + name: 'aboutDietScreen', + builder: (context, state) => AboutDietScreen( + datum: state.extra as Datum, + ), + ), GoRoute( path: '/blog-post-create-screen', name: 'blogPostCreateScreen', diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart new file mode 100644 index 00000000..916085ce --- /dev/null +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -0,0 +1,94 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/constants/constants.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; + +class AboutDietScreen extends StatelessWidget { + final Datum datum; + + const AboutDietScreen({super.key, required this.datum}); + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: FloatingActionButton.extended( + heroTag: 'nextButtonRegistration', + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + backgroundColor: appTheme.greenA700, + onPressed: () {}, + label: Text( + 'Начать диету', + style: theme.textTheme.titleSmall, + ), + ), + ), + appBar: AppBar(), + body: SingleChildScrollView( + padding: EdgeInsets.only(bottom: 80), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + datum.name, + style: TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Icon(Icons.timer_outlined), + SizedBox(width: 4), + Text( + datum.duration.toString() + ' дней', + style: TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 1, + ), + ), + ], + ), + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + datum.details, + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.2, + ), + ), + ), + SizedBox(height: 12), + CachedNetworkImage( + imageUrl: baseUrl + '/storage/' + datum.image, + ), + SizedBox(height: 12), + ], + ), + ), + ); + } +} diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index f3c32cd6..ec949667 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -64,8 +64,10 @@ class _FeedScreenState extends State { return _buildAllBlogPosts(); else if (_selectedChipIndex == 1) return _buildAllRecipes(); - else + else if (_selectedChipIndex == 2) return _buildDiets(); + else + return Center(child: Text(chipsLabelList[_selectedChipIndex])); }, ), ), diff --git a/lib/features/feed/presentation/widgets/diets_widget.dart b/lib/features/feed/presentation/widgets/diets_widget.dart index 58dc0db0..323c9e24 100644 --- a/lib/features/feed/presentation/widgets/diets_widget.dart +++ b/lib/features/feed/presentation/widgets/diets_widget.dart @@ -84,7 +84,7 @@ class _DietsWidgetState extends State { ), SizedBox(width: 4), Text( - '0', + widget.datum.totalLikes.toString(), style: TextStyle( fontSize: 12, color: Colors.black, @@ -113,7 +113,7 @@ class _DietsWidgetState extends State { ), SizedBox(width: 4), Text( - '0', + widget.datum.totalViews.toString(), style: TextStyle( fontSize: 12, color: Colors.black, diff --git a/lib/features/recipes/presentation/pages/about_recipes_screen.dart b/lib/features/recipes/presentation/pages/about_recipes_screen.dart index fb4ae6b7..d4e03b26 100644 --- a/lib/features/recipes/presentation/pages/about_recipes_screen.dart +++ b/lib/features/recipes/presentation/pages/about_recipes_screen.dart @@ -61,6 +61,19 @@ class AboutRecipesScreen extends StatelessWidget { ), ), SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + recipes.description, + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.2, + ), + ), + ), + SizedBox(height: 12), CachedNetworkImage( imageUrl: baseUrl + '/storage/' + recipes.image, ), @@ -124,7 +137,7 @@ class AboutRecipesScreen extends StatelessWidget { _ingredient(Ingredient ingredient) { return Text( - '• '+ ingredient.name + ' ' + ingredient.portion + ' ' + (ingredient.unit ?? ''), + '• ' + ingredient.name + ' ' + ingredient.portion + ' ' + (ingredient.unit ?? ''), style: TextStyle( color: Colors.black, fontSize: 14, From 762c54fe28601a980fd1f6cc36b2744daea597cf Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 12 Aug 2024 14:35:34 +0300 Subject: [PATCH 3/4] feat(diets): about diets screen and profile screen from diet --- lib/app_router.dart | 8 + .../presentation/pages/about_diet_screen.dart | 170 +++++++++++++++++- .../presentation/pages/profile_screen.dart | 38 ++++ 3 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 lib/features/profile/presentation/pages/profile_screen.dart diff --git a/lib/app_router.dart b/lib/app_router.dart index 752d0ab5..698cf7c1 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -12,6 +12,7 @@ import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.d import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; +import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart'; import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart'; import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; @@ -94,6 +95,13 @@ class AppRouter { recipes: state.extra as Recipes, ), ), + GoRoute( + path: '/profile-screen', + name: 'profileScreen', + builder: (context, state) => ProfileScreen( + author: state.extra as Author, + ), + ), GoRoute( path: '/about-diet-screen', name: 'aboutDietScreen', diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 916085ce..c7482976 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -3,12 +3,21 @@ import 'package:eatvio_app/core/constants/constants.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; -class AboutDietScreen extends StatelessWidget { +class AboutDietScreen extends StatefulWidget { final Datum datum; + late final List selectedIndexes; - const AboutDietScreen({super.key, required this.datum}); + AboutDietScreen({super.key, required this.datum}) { + selectedIndexes = List.generate(datum.dailySchedule.length, (_) => 0); + } + @override + State createState() => _AboutDietScreenState(); +} + +class _AboutDietScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -31,7 +40,37 @@ class AboutDietScreen extends StatelessWidget { ), ), ), - appBar: AppBar(), + appBar: AppBar( + actions: [ + InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () => GoRouter.of(context).pushNamed('profileScreen', extra: widget.datum.author), + child: Ink( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + maxRadius: 16, + minRadius: 16, + backgroundImage: NetworkImage(widget.datum.author.authorProfileIcon), + ), + SizedBox(height: 4), + Text( + widget.datum.author.authorName, + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1, + ), + ), + ], + ), + ), + ), + ], + ), body: SingleChildScrollView( padding: EdgeInsets.only(bottom: 80), child: Column( @@ -40,7 +79,7 @@ class AboutDietScreen extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( - datum.name, + widget.datum.name, style: TextStyle( color: Colors.black, fontSize: 24, @@ -57,7 +96,7 @@ class AboutDietScreen extends StatelessWidget { Icon(Icons.timer_outlined), SizedBox(width: 4), Text( - datum.duration.toString() + ' дней', + widget.datum.duration.toString() + ' дней', style: TextStyle( color: Colors.black, fontSize: 12, @@ -72,7 +111,7 @@ class AboutDietScreen extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( - datum.details, + widget.datum.details, style: TextStyle( color: Colors.black, fontSize: 14, @@ -83,12 +122,129 @@ class AboutDietScreen extends StatelessWidget { ), SizedBox(height: 12), CachedNetworkImage( - imageUrl: baseUrl + '/storage/' + datum.image, + imageUrl: baseUrl + '/storage/' + widget.datum.image, ), SizedBox(height: 12), + ListView.separated( + padding: EdgeInsets.all(16), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + return _dayOfPlan(widget.datum.dailySchedule[index], index); + }, + separatorBuilder: (context, index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Container( + height: 0.4, + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(16), + ), + ), + ), + itemCount: widget.datum.dailySchedule.length, + ), + SizedBox(height: 32), ], ), ), ); } + + _dayOfPlan(List> day, int dayNumber) { + _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: 16, vertical: 8), + label: Text((index + 1).toString()), + labelStyle: TextStyle( + color: index == widget.selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600, + fontWeight: FontWeight.w500, + fontSize: 12, + ), + selected: widget.selectedIndexes[dayNumber] == index, + backgroundColor: appTheme.gray100, + onSelected: (bool selected) { + setState(() { + widget.selectedIndexes[dayNumber] = index; + }); + }, + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'День ' + (dayNumber + 1).toString(), + style: TextStyle( + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1, + ), + ), + SizedBox(height: 4), + Text( + 'Приём пищи', + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _choiceChipWidget(0), + _choiceChipWidget(1), + _choiceChipWidget(2), + _choiceChipWidget(3), + _choiceChipWidget(4), + ], + ), + SizedBox(height: 8), + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index]), + separatorBuilder: (context, index) => SizedBox(height: 4), + itemCount: day[widget.selectedIndexes[dayNumber]].length, + ), + ], + ); + } + + _daily(DailySchedule daily) { + return Row( + children: [ + Text( + '• ' + daily.name, + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.2, + ), + ), + Spacer(), + Text( + daily.quantity.toString() + ' ' + daily.portion.name, + style: TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.2, + ), + ), + ], + ); + } } diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart new file mode 100644 index 00000000..68c2616f --- /dev/null +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -0,0 +1,38 @@ +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:flutter/material.dart'; + +class ProfileScreen extends StatelessWidget { + final Author author; + + const ProfileScreen({super.key, required this.author}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Expanded( + child: Center( + child: Column( + children: [ + CircleAvatar( + maxRadius: 128, + minRadius: 128, + backgroundImage: NetworkImage(author.authorProfileIcon), + ), + SizedBox(height: 32), + Text( + author.authorName, + style: TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.w400, + height: 1, + ), + ), + ], + ), + ), + ), + ); + } +} From 1e992e9dd62964d07a496456f3ce65f00f945c2e Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Tue, 13 Aug 2024 16:25:25 +0300 Subject: [PATCH 4/4] feat(diets): fix profile --- .../presentation/pages/profile_screen.dart | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 68c2616f..bbfead98 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -10,28 +10,24 @@ class ProfileScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), - body: Expanded( - child: Center( - child: Column( - children: [ - CircleAvatar( - maxRadius: 128, - minRadius: 128, - backgroundImage: NetworkImage(author.authorProfileIcon), - ), - SizedBox(height: 32), - Text( - author.authorName, - style: TextStyle( - color: Colors.black, - fontSize: 24, - fontWeight: FontWeight.w400, - height: 1, - ), - ), - ], + body: Column( + children: [ + CircleAvatar( + maxRadius: 128, + minRadius: 128, + backgroundImage: NetworkImage(author.authorProfileIcon), ), - ), + SizedBox(height: 32), + Text( + author.authorName, + style: TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.w400, + height: 1, + ), + ), + ], ), ); }