From 02f4008394489ce2c85ee6e3b6c269b0a30b6cd7 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Fri, 28 Jun 2024 14:41:30 +0300 Subject: [PATCH] refactor(blog_post): Dio, retrfit and freezed implemented for blog post show screen --- lib/app_router.dart | 106 ++- .../blog_post/models/blog_post_model.dart | 23 + .../models/blog_post_model.freezed.dart | 351 ++++++++++ .../blog_post/models/blog_post_model.g.dart | 35 + .../presentation/bloc/blog_post_cubit.dart | 21 + .../presentation/bloc/blog_post_state.dart | 19 + .../bloc/blog_post_state.freezed.dart | 622 ++++++++++++++++++ .../presentation/pages}/blog_post_screen.dart | 255 ++++--- .../repositories/blog_post_repository.dart | 16 + .../blog_post/services/blog_post_service.dart | 13 + .../services/blog_post_service.g.dart | 77 +++ .../bloc/feed_cubit/feed_cubit.dart | 2 +- .../feed/presentation/pages/feed_screen.dart | 10 +- lib/locator_service.dart | 17 +- .../blog_post_screen/bloc/blog_post_bloc.dart | 184 ------ .../bloc/blog_post_event.dart | 44 -- .../bloc/blog_post_state.dart | 70 -- 17 files changed, 1397 insertions(+), 468 deletions(-) create mode 100644 lib/features/blog_post/models/blog_post_model.dart create mode 100644 lib/features/blog_post/models/blog_post_model.freezed.dart create mode 100644 lib/features/blog_post/models/blog_post_model.g.dart create mode 100644 lib/features/blog_post/presentation/bloc/blog_post_cubit.dart create mode 100644 lib/features/blog_post/presentation/bloc/blog_post_state.dart create mode 100644 lib/features/blog_post/presentation/bloc/blog_post_state.freezed.dart rename lib/{presentation/feed/blog_post_screen => features/blog_post/presentation/pages}/blog_post_screen.dart (62%) create mode 100644 lib/features/blog_post/repositories/blog_post_repository.dart create mode 100644 lib/features/blog_post/services/blog_post_service.dart create mode 100644 lib/features/blog_post/services/blog_post_service.g.dart delete mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart delete mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart delete mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart diff --git a/lib/app_router.dart b/lib/app_router.dart index c40516b9..0b8c0972 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -7,7 +7,7 @@ import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_scr import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; +import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart'; import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; @@ -45,7 +45,8 @@ class AppRouter { path: '/auth-screen', name: 'authScreen', redirect: (context, state) async { - if (await TokenStorage.haveTokenInStorage()) return '/my-profile-screen'; + if (await TokenStorage.haveTokenInStorage()) + return '/my-profile-screen'; return null; }, builder: (context, state) => AuthScreen.builder(context), @@ -58,72 +59,120 @@ class AppRouter { GoRoute( path: 'credentials-screen', name: 'credentialsScreen', - builder: (context, state) => CredentialsScreen.builder(context, state.extra), + builder: (context, state) => + CredentialsScreen.builder(context, state.extra), routes: [ GoRoute( path: 'name-screen', name: 'nameScreen', - builder: (context, state) => NameScreen.builder(context, state.extra), + builder: (context, state) => + NameScreen.builder(context, state.extra), routes: [ GoRoute( path: 'gender-screen', name: 'genderScreen', - builder: (context, state) => GenderScreen.builder(context, state.extra), + builder: (context, state) => + GenderScreen.builder(context, state.extra), routes: [ GoRoute( path: 'date-of-birth-screen', name: 'dateOfBirthScreen', - builder: (context, state) => DateOfBirthScreen.builder(context, state.extra), + builder: (context, state) => + DateOfBirthScreen.builder(context, state.extra), routes: [ GoRoute( path: 'city-of-living-screen', name: 'cityOfLivingScreen', - builder: (context, state) => CityOfLivingScreen.builder(context, state.extra), + builder: (context, state) => + CityOfLivingScreen.builder( + context, state.extra), routes: [ GoRoute( path: 'body-circumferences-screen', name: 'bodyCircumferencesScreen', - builder: (context, state) => BodyCircumferencesScreen.builder(context, state.extra), + builder: (context, state) => + BodyCircumferencesScreen.builder( + context, state.extra), routes: [ GoRoute( path: 'weight-screen', name: 'weightScreen', - builder: (context, state) => WeightScreen.builder(context, state.extra), + builder: (context, state) => + WeightScreen.builder( + context, state.extra), routes: [ GoRoute( path: 'height-screen', name: 'heightScreen', - builder: (context, state) => HeightScreen.builder(context, state.extra), + builder: (context, state) => + HeightScreen.builder( + context, state.extra), routes: [ GoRoute( path: 'blood-pressure-screen', name: 'bloodPressureScreen', - builder: (context, state) => BloodPressureScreen.builder(context, state.extra), + builder: (context, state) => + BloodPressureScreen.builder( + context, state.extra), routes: [ GoRoute( - path: 'favorite-sports-screen', - name: 'favoriteSportsScreen', - builder: (context, state) => FavoriteSportsScreen.builder(context, state.extra), + path: + 'favorite-sports-screen', + name: + 'favoriteSportsScreen', + builder: (context, state) => + FavoriteSportsScreen + .builder(context, + state.extra), routes: [ GoRoute( name: 'allergiesScreen', path: 'allergiesScreen', - builder: (context, state) => AllergiesScreen.builder(context, state.extra), + builder: (context, + state) => + AllergiesScreen + .builder( + context, + state + .extra), routes: [ GoRoute( - name: 'medicineIntakeScreen', - path: 'medicine-intake-screen', - builder: (context, state) => MedicineIntakeScreen.builder(context, state.extra), + name: + 'medicineIntakeScreen', + path: + 'medicine-intake-screen', + builder: (context, + state) => + MedicineIntakeScreen + .builder( + context, + state + .extra), ), GoRoute( - name: 'favoriteFoodsScreen', - path: 'favorite-foods-screen', - builder: (context, state) => FavoriteFoodsScreen.builder(context, state.extra), + name: + 'favoriteFoodsScreen', + path: + 'favorite-foods-screen', + builder: (context, + state) => + FavoriteFoodsScreen + .builder( + context, + state + .extra), routes: [ GoRoute( - path: 'additional-information-screen', - name: 'additionalInformationScreen', - builder: (context, state) => AdditionalInformationScreen.builder(context, state.extra), + path: + 'additional-information-screen', + name: + 'additionalInformationScreen', + builder: (context, + state) => + AdditionalInformationScreen.builder( + context, + state + .extra), ), ], ), @@ -171,15 +220,14 @@ class AppRouter { GoRoute( path: 'blog-post-create-screen', name: 'blogPostCreateScreen', - builder: (context, state) => BlogPostCreateScreen.builder(context), + builder: (context, state) => + BlogPostCreateScreen.builder(context), ), GoRoute( path: 'blog-post-screen', name: 'blogPostScreen', - builder: (context, state) => BlogPostScreen.builder( - context, - state.extra.toString(), - ), + builder: (context, state) => + BlogPostScreen(blogPostID: state.extra as String), routes: [ GoRoute( path: 'blog-post-edit-screen', diff --git a/lib/features/blog_post/models/blog_post_model.dart b/lib/features/blog_post/models/blog_post_model.dart new file mode 100644 index 00000000..df39fe1e --- /dev/null +++ b/lib/features/blog_post/models/blog_post_model.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'blog_post_model.freezed.dart'; +part 'blog_post_model.g.dart'; + +@freezed +class BlogPostModel with _$BlogPostModel { + const factory BlogPostModel({ + required int id, + required String state, + String? image, + required String description, + String? author_name, + String? author_avatar_path, + @JsonKey(name: "youtube_url") required String youtubeUrl, + @JsonKey(name: "created_at") required DateTime createdAt, + @JsonKey(name: "updated_at") required DateTime updatedAt, + required String title, + }) = _BlogPostModel; + + factory BlogPostModel.fromJson(Map json) => + _$BlogPostModelFromJson(json); +} diff --git a/lib/features/blog_post/models/blog_post_model.freezed.dart b/lib/features/blog_post/models/blog_post_model.freezed.dart new file mode 100644 index 00000000..7c0a750e --- /dev/null +++ b/lib/features/blog_post/models/blog_post_model.freezed.dart @@ -0,0 +1,351 @@ +// 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 'blog_post_model.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'); + +BlogPostModel _$BlogPostModelFromJson(Map json) { + return _BlogPostModel.fromJson(json); +} + +/// @nodoc +mixin _$BlogPostModel { + int get id => throw _privateConstructorUsedError; + String get state => throw _privateConstructorUsedError; + String? get image => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String? get author_name => throw _privateConstructorUsedError; + String? get author_avatar_path => throw _privateConstructorUsedError; + @JsonKey(name: "youtube_url") + String get youtubeUrl => throw _privateConstructorUsedError; + @JsonKey(name: "created_at") + DateTime get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: "updated_at") + DateTime get updatedAt => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BlogPostModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BlogPostModelCopyWith<$Res> { + factory $BlogPostModelCopyWith( + BlogPostModel value, $Res Function(BlogPostModel) then) = + _$BlogPostModelCopyWithImpl<$Res, BlogPostModel>; + @useResult + $Res call( + {int id, + String state, + String? image, + String description, + String? author_name, + String? author_avatar_path, + @JsonKey(name: "youtube_url") String youtubeUrl, + @JsonKey(name: "created_at") DateTime createdAt, + @JsonKey(name: "updated_at") DateTime updatedAt, + String title}); +} + +/// @nodoc +class _$BlogPostModelCopyWithImpl<$Res, $Val extends BlogPostModel> + implements $BlogPostModelCopyWith<$Res> { + _$BlogPostModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? state = null, + Object? image = freezed, + Object? description = null, + Object? author_name = freezed, + Object? author_avatar_path = freezed, + Object? youtubeUrl = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? title = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + author_name: freezed == author_name + ? _value.author_name + : author_name // ignore: cast_nullable_to_non_nullable + as String?, + author_avatar_path: freezed == author_avatar_path + ? _value.author_avatar_path + : author_avatar_path // ignore: cast_nullable_to_non_nullable + as String?, + youtubeUrl: null == youtubeUrl + ? _value.youtubeUrl + : youtubeUrl // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BlogPostModelImplCopyWith<$Res> + implements $BlogPostModelCopyWith<$Res> { + factory _$$BlogPostModelImplCopyWith( + _$BlogPostModelImpl value, $Res Function(_$BlogPostModelImpl) then) = + __$$BlogPostModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String state, + String? image, + String description, + String? author_name, + String? author_avatar_path, + @JsonKey(name: "youtube_url") String youtubeUrl, + @JsonKey(name: "created_at") DateTime createdAt, + @JsonKey(name: "updated_at") DateTime updatedAt, + String title}); +} + +/// @nodoc +class __$$BlogPostModelImplCopyWithImpl<$Res> + extends _$BlogPostModelCopyWithImpl<$Res, _$BlogPostModelImpl> + implements _$$BlogPostModelImplCopyWith<$Res> { + __$$BlogPostModelImplCopyWithImpl( + _$BlogPostModelImpl _value, $Res Function(_$BlogPostModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? state = null, + Object? image = freezed, + Object? description = null, + Object? author_name = freezed, + Object? author_avatar_path = freezed, + Object? youtubeUrl = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? title = null, + }) { + return _then(_$BlogPostModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + author_name: freezed == author_name + ? _value.author_name + : author_name // ignore: cast_nullable_to_non_nullable + as String?, + author_avatar_path: freezed == author_avatar_path + ? _value.author_avatar_path + : author_avatar_path // ignore: cast_nullable_to_non_nullable + as String?, + youtubeUrl: null == youtubeUrl + ? _value.youtubeUrl + : youtubeUrl // ignore: cast_nullable_to_non_nullable + as String, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BlogPostModelImpl implements _BlogPostModel { + const _$BlogPostModelImpl( + {required this.id, + required this.state, + this.image, + required this.description, + this.author_name, + this.author_avatar_path, + @JsonKey(name: "youtube_url") required this.youtubeUrl, + @JsonKey(name: "created_at") required this.createdAt, + @JsonKey(name: "updated_at") required this.updatedAt, + required this.title}); + + factory _$BlogPostModelImpl.fromJson(Map json) => + _$$BlogPostModelImplFromJson(json); + + @override + final int id; + @override + final String state; + @override + final String? image; + @override + final String description; + @override + final String? author_name; + @override + final String? author_avatar_path; + @override + @JsonKey(name: "youtube_url") + final String youtubeUrl; + @override + @JsonKey(name: "created_at") + final DateTime createdAt; + @override + @JsonKey(name: "updated_at") + final DateTime updatedAt; + @override + final String title; + + @override + String toString() { + return 'BlogPostModel(id: $id, state: $state, image: $image, description: $description, author_name: $author_name, author_avatar_path: $author_avatar_path, youtubeUrl: $youtubeUrl, createdAt: $createdAt, updatedAt: $updatedAt, title: $title)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BlogPostModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.state, state) || other.state == state) && + (identical(other.image, image) || other.image == image) && + (identical(other.description, description) || + other.description == description) && + (identical(other.author_name, author_name) || + other.author_name == author_name) && + (identical(other.author_avatar_path, author_avatar_path) || + other.author_avatar_path == author_avatar_path) && + (identical(other.youtubeUrl, youtubeUrl) || + other.youtubeUrl == youtubeUrl) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + (identical(other.title, title) || other.title == title)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, state, image, description, + author_name, author_avatar_path, youtubeUrl, createdAt, updatedAt, title); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BlogPostModelImplCopyWith<_$BlogPostModelImpl> get copyWith => + __$$BlogPostModelImplCopyWithImpl<_$BlogPostModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BlogPostModelImplToJson( + this, + ); + } +} + +abstract class _BlogPostModel implements BlogPostModel { + const factory _BlogPostModel( + {required final int id, + required final String state, + final String? image, + required final String description, + final String? author_name, + final String? author_avatar_path, + @JsonKey(name: "youtube_url") required final String youtubeUrl, + @JsonKey(name: "created_at") required final DateTime createdAt, + @JsonKey(name: "updated_at") required final DateTime updatedAt, + required final String title}) = _$BlogPostModelImpl; + + factory _BlogPostModel.fromJson(Map json) = + _$BlogPostModelImpl.fromJson; + + @override + int get id; + @override + String get state; + @override + String? get image; + @override + String get description; + @override + String? get author_name; + @override + String? get author_avatar_path; + @override + @JsonKey(name: "youtube_url") + String get youtubeUrl; + @override + @JsonKey(name: "created_at") + DateTime get createdAt; + @override + @JsonKey(name: "updated_at") + DateTime get updatedAt; + @override + String get title; + @override + @JsonKey(ignore: true) + _$$BlogPostModelImplCopyWith<_$BlogPostModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/blog_post/models/blog_post_model.g.dart b/lib/features/blog_post/models/blog_post_model.g.dart new file mode 100644 index 00000000..2e3233c4 --- /dev/null +++ b/lib/features/blog_post/models/blog_post_model.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'blog_post_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BlogPostModelImpl _$$BlogPostModelImplFromJson(Map json) => + _$BlogPostModelImpl( + id: (json['id'] as num).toInt(), + state: json['state'] as String, + image: json['image'] as String?, + description: json['description'] as String, + author_name: json['author_name'] as String?, + author_avatar_path: json['author_avatar_path'] as String?, + youtubeUrl: json['youtube_url'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + title: json['title'] as String, + ); + +Map _$$BlogPostModelImplToJson(_$BlogPostModelImpl instance) => + { + 'id': instance.id, + 'state': instance.state, + 'image': instance.image, + 'description': instance.description, + 'author_name': instance.author_name, + 'author_avatar_path': instance.author_avatar_path, + 'youtube_url': instance.youtubeUrl, + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'title': instance.title, + }; diff --git a/lib/features/blog_post/presentation/bloc/blog_post_cubit.dart b/lib/features/blog_post/presentation/bloc/blog_post_cubit.dart new file mode 100644 index 00000000..90520c95 --- /dev/null +++ b/lib/features/blog_post/presentation/bloc/blog_post_cubit.dart @@ -0,0 +1,21 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/blog_post/presentation/bloc/blog_post_state.dart'; +import 'package:eatvio_app/features/blog_post/repositories/blog_post_repository.dart'; + +class BlogPostCubit extends Cubit { + final BlogPostRepository blogPostRepository; + + BlogPostCubit({required this.blogPostRepository}) + : super(const BlogPostState.loading()); + + loadBlogPost(String blogPostID) async { + emit(const BlogPostState.loading()); + try { + final blogPost = await blogPostRepository.getBlogPost(blogPostID); + emit(BlogPostState.loaded(blogPost: blogPost)); + } catch (e) { + emit(BlogPostState.error(errorMessage: e.toString())); + print(e); + } + } +} diff --git a/lib/features/blog_post/presentation/bloc/blog_post_state.dart b/lib/features/blog_post/presentation/bloc/blog_post_state.dart new file mode 100644 index 00000000..c8a4d0e7 --- /dev/null +++ b/lib/features/blog_post/presentation/bloc/blog_post_state.dart @@ -0,0 +1,19 @@ +import 'package:eatvio_app/features/blog_post/models/blog_post_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'blog_post_state.freezed.dart'; + +@freezed +abstract class BlogPostState with _$BlogPostState { + const factory BlogPostState() = _BlogPostState; + + const factory BlogPostState.loading() = BlogPostStateLoading; + + const factory BlogPostState.loaded({ + required BlogPostModel blogPost, + }) = BlogPostStateLoaded; + + const factory BlogPostState.error({ + required String errorMessage, + }) = BlogPostStateError; +} diff --git a/lib/features/blog_post/presentation/bloc/blog_post_state.freezed.dart b/lib/features/blog_post/presentation/bloc/blog_post_state.freezed.dart new file mode 100644 index 00000000..4f99a3cd --- /dev/null +++ b/lib/features/blog_post/presentation/bloc/blog_post_state.freezed.dart @@ -0,0 +1,622 @@ +// 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 'blog_post_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 _$BlogPostState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(BlogPostModel blogPost) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(BlogPostModel blogPost)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(BlogPostModel blogPost)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_BlogPostState value) $default, { + required TResult Function(BlogPostStateLoading value) loading, + required TResult Function(BlogPostStateLoaded value) loaded, + required TResult Function(BlogPostStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_BlogPostState value)? $default, { + TResult? Function(BlogPostStateLoading value)? loading, + TResult? Function(BlogPostStateLoaded value)? loaded, + TResult? Function(BlogPostStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_BlogPostState value)? $default, { + TResult Function(BlogPostStateLoading value)? loading, + TResult Function(BlogPostStateLoaded value)? loaded, + TResult Function(BlogPostStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BlogPostStateCopyWith<$Res> { + factory $BlogPostStateCopyWith( + BlogPostState value, $Res Function(BlogPostState) then) = + _$BlogPostStateCopyWithImpl<$Res, BlogPostState>; +} + +/// @nodoc +class _$BlogPostStateCopyWithImpl<$Res, $Val extends BlogPostState> + implements $BlogPostStateCopyWith<$Res> { + _$BlogPostStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$BlogPostStateImplCopyWith<$Res> { + factory _$$BlogPostStateImplCopyWith( + _$BlogPostStateImpl value, $Res Function(_$BlogPostStateImpl) then) = + __$$BlogPostStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$BlogPostStateImplCopyWithImpl<$Res> + extends _$BlogPostStateCopyWithImpl<$Res, _$BlogPostStateImpl> + implements _$$BlogPostStateImplCopyWith<$Res> { + __$$BlogPostStateImplCopyWithImpl( + _$BlogPostStateImpl _value, $Res Function(_$BlogPostStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$BlogPostStateImpl implements _BlogPostState { + const _$BlogPostStateImpl(); + + @override + String toString() { + return 'BlogPostState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$BlogPostStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(BlogPostModel blogPost) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(BlogPostModel blogPost)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(BlogPostModel blogPost)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_BlogPostState value) $default, { + required TResult Function(BlogPostStateLoading value) loading, + required TResult Function(BlogPostStateLoaded value) loaded, + required TResult Function(BlogPostStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_BlogPostState value)? $default, { + TResult? Function(BlogPostStateLoading value)? loading, + TResult? Function(BlogPostStateLoaded value)? loaded, + TResult? Function(BlogPostStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_BlogPostState value)? $default, { + TResult Function(BlogPostStateLoading value)? loading, + TResult Function(BlogPostStateLoaded value)? loaded, + TResult Function(BlogPostStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _BlogPostState implements BlogPostState { + const factory _BlogPostState() = _$BlogPostStateImpl; +} + +/// @nodoc +abstract class _$$BlogPostStateLoadingImplCopyWith<$Res> { + factory _$$BlogPostStateLoadingImplCopyWith(_$BlogPostStateLoadingImpl value, + $Res Function(_$BlogPostStateLoadingImpl) then) = + __$$BlogPostStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$BlogPostStateLoadingImplCopyWithImpl<$Res> + extends _$BlogPostStateCopyWithImpl<$Res, _$BlogPostStateLoadingImpl> + implements _$$BlogPostStateLoadingImplCopyWith<$Res> { + __$$BlogPostStateLoadingImplCopyWithImpl(_$BlogPostStateLoadingImpl _value, + $Res Function(_$BlogPostStateLoadingImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$BlogPostStateLoadingImpl implements BlogPostStateLoading { + const _$BlogPostStateLoadingImpl(); + + @override + String toString() { + return 'BlogPostState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BlogPostStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(BlogPostModel blogPost) loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(BlogPostModel blogPost)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(BlogPostModel blogPost)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_BlogPostState value) $default, { + required TResult Function(BlogPostStateLoading value) loading, + required TResult Function(BlogPostStateLoaded value) loaded, + required TResult Function(BlogPostStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_BlogPostState value)? $default, { + TResult? Function(BlogPostStateLoading value)? loading, + TResult? Function(BlogPostStateLoaded value)? loaded, + TResult? Function(BlogPostStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_BlogPostState value)? $default, { + TResult Function(BlogPostStateLoading value)? loading, + TResult Function(BlogPostStateLoaded value)? loaded, + TResult Function(BlogPostStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class BlogPostStateLoading implements BlogPostState { + const factory BlogPostStateLoading() = _$BlogPostStateLoadingImpl; +} + +/// @nodoc +abstract class _$$BlogPostStateLoadedImplCopyWith<$Res> { + factory _$$BlogPostStateLoadedImplCopyWith(_$BlogPostStateLoadedImpl value, + $Res Function(_$BlogPostStateLoadedImpl) then) = + __$$BlogPostStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({BlogPostModel blogPost}); + + $BlogPostModelCopyWith<$Res> get blogPost; +} + +/// @nodoc +class __$$BlogPostStateLoadedImplCopyWithImpl<$Res> + extends _$BlogPostStateCopyWithImpl<$Res, _$BlogPostStateLoadedImpl> + implements _$$BlogPostStateLoadedImplCopyWith<$Res> { + __$$BlogPostStateLoadedImplCopyWithImpl(_$BlogPostStateLoadedImpl _value, + $Res Function(_$BlogPostStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? blogPost = null, + }) { + return _then(_$BlogPostStateLoadedImpl( + blogPost: null == blogPost + ? _value.blogPost + : blogPost // ignore: cast_nullable_to_non_nullable + as BlogPostModel, + )); + } + + @override + @pragma('vm:prefer-inline') + $BlogPostModelCopyWith<$Res> get blogPost { + return $BlogPostModelCopyWith<$Res>(_value.blogPost, (value) { + return _then(_value.copyWith(blogPost: value)); + }); + } +} + +/// @nodoc + +class _$BlogPostStateLoadedImpl implements BlogPostStateLoaded { + const _$BlogPostStateLoadedImpl({required this.blogPost}); + + @override + final BlogPostModel blogPost; + + @override + String toString() { + return 'BlogPostState.loaded(blogPost: $blogPost)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BlogPostStateLoadedImpl && + (identical(other.blogPost, blogPost) || + other.blogPost == blogPost)); + } + + @override + int get hashCode => Object.hash(runtimeType, blogPost); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BlogPostStateLoadedImplCopyWith<_$BlogPostStateLoadedImpl> get copyWith => + __$$BlogPostStateLoadedImplCopyWithImpl<_$BlogPostStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(BlogPostModel blogPost) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(blogPost); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(BlogPostModel blogPost)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(blogPost); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(BlogPostModel blogPost)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(blogPost); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_BlogPostState value) $default, { + required TResult Function(BlogPostStateLoading value) loading, + required TResult Function(BlogPostStateLoaded value) loaded, + required TResult Function(BlogPostStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_BlogPostState value)? $default, { + TResult? Function(BlogPostStateLoading value)? loading, + TResult? Function(BlogPostStateLoaded value)? loaded, + TResult? Function(BlogPostStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_BlogPostState value)? $default, { + TResult Function(BlogPostStateLoading value)? loading, + TResult Function(BlogPostStateLoaded value)? loaded, + TResult Function(BlogPostStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class BlogPostStateLoaded implements BlogPostState { + const factory BlogPostStateLoaded({required final BlogPostModel blogPost}) = + _$BlogPostStateLoadedImpl; + + BlogPostModel get blogPost; + @JsonKey(ignore: true) + _$$BlogPostStateLoadedImplCopyWith<_$BlogPostStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$BlogPostStateErrorImplCopyWith<$Res> { + factory _$$BlogPostStateErrorImplCopyWith(_$BlogPostStateErrorImpl value, + $Res Function(_$BlogPostStateErrorImpl) then) = + __$$BlogPostStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$BlogPostStateErrorImplCopyWithImpl<$Res> + extends _$BlogPostStateCopyWithImpl<$Res, _$BlogPostStateErrorImpl> + implements _$$BlogPostStateErrorImplCopyWith<$Res> { + __$$BlogPostStateErrorImplCopyWithImpl(_$BlogPostStateErrorImpl _value, + $Res Function(_$BlogPostStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$BlogPostStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$BlogPostStateErrorImpl implements BlogPostStateError { + const _$BlogPostStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'BlogPostState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BlogPostStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BlogPostStateErrorImplCopyWith<_$BlogPostStateErrorImpl> get copyWith => + __$$BlogPostStateErrorImplCopyWithImpl<_$BlogPostStateErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(BlogPostModel blogPost) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(BlogPostModel blogPost)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(BlogPostModel blogPost)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_BlogPostState value) $default, { + required TResult Function(BlogPostStateLoading value) loading, + required TResult Function(BlogPostStateLoaded value) loaded, + required TResult Function(BlogPostStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_BlogPostState value)? $default, { + TResult? Function(BlogPostStateLoading value)? loading, + TResult? Function(BlogPostStateLoaded value)? loaded, + TResult? Function(BlogPostStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_BlogPostState value)? $default, { + TResult Function(BlogPostStateLoading value)? loading, + TResult Function(BlogPostStateLoaded value)? loaded, + TResult Function(BlogPostStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class BlogPostStateError implements BlogPostState { + const factory BlogPostStateError({required final String errorMessage}) = + _$BlogPostStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$BlogPostStateErrorImplCopyWith<_$BlogPostStateErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart similarity index 62% rename from lib/presentation/feed/blog_post_screen/blog_post_screen.dart rename to lib/features/blog_post/presentation/pages/blog_post_screen.dart index dd2252b4..f9fc9696 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -1,8 +1,8 @@ +import 'package:eatvio_app/features/blog_post/presentation/bloc/blog_post_cubit.dart'; +import 'package:eatvio_app/features/blog_post/presentation/bloc/blog_post_state.dart'; +import 'package:eatvio_app/locator_service.dart'; import 'package:go_router/go_router.dart'; - -import 'bloc/blog_post_bloc.dart'; -import 'models/blog_post_model.dart'; -import 'widgets/comment_widget.dart'; +import 'package:flutter/cupertino.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -12,53 +12,29 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class BlogPostScreen extends StatelessWidget { - const BlogPostScreen({Key? key}) : super(key: key); - - static Widget builder(BuildContext context, String blog_postID) { - return BlocProvider( - create: (context) => BlogPostBloc( - BlogPostState( - blog_postModelObj: BlogPostModel( - blog_postID: blog_postID.toString(), - ), - ), - )..add( - BlogPostInitialEvent(), - ), - child: BlogPostScreen(), - ); - } + final String blogPostID; + const BlogPostScreen({super.key, required this.blogPostID}); Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is BlogPostInitialState) { - return Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); - } else if (state is BlogPostLoadedState) { - // Данные загружены, можно строить экран. - return _buildBlogPost(context, state); - } else if (state is BlogPostErrorState) { - // Произошла ошибка при загрузке данных. - return Center( - child: Text(state.errorMessage), - ); - } else { - return Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); - // Другие состояния, если они есть. - } - }, + return BlocProvider( + create: (context) => sl()..loadBlogPost(blogPostID), + child: BlocBuilder( + builder: (context, state) { + if (state is BlogPostStateLoaded) { + return _buildBlogPost(context, state); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), ); } - _buildBlogPost(BuildContext context, BlogPostLoadedState state) { + _buildBlogPost(BuildContext context, BlogPostStateLoaded state) { return SafeArea( child: Scaffold( resizeToAvoidBottomInset: false, @@ -82,8 +58,8 @@ class BlogPostScreen extends StatelessWidget { ); } - _buildHeaderRow(BuildContext context, BlogPostLoadedState state) { - final String name = state.author?['name']; + _buildHeaderRow(BuildContext context, BlogPostStateLoaded state) { + final String name = state.blogPost.title; return Container( margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), padding: EdgeInsets.symmetric( @@ -121,7 +97,8 @@ class BlogPostScreen extends StatelessWidget { ), ), CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + (state.author?['profile_photo_path'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + + (state.blogPost.author_avatar_path ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 25, @@ -141,7 +118,7 @@ class BlogPostScreen extends StatelessWidget { ); } - _buildBlogPostData(BuildContext context, BlogPostLoadedState state) { + _buildBlogPostData(BuildContext context, BlogPostStateLoaded state) { return Container( margin: EdgeInsets.only( left: 16.h, @@ -161,79 +138,90 @@ class BlogPostScreen extends StatelessWidget { ), ], ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - Text( - state.blog_post?['title'], - style: CustomTextStyles.titleMediumBlack, - ), - Spacer(), - PopupMenuButton( - onSelected: (value) => tapMenu(value, context, state), - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: 'edit', - child: Text('Редактировать'), - ), - PopupMenuItem( - value: 'delete', - child: Text('Удалить'), - ), - ], - ), - ], - ), - SizedBox(height: 8.0), - _buildImage(state), - SizedBox(height: 16.0), - Text( - state.blog_post?['description'], - style: CustomTextStyles.bodyMediumBluegray800, - ), - SizedBox(height: 16.0), - Align( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Text( + state.blogPost.title, + style: CustomTextStyles.titleMediumBlack, + ), + Spacer(), + PopupMenuButton( + onSelected: (value) => tapMenu(value, context, state), + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: 'edit', + child: Text('Редактировать'), + ), + PopupMenuItem( + value: 'delete', + child: Text('Удалить'), + ), + ], + ), + ], + ), + SizedBox(height: 8.0), + _buildImage(state), + SizedBox(height: 16.0), + Text( + state.blogPost.description, + style: CustomTextStyles.bodyMediumBluegray800, + ), + SizedBox(height: 16.0), + Align( alignment: Alignment.topLeft, child: Text( - DateFormat('dd.MM.yyyy').format(DateTime.parse(state.blog_post?['updated_at'])), + DateFormat('dd.MM.yyyy').format(state.blogPost.updatedAt), style: theme.textTheme.bodyMedium, - ),), - SizedBox(height: 16.0), - Row(children: [ - Padding( - padding: EdgeInsets.only(left: 24), - child: Row(children: [ - Padding( - padding: EdgeInsets.only(right: 8), - child: Icon(Icons.favorite_outline), - ), - Text( - "Нравится", - style: CustomTextStyles.titleMediumSemiBold, - ), - ],), + ), ), - Spacer(), - Padding( - padding: EdgeInsets.only(right: 24), - child: Row(children: [ - Padding( - padding: const EdgeInsets.only(right: 8), - child: Icon(Icons.share_outlined), + SizedBox(height: 16.0), + Row( + children: [ + Padding( + padding: EdgeInsets.only(left: 24), + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(right: 8), + child: Icon(Icons.favorite_outline), + ), + Text( + "Нравится", + style: CustomTextStyles.titleMediumSemiBold, + ), + ], ), - Text( - "Поделиться", - style: CustomTextStyles.titleMediumSemiBold, + ), + Spacer(), + Padding( + padding: EdgeInsets.only(right: 24), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: Icon(Icons.share_outlined), + ), + Text( + "Поделиться", + style: CustomTextStyles.titleMediumSemiBold, + ), + ], ), - ],),), - ],), - ],), + ), + ], + ), + ], + ), ); } - _buildImage(BlogPostLoadedState state) { + _buildImage(BlogPostStateLoaded state) { return CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + (state.blog_post?['image'] ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (state.blogPost.image ?? ""), imageBuilder: (context, imageProvider) => Stack( children: [ ClipRRect( @@ -290,7 +278,7 @@ class BlogPostScreen extends StatelessWidget { ), SizedBox(width: 4.0), Text( - '${state.comments?.length}', + '42', style: TextStyle(color: Colors.white), ), ], @@ -306,18 +294,18 @@ class BlogPostScreen extends StatelessWidget { ); } - void tapMenu(String value, BuildContext context, BlogPostLoadedState state) { + void tapMenu(String value, BuildContext context, BlogPostStateLoaded state) { if (value == 'edit') { GoRouter.of(context).goNamed( 'blogPostEditScreen', - extra: state.blog_post?['id'].toString(), + extra: state.blogPost.id.toString(), ); } else if (value == 'delete') { - context.read().add(DeleteEntry(state.blog_post?['id'])); + // context.read().add(DeleteEntry(state.blog_post?['id'])); } } - _buildTextField(BuildContext context, BlogPostLoadedState state) { + _buildTextField(BuildContext context, BlogPostStateLoaded state) { return Container( margin: EdgeInsets.only( left: 16.h, @@ -343,7 +331,7 @@ class BlogPostScreen extends StatelessWidget { Column( children: [ CustomTextFormField( - controller: state.commentController, + // controller: state.commentController, maxLines: 4, ), SizedBox(height: 15.h), @@ -354,7 +342,9 @@ class BlogPostScreen extends StatelessWidget { decoration: BoxDecoration( color: Colors.transparent, ), - onPressed: () => context.read().add(CreateComment(state.commentController!.text)), + /* onPressed: () => context + .read() + .add(CreateComment(state.commentController!.text)),*/ // Установите прозрачный фон ), ], @@ -364,9 +354,9 @@ class BlogPostScreen extends StatelessWidget { ); } - _buildCommentSection(BuildContext context, BlogPostLoadedState state) { - if (state.comments != null && state.comments!.isNotEmpty) { - return Column( + _buildCommentSection(BuildContext context, BlogPostStateLoaded state) { + /* if (state.comments != null && state.comments!.isNotEmpty) { + return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ for (int i = 0; i < state.comments!.length; i++) @@ -380,15 +370,14 @@ class BlogPostScreen extends StatelessWidget { ], ); } else { - // Return a message or placeholder widget when there are no comments - return Center( - child: Text('No comments available'), - ); - } - } - - /// Navigates to the previous screen. - onTapArrowleftone(BuildContext context) { - GoRouter.of(context).pop(); + // Return a message or placeholder widget when there are no comments*/ + return Center( + child: Text('No comments available'), + ); } } + +/// Navigates to the previous screen. +onTapArrowleftone(BuildContext context) { + GoRouter.of(context).pop(); +} diff --git a/lib/features/blog_post/repositories/blog_post_repository.dart b/lib/features/blog_post/repositories/blog_post_repository.dart new file mode 100644 index 00000000..b0c5a7a4 --- /dev/null +++ b/lib/features/blog_post/repositories/blog_post_repository.dart @@ -0,0 +1,16 @@ +import 'package:eatvio_app/features/blog_post/models/blog_post_model.dart'; +import 'package:eatvio_app/features/blog_post/services/blog_post_service.dart'; + +class BlogPostRepository { + final BlogPostService blogPostService; + + BlogPostRepository({required this.blogPostService}); + + Future getBlogPost(String blogPostID) async { + try { + return await blogPostService.getBlogPost(blogPostID); + } catch (e) { + rethrow; + } + } +} diff --git a/lib/features/blog_post/services/blog_post_service.dart b/lib/features/blog_post/services/blog_post_service.dart new file mode 100644 index 00000000..a406a7f3 --- /dev/null +++ b/lib/features/blog_post/services/blog_post_service.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/blog_post/models/blog_post_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'blog_post_service.g.dart'; + +@RestApi() +abstract class BlogPostService { + factory BlogPostService(Dio dio, {String baseUrl}) = _BlogPostService; + + @GET('/feed/show/{id}') + Future getBlogPost(@Path() String id); +} diff --git a/lib/features/blog_post/services/blog_post_service.g.dart b/lib/features/blog_post/services/blog_post_service.g.dart new file mode 100644 index 00000000..7af46076 --- /dev/null +++ b/lib/features/blog_post/services/blog_post_service.g.dart @@ -0,0 +1,77 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'blog_post_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers + +class _BlogPostService implements BlogPostService { + _BlogPostService( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future getBlogPost(String id) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _result = await _dio + .fetch>(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/feed/show/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = BlogPostModel.fromJson(_result.data!); + return value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart index 6a522235..02ee7671 100644 --- a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart +++ b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart @@ -8,7 +8,7 @@ class FeedCubit extends Cubit { FeedCubit({required this.feedRepository}) : super(const FeedState.loading()); loadFeedBlogPostList() async { - emit(const FeedStateLoading()); + emit(const FeedState.loading()); try { final feedBlogPostList = await feedRepository.getFeedBlogPostList(); emit(FeedState.loaded(feedBlogPostList: feedBlogPostList)); diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index dca0eef8..d42e776a 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -100,7 +100,8 @@ class FeedScreen extends StatelessWidget { flex: 1, child: CustomOutlinedButton( text: "+", - onPressed: () => GoRouter.of(context).goNamed('blogPostCreateScreen'), + onPressed: () => + GoRouter.of(context).goNamed('blogPostCreateScreen'), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, ), ), @@ -144,7 +145,7 @@ class FeedScreen extends StatelessWidget { children: [ ListTile( leading: CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + ('' ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (''), /// todo profile photo path imageBuilder: (context, imageProvider) => CircleAvatar( @@ -215,11 +216,12 @@ class FeedScreen extends StatelessWidget { ), SizedBox(height: 16.0), CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + (post.image ?? ''), + imageUrl: dotenv.env['STORAGE_URL']! + (post.image), imageBuilder: (context, imageProvider) => Stack( children: [ ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8.0)), + borderRadius: + const BorderRadius.all(Radius.circular(8.0)), child: Image( image: imageProvider, ), diff --git a/lib/locator_service.dart b/lib/locator_service.dart index 9780f414..2f4ee2f7 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -1,5 +1,8 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; +import 'package:eatvio_app/features/blog_post/presentation/bloc/blog_post_cubit.dart'; +import 'package:eatvio_app/features/blog_post/repositories/blog_post_repository.dart'; +import 'package:eatvio_app/features/blog_post/services/blog_post_service.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/services/chat_service.dart'; @@ -14,16 +17,24 @@ final sl = GetIt.instance; Future init() async { // BLoC sl.registerFactory(() => ChatRoomCubit(chatRepository: sl())); - sl.registerFactory(() => InitializationCubit(chatRepository: sl())); + sl.registerFactory( + () => InitializationCubit(chatRepository: sl())); sl.registerFactory(() => FeedCubit(feedRepository: sl())); + sl.registerFactory( + () => BlogPostCubit(blogPostRepository: sl())); // Repository - sl.registerLazySingleton(() => ChatRepository(chatService: sl())); - sl.registerLazySingleton(() => FeedRepository(feedService: sl())); + sl.registerLazySingleton( + () => ChatRepository(chatService: sl())); + sl.registerLazySingleton( + () => FeedRepository(feedService: sl())); + sl.registerLazySingleton( + () => BlogPostRepository(blogPostService: sl())); // Services sl.registerLazySingleton(() => ChatService(sl())); sl.registerLazySingleton(() => FeedService(sl())); + sl.registerLazySingleton(() => BlogPostService(sl())); // External sl.registerLazySingleton( diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart deleted file mode 100644 index b93c18ce..00000000 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart +++ /dev/null @@ -1,184 +0,0 @@ -import 'dart:async'; - -import 'package:eatvio_app/app_router.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; -import 'package:intl/intl.dart'; -import 'dart:convert'; -import '/core/app_export.dart'; -import 'package:eatvio_app/presentation/feed/blog_post_screen/models/blog_post_model.dart'; -import 'package:go_router/go_router.dart'; -part 'blog_post_event.dart'; -part 'blog_post_state.dart'; - -/// A bloc that manages the state of a BlogPost according to the event that is dispatched to it. -class BlogPostBloc extends Bloc { - BlogPostBloc(BlogPostState initialState) : super(initialState) { - on(_onInitialize); - on(_deleteEntry); - on(_createComment); - } - - _onInitialize( - BlogPostInitialEvent event, - Emitter emit, - ) async { - try { - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - final String? blog_postID = state.blog_postModelObj?.blog_postID; - final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/show/$blog_postID'), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - if (data is Map) { - if (state is BlogPostLoadedState) { - var currentState = state as BlogPostLoadedState; - emit(currentState.copyWith( - blog_postModelObj: currentState.blog_postModelObj, - blog_post: currentState.blog_post, - author: currentState.author, - comments: currentState.comments, - token: currentState.token,),); - } else { - emit(BlogPostLoadedState( - blog_post: data, - token: token, - commentController: TextEditingController(), - author: await _fetchAuthor(data['id']), - comments: fillCommentList(await _fetchComments(data['id'])),),); - } - } else { - // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку - emit(BlogPostErrorState('Некоторые необходимые данные отсутствуют.')); - } - } else { - // Статус ответа не равен 200, обрабатываем ошибку - emit(BlogPostErrorState('Ошибка сервера: ${response.statusCode}')); - } - } catch (e) { - // Ошибка при декодировании JSON или другая ошибка выполнения - emit(BlogPostErrorState('Ошибка при получении данных о чатах: $e')); - } - } - - FutureOr _createComment( - CreateComment event, Emitter emit,) async { - if (state is BlogPostLoadedState) { - var currentState = state as BlogPostLoadedState; - // Отправляем данные по HTTP маршруту и ждем ответ - var response = await _sendCommentData( - currentState.token, event.body, currentState.blog_post?['id'],); - if (response.statusCode == 200) { - var updatedComments = fillCommentList( - await _fetchComments(currentState.blog_post?['id']),); - var updatedState = currentState.copyWith( - comments: updatedComments, - commentController: - TextEditingController(), // Clear the comment input field - ); - emit(updatedState); // Emit the updated state to trigger a screen update - } - } - } - - FutureOr _deleteEntry( - DeleteEntry event, Emitter emit,) async { - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - final int id = event.id; - final response = await http.post( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/delete/$id'), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - if (response.statusCode == 200) { - AppRouter.navigatorKey.currentContext?.goNamed('feedScreen'); - } else { - // Статус ответа не равен 200, обрабатываем ошибку - emit(BlogPostErrorState('Ошибка: ${response.statusCode}')); - } - } - - _fetchAuthor(int id) async { - final box = await Hive.openBox('token'); - final token = box.get('token'); - - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - - final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/getAuthor/$id'), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - final data = jsonDecode(response.body); - return data; - } - - Future _fetchComments(int id) async { - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/getComments/$id'), - headers: { - 'Authorization': 'Bearer $token', - }, - ); - final data = jsonDecode(response.body); - return data; - } - - List fillCommentList(List comments) { - List list = []; - for (int i = 0; i < comments.length; i++) { - list.add(CommentModel( - id: comments[i]['id'].toString(), - userImage: comments[i]['profile_photo_path'], - username: comments[i]['name'], - dateText: DateFormat('dd.MM') - .format(DateTime.parse(comments[i]['updated_at'])), - greetingText: comments[i]['body'],),); - } - return list; - } - - Future _sendCommentData( - String? token, String body, int id,) async { - String url = dotenv.env['SERVER_ROOT']! + '/feed/createComment/$id'; - final Map credentials = { - 'id': id, - 'body': body, - }; - - var response = await http.post(Uri.parse(url), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer $token', - }, - body: json.encode(credentials), - encoding: utf8,); - - return response; - } -} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart deleted file mode 100644 index e1435112..00000000 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart +++ /dev/null @@ -1,44 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'blog_post_bloc.dart'; - -/// Abstract class for all events that can be dispatched from the -///BlogPost widget. -/// -/// Events must be immutable and implement the [Equatable] interface. -@immutable -abstract class BlogPostEvent extends Equatable {} - -/// Event that is dispatched when the BlogPost widget is first created. -class BlogPostInitialEvent extends BlogPostEvent { - @override - List get props => []; -} - -class DeleteEntry extends BlogPostEvent { - DeleteEntry(this.id); - final int id; - @override - List get props => [id]; -} - -class FetchAuthor extends BlogPostEvent { - FetchAuthor(this.id); - final int id; - @override - List get props => [id]; -} - -class FetchComments extends BlogPostEvent { - FetchComments(this.id); - final int id; - @override - List get props => [id]; -} - -class CreateComment extends BlogPostEvent { - CreateComment(this.body); - final String body; - @override - List get props => [body]; -} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart deleted file mode 100644 index 982eac90..00000000 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart +++ /dev/null @@ -1,70 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'blog_post_bloc.dart'; - -/// Represents the state of BlogPost in the application. -/// - -class BlogPostState extends Equatable { - BlogPostState({ - this.blog_postModelObj, - }); - - BlogPostModel? blog_postModelObj; - - @override - List get props => [blog_postModelObj]; - BlogPostState copyWith({BlogPostModel? blog_postModelObj}) { - return BlogPostState( - blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, - ); - } -} - -class BlogPostLoadedState extends BlogPostState { - BlogPostLoadedState( - {this.blog_postModelObj, - this.blog_post, - this.author, - this.comments, - this.token, - this.commentController,}); - - BlogPostModel? blog_postModelObj; - Map? blog_post; - Map? author; - List? comments; - TextEditingController? commentController; - String? token; - @override - List get props => [blog_postModelObj, blog_post, author, comments]; - BlogPostLoadedState copyWith( - {BlogPostModel? blog_postModelObj, - Map? blog_post, - Map? author, - TextEditingController? commentController, - String? token, - List? comments,}) { - return BlogPostLoadedState( - blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, - blog_post: blog_post ?? this.blog_post, - author: author ?? this.author, - comments: comments ?? this.comments, - token: token ?? this.token, - commentController: commentController ?? this.commentController,); - } -} - -class BlogPostErrorState extends BlogPostState { - final String errorMessage; - - BlogPostErrorState(this.errorMessage); - - @override - List get props => [errorMessage]; -} - -class BlogPostInitialState extends BlogPostState { - BlogPostInitialState({required BlogPostModel blog_postModelObj}) - : super(blog_postModelObj: blog_postModelObj); -}