From f697f403f476cc31958c0f81105bceafe45a4295 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Wed, 14 Aug 2024 14:36:06 +0300 Subject: [PATCH] feat(recipe): Models, service and repository for eecpe create. Recipe create now uses cubit --- lib/features/recipes/models/image.dart | 14 + .../recipes/models/image.freezed.dart | 147 ++ lib/features/recipes/models/image.g.dart | 16 + lib/features/recipes/models/ingredient.dart | 18 + .../recipes/models/ingredient.freezed.dart | 208 +++ lib/features/recipes/models/ingredient.g.dart | 23 + lib/features/recipes/models/recipe_model.dart | 25 + .../recipes/models/recipe_model.freezed.dart | 356 +++++ .../recipes/models/recipe_model.g.dart | 37 + lib/features/recipes/models/step.dart | 17 + lib/features/recipes/models/step.freezed.dart | 199 +++ lib/features/recipes/models/step.g.dart | 22 + .../bloc/recipe_create_cubit.dart | 170 +++ .../bloc/recipe_create_state.dart | 39 + .../bloc/recipe_create_state.freezed.dart | 1189 +++++++++++++++++ .../pages/recipe_create_screen.dart | 341 +++-- .../reporitories/recipe_repository.dart | 14 + .../recipes/services/recipe_service.dart | 13 + .../recipes/services/recipe_service.g.dart | 78 ++ lib/locator_service.dart | 8 + lib/main.dart | 37 +- .../bloc/recipe_create_bloc.dart | 193 --- .../bloc/recipe_create_event.dart | 44 - .../bloc/recipe_create_state.dart | 92 -- .../models/recipe_create_model.dart | 17 - .../recipe_create_screen.dart | 413 ------ 26 files changed, 2817 insertions(+), 913 deletions(-) create mode 100644 lib/features/recipes/models/image.dart create mode 100644 lib/features/recipes/models/image.freezed.dart create mode 100644 lib/features/recipes/models/image.g.dart create mode 100644 lib/features/recipes/models/ingredient.dart create mode 100644 lib/features/recipes/models/ingredient.freezed.dart create mode 100644 lib/features/recipes/models/ingredient.g.dart create mode 100644 lib/features/recipes/models/recipe_model.dart create mode 100644 lib/features/recipes/models/recipe_model.freezed.dart create mode 100644 lib/features/recipes/models/recipe_model.g.dart create mode 100644 lib/features/recipes/models/step.dart create mode 100644 lib/features/recipes/models/step.freezed.dart create mode 100644 lib/features/recipes/models/step.g.dart create mode 100644 lib/features/recipes/presentation/bloc/recipe_create_cubit.dart create mode 100644 lib/features/recipes/presentation/bloc/recipe_create_state.dart create mode 100644 lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart create mode 100644 lib/features/recipes/reporitories/recipe_repository.dart create mode 100644 lib/features/recipes/services/recipe_service.dart create mode 100644 lib/features/recipes/services/recipe_service.g.dart delete mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart delete mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart delete mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart delete mode 100644 lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart delete mode 100644 lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart diff --git a/lib/features/recipes/models/image.dart b/lib/features/recipes/models/image.dart new file mode 100644 index 00000000..f10e8db7 --- /dev/null +++ b/lib/features/recipes/models/image.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'image.freezed.dart'; + +part 'image.g.dart'; + +@freezed +class Image with _$Image { + const factory Image({ + required String path, + }) = _Image; + + factory Image.fromJson(Map json) => _$ImageFromJson(json); +} diff --git a/lib/features/recipes/models/image.freezed.dart b/lib/features/recipes/models/image.freezed.dart new file mode 100644 index 00000000..3d4cc2ed --- /dev/null +++ b/lib/features/recipes/models/image.freezed.dart @@ -0,0 +1,147 @@ +// 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 'image.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'); + +Image _$ImageFromJson(Map json) { + return _Image.fromJson(json); +} + +/// @nodoc +mixin _$Image { + String get path => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ImageCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ImageCopyWith<$Res> { + factory $ImageCopyWith(Image value, $Res Function(Image) then) = + _$ImageCopyWithImpl<$Res, Image>; + @useResult + $Res call({String path}); +} + +/// @nodoc +class _$ImageCopyWithImpl<$Res, $Val extends Image> + implements $ImageCopyWith<$Res> { + _$ImageCopyWithImpl(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? path = null, + }) { + return _then(_value.copyWith( + path: null == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ImageImplCopyWith<$Res> implements $ImageCopyWith<$Res> { + factory _$$ImageImplCopyWith( + _$ImageImpl value, $Res Function(_$ImageImpl) then) = + __$$ImageImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String path}); +} + +/// @nodoc +class __$$ImageImplCopyWithImpl<$Res> + extends _$ImageCopyWithImpl<$Res, _$ImageImpl> + implements _$$ImageImplCopyWith<$Res> { + __$$ImageImplCopyWithImpl( + _$ImageImpl _value, $Res Function(_$ImageImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? path = null, + }) { + return _then(_$ImageImpl( + path: null == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ImageImpl implements _Image { + const _$ImageImpl({required this.path}); + + factory _$ImageImpl.fromJson(Map json) => + _$$ImageImplFromJson(json); + + @override + final String path; + + @override + String toString() { + return 'Image(path: $path)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ImageImpl && + (identical(other.path, path) || other.path == path)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, path); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ImageImplCopyWith<_$ImageImpl> get copyWith => + __$$ImageImplCopyWithImpl<_$ImageImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ImageImplToJson( + this, + ); + } +} + +abstract class _Image implements Image { + const factory _Image({required final String path}) = _$ImageImpl; + + factory _Image.fromJson(Map json) = _$ImageImpl.fromJson; + + @override + String get path; + @override + @JsonKey(ignore: true) + _$$ImageImplCopyWith<_$ImageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/recipes/models/image.g.dart b/lib/features/recipes/models/image.g.dart new file mode 100644 index 00000000..0d54e19f --- /dev/null +++ b/lib/features/recipes/models/image.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'image.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ImageImpl _$$ImageImplFromJson(Map json) => _$ImageImpl( + path: json['path'] as String, + ); + +Map _$$ImageImplToJson(_$ImageImpl instance) => + { + 'path': instance.path, + }; diff --git a/lib/features/recipes/models/ingredient.dart b/lib/features/recipes/models/ingredient.dart new file mode 100644 index 00000000..bf00fe1b --- /dev/null +++ b/lib/features/recipes/models/ingredient.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'ingredient.freezed.dart'; + +part 'ingredient.g.dart'; + +@freezed +class Ingredient with _$Ingredient { + const factory Ingredient({ + int? id, + required String product_name, + required String unit, + required num portion, + }) = _Ingredient; + + factory Ingredient.fromJson(Map json) => + _$IngredientFromJson(json); +} diff --git a/lib/features/recipes/models/ingredient.freezed.dart b/lib/features/recipes/models/ingredient.freezed.dart new file mode 100644 index 00000000..2555a744 --- /dev/null +++ b/lib/features/recipes/models/ingredient.freezed.dart @@ -0,0 +1,208 @@ +// 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 'ingredient.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'); + +Ingredient _$IngredientFromJson(Map json) { + return _Ingredient.fromJson(json); +} + +/// @nodoc +mixin _$Ingredient { + int? get id => throw _privateConstructorUsedError; + String get product_name => throw _privateConstructorUsedError; + String get unit => throw _privateConstructorUsedError; + num get portion => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IngredientCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IngredientCopyWith<$Res> { + factory $IngredientCopyWith( + Ingredient value, $Res Function(Ingredient) then) = + _$IngredientCopyWithImpl<$Res, Ingredient>; + @useResult + $Res call({int? id, String product_name, String unit, num portion}); +} + +/// @nodoc +class _$IngredientCopyWithImpl<$Res, $Val extends Ingredient> + implements $IngredientCopyWith<$Res> { + _$IngredientCopyWithImpl(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 = freezed, + Object? product_name = null, + Object? unit = null, + Object? portion = null, + }) { + return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + product_name: null == product_name + ? _value.product_name + : product_name // ignore: cast_nullable_to_non_nullable + as String, + unit: null == unit + ? _value.unit + : unit // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as num, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IngredientImplCopyWith<$Res> + implements $IngredientCopyWith<$Res> { + factory _$$IngredientImplCopyWith( + _$IngredientImpl value, $Res Function(_$IngredientImpl) then) = + __$$IngredientImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int? id, String product_name, String unit, num portion}); +} + +/// @nodoc +class __$$IngredientImplCopyWithImpl<$Res> + extends _$IngredientCopyWithImpl<$Res, _$IngredientImpl> + implements _$$IngredientImplCopyWith<$Res> { + __$$IngredientImplCopyWithImpl( + _$IngredientImpl _value, $Res Function(_$IngredientImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? product_name = null, + Object? unit = null, + Object? portion = null, + }) { + return _then(_$IngredientImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + product_name: null == product_name + ? _value.product_name + : product_name // ignore: cast_nullable_to_non_nullable + as String, + unit: null == unit + ? _value.unit + : unit // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as num, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IngredientImpl implements _Ingredient { + const _$IngredientImpl( + {this.id, + required this.product_name, + required this.unit, + required this.portion}); + + factory _$IngredientImpl.fromJson(Map json) => + _$$IngredientImplFromJson(json); + + @override + final int? id; + @override + final String product_name; + @override + final String unit; + @override + final num portion; + + @override + String toString() { + return 'Ingredient(id: $id, product_name: $product_name, unit: $unit, portion: $portion)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IngredientImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.product_name, product_name) || + other.product_name == product_name) && + (identical(other.unit, unit) || other.unit == unit) && + (identical(other.portion, portion) || other.portion == portion)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, product_name, unit, portion); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IngredientImplCopyWith<_$IngredientImpl> get copyWith => + __$$IngredientImplCopyWithImpl<_$IngredientImpl>(this, _$identity); + + @override + Map toJson() { + return _$$IngredientImplToJson( + this, + ); + } +} + +abstract class _Ingredient implements Ingredient { + const factory _Ingredient( + {final int? id, + required final String product_name, + required final String unit, + required final num portion}) = _$IngredientImpl; + + factory _Ingredient.fromJson(Map json) = + _$IngredientImpl.fromJson; + + @override + int? get id; + @override + String get product_name; + @override + String get unit; + @override + num get portion; + @override + @JsonKey(ignore: true) + _$$IngredientImplCopyWith<_$IngredientImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/recipes/models/ingredient.g.dart b/lib/features/recipes/models/ingredient.g.dart new file mode 100644 index 00000000..7e3c9d91 --- /dev/null +++ b/lib/features/recipes/models/ingredient.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ingredient.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IngredientImpl _$$IngredientImplFromJson(Map json) => + _$IngredientImpl( + id: (json['id'] as num?)?.toInt(), + product_name: json['product_name'] as String, + unit: json['unit'] as String, + portion: json['portion'] as num, + ); + +Map _$$IngredientImplToJson(_$IngredientImpl instance) => + { + 'id': instance.id, + 'product_name': instance.product_name, + 'unit': instance.unit, + 'portion': instance.portion, + }; diff --git a/lib/features/recipes/models/recipe_model.dart b/lib/features/recipes/models/recipe_model.dart new file mode 100644 index 00000000..2ab3ea81 --- /dev/null +++ b/lib/features/recipes/models/recipe_model.dart @@ -0,0 +1,25 @@ +import 'package:eatvio_app/features/recipes/models/image.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; +import 'package:eatvio_app/features/recipes/models/step.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'recipe_model.freezed.dart'; +part 'recipe_model.g.dart'; + +@freezed +class RecipeModel with _$RecipeModel { + const factory RecipeModel({ + int? id, + required List ingredients, + required List steps, + required String state, + required String description, + required String type, + required Image image, + String? video_url, + num? cooking_time, + }) = _RecipeModel; + + factory RecipeModel.fromJson(Map json) => + _$RecipeModelFromJson(json); +} diff --git a/lib/features/recipes/models/recipe_model.freezed.dart b/lib/features/recipes/models/recipe_model.freezed.dart new file mode 100644 index 00000000..984d3bd7 --- /dev/null +++ b/lib/features/recipes/models/recipe_model.freezed.dart @@ -0,0 +1,356 @@ +// 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 'recipe_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'); + +RecipeModel _$RecipeModelFromJson(Map json) { + return _RecipeModel.fromJson(json); +} + +/// @nodoc +mixin _$RecipeModel { + int? get id => throw _privateConstructorUsedError; + List get ingredients => throw _privateConstructorUsedError; + List get steps => throw _privateConstructorUsedError; + String get state => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + Image get image => throw _privateConstructorUsedError; + String? get video_url => throw _privateConstructorUsedError; + num? get cooking_time => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $RecipeModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RecipeModelCopyWith<$Res> { + factory $RecipeModelCopyWith( + RecipeModel value, $Res Function(RecipeModel) then) = + _$RecipeModelCopyWithImpl<$Res, RecipeModel>; + @useResult + $Res call( + {int? id, + List ingredients, + List steps, + String state, + String description, + String type, + Image image, + String? video_url, + num? cooking_time}); + + $ImageCopyWith<$Res> get image; +} + +/// @nodoc +class _$RecipeModelCopyWithImpl<$Res, $Val extends RecipeModel> + implements $RecipeModelCopyWith<$Res> { + _$RecipeModelCopyWithImpl(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 = freezed, + Object? ingredients = null, + Object? steps = null, + Object? state = null, + Object? description = null, + Object? type = null, + Object? image = null, + Object? video_url = freezed, + Object? cooking_time = freezed, + }) { + return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + ingredients: null == ingredients + ? _value.ingredients + : ingredients // ignore: cast_nullable_to_non_nullable + as List, + steps: null == steps + ? _value.steps + : steps // ignore: cast_nullable_to_non_nullable + as List, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + image: null == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as Image, + video_url: freezed == video_url + ? _value.video_url + : video_url // ignore: cast_nullable_to_non_nullable + as String?, + cooking_time: freezed == cooking_time + ? _value.cooking_time + : cooking_time // ignore: cast_nullable_to_non_nullable + as num?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ImageCopyWith<$Res> get image { + return $ImageCopyWith<$Res>(_value.image, (value) { + return _then(_value.copyWith(image: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$RecipeModelImplCopyWith<$Res> + implements $RecipeModelCopyWith<$Res> { + factory _$$RecipeModelImplCopyWith( + _$RecipeModelImpl value, $Res Function(_$RecipeModelImpl) then) = + __$$RecipeModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? id, + List ingredients, + List steps, + String state, + String description, + String type, + Image image, + String? video_url, + num? cooking_time}); + + @override + $ImageCopyWith<$Res> get image; +} + +/// @nodoc +class __$$RecipeModelImplCopyWithImpl<$Res> + extends _$RecipeModelCopyWithImpl<$Res, _$RecipeModelImpl> + implements _$$RecipeModelImplCopyWith<$Res> { + __$$RecipeModelImplCopyWithImpl( + _$RecipeModelImpl _value, $Res Function(_$RecipeModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? ingredients = null, + Object? steps = null, + Object? state = null, + Object? description = null, + Object? type = null, + Object? image = null, + Object? video_url = freezed, + Object? cooking_time = freezed, + }) { + return _then(_$RecipeModelImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + ingredients: null == ingredients + ? _value._ingredients + : ingredients // ignore: cast_nullable_to_non_nullable + as List, + steps: null == steps + ? _value._steps + : steps // ignore: cast_nullable_to_non_nullable + as List, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + image: null == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as Image, + video_url: freezed == video_url + ? _value.video_url + : video_url // ignore: cast_nullable_to_non_nullable + as String?, + cooking_time: freezed == cooking_time + ? _value.cooking_time + : cooking_time // ignore: cast_nullable_to_non_nullable + as num?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$RecipeModelImpl implements _RecipeModel { + const _$RecipeModelImpl( + {this.id, + required final List ingredients, + required final List steps, + required this.state, + required this.description, + required this.type, + required this.image, + this.video_url, + this.cooking_time}) + : _ingredients = ingredients, + _steps = steps; + + factory _$RecipeModelImpl.fromJson(Map json) => + _$$RecipeModelImplFromJson(json); + + @override + final int? id; + final List _ingredients; + @override + List get ingredients { + if (_ingredients is EqualUnmodifiableListView) return _ingredients; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_ingredients); + } + + final List _steps; + @override + List get steps { + if (_steps is EqualUnmodifiableListView) return _steps; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_steps); + } + + @override + final String state; + @override + final String description; + @override + final String type; + @override + final Image image; + @override + final String? video_url; + @override + final num? cooking_time; + + @override + String toString() { + return 'RecipeModel(id: $id, ingredients: $ingredients, steps: $steps, state: $state, description: $description, type: $type, image: $image, video_url: $video_url, cooking_time: $cooking_time)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RecipeModelImpl && + (identical(other.id, id) || other.id == id) && + const DeepCollectionEquality() + .equals(other._ingredients, _ingredients) && + const DeepCollectionEquality().equals(other._steps, _steps) && + (identical(other.state, state) || other.state == state) && + (identical(other.description, description) || + other.description == description) && + (identical(other.type, type) || other.type == type) && + (identical(other.image, image) || other.image == image) && + (identical(other.video_url, video_url) || + other.video_url == video_url) && + (identical(other.cooking_time, cooking_time) || + other.cooking_time == cooking_time)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + const DeepCollectionEquality().hash(_ingredients), + const DeepCollectionEquality().hash(_steps), + state, + description, + type, + image, + video_url, + cooking_time); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RecipeModelImplCopyWith<_$RecipeModelImpl> get copyWith => + __$$RecipeModelImplCopyWithImpl<_$RecipeModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RecipeModelImplToJson( + this, + ); + } +} + +abstract class _RecipeModel implements RecipeModel { + const factory _RecipeModel( + {final int? id, + required final List ingredients, + required final List steps, + required final String state, + required final String description, + required final String type, + required final Image image, + final String? video_url, + final num? cooking_time}) = _$RecipeModelImpl; + + factory _RecipeModel.fromJson(Map json) = + _$RecipeModelImpl.fromJson; + + @override + int? get id; + @override + List get ingredients; + @override + List get steps; + @override + String get state; + @override + String get description; + @override + String get type; + @override + Image get image; + @override + String? get video_url; + @override + num? get cooking_time; + @override + @JsonKey(ignore: true) + _$$RecipeModelImplCopyWith<_$RecipeModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/recipes/models/recipe_model.g.dart b/lib/features/recipes/models/recipe_model.g.dart new file mode 100644 index 00000000..35cd7d47 --- /dev/null +++ b/lib/features/recipes/models/recipe_model.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'recipe_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$RecipeModelImpl _$$RecipeModelImplFromJson(Map json) => + _$RecipeModelImpl( + id: (json['id'] as num?)?.toInt(), + ingredients: (json['ingredients'] as List) + .map((e) => Ingredient.fromJson(e as Map)) + .toList(), + steps: (json['steps'] as List) + .map((e) => Step.fromJson(e as Map)) + .toList(), + state: json['state'] as String, + description: json['description'] as String, + type: json['type'] as String, + image: Image.fromJson(json['image'] as Map), + video_url: json['video_url'] as String?, + cooking_time: json['cooking_time'] as num?, + ); + +Map _$$RecipeModelImplToJson(_$RecipeModelImpl instance) => + { + 'id': instance.id, + 'ingredients': instance.ingredients, + 'steps': instance.steps, + 'state': instance.state, + 'description': instance.description, + 'type': instance.type, + 'image': instance.image, + 'video_url': instance.video_url, + 'cooking_time': instance.cooking_time, + }; diff --git a/lib/features/recipes/models/step.dart b/lib/features/recipes/models/step.dart new file mode 100644 index 00000000..735f72c2 --- /dev/null +++ b/lib/features/recipes/models/step.dart @@ -0,0 +1,17 @@ +import 'package:eatvio_app/features/recipes/models/image.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'step.freezed.dart'; + +part 'step.g.dart'; + +@freezed +class Step with _$Step { + const factory Step({ + int? id, + Image? image, + String? description, + }) = _Step; + + factory Step.fromJson(Map json) => _$StepFromJson(json); +} diff --git a/lib/features/recipes/models/step.freezed.dart b/lib/features/recipes/models/step.freezed.dart new file mode 100644 index 00000000..1ff4d0c1 --- /dev/null +++ b/lib/features/recipes/models/step.freezed.dart @@ -0,0 +1,199 @@ +// 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 'step.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'); + +Step _$StepFromJson(Map json) { + return _Step.fromJson(json); +} + +/// @nodoc +mixin _$Step { + int? get id => throw _privateConstructorUsedError; + Image? get image => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $StepCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $StepCopyWith<$Res> { + factory $StepCopyWith(Step value, $Res Function(Step) then) = + _$StepCopyWithImpl<$Res, Step>; + @useResult + $Res call({int? id, Image? image, String? description}); + + $ImageCopyWith<$Res>? get image; +} + +/// @nodoc +class _$StepCopyWithImpl<$Res, $Val extends Step> + implements $StepCopyWith<$Res> { + _$StepCopyWithImpl(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 = freezed, + Object? image = freezed, + Object? description = freezed, + }) { + return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as Image?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ImageCopyWith<$Res>? get image { + if (_value.image == null) { + return null; + } + + return $ImageCopyWith<$Res>(_value.image!, (value) { + return _then(_value.copyWith(image: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$StepImplCopyWith<$Res> implements $StepCopyWith<$Res> { + factory _$$StepImplCopyWith( + _$StepImpl value, $Res Function(_$StepImpl) then) = + __$$StepImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int? id, Image? image, String? description}); + + @override + $ImageCopyWith<$Res>? get image; +} + +/// @nodoc +class __$$StepImplCopyWithImpl<$Res> + extends _$StepCopyWithImpl<$Res, _$StepImpl> + implements _$$StepImplCopyWith<$Res> { + __$$StepImplCopyWithImpl(_$StepImpl _value, $Res Function(_$StepImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? image = freezed, + Object? description = freezed, + }) { + return _then(_$StepImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as Image?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$StepImpl implements _Step { + const _$StepImpl({this.id, this.image, this.description}); + + factory _$StepImpl.fromJson(Map json) => + _$$StepImplFromJson(json); + + @override + final int? id; + @override + final Image? image; + @override + final String? description; + + @override + String toString() { + return 'Step(id: $id, image: $image, description: $description)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StepImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.image, image) || other.image == image) && + (identical(other.description, description) || + other.description == description)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, image, description); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$StepImplCopyWith<_$StepImpl> get copyWith => + __$$StepImplCopyWithImpl<_$StepImpl>(this, _$identity); + + @override + Map toJson() { + return _$$StepImplToJson( + this, + ); + } +} + +abstract class _Step implements Step { + const factory _Step( + {final int? id, + final Image? image, + final String? description}) = _$StepImpl; + + factory _Step.fromJson(Map json) = _$StepImpl.fromJson; + + @override + int? get id; + @override + Image? get image; + @override + String? get description; + @override + @JsonKey(ignore: true) + _$$StepImplCopyWith<_$StepImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/recipes/models/step.g.dart b/lib/features/recipes/models/step.g.dart new file mode 100644 index 00000000..688769ce --- /dev/null +++ b/lib/features/recipes/models/step.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'step.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$StepImpl _$$StepImplFromJson(Map json) => _$StepImpl( + id: (json['id'] as num?)?.toInt(), + image: json['image'] == null + ? null + : Image.fromJson(json['image'] as Map), + description: json['description'] as String?, + ); + +Map _$$StepImplToJson(_$StepImpl instance) => + { + 'id': instance.id, + 'image': instance.image, + 'description': instance.description, + }; diff --git a/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart b/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart new file mode 100644 index 00000000..5ff4f3ec --- /dev/null +++ b/lib/features/recipes/presentation/bloc/recipe_create_cubit.dart @@ -0,0 +1,170 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; +import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart'; +import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; + +class RecipeCreateCubit extends Cubit { + final RecipeRepository recipeRepository; + + RecipeCreateCubit({required this.recipeRepository}) + : super(const RecipeCreateState.loading()); + + loadRecipeCreate() { + emit(const RecipeCreateStateLoading()); + try { + emit(RecipeCreateState.loaded( + picker: ImagePicker(), + titleController: TextEditingController(), + productNameController: TextEditingController(), + ingredientController: TextEditingController(), + stepDescriptionController: TextEditingController(), + youtubeLinkController: TextEditingController(), + stepControllers: [TextEditingController()], + stepImages: [null], + ingredients: [], + numberOfSteps: 1, + ingredientAsItems: false, + types: [ + SelectionPopupModel(title: 'Первые блюда'), + SelectionPopupModel(title: 'Вторые блюда'), + SelectionPopupModel(title: 'Cалаты'), + SelectionPopupModel(title: 'Перекусы/закуски'), + SelectionPopupModel(title: 'Сладости'), + SelectionPopupModel(title: 'Напитки'), + SelectionPopupModel(title: 'Прочее'), + ], + cookingTimes: [ + SelectionPopupModel(title: '10 минут'), + SelectionPopupModel(title: '20 минут'), + SelectionPopupModel(title: '30 минут'), + SelectionPopupModel(title: '1 час'), + SelectionPopupModel(title: '2 часа'), + SelectionPopupModel(title: '10 дней'), + ], + statuses: [ + SelectionPopupModel(title: 'Активна'), + SelectionPopupModel(title: 'Неактивна'), + ], + )); + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + uploadRecipeImage(XFile? pickedFile) { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + var updatedState = currentState.copyWith( + recipeImage: pickedFile, + ); + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + uploadStepImage(XFile? pickedFile, int stepNumber) { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + + var updatedStepImages = List.from(currentState.stepImages); + + if (stepNumber >= 0 && stepNumber < updatedStepImages.length) { + updatedStepImages[stepNumber] = pickedFile; + } + + var updatedState = currentState.copyWith( + stepImages: updatedStepImages, + ); + + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + switchUnits(bool value) { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + var updatedState = currentState.copyWith(ingredientAsItems: value); + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + addIngredient(Ingredient ingredient) { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + + bool ingredientExists = currentState.ingredients.any( + (existingIngredient) => + existingIngredient.product_name == ingredient.product_name); + + if (ingredientExists) { + emit(RecipeCreateState.error( + errorMessage: 'Ингредиент уже существует.')); + return; + } + + List newIngredients = + List.from(currentState.ingredients); + newIngredients.add(ingredient); + + var updatedState = currentState.copyWith(ingredients: newIngredients); + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + addStep() { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + List newStepImages = List.from(currentState.stepImages); + newStepImages.add(null); + List newStepControllers = + List.from(currentState.stepControllers); + newStepControllers.add(TextEditingController()); + var updatedState = currentState.copyWith( + stepImages: newStepImages, + stepControllers: newStepControllers, + numberOfSteps: currentState.numberOfSteps + 1); + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } + + removeStep() { + try { + if (state is RecipeCreateStateLoaded) { + var currentState = state as RecipeCreateStateLoaded; + List newStepImages = currentState.stepImages; + newStepImages.removeLast(); + List newStepControllers = + List.from(currentState.stepControllers); + newStepControllers.removeLast(); + var updatedState = currentState.copyWith( + stepImages: newStepImages, + stepControllers: newStepControllers, + numberOfSteps: currentState.numberOfSteps - 1); + emit(updatedState); + } + } catch (e) { + emit(RecipeCreateState.error(errorMessage: e.toString())); + } + } +} diff --git a/lib/features/recipes/presentation/bloc/recipe_create_state.dart b/lib/features/recipes/presentation/bloc/recipe_create_state.dart new file mode 100644 index 00000000..796e27e7 --- /dev/null +++ b/lib/features/recipes/presentation/bloc/recipe_create_state.dart @@ -0,0 +1,39 @@ +import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../../../../data/models/selectionPopupModel/selection_popup_model.dart'; + +part 'recipe_create_state.freezed.dart'; + +@freezed +abstract class RecipeCreateState with _$RecipeCreateState { + const factory RecipeCreateState() = _RecipeCreateState; + + const factory RecipeCreateState.loading() = RecipeCreateStateLoading; + + const factory RecipeCreateState.loaded({ + RecipeModel? recipe, + required ImagePicker picker, + required TextEditingController titleController, + required TextEditingController productNameController, + required TextEditingController ingredientController, + required TextEditingController stepDescriptionController, + required TextEditingController youtubeLinkController, + required List stepControllers, + XFile? recipeImage, + required List stepImages, + required List ingredients, + required int numberOfSteps, + required bool ingredientAsItems, + required List types, + required List cookingTimes, + required List statuses, + }) = RecipeCreateStateLoaded; + + const factory RecipeCreateState.error({ + required String errorMessage, + }) = RecipeCreateStateError; +} diff --git a/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart b/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart new file mode 100644 index 00000000..783252e9 --- /dev/null +++ b/lib/features/recipes/presentation/bloc/recipe_create_state.freezed.dart @@ -0,0 +1,1189 @@ +// 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 'recipe_create_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 _$RecipeCreateState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses) + loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_RecipeCreateState value) $default, { + required TResult Function(RecipeCreateStateLoading value) loading, + required TResult Function(RecipeCreateStateLoaded value) loaded, + required TResult Function(RecipeCreateStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_RecipeCreateState value)? $default, { + TResult? Function(RecipeCreateStateLoading value)? loading, + TResult? Function(RecipeCreateStateLoaded value)? loaded, + TResult? Function(RecipeCreateStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_RecipeCreateState value)? $default, { + TResult Function(RecipeCreateStateLoading value)? loading, + TResult Function(RecipeCreateStateLoaded value)? loaded, + TResult Function(RecipeCreateStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RecipeCreateStateCopyWith<$Res> { + factory $RecipeCreateStateCopyWith( + RecipeCreateState value, $Res Function(RecipeCreateState) then) = + _$RecipeCreateStateCopyWithImpl<$Res, RecipeCreateState>; +} + +/// @nodoc +class _$RecipeCreateStateCopyWithImpl<$Res, $Val extends RecipeCreateState> + implements $RecipeCreateStateCopyWith<$Res> { + _$RecipeCreateStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$RecipeCreateStateImplCopyWith<$Res> { + factory _$$RecipeCreateStateImplCopyWith(_$RecipeCreateStateImpl value, + $Res Function(_$RecipeCreateStateImpl) then) = + __$$RecipeCreateStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RecipeCreateStateImplCopyWithImpl<$Res> + extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateImpl> + implements _$$RecipeCreateStateImplCopyWith<$Res> { + __$$RecipeCreateStateImplCopyWithImpl(_$RecipeCreateStateImpl _value, + $Res Function(_$RecipeCreateStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$RecipeCreateStateImpl implements _RecipeCreateState { + const _$RecipeCreateStateImpl(); + + @override + String toString() { + return 'RecipeCreateState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RecipeCreateStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses) + loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_RecipeCreateState value) $default, { + required TResult Function(RecipeCreateStateLoading value) loading, + required TResult Function(RecipeCreateStateLoaded value) loaded, + required TResult Function(RecipeCreateStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_RecipeCreateState value)? $default, { + TResult? Function(RecipeCreateStateLoading value)? loading, + TResult? Function(RecipeCreateStateLoaded value)? loaded, + TResult? Function(RecipeCreateStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_RecipeCreateState value)? $default, { + TResult Function(RecipeCreateStateLoading value)? loading, + TResult Function(RecipeCreateStateLoaded value)? loaded, + TResult Function(RecipeCreateStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _RecipeCreateState implements RecipeCreateState { + const factory _RecipeCreateState() = _$RecipeCreateStateImpl; +} + +/// @nodoc +abstract class _$$RecipeCreateStateLoadingImplCopyWith<$Res> { + factory _$$RecipeCreateStateLoadingImplCopyWith( + _$RecipeCreateStateLoadingImpl value, + $Res Function(_$RecipeCreateStateLoadingImpl) then) = + __$$RecipeCreateStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RecipeCreateStateLoadingImplCopyWithImpl<$Res> + extends _$RecipeCreateStateCopyWithImpl<$Res, + _$RecipeCreateStateLoadingImpl> + implements _$$RecipeCreateStateLoadingImplCopyWith<$Res> { + __$$RecipeCreateStateLoadingImplCopyWithImpl( + _$RecipeCreateStateLoadingImpl _value, + $Res Function(_$RecipeCreateStateLoadingImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$RecipeCreateStateLoadingImpl implements RecipeCreateStateLoading { + const _$RecipeCreateStateLoadingImpl(); + + @override + String toString() { + return 'RecipeCreateState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RecipeCreateStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses) + loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_RecipeCreateState value) $default, { + required TResult Function(RecipeCreateStateLoading value) loading, + required TResult Function(RecipeCreateStateLoaded value) loaded, + required TResult Function(RecipeCreateStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_RecipeCreateState value)? $default, { + TResult? Function(RecipeCreateStateLoading value)? loading, + TResult? Function(RecipeCreateStateLoaded value)? loaded, + TResult? Function(RecipeCreateStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_RecipeCreateState value)? $default, { + TResult Function(RecipeCreateStateLoading value)? loading, + TResult Function(RecipeCreateStateLoaded value)? loaded, + TResult Function(RecipeCreateStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class RecipeCreateStateLoading implements RecipeCreateState { + const factory RecipeCreateStateLoading() = _$RecipeCreateStateLoadingImpl; +} + +/// @nodoc +abstract class _$$RecipeCreateStateLoadedImplCopyWith<$Res> { + factory _$$RecipeCreateStateLoadedImplCopyWith( + _$RecipeCreateStateLoadedImpl value, + $Res Function(_$RecipeCreateStateLoadedImpl) then) = + __$$RecipeCreateStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call( + {RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses}); + + $RecipeModelCopyWith<$Res>? get recipe; +} + +/// @nodoc +class __$$RecipeCreateStateLoadedImplCopyWithImpl<$Res> + extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateLoadedImpl> + implements _$$RecipeCreateStateLoadedImplCopyWith<$Res> { + __$$RecipeCreateStateLoadedImplCopyWithImpl( + _$RecipeCreateStateLoadedImpl _value, + $Res Function(_$RecipeCreateStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recipe = freezed, + Object? picker = null, + Object? titleController = null, + Object? productNameController = null, + Object? ingredientController = null, + Object? stepDescriptionController = null, + Object? youtubeLinkController = null, + Object? stepControllers = null, + Object? recipeImage = freezed, + Object? stepImages = null, + Object? ingredients = null, + Object? numberOfSteps = null, + Object? ingredientAsItems = null, + Object? types = null, + Object? cookingTimes = null, + Object? statuses = null, + }) { + return _then(_$RecipeCreateStateLoadedImpl( + recipe: freezed == recipe + ? _value.recipe + : recipe // ignore: cast_nullable_to_non_nullable + as RecipeModel?, + picker: null == picker + ? _value.picker + : picker // ignore: cast_nullable_to_non_nullable + as ImagePicker, + titleController: null == titleController + ? _value.titleController + : titleController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + productNameController: null == productNameController + ? _value.productNameController + : productNameController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + ingredientController: null == ingredientController + ? _value.ingredientController + : ingredientController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + stepDescriptionController: null == stepDescriptionController + ? _value.stepDescriptionController + : stepDescriptionController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + youtubeLinkController: null == youtubeLinkController + ? _value.youtubeLinkController + : youtubeLinkController // ignore: cast_nullable_to_non_nullable + as TextEditingController, + stepControllers: null == stepControllers + ? _value._stepControllers + : stepControllers // ignore: cast_nullable_to_non_nullable + as List, + recipeImage: freezed == recipeImage + ? _value.recipeImage + : recipeImage // ignore: cast_nullable_to_non_nullable + as XFile?, + stepImages: null == stepImages + ? _value._stepImages + : stepImages // ignore: cast_nullable_to_non_nullable + as List, + ingredients: null == ingredients + ? _value._ingredients + : ingredients // ignore: cast_nullable_to_non_nullable + as List, + numberOfSteps: null == numberOfSteps + ? _value.numberOfSteps + : numberOfSteps // ignore: cast_nullable_to_non_nullable + as int, + ingredientAsItems: null == ingredientAsItems + ? _value.ingredientAsItems + : ingredientAsItems // ignore: cast_nullable_to_non_nullable + as bool, + types: null == types + ? _value._types + : types // ignore: cast_nullable_to_non_nullable + as List, + cookingTimes: null == cookingTimes + ? _value._cookingTimes + : cookingTimes // ignore: cast_nullable_to_non_nullable + as List, + statuses: null == statuses + ? _value._statuses + : statuses // ignore: cast_nullable_to_non_nullable + as List, + )); + } + + @override + @pragma('vm:prefer-inline') + $RecipeModelCopyWith<$Res>? get recipe { + if (_value.recipe == null) { + return null; + } + + return $RecipeModelCopyWith<$Res>(_value.recipe!, (value) { + return _then(_value.copyWith(recipe: value)); + }); + } +} + +/// @nodoc + +class _$RecipeCreateStateLoadedImpl implements RecipeCreateStateLoaded { + const _$RecipeCreateStateLoadedImpl( + {this.recipe, + required this.picker, + required this.titleController, + required this.productNameController, + required this.ingredientController, + required this.stepDescriptionController, + required this.youtubeLinkController, + required final List stepControllers, + this.recipeImage, + required final List stepImages, + required final List ingredients, + required this.numberOfSteps, + required this.ingredientAsItems, + required final List types, + required final List cookingTimes, + required final List statuses}) + : _stepControllers = stepControllers, + _stepImages = stepImages, + _ingredients = ingredients, + _types = types, + _cookingTimes = cookingTimes, + _statuses = statuses; + + @override + final RecipeModel? recipe; + @override + final ImagePicker picker; + @override + final TextEditingController titleController; + @override + final TextEditingController productNameController; + @override + final TextEditingController ingredientController; + @override + final TextEditingController stepDescriptionController; + @override + final TextEditingController youtubeLinkController; + final List _stepControllers; + @override + List get stepControllers { + if (_stepControllers is EqualUnmodifiableListView) return _stepControllers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_stepControllers); + } + + @override + final XFile? recipeImage; + final List _stepImages; + @override + List get stepImages { + if (_stepImages is EqualUnmodifiableListView) return _stepImages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_stepImages); + } + + final List _ingredients; + @override + List get ingredients { + if (_ingredients is EqualUnmodifiableListView) return _ingredients; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_ingredients); + } + + @override + final int numberOfSteps; + @override + final bool ingredientAsItems; + final List _types; + @override + List get types { + if (_types is EqualUnmodifiableListView) return _types; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_types); + } + + final List _cookingTimes; + @override + List get cookingTimes { + if (_cookingTimes is EqualUnmodifiableListView) return _cookingTimes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_cookingTimes); + } + + final List _statuses; + @override + List get statuses { + if (_statuses is EqualUnmodifiableListView) return _statuses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_statuses); + } + + @override + String toString() { + return 'RecipeCreateState.loaded(recipe: $recipe, picker: $picker, titleController: $titleController, productNameController: $productNameController, ingredientController: $ingredientController, stepDescriptionController: $stepDescriptionController, youtubeLinkController: $youtubeLinkController, stepControllers: $stepControllers, recipeImage: $recipeImage, stepImages: $stepImages, ingredients: $ingredients, numberOfSteps: $numberOfSteps, ingredientAsItems: $ingredientAsItems, types: $types, cookingTimes: $cookingTimes, statuses: $statuses)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RecipeCreateStateLoadedImpl && + (identical(other.recipe, recipe) || other.recipe == recipe) && + (identical(other.picker, picker) || other.picker == picker) && + (identical(other.titleController, titleController) || + other.titleController == titleController) && + (identical(other.productNameController, productNameController) || + other.productNameController == productNameController) && + (identical(other.ingredientController, ingredientController) || + other.ingredientController == ingredientController) && + (identical(other.stepDescriptionController, + stepDescriptionController) || + other.stepDescriptionController == stepDescriptionController) && + (identical(other.youtubeLinkController, youtubeLinkController) || + other.youtubeLinkController == youtubeLinkController) && + const DeepCollectionEquality() + .equals(other._stepControllers, _stepControllers) && + (identical(other.recipeImage, recipeImage) || + other.recipeImage == recipeImage) && + const DeepCollectionEquality() + .equals(other._stepImages, _stepImages) && + const DeepCollectionEquality() + .equals(other._ingredients, _ingredients) && + (identical(other.numberOfSteps, numberOfSteps) || + other.numberOfSteps == numberOfSteps) && + (identical(other.ingredientAsItems, ingredientAsItems) || + other.ingredientAsItems == ingredientAsItems) && + const DeepCollectionEquality().equals(other._types, _types) && + const DeepCollectionEquality() + .equals(other._cookingTimes, _cookingTimes) && + const DeepCollectionEquality().equals(other._statuses, _statuses)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + recipe, + picker, + titleController, + productNameController, + ingredientController, + stepDescriptionController, + youtubeLinkController, + const DeepCollectionEquality().hash(_stepControllers), + recipeImage, + const DeepCollectionEquality().hash(_stepImages), + const DeepCollectionEquality().hash(_ingredients), + numberOfSteps, + ingredientAsItems, + const DeepCollectionEquality().hash(_types), + const DeepCollectionEquality().hash(_cookingTimes), + const DeepCollectionEquality().hash(_statuses)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RecipeCreateStateLoadedImplCopyWith<_$RecipeCreateStateLoadedImpl> + get copyWith => __$$RecipeCreateStateLoadedImplCopyWithImpl< + _$RecipeCreateStateLoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses) + loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded( + recipe, + picker, + titleController, + productNameController, + ingredientController, + stepDescriptionController, + youtubeLinkController, + stepControllers, + recipeImage, + stepImages, + ingredients, + numberOfSteps, + ingredientAsItems, + types, + cookingTimes, + statuses); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call( + recipe, + picker, + titleController, + productNameController, + ingredientController, + stepDescriptionController, + youtubeLinkController, + stepControllers, + recipeImage, + stepImages, + ingredients, + numberOfSteps, + ingredientAsItems, + types, + cookingTimes, + statuses); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded( + recipe, + picker, + titleController, + productNameController, + ingredientController, + stepDescriptionController, + youtubeLinkController, + stepControllers, + recipeImage, + stepImages, + ingredients, + numberOfSteps, + ingredientAsItems, + types, + cookingTimes, + statuses); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_RecipeCreateState value) $default, { + required TResult Function(RecipeCreateStateLoading value) loading, + required TResult Function(RecipeCreateStateLoaded value) loaded, + required TResult Function(RecipeCreateStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_RecipeCreateState value)? $default, { + TResult? Function(RecipeCreateStateLoading value)? loading, + TResult? Function(RecipeCreateStateLoaded value)? loaded, + TResult? Function(RecipeCreateStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_RecipeCreateState value)? $default, { + TResult Function(RecipeCreateStateLoading value)? loading, + TResult Function(RecipeCreateStateLoaded value)? loaded, + TResult Function(RecipeCreateStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class RecipeCreateStateLoaded implements RecipeCreateState { + const factory RecipeCreateStateLoaded( + {final RecipeModel? recipe, + required final ImagePicker picker, + required final TextEditingController titleController, + required final TextEditingController productNameController, + required final TextEditingController ingredientController, + required final TextEditingController stepDescriptionController, + required final TextEditingController youtubeLinkController, + required final List stepControllers, + final XFile? recipeImage, + required final List stepImages, + required final List ingredients, + required final int numberOfSteps, + required final bool ingredientAsItems, + required final List types, + required final List cookingTimes, + required final List statuses}) = + _$RecipeCreateStateLoadedImpl; + + RecipeModel? get recipe; + ImagePicker get picker; + TextEditingController get titleController; + TextEditingController get productNameController; + TextEditingController get ingredientController; + TextEditingController get stepDescriptionController; + TextEditingController get youtubeLinkController; + List get stepControllers; + XFile? get recipeImage; + List get stepImages; + List get ingredients; + int get numberOfSteps; + bool get ingredientAsItems; + List get types; + List get cookingTimes; + List get statuses; + @JsonKey(ignore: true) + _$$RecipeCreateStateLoadedImplCopyWith<_$RecipeCreateStateLoadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RecipeCreateStateErrorImplCopyWith<$Res> { + factory _$$RecipeCreateStateErrorImplCopyWith( + _$RecipeCreateStateErrorImpl value, + $Res Function(_$RecipeCreateStateErrorImpl) then) = + __$$RecipeCreateStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$RecipeCreateStateErrorImplCopyWithImpl<$Res> + extends _$RecipeCreateStateCopyWithImpl<$Res, _$RecipeCreateStateErrorImpl> + implements _$$RecipeCreateStateErrorImplCopyWith<$Res> { + __$$RecipeCreateStateErrorImplCopyWithImpl( + _$RecipeCreateStateErrorImpl _value, + $Res Function(_$RecipeCreateStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$RecipeCreateStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$RecipeCreateStateErrorImpl implements RecipeCreateStateError { + const _$RecipeCreateStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'RecipeCreateState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RecipeCreateStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RecipeCreateStateErrorImplCopyWith<_$RecipeCreateStateErrorImpl> + get copyWith => __$$RecipeCreateStateErrorImplCopyWithImpl< + _$RecipeCreateStateErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses) + loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function( + RecipeModel? recipe, + ImagePicker picker, + TextEditingController titleController, + TextEditingController productNameController, + TextEditingController ingredientController, + TextEditingController stepDescriptionController, + TextEditingController youtubeLinkController, + List stepControllers, + XFile? recipeImage, + List stepImages, + List ingredients, + int numberOfSteps, + bool ingredientAsItems, + List types, + List cookingTimes, + List statuses)? + loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_RecipeCreateState value) $default, { + required TResult Function(RecipeCreateStateLoading value) loading, + required TResult Function(RecipeCreateStateLoaded value) loaded, + required TResult Function(RecipeCreateStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_RecipeCreateState value)? $default, { + TResult? Function(RecipeCreateStateLoading value)? loading, + TResult? Function(RecipeCreateStateLoaded value)? loaded, + TResult? Function(RecipeCreateStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_RecipeCreateState value)? $default, { + TResult Function(RecipeCreateStateLoading value)? loading, + TResult Function(RecipeCreateStateLoaded value)? loaded, + TResult Function(RecipeCreateStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class RecipeCreateStateError implements RecipeCreateState { + const factory RecipeCreateStateError({required final String errorMessage}) = + _$RecipeCreateStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$RecipeCreateStateErrorImplCopyWith<_$RecipeCreateStateErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index e087c34e..0f5973cb 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -1,72 +1,56 @@ -//import 'dart:io'; - import 'dart:io'; - import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; +import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; +import 'package:eatvio_app/features/recipes/models/image.dart' as image; +import 'package:eatvio_app/features/recipes/models/step.dart' as step; +import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart'; +import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart'; import 'package:eatvio_app/widgets/custom_drop_down.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; -import 'package:image_picker/image_picker.dart'; import 'package:flutter/material.dart'; -import '../../../../presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:image_picker/image_picker.dart'; -class RecipeCreateScreen extends StatefulWidget { - RecipeCreateScreen({ - super.key, - }); - - @override - State createState() => _RecipeCreateScreenState(); -} - -class _RecipeCreateScreenState extends State { - final TextEditingController titleController = TextEditingController(); - - final TextEditingController productNameController = TextEditingController(); - - final TextEditingController portionController = TextEditingController(); - - final TextEditingController stepDescriptionController = - TextEditingController(); - - final TextEditingController youtubeLinkController = TextEditingController(); - - final List types = [ - SelectionPopupModel(title: 'Первые блюда'), - SelectionPopupModel(title: 'Вторые блюда'), - SelectionPopupModel(title: 'Cалаты'), - SelectionPopupModel(title: 'Перекусы/закуски'), - SelectionPopupModel(title: 'Сладости'), - SelectionPopupModel(title: 'Напитки'), - SelectionPopupModel(title: 'Прочее'), - ]; - - final List cookingTimes = [ - SelectionPopupModel(title: '10 минут'), - SelectionPopupModel(title: '20 минут'), - SelectionPopupModel(title: '30 минут'), - SelectionPopupModel(title: '1 час'), - SelectionPopupModel(title: '2 часа'), - SelectionPopupModel(title: '10 дней'), - ]; - - final List statuses = [ - SelectionPopupModel(title: 'Активна'), - SelectionPopupModel(title: 'Неактивна'), - ]; - - int numberOfSteps = 1; - XFile? recipeImage; // Переменная для общей фотографии рецепта - List stepImages = []; // Список фотографий для каждого шага - final ImagePicker _picker = ImagePicker(); - bool portionAsItems = false; - - @override - void initState() { - super.initState(); - stepImages.add(null); // Добавляем начальный элемент для первого шага +// ignore: must_be_immutable +class RecipeCreateScreen extends StatelessWidget { + RecipeCreateScreen({Key? key}) : super(key: key); + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is RecipeCreateStateLoaded) { + return _buildRecipeCreate(context, state); + } else if (state is RecipeCreateStateLoading) { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } else if (state is RecipeCreateStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text(state.errorMessage), + ), + ), + ); + } else + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text("Неопределенная ошибка"), + ), + ), + ); + }, + ); } - Widget build(BuildContext context) { + Widget _buildRecipeCreate( + BuildContext context, RecipeCreateStateLoaded state) { return Scaffold( appBar: AppBar( toolbarHeight: 0, @@ -94,30 +78,28 @@ class _RecipeCreateScreenState extends State { Text('Заголовок'), SizedBox(height: 8), CustomTextFormField( - validator: _validateTitle, fillColor: appTheme.gray50, - controller: titleController, + controller: state.titleController, hintText: 'Заголовок', ), SizedBox(height: 16), Text('Тип блюда'), SizedBox(height: 8), CustomDropDown( - value: types[0], - items: types, + value: state.types[0], + items: state.types, ), SizedBox(height: 16), Text('Изображение'), GestureDetector( onTap: () async { - final pickedFile = await _picker.pickImage( - source: ImageSource.gallery); - setState(() { - recipeImage = - pickedFile; // Сохраняем общее изображение рецепта - }); + final pickedFile = await state.picker + .pickImage(source: ImageSource.gallery); + context + .read() + .uploadRecipeImage(pickedFile); }, - child: recipeImage == null + child: state.recipeImage == null ? Container( height: 128, decoration: AppDecoration.fillGray.copyWith( @@ -139,49 +121,53 @@ class _RecipeCreateScreenState extends State { ], ), ) - : Image.file(File(recipeImage!.path), + : Image.file(File(state.recipeImage!.path), height: 200, fit: BoxFit.cover), ), SizedBox(height: 16), Text('Время приготовления'), SizedBox(height: 8), CustomDropDown( - items: cookingTimes, - value: cookingTimes[0], + items: state.cookingTimes, + value: state.cookingTimes[0], ), SizedBox(height: 16), Text('Статус'), SizedBox(height: 8), CustomDropDown( - items: statuses, - value: statuses[0], + items: state.statuses, + value: state.statuses[0], ), SizedBox(height: 24), + if (!state.ingredients.isEmpty) + for (int j = 0; j < state.ingredients.length; j++) + _buildIngredient(state.ingredients[j]), + SizedBox(height: 24), CustomTextFormField( hintText: 'Введите название продукта', - controller: productNameController, + controller: state.productNameController, ), SizedBox(height: 24), CustomTextFormField( hintText: 'Порция', fillColor: appTheme.gray50, - controller: portionController, + controller: state.ingredientController, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], ), SizedBox(height: 8), Row(children: [ Text('гр'), Switch( - value: portionAsItems, - onChanged: (bool value) { - setState(() { - portionAsItems = value; - }); - }, - activeColor: appTheme.blueA400, // Цвет активной части - inactiveThumbColor: - Colors.grey, // Цвет ползунка, когда выключен - inactiveTrackColor: Colors - .grey.shade300, // Цвет трека, когда выключен + value: state.ingredientAsItems, + onChanged: (bool value) => context + .read() + .switchUnits(value), + activeColor: appTheme.blueA400, + inactiveThumbColor: Colors.grey, + inactiveTrackColor: Colors.grey.shade300, ), Text('шт'), ]), @@ -190,19 +176,27 @@ class _RecipeCreateScreenState extends State { height: 48, width: MediaQuery.of(context).size.width, child: FloatingActionButton.extended( - highlightElevation: 0, - elevation: 0, - focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - backgroundColor: appTheme.blueA400, - onPressed: () {}, - label: Text( - '+', - style: theme.textTheme.titleSmall, - ), - ), + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + backgroundColor: appTheme.blueA400, + label: Text( + '+', + style: theme.textTheme.titleSmall, + ), + onPressed: () => context + .read() + .addIngredient(new Ingredient( + product_name: + state.productNameController.text, + portion: int.parse( + state.ingredientController.text), + unit: state.ingredientAsItems + ? 'pcs' + : 'gr'))), ), SizedBox(height: 24), Text('Описание рецепта'), @@ -210,10 +204,11 @@ class _RecipeCreateScreenState extends State { CustomTextFormField( maxLines: 8, minLines: 8, - controller: stepDescriptionController, + controller: state.stepDescriptionController, ), SizedBox(height: 24), - for (int i = 0; i < numberOfSteps; i++) _buildStep(i), + for (int i = 0; i < state.numberOfSteps; i++) + _buildStep(context, i, state), SizedBox(height: 24), Container( height: 48, @@ -226,12 +221,8 @@ class _RecipeCreateScreenState extends State { borderRadius: BorderRadius.circular(8), ), backgroundColor: appTheme.blueA400, - onPressed: () { - setState(() { - stepImages.add(null); - numberOfSteps++; - }); - }, + onPressed: () => + context.read().addStep(), label: Text( '+', style: theme.textTheme.titleSmall, @@ -250,14 +241,8 @@ class _RecipeCreateScreenState extends State { borderRadius: BorderRadius.circular(8), ), backgroundColor: Colors.grey, - onPressed: () { - setState(() { - if (numberOfSteps > 1) { - numberOfSteps--; - stepImages.removeLast(); - } - }); - }, + onPressed: () => + context.read().removeStep(), label: Text( '-', style: theme.textTheme.titleSmall, @@ -270,7 +255,7 @@ class _RecipeCreateScreenState extends State { CustomTextFormField( hintText: 'Введите описание шага', fillColor: appTheme.gray50, - controller: youtubeLinkController, + controller: state.youtubeLinkController, ), SizedBox(height: 24), Container( @@ -300,25 +285,25 @@ class _RecipeCreateScreenState extends State { ); } - _buildStep(int stepNumber) { + _buildStep( + BuildContext context, int stepNumber, RecipeCreateStateLoaded state) { return Column( children: [ Row(children: [ Text('Шаг ${stepNumber + 1}'), Spacer(), - Text('Всего шагов: $numberOfSteps') + Text('Всего шагов: ${state.numberOfSteps}') ]), SizedBox(height: 8), GestureDetector( onTap: () async { final pickedFile = - await _picker.pickImage(source: ImageSource.gallery); - setState(() { - stepImages[stepNumber] = - pickedFile; // Сохраняем изображение для текущего шага - }); + await state.picker.pickImage(source: ImageSource.gallery); + context + .read() + .uploadStepImage(pickedFile, stepNumber); }, - child: stepImages[stepNumber] == null + child: state.stepImages[stepNumber] == null ? Container( height: 128, decoration: AppDecoration.fillGray.copyWith( @@ -341,7 +326,7 @@ class _RecipeCreateScreenState extends State { ], ), ) - : Image.file(File(stepImages[stepNumber]!.path), + : Image.file(File(state.stepImages[stepNumber]!.path), height: 150, fit: BoxFit.cover)), SizedBox(height: 16), CustomTextFormField( @@ -349,28 +334,96 @@ class _RecipeCreateScreenState extends State { maxLines: 8, minLines: 8, fillColor: appTheme.gray50, + controller: state.stepControllers[stepNumber], ), SizedBox(height: 24), ], ); } -} -addImage(BuildContext context, bool isLoading) { - if (!isLoading) { - return () async { - final picker = ImagePicker(); - XFile? image = await picker.pickImage(source: ImageSource.gallery); - if (image != null) { - context.read().add(UploadImages(image)); + _buildIngredient(Ingredient ingredient) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 8.h, + vertical: 6.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only( + left: 7.h, + top: 5.v, + ), + child: Text( + ingredient.product_name.length > 8 + ? ingredient.product_name.substring(0, 8) + : ingredient.product_name, + style: CustomTextStyles.titleMedium16, + ), + ), + Padding( + padding: EdgeInsets.only( + left: 20.h, + top: 3.v, + bottom: 3.v, + ), + child: Text( + ingredient.portion.toString() + + ' ' + + ingredient.unit.toString().tr, + ), + ), + Spacer(), + GestureDetector( + // onTap: removeIngredient(context, ingredient), + child: Padding( + padding: const EdgeInsets.only(right: 10), + child: Icon( + Icons.close, + color: appTheme.blueGray300, + ), + ), + ), + ], + ), + ); + } + + RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) { + // Подготовка ингредиентов + List ingredients = state.ingredients; + + // Подготовка шагов (например, можно создать шаги из stepControllers) + List steps = []; + for (int i = 0; i < state.stepControllers.length; i++) { + if (state.stepControllers[i].text.isNotEmpty) { + steps.add(step.Step(description: state.stepControllers[i].text)); } - }; - } -} + } -String? _validateTitle(value) { - if (value!.isEmpty) { - return 'Пожалуйста, введите заголовок'; + // Подготовка изображения + image.Image pic = image.Image(path: state.recipeImage?.path ?? ''); + + // Создание экземпляра RecipeModel + return RecipeModel( + id: null, + ingredients: ingredients, + steps: steps, + state: 'draft', + description: state.titleController.text, + type: state.types.first.value, + image: pic, + video_url: state.youtubeLinkController.text.isNotEmpty + ? state.youtubeLinkController.text + : null, + cooking_time: + state.cookingTimes.isNotEmpty ? state.cookingTimes.first.value : null, + ); } - return null; } diff --git a/lib/features/recipes/reporitories/recipe_repository.dart b/lib/features/recipes/reporitories/recipe_repository.dart new file mode 100644 index 00000000..621da2fb --- /dev/null +++ b/lib/features/recipes/reporitories/recipe_repository.dart @@ -0,0 +1,14 @@ +import 'package:eatvio_app/features/recipes/services/recipe_service.dart'; + +class RecipeRepository { + final RecipeService recipeService; + + RecipeRepository({required this.recipeService}); + /*Future getRecipe(int recipeID) async { + try { + return (await recipeService.getRecipe(recipeID)); + } catch (e) { + rethrow; + } + }*/ +} diff --git a/lib/features/recipes/services/recipe_service.dart b/lib/features/recipes/services/recipe_service.dart new file mode 100644 index 00000000..16b3b890 --- /dev/null +++ b/lib/features/recipes/services/recipe_service.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'recipe_service.g.dart'; + +@RestApi() +abstract class RecipeService { + factory RecipeService(Dio dio, {String baseUrl}) = _RecipeService; + + @GET('/api/recipe/{id}/show/') + Future> getRecipe(@Path() int id); +} diff --git a/lib/features/recipes/services/recipe_service.g.dart b/lib/features/recipes/services/recipe_service.g.dart new file mode 100644 index 00000000..04b47319 --- /dev/null +++ b/lib/features/recipes/services/recipe_service.g.dart @@ -0,0 +1,78 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'recipe_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element + +class _RecipeService implements RecipeService { + _RecipeService( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future> getRecipe(int 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, + '/api/recipe/${id}/show/', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + var _value = _result.data!.map((k, dynamic v) => + MapEntry(k, RecipeModel.fromJson(v as Map))); + 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/locator_service.dart b/lib/locator_service.dart index 2c05df67..19489c76 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -19,6 +19,9 @@ import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/services/feed_service.dart'; +import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart'; +import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart'; +import 'package:eatvio_app/features/recipes/services/recipe_service.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart'; @@ -44,6 +47,8 @@ Future init() async { sl.registerFactory(() => DiaryBloc(diaryRepository: sl())); sl.registerFactory( () => CalendarCubit(calendarRepository: sl())); + sl.registerFactory( + () => RecipeCreateCubit(recipeRepository: sl())); // Repository sl.registerLazySingleton( @@ -58,6 +63,8 @@ Future init() async { () => DiaryRepository(diaryService: sl())); sl.registerLazySingleton( () => CalendarRepository(calendarService: sl())); + sl.registerLazySingleton( + () => RecipeRepository(recipeService: sl())); // Services sl.registerLazySingleton(() => ChatService(sl())); @@ -67,6 +74,7 @@ Future init() async { () => RegistrationService(sl())); sl.registerLazySingleton(() => DiaryService(sl())); sl.registerLazySingleton(() => CalendarService(sl())); + sl.registerLazySingleton(() => RecipeService(sl())); // External sl.registerLazySingleton( diff --git a/lib/main.dart b/lib/main.dart index fdc96e6a..3fe98f25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/m import 'package:eatvio_app/features/diary/presentation/bloc/diary_bloc.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/recipes/presentation/bloc/recipe_create_cubit.dart'; import 'package:eatvio_app/locator_service.dart' as di; import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/bloc/my_profile_bloc.dart'; @@ -45,18 +46,34 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => sl()), - BlocProvider(create: (context) => sl()), - BlocProvider(create: (context) => sl()), - BlocProvider(create: (context) => ThemeBloc(ThemeState(themeType: PrefUtils().getThemeData()))), - BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), - BlocProvider(create: (context) => sl()..loadRecipesList()), - BlocProvider(create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent())), - BlocProvider(create: (context) => sl()..add(LoadDiary())), + BlocProvider( + create: (context) => sl()), + BlocProvider( + create: (context) => sl()), + BlocProvider( + create: (context) => sl()), + BlocProvider( + create: (context) => + ThemeBloc(ThemeState(themeType: PrefUtils().getThemeData()))), + BlocProvider( + create: (context) => sl()..loadFeedBlogPostList()), + BlocProvider( + create: (context) => sl()..loadRecipesList()), + BlocProvider( + create: (context) => MyProfileBloc( + MyProfileState(myProfileModelObj: MyProfileModel())) + ..add(MyProfileInitialEvent())), + BlocProvider( + create: (context) => sl()..add(LoadDiary())), BlocProvider( - create: (context) => ChatSelectBloc(ChatSelectState(chatSelectModelObj: ChatSelectModel()))..add(ChatSelectInitialEvent()), + create: (context) => ChatSelectBloc( + ChatSelectState(chatSelectModelObj: ChatSelectModel())) + ..add(ChatSelectInitialEvent()), ), - BlocProvider(create: (context) => sl()..loadCalendarList()), + BlocProvider( + create: (context) => sl()..loadCalendarList()), + BlocProvider( + create: (context) => sl()..loadRecipeCreate()), ], child: BlocListener( listener: (BuildContext context, InitializationState state) {}, diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart deleted file mode 100644 index c0b0e2ee..00000000 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:eatvio_app/app_router.dart'; -import 'package:eatvio_app/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hive/hive.dart'; -import 'package:http/http.dart' as http; - -import '/core/app_export.dart'; - -part 'recipe_create_event.dart'; -part 'recipe_create_state.dart'; - -/// A bloc that manages the state of a RecipeCreate according to the event that is dispatched to it. -class RecipeCreateBloc extends Bloc { - RecipeCreateBloc(RecipeCreateState initialState) : super(initialState) { - on(_onInitialize); - on(_createRecipe); - on(_addStep); - on(_removeStep); - on(_reloadScreen); - } - - _onInitialize( - RecipeCreateInitialEvent event, - Emitter emit, - ) async { - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - if (state is RecipeCreateLoadedState) { - var currentState = state as RecipeCreateLoadedState; - emit( - currentState.copyWith( - recipeModelObj: currentState.recipeModelObj, - title: currentState.title, - token: currentState.token, - description: currentState.description, - video_url: currentState.video_url, - steps: currentState.steps, - stepDescriptions: currentState.stepDescriptions, - tags: currentState.tags, - ), - ); - } else { - final Map step = { - 'description': '', - 'image': '', - }; - emit( - RecipeCreateLoadedState( - token: token, - title: TextEditingController(), - description: TextEditingController(), - video_url: TextEditingController(), - tags: TextEditingController(), - stepDescriptions: [TextEditingController()], - steps: [step], - ), - ); - } - } - - FutureOr _addStep( - AddStep event, - Emitter emit, - ) async { - if (state is RecipeCreateLoadedState) { - var currentState = state as RecipeCreateLoadedState; - - List? updatedSteps = currentState.steps; - List? updatedStepDescriptions = currentState.stepDescriptions; - - updatedSteps!.add({ - 'description': '', - 'image': '', - }); - updatedStepDescriptions!.add(TextEditingController()); - emit( - currentState.copyWith( - steps: updatedSteps, - stepDescriptions: updatedStepDescriptions, - ), - ); - await _reloadScreen(event, emit); - } - } - - FutureOr _removeStep( - RemoveStep event, - Emitter emit, - ) async { - if (state is RecipeCreateLoadedState) { - var currentState = state as RecipeCreateLoadedState; - - List? updatedSteps = currentState.steps; - List? updatedStepDescriptions = currentState.stepDescriptions; - - updatedSteps!.removeAt(event.id); - updatedStepDescriptions!.removeAt(event.id); - emit( - currentState.copyWith( - steps: updatedSteps, - stepDescriptions: updatedStepDescriptions, - ), - ); - await _reloadScreen(event, emit); - } - } - - FutureOr _createRecipe( - CreateRecipe event, - Emitter emit, - ) async { - if (state is RecipeCreateLoadedState) { - var currentState = state as RecipeCreateLoadedState; - - var response = await sendRecipe( - currentState.title?.text, - currentState.video_url?.text, - currentState.description?.text, - currentState.steps, - ); - - if (response.statusCode == 200) { - AppRouter.navigatorKey.currentContext?.goNamed('recipeSelectScreen'); - } else { - print( - 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}', - ); - } - } - } - - FutureOr sendRecipe( - String? title, - String? video_url, - String? description, - List? steps, - ) async { - String url = '${baseUrl}/api/recipe/create'; - final Map credentials = { - 'title': title, - 'description': description, - 'video_url': video_url, - 'steps': steps, - 'status': 'active', - }; - - var response = await http.post( - Uri.parse(url), - headers: { - 'Content-Type': 'application/json', - }, - body: json.encode(credentials), - encoding: utf8, - ); - - return response; - } - - FutureOr _reloadScreen( - RecipeCreateEvent? event, - Emitter emit, - ) async { - print('Reloading..'); - final box = await Hive.openBox('token'); - final token = box.get('token'); - if (token == null) { - AppRouter.navigatorKey.currentContext?.goNamed('authScreen'); - } - if (state is RecipeCreateLoadedState) { - var currentState = state as RecipeCreateLoadedState; - emit( - currentState.copyWith( - recipeModelObj: currentState.recipeModelObj, - title: currentState.title, - token: currentState.token, - description: currentState.description, - video_url: currentState.video_url, - steps: currentState.steps, - stepDescriptions: currentState.stepDescriptions, - tags: currentState.tags, - ), - ); - } - } -} diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart deleted file mode 100644 index 85d8ee4f..00000000 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart +++ /dev/null @@ -1,44 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'recipe_create_bloc.dart'; - -/// Abstract class for all events that can be dispatched from the -///RecipeCreate widget. -/// -/// Events must be immutable and implement the [Equatable] interface. -@immutable -abstract class RecipeCreateEvent extends Equatable {} - -/// Event that is dispatched when the RecipeCreate widget is first created. -class RecipeCreateInitialEvent extends RecipeCreateEvent { - @override - List get props => []; -} - -class CreateRecipe extends RecipeCreateEvent { - @override - List get props => []; -} - -class AddStep extends RecipeCreateEvent { - AddStep(); - - @override - List get props => []; -} - -class RemoveStep extends RecipeCreateEvent { - RemoveStep(this.id); - - final int id; - - @override - List get props => [id]; -} - -class ReloadScreen extends RecipeCreateEvent { - ReloadScreen(); - - @override - List get props => []; -} diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart deleted file mode 100644 index d2e1b2b9..00000000 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart +++ /dev/null @@ -1,92 +0,0 @@ -// ignore_for_file: must_be_immutable - -part of 'recipe_create_bloc.dart'; - -/// Represents the state of RecipeCreate in the application. -/// - -class RecipeCreateState extends Equatable { - RecipeCreateState({ - this.recipeModelObj, - }); - - RecipeCreateModel? recipeModelObj; - - @override - List get props => [recipeModelObj]; - - RecipeCreateState copyWith({RecipeCreateModel? recipeModelObj}) { - return RecipeCreateState( - recipeModelObj: recipeModelObj ?? this.recipeModelObj, - ); - } -} - -class RecipeCreateLoadedState extends RecipeCreateState { - RecipeCreateLoadedState({ - this.recipeModelObj, - this.token, - this.title, - this.description, - this.video_url, - this.tags, - this.stepDescriptions, - this.steps, - }); - - RecipeCreateModel? recipeModelObj; - String? token; - TextEditingController? title; - TextEditingController? description; - TextEditingController? video_url; - TextEditingController? tags; - List? steps; - List? stepDescriptions; - - @override - List get props => [ - recipeModelObj, - title, - token, - description, - video_url, - steps, - tags, - stepDescriptions, - ]; - - RecipeCreateLoadedState copyWith({ - RecipeCreateModel? recipeModelObj, - String? token, - TextEditingController? title, - TextEditingController? description, - TextEditingController? video_url, - TextEditingController? tags, - List? steps, - List? stepDescriptions, - }) { - return RecipeCreateLoadedState( - recipeModelObj: recipeModelObj ?? this.recipeModelObj, - title: title ?? this.title, - description: description ?? this.description, - video_url: video_url ?? this.video_url, - token: token ?? this.token, - steps: steps ?? this.steps, - tags: tags ?? this.tags, - stepDescriptions: stepDescriptions ?? this.stepDescriptions, - ); - } -} - -class RecipeCreateErrorState extends RecipeCreateState { - final String errorMessage; - - RecipeCreateErrorState(this.errorMessage); - - @override - List get props => [errorMessage]; -} - -class RecipeCreateInitialState extends RecipeCreateState { - RecipeCreateInitialState({required RecipeCreateModel recipeModelObj}) : super(recipeModelObj: recipeModelObj); -} diff --git a/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart b/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart deleted file mode 100644 index b25f295e..00000000 --- a/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart +++ /dev/null @@ -1,17 +0,0 @@ -// ignore_for_file: must_be_immutable - -import 'package:equatable/equatable.dart'; - -/// This class defines the variables used in the [recipe_create_screen], -/// and is typically used to hold data that is passed between different parts of the application. - -class RecipeCreateModel extends Equatable { - RecipeCreateModel(); - - RecipeCreateModel copyWith() { - return RecipeCreateModel(); - } - - @override - List get props => []; -} diff --git a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart deleted file mode 100644 index 887e66f5..00000000 --- a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart +++ /dev/null @@ -1,413 +0,0 @@ -import 'package:eatvio_app/core/app_export.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; -import 'package:eatvio_app/widgets/custom_outlined_button.dart'; -import 'package:eatvio_app/widgets/custom_text_form_field.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import 'bloc/recipe_create_bloc.dart'; -import 'models/recipe_create_model.dart'; - -class RecipeCreateScreen extends StatelessWidget { - const RecipeCreateScreen({Key? key}) : super(key: key); - - static Widget builder(BuildContext context) { - return BlocProvider( - create: (context) => RecipeCreateBloc( - RecipeCreateState(recipeModelObj: RecipeCreateModel())) - ..add(RecipeCreateInitialEvent()), - child: RecipeCreateScreen(), - ); - } - - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is RecipeCreateInitialState) { - // Это начальное состояние, данные ещё не загружены. - return Container( - color: Colors.white, - ); - } else if (state is RecipeCreateLoadedState) { - // Данные загружены, можно строить экран. - return _buildRecipeCreate(context, state); - } else if (state is RecipeCreateErrorState) { - // Произошла ошибка при загрузке данных. - return Center(child: Text(state.errorMessage)); - } else { - // Другие состояния, если они есть. - return Container(); // Пустой контейнер для необработанных состояний. - } - }, - ); - } - - _buildRecipeCreate(BuildContext context, RecipeCreateLoadedState state) { - mediaQueryData = MediaQuery.of(context); - List steps = []; - if (state.steps != null) { - for (int i = 0; i < state.steps!.length; i++) { - steps.add(_buildStep(context, state, i)); - } - } - - return RefreshIndicator( - onRefresh: refreshPage(context), - child: Scaffold( - appBar: AppBar( - toolbarHeight: 0, - automaticallyImplyLeading: false, - ), - resizeToAvoidBottomInset: false, - body: Container( - width: double.maxFinite, - decoration: BoxDecoration(color: Colors.grey[200]), - child: ListView( - children: [ - _buldAppBar(context), - SizedBox(height: 20.v), - _buildRecipeInfo(state, context), - SizedBox(height: 20.v), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: steps, - ), - SizedBox(height: 20.v), - _buildAdditional(context, state), - ], - ), - ), - ), - ); - } - - _buldAppBar(BuildContext context) { - return Container( - margin: EdgeInsets.all(16.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: CustomAppBar( - height: 54.v, - leadingWidth: 28.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), - onTap: onTapBack(context), - ), - title: Align( - alignment: Alignment.center, - child: AppbarSubtitle( - text: "Добавление рецепта", - ), - ), - ), - ); - } - - _buildRecipeInfo(RecipeCreateLoadedState state, BuildContext context) { - return Container( - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: 16.0), - Align(alignment: Alignment.centerLeft, child: Text("Изображение")), - _buildAddPhoto(context, state), - SizedBox(height: 16.0), - Align( - alignment: Alignment.centerLeft, child: Text("Название рецепта")), - CustomTextFormField( - controller: state.title, - hintText: "Введите название рецепта", - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 16.0), - Align(alignment: Alignment.centerLeft, child: Text("Описание")), - CustomTextFormField( - controller: state.description, - hintText: "Введите описание рецепта", - maxLines: 4, - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 16.0), - ], - ), - ); - } - - refreshPage(BuildContext context) { - return () async { - context.read().add(ReloadScreen()); - }; - } - - _buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) { - bool isLoading = false; - return GestureDetector( - child: Stack( - children: [ - if (!isLoading) - Container( - padding: EdgeInsets.symmetric( - horizontal: 40.h, - vertical: 80.v, - ), - decoration: AppDecoration.fillGray.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder5, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only( - left: 7.h, - top: 5.v, - ), - child: Text( - "Добавить фото", - selectionColor: appTheme.blueA400, - ), - ), - Spacer(), - Icon( - Icons.photo_library, - color: appTheme.blueGray300, - ), - ], - ), - ), - /*if (isLoading) - Container( - margin: EdgeInsets.symmetric(horizontal: 10.h), - padding: EdgeInsets.symmetric( - horizontal: 40.h, - vertical: 80.v, - ), - decoration: AppDecoration.fillGray.copyWith( - borderRadius: BorderRadiusStyle.roundedBorder5, - ), - child: Center( - child: CircularProgressIndicator( - color: appTheme - .blueGray300), // Замените на свой прелоадер при необходимости - ), - ),*/ - ], - ), - ); - } - - _buildStep(BuildContext context, RecipeCreateLoadedState state, int i) { - bool isLastStep = i == state.steps!.length - 1; - bool isFirstStep = i == 0; - - return Container( - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - "Шаг ${i + 1}", - style: CustomTextStyles.titleMediumSemiBold, - ), - ), - SizedBox(height: 8.0), - CustomTextFormField( - controller: state.stepDescriptions?[i], - hintText: "Введите описание шага", - maxLines: 4, - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 16.0), - _buildAddPhoto(context, state), - SizedBox(height: 16.0), - if (isFirstStep && isLastStep) - CustomOutlinedButton( - onPressed: () => context.read().add(AddStep()), - text: "Добавить шаг", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - if (isLastStep && - !isFirstStep) // Добавляем кнопку "Добавить шаг" только для последнего шага - Column( - children: [ - SizedBox(height: 16.0), - Row( - children: [ - Flexible( - flex: 5, - child: CustomOutlinedButton( - onPressed: () => - context.read().add(AddStep()), - text: "Добавить шаг", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ), - SizedBox(width: 8), // Add some spacing between the buttons - Flexible( - flex: 1, - child: CustomOutlinedButton( - text: "х", - onPressed: () => - context.read().add(RemoveStep(i)), - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ), - ], - ), - ], - ), - if (!isLastStep && !isFirstStep) - CustomOutlinedButton( - onPressed: () => - context.read().add(RemoveStep(i)), - text: "x", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ], - ), - ); - } - - _buildAdditional(BuildContext context, RecipeCreateLoadedState state) { - return Container( - margin: EdgeInsets.only( - left: 16.h, - right: 16.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Align(alignment: Alignment.centerLeft, child: Text("Теги")), - CustomTextFormField( - controller: state.tags, - hintText: "Введите теги, через запятую", - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 8.0), - Align( - alignment: Alignment.centerLeft, - child: Text("Ссылка на видео (необязательно)")), - CustomTextFormField( - controller: state.video_url, - hintText: "YouTube", - fillColor: appTheme.gray50, - borderDecoration: OutlineInputBorder( - borderRadius: BorderRadius.circular(5.h), - borderSide: BorderSide( - color: appTheme.gray50, - width: 1, - ), - ), - ), - SizedBox(height: 16.0), - CustomOutlinedButton( - text: "Добавить рецепт", - buttonStyle: CustomButtonStyles.outlinePrimaryTL15, - ), - ], - ), - ); - } - - Function onTapBack(BuildContext context) { - return () { - onTapArrowLeft(context); - }; - } - - onTapArrowLeft(BuildContext context) { - GoRouter.of(context).pop(); - } -}