From a94e2b9db487fe64ea41a15e2651a00ff6761399 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 17 Jul 2024 17:04:00 +0300 Subject: [PATCH 1/8] feature(blogPosts): add image load, fix blog post view --- .../blog_post/services/blog_post_service.dart | 2 +- .../services/blog_post_service.g.dart | 2 +- .../pages/blog_post_create_screen.dart | 1 - .../bloc/feed_cubit/feed_cubit.dart | 1 - .../bloc/blog_post_create_bloc.dart | 31 ++++++++----------- .../bloc/blog_post_create_state.dart | 13 +++++++- .../bloc/blog_post_edit_bloc.dart | 2 +- lib/widgets/custom_edit_text_form_field.dart | 2 +- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/features/blog_post/services/blog_post_service.dart b/lib/features/blog_post/services/blog_post_service.dart index 00d8e560..dfbc3618 100644 --- a/lib/features/blog_post/services/blog_post_service.dart +++ b/lib/features/blog_post/services/blog_post_service.dart @@ -8,6 +8,6 @@ part 'blog_post_service.g.dart'; abstract class BlogPostService { factory BlogPostService(Dio dio, {String baseUrl}) = _BlogPostService; - @GET('/api/feed/show/{id}') + @GET('/api/blogPosts/show/{id}') Future getBlogPost(@Path() String id); } diff --git a/lib/features/blog_post/services/blog_post_service.g.dart b/lib/features/blog_post/services/blog_post_service.g.dart index 40905775..5ea30fcb 100644 --- a/lib/features/blog_post/services/blog_post_service.g.dart +++ b/lib/features/blog_post/services/blog_post_service.g.dart @@ -32,7 +32,7 @@ class _BlogPostService implements BlogPostService { ) .compose( _dio.options, - '/api/feed/show/${id}', + '/api/blogPosts/show/${id}', queryParameters: queryParameters, data: _data, ) diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index 755ce127..53628bc2 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -44,7 +44,6 @@ class BlogPostCreateScreen extends StatelessWidget { } _buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) { - print(state.image); return Scaffold( resizeToAvoidBottomInset: false, body: Container( diff --git a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart index 02ee7671..c052c26b 100644 --- a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart +++ b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart @@ -8,7 +8,6 @@ class FeedCubit extends Cubit { FeedCubit({required this.feedRepository}) : super(const FeedState.loading()); loadFeedBlogPostList() async { - emit(const FeedState.loading()); try { final feedBlogPostList = await feedRepository.getFeedBlogPostList(); emit(FeedState.loaded(feedBlogPostList: feedBlogPostList)); diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart index 624a1ef4..654e963e 100644 --- a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart @@ -1,6 +1,4 @@ import 'dart:async'; -import 'dart:convert'; - import 'package:dio/dio.dart'; import 'package:eatvio_app/app_router.dart'; import 'package:eatvio_app/locator_service.dart'; @@ -14,6 +12,7 @@ import 'package:image_picker/image_picker.dart'; import '/core/app_export.dart'; part 'blog_post_create_event.dart'; + part 'blog_post_create_state.dart'; /// A bloc that manages the state of a BlogPostCreate according to the event that is dispatched to it. @@ -70,30 +69,30 @@ class BlogPostCreateBloc extends Bloc currentState.image, ); - if (response.statusCode == 200) { + if (response.statusCode == 200 || response.statusCode == 201) { AppRouter.navigatorKey.currentContext?.goNamed('feedScreen'); } else { print( - 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}', + 'Failed to create blog post. Status Code: ${response.statusCode} ${response.data}', ); } } } - sendBlogPost(String? title, String? youtubeURL, String? description, XFile? image) async { - String url = '/blog_post/create'; + Future sendBlogPost(String? title, String? youtubeURL, String? description, XFile? image) async { + String url = baseUrl + '/api/blogPosts/create'; - final Map credentials = { + FormData formData = FormData.fromMap({ 'title': title, 'description': description, - 'youtube_url': youtubeURL, - 'status': 'active', - 'image': image, - }; + 'videoUrl': youtubeURL, + 'state': 'active', + 'image': await MultipartFile.fromFile(image!.path, filename: image.name), + }); - var response = await sl().post( + final response = await sl().post( url, - data: json.encode(credentials), + data: formData, ); return response; @@ -102,11 +101,7 @@ class BlogPostCreateBloc extends Bloc FutureOr _uploadImages(UploadImages event, Emitter emit) { if (state is BlogPostCreateLoadedState) { var currentState = state as BlogPostCreateLoadedState; - emit( - currentState.copyWith( - image: event.image, - ), - ); + emit(currentState.copyWith(image: event.image)); } } } diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart index 33dc59b5..8e0c8e87 100644 --- a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart @@ -14,6 +14,7 @@ class BlogPostCreateState extends Equatable { @override List get props => [blog_postModelObj]; + BlogPostCreateState copyWith({BlogPostCreateModel? blog_postModelObj}) { return BlogPostCreateState( blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, @@ -39,8 +40,18 @@ class BlogPostCreateLoadedState extends BlogPostCreateState { String? token; bool isLoading; XFile? image; + @override - List get props => [blog_postModelObj, title, description, youtubeURL]; + List get props => [ + blog_postModelObj, + title, + description, + youtubeURL, + token, + image, + isLoading, + ]; + BlogPostCreateLoadedState copyWith({ BlogPostCreateModel? blog_postModelObj, TextEditingController? title, diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart index 02e496fa..c9c6bf9c 100644 --- a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart @@ -33,7 +33,7 @@ class BlogPostEditBloc extends Bloc { } final String? blog_postID = state.blog_postModelObj?.blog_postID; final response = await http.get( - Uri.parse(baseUrl + '/api/feed/show/$blog_postID'), + Uri.parse(baseUrl + '/api/blogPosts/show/$blog_postID'), headers: { 'Authorization': 'Bearer $token', }, diff --git a/lib/widgets/custom_edit_text_form_field.dart b/lib/widgets/custom_edit_text_form_field.dart index 0853d3e5..13a3df86 100644 --- a/lib/widgets/custom_edit_text_form_field.dart +++ b/lib/widgets/custom_edit_text_form_field.dart @@ -8,7 +8,7 @@ class CustomTextFormField extends StatelessWidget { this.width, this.controller, this.focusNode, - this.autofocus = true, + this.autofocus = false, this.textStyle, this.obscureText = false, this.textInputAction = TextInputAction.next, From e472121bbc95770f189da3b3ea4713ca79365fe5 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 17 Jul 2024 17:08:01 +0300 Subject: [PATCH 2/8] feature(blogPosts): pick only one image --- .../presentation/pages/blog_post_create_screen.dart | 8 +++----- .../bloc/blog_post_create_event.dart | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index 53628bc2..39c3f4e4 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -191,11 +191,9 @@ addImage(BuildContext context, bool isLoading) { if (!isLoading) { return () async { final picker = ImagePicker(); - List? images = await picker.pickMultiImage(); - if (images.isNotEmpty) { - for (var image in images) { - context.read().add(UploadImages(image)); - } + XFile? image = await picker.pickImage(source: ImageSource.gallery); + if (image != null) { + context.read().add(UploadImages(image)); } }; } diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart index 778cd85d..6ce10caa 100644 --- a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart @@ -22,7 +22,9 @@ class CreateBlogPost extends BlogPostCreateEvent { class UploadImages extends BlogPostCreateEvent { UploadImages(this.image); + final XFile image; + @override List get props => [image]; } From 818721a993fc36491e38ba3831fa4b990a32b073 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Sun, 28 Jul 2024 18:39:36 +0300 Subject: [PATCH 3/8] feature(registration): add location endpoint with debounce --- .../user_registration_model.dart | 5 +- .../user_registration_model.freezed.dart | 75 +++---- .../user_registration_model.g.dart | 8 +- .../registration/models/location_model.dart | 15 ++ .../models/location_model.freezed.dart | 189 ++++++++++++++++++ .../registration/models/location_model.g.dart | 19 ++ .../registration_cubit.dart | 14 +- .../pages/city_of_living_screen.dart | 61 ++---- .../pages/registration_screen.dart | 2 +- .../repositories/registration_repository.dart | 9 + .../services/registration_service.dart | 6 + .../services/registration_service.g.dart | 27 +++ .../bloc/blog_post_create_bloc.dart | 6 +- 13 files changed, 345 insertions(+), 91 deletions(-) create mode 100644 lib/features/registration/models/location_model.dart create mode 100644 lib/features/registration/models/location_model.freezed.dart create mode 100644 lib/features/registration/models/location_model.g.dart diff --git a/lib/data/models/userRegistrationModel/user_registration_model.dart b/lib/data/models/userRegistrationModel/user_registration_model.dart index 5e308189..a86d0648 100644 --- a/lib/data/models/userRegistrationModel/user_registration_model.dart +++ b/lib/data/models/userRegistrationModel/user_registration_model.dart @@ -15,8 +15,9 @@ class UserRegistrationSheet with _$UserRegistrationSheet { @Default('accepted') @JsonKey(name: 'terms') String termsAccepted, required String birthdate, required String gender, - required String country, - required String city, + required String location, + @JsonKey(name: 'response_body') + required String responseBody, required String lang, required int weight, required int height, diff --git a/lib/data/models/userRegistrationModel/user_registration_model.freezed.dart b/lib/data/models/userRegistrationModel/user_registration_model.freezed.dart index 4aa91df8..70f656fe 100644 --- a/lib/data/models/userRegistrationModel/user_registration_model.freezed.dart +++ b/lib/data/models/userRegistrationModel/user_registration_model.freezed.dart @@ -34,8 +34,9 @@ mixin _$UserRegistrationSheet { String get termsAccepted => throw _privateConstructorUsedError; String get birthdate => throw _privateConstructorUsedError; String get gender => throw _privateConstructorUsedError; - String get country => throw _privateConstructorUsedError; - String get city => throw _privateConstructorUsedError; + String get location => throw _privateConstructorUsedError; + @JsonKey(name: 'response_body') + String get responseBody => throw _privateConstructorUsedError; String get lang => throw _privateConstructorUsedError; int get weight => throw _privateConstructorUsedError; int get height => throw _privateConstructorUsedError; @@ -90,8 +91,8 @@ abstract class $UserRegistrationSheetCopyWith<$Res> { @JsonKey(name: 'terms') String termsAccepted, String birthdate, String gender, - String country, - String city, + String location, + @JsonKey(name: 'response_body') String responseBody, String lang, int weight, int height, @@ -134,8 +135,8 @@ class _$UserRegistrationSheetCopyWithImpl<$Res, Object? termsAccepted = null, Object? birthdate = null, Object? gender = null, - Object? country = null, - Object? city = null, + Object? location = null, + Object? responseBody = null, Object? lang = null, Object? weight = null, Object? height = null, @@ -191,13 +192,13 @@ class _$UserRegistrationSheetCopyWithImpl<$Res, ? _value.gender : gender // ignore: cast_nullable_to_non_nullable as String, - country: null == country - ? _value.country - : country // ignore: cast_nullable_to_non_nullable + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable as String, - city: null == city - ? _value.city - : city // ignore: cast_nullable_to_non_nullable + responseBody: null == responseBody + ? _value.responseBody + : responseBody // ignore: cast_nullable_to_non_nullable as String, lang: null == lang ? _value.lang @@ -290,8 +291,8 @@ abstract class _$$UserRegistrationSheetImplCopyWith<$Res> @JsonKey(name: 'terms') String termsAccepted, String birthdate, String gender, - String country, - String city, + String location, + @JsonKey(name: 'response_body') String responseBody, String lang, int weight, int height, @@ -332,8 +333,8 @@ class __$$UserRegistrationSheetImplCopyWithImpl<$Res> Object? termsAccepted = null, Object? birthdate = null, Object? gender = null, - Object? country = null, - Object? city = null, + Object? location = null, + Object? responseBody = null, Object? lang = null, Object? weight = null, Object? height = null, @@ -389,13 +390,13 @@ class __$$UserRegistrationSheetImplCopyWithImpl<$Res> ? _value.gender : gender // ignore: cast_nullable_to_non_nullable as String, - country: null == country - ? _value.country - : country // ignore: cast_nullable_to_non_nullable + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable as String, - city: null == city - ? _value.city - : city // ignore: cast_nullable_to_non_nullable + responseBody: null == responseBody + ? _value.responseBody + : responseBody // ignore: cast_nullable_to_non_nullable as String, lang: null == lang ? _value.lang @@ -483,8 +484,8 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet { @JsonKey(name: 'terms') this.termsAccepted = 'accepted', required this.birthdate, required this.gender, - required this.country, - required this.city, + required this.location, + @JsonKey(name: 'response_body') required this.responseBody, required this.lang, required this.weight, required this.height, @@ -529,9 +530,10 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet { @override final String gender; @override - final String country; + final String location; @override - final String city; + @JsonKey(name: 'response_body') + final String responseBody; @override final String lang; @override @@ -583,7 +585,7 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet { @override String toString() { - return 'UserRegistrationSheet(name: $name, firstName: $firstName, lastName: $lastName, email: $email, password: $password, passwordConfirmation: $passwordConfirmation, termsAccepted: $termsAccepted, birthdate: $birthdate, gender: $gender, country: $country, city: $city, lang: $lang, weight: $weight, height: $height, chestCircumference: $chestCircumference, waistCircumference: $waistCircumference, hipCircumference: $hipCircumference, bloodPressureUp: $bloodPressureUp, bloodPressureDown: $bloodPressureDown, hasHypo: $hasHypo, hasHyper: $hasHyper, aboutAllergy: $aboutAllergy, aboutTakingPils: $aboutTakingPils, aboutSportHobbies: $aboutSportHobbies, favoriteFoods: $favoriteFoods, unfavoriteFoods: $unfavoriteFoods, additionalInfo: $additionalInfo, activityCoefficient: $activityCoefficient)'; + return 'UserRegistrationSheet(name: $name, firstName: $firstName, lastName: $lastName, email: $email, password: $password, passwordConfirmation: $passwordConfirmation, termsAccepted: $termsAccepted, birthdate: $birthdate, gender: $gender, location: $location, responseBody: $responseBody, lang: $lang, weight: $weight, height: $height, chestCircumference: $chestCircumference, waistCircumference: $waistCircumference, hipCircumference: $hipCircumference, bloodPressureUp: $bloodPressureUp, bloodPressureDown: $bloodPressureDown, hasHypo: $hasHypo, hasHyper: $hasHyper, aboutAllergy: $aboutAllergy, aboutTakingPils: $aboutTakingPils, aboutSportHobbies: $aboutSportHobbies, favoriteFoods: $favoriteFoods, unfavoriteFoods: $unfavoriteFoods, additionalInfo: $additionalInfo, activityCoefficient: $activityCoefficient)'; } @override @@ -606,8 +608,10 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet { (identical(other.birthdate, birthdate) || other.birthdate == birthdate) && (identical(other.gender, gender) || other.gender == gender) && - (identical(other.country, country) || other.country == country) && - (identical(other.city, city) || other.city == city) && + (identical(other.location, location) || + other.location == location) && + (identical(other.responseBody, responseBody) || + other.responseBody == responseBody) && (identical(other.lang, lang) || other.lang == lang) && (identical(other.weight, weight) || other.weight == weight) && (identical(other.height, height) || other.height == height) && @@ -653,8 +657,8 @@ class _$UserRegistrationSheetImpl implements _UserRegistrationSheet { termsAccepted, birthdate, gender, - country, - city, + location, + responseBody, lang, weight, height, @@ -701,8 +705,8 @@ abstract class _UserRegistrationSheet implements UserRegistrationSheet { @JsonKey(name: 'terms') final String termsAccepted, required final String birthdate, required final String gender, - required final String country, - required final String city, + required final String location, + @JsonKey(name: 'response_body') required final String responseBody, required final String lang, required final int weight, required final int height, @@ -752,9 +756,10 @@ abstract class _UserRegistrationSheet implements UserRegistrationSheet { @override String get gender; @override - String get country; + String get location; @override - String get city; + @JsonKey(name: 'response_body') + String get responseBody; @override String get lang; @override diff --git a/lib/data/models/userRegistrationModel/user_registration_model.g.dart b/lib/data/models/userRegistrationModel/user_registration_model.g.dart index 132240b7..6ca8a1cf 100644 --- a/lib/data/models/userRegistrationModel/user_registration_model.g.dart +++ b/lib/data/models/userRegistrationModel/user_registration_model.g.dart @@ -18,8 +18,8 @@ _$UserRegistrationSheetImpl _$$UserRegistrationSheetImplFromJson( termsAccepted: json['terms'] as String? ?? 'accepted', birthdate: json['birthdate'] as String, gender: json['gender'] as String, - country: json['country'] as String, - city: json['city'] as String, + location: json['location'] as String, + responseBody: json['response_body'] as String, lang: json['lang'] as String, weight: (json['weight'] as num).toInt(), height: (json['height'] as num).toInt(), @@ -52,8 +52,8 @@ Map _$$UserRegistrationSheetImplToJson( 'terms': instance.termsAccepted, 'birthdate': instance.birthdate, 'gender': instance.gender, - 'country': instance.country, - 'city': instance.city, + 'location': instance.location, + 'response_body': instance.responseBody, 'lang': instance.lang, 'weight': instance.weight, 'height': instance.height, diff --git a/lib/features/registration/models/location_model.dart b/lib/features/registration/models/location_model.dart new file mode 100644 index 00000000..3632a3e4 --- /dev/null +++ b/lib/features/registration/models/location_model.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'location_model.g.dart'; +part 'location_model.freezed.dart'; + +@freezed +class LocationModel with _$LocationModel { + const factory LocationModel({ + required Map locations, + @JsonKey(name: 'response_body') + required String responseBody, +}) = _LocationModel; + + factory LocationModel.fromJson(Map json) => _$LocationModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/registration/models/location_model.freezed.dart b/lib/features/registration/models/location_model.freezed.dart new file mode 100644 index 00000000..71cbcb49 --- /dev/null +++ b/lib/features/registration/models/location_model.freezed.dart @@ -0,0 +1,189 @@ +// 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 'location_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'); + +LocationModel _$LocationModelFromJson(Map json) { + return _LocationModel.fromJson(json); +} + +/// @nodoc +mixin _$LocationModel { + Map get locations => throw _privateConstructorUsedError; + @JsonKey(name: 'response_body') + String get responseBody => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $LocationModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LocationModelCopyWith<$Res> { + factory $LocationModelCopyWith( + LocationModel value, $Res Function(LocationModel) then) = + _$LocationModelCopyWithImpl<$Res, LocationModel>; + @useResult + $Res call( + {Map locations, + @JsonKey(name: 'response_body') String responseBody}); +} + +/// @nodoc +class _$LocationModelCopyWithImpl<$Res, $Val extends LocationModel> + implements $LocationModelCopyWith<$Res> { + _$LocationModelCopyWithImpl(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? locations = null, + Object? responseBody = null, + }) { + return _then(_value.copyWith( + locations: null == locations + ? _value.locations + : locations // ignore: cast_nullable_to_non_nullable + as Map, + responseBody: null == responseBody + ? _value.responseBody + : responseBody // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LocationModelImplCopyWith<$Res> + implements $LocationModelCopyWith<$Res> { + factory _$$LocationModelImplCopyWith( + _$LocationModelImpl value, $Res Function(_$LocationModelImpl) then) = + __$$LocationModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Map locations, + @JsonKey(name: 'response_body') String responseBody}); +} + +/// @nodoc +class __$$LocationModelImplCopyWithImpl<$Res> + extends _$LocationModelCopyWithImpl<$Res, _$LocationModelImpl> + implements _$$LocationModelImplCopyWith<$Res> { + __$$LocationModelImplCopyWithImpl( + _$LocationModelImpl _value, $Res Function(_$LocationModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? locations = null, + Object? responseBody = null, + }) { + return _then(_$LocationModelImpl( + locations: null == locations + ? _value._locations + : locations // ignore: cast_nullable_to_non_nullable + as Map, + responseBody: null == responseBody + ? _value.responseBody + : responseBody // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$LocationModelImpl implements _LocationModel { + const _$LocationModelImpl( + {required final Map locations, + @JsonKey(name: 'response_body') required this.responseBody}) + : _locations = locations; + + factory _$LocationModelImpl.fromJson(Map json) => + _$$LocationModelImplFromJson(json); + + final Map _locations; + @override + Map get locations { + if (_locations is EqualUnmodifiableMapView) return _locations; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_locations); + } + + @override + @JsonKey(name: 'response_body') + final String responseBody; + + @override + String toString() { + return 'LocationModel(locations: $locations, responseBody: $responseBody)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocationModelImpl && + const DeepCollectionEquality() + .equals(other._locations, _locations) && + (identical(other.responseBody, responseBody) || + other.responseBody == responseBody)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_locations), responseBody); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LocationModelImplCopyWith<_$LocationModelImpl> get copyWith => + __$$LocationModelImplCopyWithImpl<_$LocationModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LocationModelImplToJson( + this, + ); + } +} + +abstract class _LocationModel implements LocationModel { + const factory _LocationModel( + {required final Map locations, + @JsonKey(name: 'response_body') required final String responseBody}) = + _$LocationModelImpl; + + factory _LocationModel.fromJson(Map json) = + _$LocationModelImpl.fromJson; + + @override + Map get locations; + @override + @JsonKey(name: 'response_body') + String get responseBody; + @override + @JsonKey(ignore: true) + _$$LocationModelImplCopyWith<_$LocationModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/registration/models/location_model.g.dart b/lib/features/registration/models/location_model.g.dart new file mode 100644 index 00000000..04a199fb --- /dev/null +++ b/lib/features/registration/models/location_model.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LocationModelImpl _$$LocationModelImplFromJson(Map json) => + _$LocationModelImpl( + locations: json['locations'] as Map, + responseBody: json['response_body'] as String, + ); + +Map _$$LocationModelImplToJson(_$LocationModelImpl instance) => + { + 'locations': instance.locations, + 'response_body': instance.responseBody, + }; diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index f9e767fc..af81fda8 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -21,8 +21,8 @@ class RegistrationCubit extends Cubit { passwordConfirmation: '', birthdate: '', gender: '', - country: '', - city: '', + location: '', + responseBody: '', lang: '', weight: 0, height: 0, @@ -54,4 +54,14 @@ class RegistrationCubit extends Cubit { rethrow; } } + + Future> getLocationsByQuery(String query) async { + try { + final location = (await registrationRepository.getLocationsListByQuery(query)); + emit(RegistrationState(sheet: state.sheet.copyWith(responseBody: location.responseBody))); + return location.locations.values.toList().map((e) => e.toString()).toList(); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/registration/presentation/pages/city_of_living_screen.dart b/lib/features/registration/presentation/pages/city_of_living_screen.dart index 02e5c65e..0a3dd291 100644 --- a/lib/features/registration/presentation/pages/city_of_living_screen.dart +++ b/lib/features/registration/presentation/pages/city_of_living_screen.dart @@ -1,8 +1,6 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart'; -import 'package:eatvio_app/features/registration/consts/cities_list.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; -import 'package:eatvio_app/widgets/custom_drop_down.dart'; import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; @@ -27,15 +25,8 @@ class _CityOfLivingScreenState extends State { @override void initState() { super.initState(); - if (widget.sheet.country.isNotEmpty) { - selectedItem = dropdownItemList.firstWhere((item) => item.title == widget.sheet.country); - } else { - selectedItem = dropdownItemList.first; - final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(country: selectedItem.title)); - } - if (widget.sheet.city.isNotEmpty) { - cityController.text = widget.sheet.city; + if (widget.sheet.location.isNotEmpty) { + cityController.text = widget.sheet.location; } } @@ -46,39 +37,19 @@ class _CityOfLivingScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("country".tr), - SizedBox(height: 4), - CustomDropDown( - icon: Container( - margin: EdgeInsets.fromLTRB(30.h, 11.v, 17.h, 11.v), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(1.h)), - child: CustomImageView(imagePath: ImageConstant.imgFavorite, height: 12.adaptSize, width: 12.adaptSize), - ), - hintText: "Не выбрано".tr, - items: dropdownItemList, - value: selectedItem, - onChanged: (newValue) { - setState(() { - selectedItem = newValue; - }); - final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(country: newValue.title)); - }, - ), - SizedBox(height: 16), - Text("Населенный пункт"), + Text("Местоположение"), SizedBox(height: 4), TypeAheadField( - debounceDuration: Duration.zero, + debounceDuration: Duration(milliseconds: 300), controller: cityController, builder: (context, controller, focus) => CustomTextFormField( - controller: cityController, - focusNode: focus, - textStyle: TextStyle( - fontSize: 16, - color: Colors.black, - ) - ), + controller: cityController, + focusNode: focus, + autofocus: true, + textStyle: TextStyle( + fontSize: 16, + color: Colors.black, + )), itemBuilder: (BuildContext context, String item) { return Padding( padding: const EdgeInsets.all(8.0), @@ -96,10 +67,14 @@ class _CityOfLivingScreenState extends State { cityController.text = item; }); final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(city: item)); + registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: item)); }, - suggestionsCallback: (query) { - return citiesList.where((country) => country.toLowerCase().contains(query.toLowerCase())).toList(); + suggestionsCallback: (query) async { + if (query.length >= 3) { + final registrationCubit = context.read(); + return (await registrationCubit.getLocationsByQuery(query)); + } + return null; }, ), ], diff --git a/lib/features/registration/presentation/pages/registration_screen.dart b/lib/features/registration/presentation/pages/registration_screen.dart index 1688ef2c..08562908 100644 --- a/lib/features/registration/presentation/pages/registration_screen.dart +++ b/lib/features/registration/presentation/pages/registration_screen.dart @@ -244,7 +244,7 @@ class _RegistrationScreenState extends State { return false; } case 6: - if (state.sheet.country.isNotEmpty && state.sheet.city.isNotEmpty) + if (state.sheet.location.isNotEmpty) return true; else { showDialog( diff --git a/lib/features/registration/repositories/registration_repository.dart b/lib/features/registration/repositories/registration_repository.dart index aa388801..31a1ff11 100644 --- a/lib/features/registration/repositories/registration_repository.dart +++ b/lib/features/registration/repositories/registration_repository.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart'; +import 'package:eatvio_app/features/registration/models/location_model.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart'; class RegistrationRepository { @@ -13,4 +14,12 @@ class RegistrationRepository { rethrow; } } + + Future getLocationsListByQuery(String query) async { + try { + return (await registrationService.getLocations(query: query)); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/registration/services/registration_service.dart b/lib/features/registration/services/registration_service.dart index fefca6ad..af4bd836 100644 --- a/lib/features/registration/services/registration_service.dart +++ b/lib/features/registration/services/registration_service.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/registration/models/location_model.dart'; import 'package:retrofit/retrofit.dart'; part 'registration_service.g.dart'; @@ -11,4 +12,9 @@ abstract class RegistrationService { Future> register({ @Body() required Map sheet, }); + + @GET('/api/locations') + Future getLocations({ + @Query('user_address') required String query, + }); } diff --git a/lib/features/registration/services/registration_service.g.dart b/lib/features/registration/services/registration_service.g.dart index 269d16cc..6c9ce5bc 100644 --- a/lib/features/registration/services/registration_service.g.dart +++ b/lib/features/registration/services/registration_service.g.dart @@ -47,6 +47,33 @@ class _RegistrationService implements RegistrationService { return _value; } + @override + Future getLocations({required String query}) async { + final _extra = {}; + final queryParameters = {r'user_address': query}; + final _headers = {}; + const Map? _data = null; + final _result = await _dio + .fetch>(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/locations', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final _value = LocationModel.fromJson(_result.data!); + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart index 654e963e..184938ad 100644 --- a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart @@ -72,9 +72,7 @@ class BlogPostCreateBloc extends Bloc if (response.statusCode == 200 || response.statusCode == 201) { AppRouter.navigatorKey.currentContext?.goNamed('feedScreen'); } else { - print( - 'Failed to create blog post. Status Code: ${response.statusCode} ${response.data}', - ); + print('Failed to create blog post. Status Code: ${response.statusCode} ${response.data}'); } } } @@ -85,7 +83,7 @@ class BlogPostCreateBloc extends Bloc FormData formData = FormData.fromMap({ 'title': title, 'description': description, - 'videoUrl': youtubeURL, + 'youtube_url': youtubeURL, 'state': 'active', 'image': await MultipartFile.fromFile(image!.path, filename: image.name), }); From f96c6b53d6e02fab2d1da56539a53751f7300e1d Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 29 Jul 2024 11:20:01 +0300 Subject: [PATCH 4/8] feature(registration): fix location model --- .../registration/models/location_model.dart | 10 ++++---- .../models/location_model.freezed.dart | 24 +++++++++---------- .../registration/models/location_model.g.dart | 3 ++- .../registration_cubit.dart | 2 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/features/registration/models/location_model.dart b/lib/features/registration/models/location_model.dart index 3632a3e4..cc6b107e 100644 --- a/lib/features/registration/models/location_model.dart +++ b/lib/features/registration/models/location_model.dart @@ -1,15 +1,15 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'location_model.g.dart'; + part 'location_model.freezed.dart'; @freezed class LocationModel with _$LocationModel { const factory LocationModel({ - required Map locations, - @JsonKey(name: 'response_body') - required String responseBody, -}) = _LocationModel; + required List locations, + @JsonKey(name: 'response_body') required String responseBody, + }) = _LocationModel; factory LocationModel.fromJson(Map json) => _$LocationModelFromJson(json); -} \ No newline at end of file +} diff --git a/lib/features/registration/models/location_model.freezed.dart b/lib/features/registration/models/location_model.freezed.dart index 71cbcb49..f640ca2e 100644 --- a/lib/features/registration/models/location_model.freezed.dart +++ b/lib/features/registration/models/location_model.freezed.dart @@ -20,7 +20,7 @@ LocationModel _$LocationModelFromJson(Map json) { /// @nodoc mixin _$LocationModel { - Map get locations => throw _privateConstructorUsedError; + List get locations => throw _privateConstructorUsedError; @JsonKey(name: 'response_body') String get responseBody => throw _privateConstructorUsedError; @@ -37,7 +37,7 @@ abstract class $LocationModelCopyWith<$Res> { _$LocationModelCopyWithImpl<$Res, LocationModel>; @useResult $Res call( - {Map locations, + {List locations, @JsonKey(name: 'response_body') String responseBody}); } @@ -61,7 +61,7 @@ class _$LocationModelCopyWithImpl<$Res, $Val extends LocationModel> locations: null == locations ? _value.locations : locations // ignore: cast_nullable_to_non_nullable - as Map, + as List, responseBody: null == responseBody ? _value.responseBody : responseBody // ignore: cast_nullable_to_non_nullable @@ -79,7 +79,7 @@ abstract class _$$LocationModelImplCopyWith<$Res> @override @useResult $Res call( - {Map locations, + {List locations, @JsonKey(name: 'response_body') String responseBody}); } @@ -101,7 +101,7 @@ class __$$LocationModelImplCopyWithImpl<$Res> locations: null == locations ? _value._locations : locations // ignore: cast_nullable_to_non_nullable - as Map, + as List, responseBody: null == responseBody ? _value.responseBody : responseBody // ignore: cast_nullable_to_non_nullable @@ -114,19 +114,19 @@ class __$$LocationModelImplCopyWithImpl<$Res> @JsonSerializable() class _$LocationModelImpl implements _LocationModel { const _$LocationModelImpl( - {required final Map locations, + {required final List locations, @JsonKey(name: 'response_body') required this.responseBody}) : _locations = locations; factory _$LocationModelImpl.fromJson(Map json) => _$$LocationModelImplFromJson(json); - final Map _locations; + final List _locations; @override - Map get locations { - if (_locations is EqualUnmodifiableMapView) return _locations; + List get locations { + if (_locations is EqualUnmodifiableListView) return _locations; // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_locations); + return EqualUnmodifiableListView(_locations); } @override @@ -170,7 +170,7 @@ class _$LocationModelImpl implements _LocationModel { abstract class _LocationModel implements LocationModel { const factory _LocationModel( - {required final Map locations, + {required final List locations, @JsonKey(name: 'response_body') required final String responseBody}) = _$LocationModelImpl; @@ -178,7 +178,7 @@ abstract class _LocationModel implements LocationModel { _$LocationModelImpl.fromJson; @override - Map get locations; + List get locations; @override @JsonKey(name: 'response_body') String get responseBody; diff --git a/lib/features/registration/models/location_model.g.dart b/lib/features/registration/models/location_model.g.dart index 04a199fb..449630a4 100644 --- a/lib/features/registration/models/location_model.g.dart +++ b/lib/features/registration/models/location_model.g.dart @@ -8,7 +8,8 @@ part of 'location_model.dart'; _$LocationModelImpl _$$LocationModelImplFromJson(Map json) => _$LocationModelImpl( - locations: json['locations'] as Map, + locations: + (json['locations'] as List).map((e) => e as String).toList(), responseBody: json['response_body'] as String, ); diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index af81fda8..c8aef3fa 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -59,7 +59,7 @@ class RegistrationCubit extends Cubit { try { final location = (await registrationRepository.getLocationsListByQuery(query)); emit(RegistrationState(sheet: state.sheet.copyWith(responseBody: location.responseBody))); - return location.locations.values.toList().map((e) => e.toString()).toList(); + return location.locations; } catch (e) { rethrow; } From 2b3a3a0e93d684ef5a5ed21aa00159759d14b177 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 29 Jul 2024 15:55:48 +0300 Subject: [PATCH 5/8] feature(blogPosts): refactor main screen --- lib/app_router.dart | 4 +- .../chat_select_screen.dart | 1 - .../presentation/pages/feed_screen_new.dart | 211 ++++++++++++++++++ .../widgets/blog_post_widget.dart | 129 +++++++++++ .../repositories/registration_repository.dart | 2 +- .../services/registration_service.dart | 10 +- .../services/registration_service.g.dart | 37 ++- 7 files changed, 386 insertions(+), 8 deletions(-) create mode 100644 lib/features/feed/presentation/pages/feed_screen_new.dart create mode 100644 lib/features/feed/presentation/widgets/blog_post_widget.dart diff --git a/lib/app_router.dart b/lib/app_router.dart index 9c23dd00..4cddc6ec 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -6,7 +6,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/b import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; -import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; +import 'package:eatvio_app/features/feed/presentation/pages/feed_screen_new.dart'; import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart'; import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart'; @@ -64,7 +64,7 @@ class AppRouter { GoRoute( path: '/feed-screen', name: 'feedScreen', - builder: (context, state) => FeedScreen(), + builder: (context, state) => FeedScreenNew(), routes: [ GoRoute( path: 'blog-post-create-screen', diff --git a/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart index 3df79dfb..32258dae 100644 --- a/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart @@ -37,7 +37,6 @@ class ChatSelectScreen extends StatelessWidget { return Scaffold( appBar: AppBar( backgroundColor: Colors.white, - forceMaterialTransparency: true, toolbarHeight: 0, ), body: NestedScrollView( diff --git a/lib/features/feed/presentation/pages/feed_screen_new.dart b/lib/features/feed/presentation/pages/feed_screen_new.dart new file mode 100644 index 00000000..e1f1b151 --- /dev/null +++ b/lib/features/feed/presentation/pages/feed_screen_new.dart @@ -0,0 +1,211 @@ +import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; +import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; +import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_widget.dart'; +import 'package:eatvio_app/theme/theme_helper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class FeedScreenNew extends StatefulWidget { + const FeedScreenNew({super.key}); + + @override + State createState() => _FeedScreenNewState(); +} + +class _FeedScreenNewState extends State { + final List chipsLabelList = ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты']; + + int _selectedChipIndex = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + toolbarHeight: 0, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarColor: Colors.white, + ), + ), + backgroundColor: Colors.grey.shade200, + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)), + ), + backgroundColor: Colors.white, + foregroundColor: Colors.white, + automaticallyImplyLeading: false, + stretchTriggerOffset: 1, + floating: true, + snap: true, + bottom: _chipGroup(), + title: Row( + children: [ + _searchBar(), + SizedBox(width: 12), + _addBlogPostButton(), + ], + ), + ), + ]; + }, + body: _buildAllBlogPosts(), + ), + ); + } + + _buildAllBlogPosts() { + return Expanded( + child: BlocBuilder( + builder: (BuildContext context, FeedState state) { + if (state is FeedStateLoaded) { + return ListView.separated( + padding: EdgeInsets.symmetric(vertical: 8), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 8); + }, + itemCount: state.feedBlogPostList.length, + ); + } else if (state is FeedStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text(state.errorMessage), + ), + ), + ); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), + ); + } + + _chipGroup() { + return PreferredSize( + preferredSize: Size(MediaQuery.of(context).size.width, 32), + child: Align( + alignment: Alignment.centerLeft, + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16), + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _choiceChipWidget(0), + SizedBox(width: 8), + _choiceChipWidget(1), + SizedBox(width: 8), + _choiceChipWidget(2), + SizedBox(width: 8), + _choiceChipWidget(3), + SizedBox(width: 8), + _choiceChipWidget(4), + ], + ), + ), + ), + ); + } + + _addBlogPostButton() { + return InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(64), + child: Ink( + decoration: BoxDecoration( + color: appTheme.blueA400, + shape: BoxShape.circle, + ), + child: Icon( + Icons.add_rounded, + color: appTheme.gray100, + ), + width: 40, + height: 40, + ), + ); + } + + _searchBar() { + return SizedBox( + height: 40, + width: MediaQuery.of(context).size.width - 84, + child: TextFormField( + style: TextStyle( + fontSize: 12, + ), + decoration: InputDecoration( + hintText: 'Поиск', + hintStyle: TextStyle( + fontSize: 12, + color: appTheme.gray600, + fontWeight: FontWeight.w400, + ), + prefixIcon: Icon( + Icons.search, + color: appTheme.gray600, + size: 16, + ), + isDense: false, + contentPadding: EdgeInsets.all(8), + fillColor: appTheme.gray100, + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + ), + ), + ); + } + + _choiceChipWidget(int index) { + return ChoiceChip( + selectedColor: appTheme.blueA400, + pressElevation: 0, + showCheckmark: false, + side: BorderSide.none, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + label: Text(chipsLabelList[index]), + labelStyle: TextStyle( + color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, + fontWeight: FontWeight.w500, + fontSize: 12, + ), + selected: _selectedChipIndex == index, + backgroundColor: appTheme.gray100, + onSelected: (bool selected) { + setState(() { + _selectedChipIndex = index; + }); + }, + ); + } +} diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart new file mode 100644 index 00000000..b7d26df1 --- /dev/null +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -0,0 +1,129 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dismissible_page/dismissible_page.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:flutter/material.dart'; + +class BlogPostWidget extends StatefulWidget { + final FeedBlogPostModel feedBlogPostModel; + + const BlogPostWidget({super.key, required this.feedBlogPostModel}); + + @override + State createState() => _BlogPostWidgetState(); +} + +class _BlogPostWidgetState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () {}, + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + CircleAvatar(radius: 20), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Имя автора'), + Text('Время поста'), + ], + ), + ], + ), + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text(widget.feedBlogPostModel.title), + ), + SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text(widget.feedBlogPostModel.description), + ), + SizedBox(height: 8), + GestureDetector( + onTap: () { + context.pushTransparentRoute( + rootNavigator: true, + PhotoViewScreen( + imageUrls: [baseUrl + '/storage/' + widget.feedBlogPostModel.image], + index: 0, + ), + ); + }, + child: Hero( + tag: baseUrl + '/storage/' + widget.feedBlogPostModel.image, + child: CachedNetworkImage( + imageUrl: baseUrl + '/storage/' + widget.feedBlogPostModel.image, + ), + ), + ), + SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + children: [ + _iconButton(Icons.favorite_border_rounded), + SizedBox(width: 8), + _iconButton(Icons.mode_comment_outlined), + SizedBox(width: 8), + _iconButton(Icons.reply_rounded), + ], + ), + ), + SizedBox(height: 8), + ], + ), + ), + ); + } + + _iconButton(IconData icon, [String? text]) { + return InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(16), + child: Ink( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: appTheme.gray100, + borderRadius: BorderRadius.circular(16), + ), + height: 32, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 20, + color: appTheme.gray600, + ), + if (text != null) SizedBox(width: 8), + if (text != null) + Text( + text, + style: TextStyle( + color: appTheme.gray600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/registration/repositories/registration_repository.dart b/lib/features/registration/repositories/registration_repository.dart index 31a1ff11..a96a32f3 100644 --- a/lib/features/registration/repositories/registration_repository.dart +++ b/lib/features/registration/repositories/registration_repository.dart @@ -17,7 +17,7 @@ class RegistrationRepository { Future getLocationsListByQuery(String query) async { try { - return (await registrationService.getLocations(query: query)); + return (await registrationService.getLocationsByAddress(query: query)); } catch (e) { rethrow; } diff --git a/lib/features/registration/services/registration_service.dart b/lib/features/registration/services/registration_service.dart index af4bd836..08a23a0e 100644 --- a/lib/features/registration/services/registration_service.dart +++ b/lib/features/registration/services/registration_service.dart @@ -13,8 +13,14 @@ abstract class RegistrationService { @Body() required Map sheet, }); - @GET('/api/locations') - Future getLocations({ + @GET('/api/location_by_address') + Future getLocationsByAddress({ @Query('user_address') required String query, }); + + @GET('/api/api/location_by_coordinate') + Future getLocationsByCoordinate({ + @Query('coordinate[position][latitude]') required String latitude, + @Query('coordinate[position][longitude]') required String longitude, + }); } diff --git a/lib/features/registration/services/registration_service.g.dart b/lib/features/registration/services/registration_service.g.dart index 6c9ce5bc..9cd29f32 100644 --- a/lib/features/registration/services/registration_service.g.dart +++ b/lib/features/registration/services/registration_service.g.dart @@ -48,7 +48,7 @@ class _RegistrationService implements RegistrationService { } @override - Future getLocations({required String query}) async { + Future getLocationsByAddress({required String query}) async { final _extra = {}; final queryParameters = {r'user_address': query}; final _headers = {}; @@ -61,7 +61,40 @@ class _RegistrationService implements RegistrationService { ) .compose( _dio.options, - '/api/locations', + '/api/location_by_address', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final _value = LocationModel.fromJson(_result.data!); + return _value; + } + + @override + Future getLocationsByCoordinate({ + required String latitude, + required String longitude, + }) async { + final _extra = {}; + final queryParameters = { + r'coordinate[position][latitude]': latitude, + r'coordinate[position][longitude]': longitude, + }; + final _headers = {}; + const Map? _data = null; + final _result = await _dio + .fetch>(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/api/location_by_coordinate', queryParameters: queryParameters, data: _data, ) From 3af1318d1ce8a15dfd422b866f3abbc8d196bc9d Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 29 Jul 2024 16:14:48 +0300 Subject: [PATCH 6/8] feature(blogPosts): fix grey screen --- .../chat_select_screen.dart | 2 +- .../presentation/pages/feed_screen_new.dart | 65 +++++++++---------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart index 32258dae..5bbd1d7a 100644 --- a/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart @@ -47,7 +47,7 @@ class ChatSelectScreen extends StatelessWidget { sliver: SliverAppBar( automaticallyImplyLeading: false, stretchTriggerOffset: 1, - forceMaterialTransparency: true, + scrolledUnderElevation: 0, floating: true, snap: true, title: Padding( diff --git a/lib/features/feed/presentation/pages/feed_screen_new.dart b/lib/features/feed/presentation/pages/feed_screen_new.dart index e1f1b151..bdd2476a 100644 --- a/lib/features/feed/presentation/pages/feed_screen_new.dart +++ b/lib/features/feed/presentation/pages/feed_screen_new.dart @@ -43,6 +43,7 @@ class _FeedScreenNewState extends State { stretchTriggerOffset: 1, floating: true, snap: true, + scrolledUnderElevation: 0, bottom: _chipGroup(), title: Row( children: [ @@ -60,40 +61,38 @@ class _FeedScreenNewState extends State { } _buildAllBlogPosts() { - return Expanded( - child: BlocBuilder( - builder: (BuildContext context, FeedState state) { - if (state is FeedStateLoaded) { - return ListView.separated( - padding: EdgeInsets.symmetric(vertical: 8), - shrinkWrap: true, - physics: BouncingScrollPhysics(), - itemBuilder: (context, index) { - return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); - }, - separatorBuilder: (context, index) { - return SizedBox(height: 8); - }, - itemCount: state.feedBlogPostList.length, - ); - } else if (state is FeedStateError) { - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage), - ), + return BlocBuilder( + builder: (BuildContext context, FeedState state) { + if (state is FeedStateLoaded) { + return ListView.separated( + padding: EdgeInsets.symmetric(vertical: 8), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 8); + }, + itemCount: state.feedBlogPostList.length, + ); + } else if (state is FeedStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text(state.errorMessage), ), - ); - } else { - return Scaffold( - body: Center( - child: CupertinoActivityIndicator(), - ), - ); - } - }, - ), + ), + ); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, ); } From e6b1218199eaddd75ca8b5909ce3627834b51a79 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 31 Jul 2024 11:36:03 +0300 Subject: [PATCH 7/8] feat(registration): add location by coordinates --- android/app/src/main/AndroidManifest.xml | 8 +- android/build.gradle | 2 +- android/settings.gradle | 4 +- ios/Runner/Info.plist | 2 + .../registration_cubit.dart | 10 ++ .../pages/city_of_living_screen.dart | 152 +++++++++++++----- .../repositories/registration_repository.dart | 8 + .../services/registration_service.dart | 2 +- .../services/registration_service.g.dart | 2 +- pubspec.yaml | 1 + 10 files changed, 143 insertions(+), 48 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 435e0177..516dace6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,9 @@ - - + + NSAllowsArbitraryLoads + NSLocationWhenInUseUsageDescription + diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index c8aef3fa..dcad0306 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -64,4 +64,14 @@ class RegistrationCubit extends Cubit { rethrow; } } + + Future> getLocationsByCoordinates(String latitude, String longitude) async { + try { + final location = (await registrationRepository.getLocationsListByCoordinates(latitude, longitude)); + emit(RegistrationState(sheet: state.sheet.copyWith(responseBody: location.responseBody))); + return location.locations; + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/registration/presentation/pages/city_of_living_screen.dart b/lib/features/registration/presentation/pages/city_of_living_screen.dart index 0a3dd291..808c0ccc 100644 --- a/lib/features/registration/presentation/pages/city_of_living_screen.dart +++ b/lib/features/registration/presentation/pages/city_of_living_screen.dart @@ -2,8 +2,10 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/data/models/userRegistrationModel/user_registration_model.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; import 'package:eatvio_app/widgets/custom_edit_text_form_field.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:location/location.dart'; class CityOfLivingScreen extends StatefulWidget { final UserRegistrationSheet sheet; @@ -21,6 +23,8 @@ class _CityOfLivingScreenState extends State { late SelectionPopupModel selectedItem; final cityController = TextEditingController(); + final suggestionsController = SuggestionsController(); + bool isLoading = false; @override void initState() { @@ -30,6 +34,34 @@ class _CityOfLivingScreenState extends State { } } + Future getLocationData() async { + Location location = new Location(); + + bool _serviceEnabled; + PermissionStatus _permissionGranted; + LocationData _locationData; + + _serviceEnabled = await location.serviceEnabled(); + if (!_serviceEnabled) { + _serviceEnabled = await location.requestService(); + if (!_serviceEnabled) { + return null; + } + } + + _permissionGranted = await location.hasPermission(); + if (_permissionGranted == PermissionStatus.denied) { + _permissionGranted = await location.requestPermission(); + if (_permissionGranted != PermissionStatus.granted) { + return null; + } + } + + _locationData = await location.getLocation(); + + return _locationData; + } + @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -39,43 +71,89 @@ class _CityOfLivingScreenState extends State { children: [ Text("Местоположение"), SizedBox(height: 4), - TypeAheadField( - debounceDuration: Duration(milliseconds: 300), - controller: cityController, - builder: (context, controller, focus) => CustomTextFormField( - controller: cityController, - focusNode: focus, - autofocus: true, - textStyle: TextStyle( - fontSize: 16, - color: Colors.black, - )), - itemBuilder: (BuildContext context, String item) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item, style: theme.textTheme.bodyLarge?.copyWith(color: Colors.black)), - ); - }, - decorationBuilder: (context, child) => Material( - type: MaterialType.card, - elevation: 4, - borderRadius: BorderRadius.circular(8), - child: child, - ), - onSelected: (item) { - setState(() { - cityController.text = item; - }); - final registrationCubit = context.read(); - registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: item)); - }, - suggestionsCallback: (query) async { - if (query.length >= 3) { - final registrationCubit = context.read(); - return (await registrationCubit.getLocationsByQuery(query)); - } - return null; - }, + Row( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width - 80, + child: TypeAheadField( + debounceDuration: Duration(milliseconds: 300), + controller: cityController, + suggestionsController: suggestionsController, + builder: (context, controller, focus) => CustomTextFormField( + controller: cityController, + focusNode: focus, + autofocus: true, + textStyle: TextStyle( + fontSize: 16, + color: Colors.black, + )), + itemBuilder: (BuildContext context, String item) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text(item, style: theme.textTheme.bodyLarge?.copyWith(color: Colors.black)), + ); + }, + decorationBuilder: (context, child) => Material( + type: MaterialType.card, + elevation: 4, + borderRadius: BorderRadius.circular(8), + child: child, + ), + onSelected: (item) { + setState(() { + cityController.text = item; + }); + final registrationCubit = context.read(); + registrationCubit.updateSheet(registrationCubit.state.sheet.copyWith(location: item)); + }, + suggestionsCallback: (query) async { + if (query.length >= 3) { + final registrationCubit = context.read(); + return (await registrationCubit.getLocationsByQuery(query)); + } + return null; + }, + ), + ), + SizedBox(width: 8), + InkWell( + borderRadius: BorderRadius.circular(8), + onTap: !isLoading + ? () async { + setState(() { + isLoading = true; + }); + final LocationData? locationData = await getLocationData(); + if (locationData != null) { + final List locations = await context + .read() + .getLocationsByCoordinates(locationData.latitude.toString(), locationData.longitude.toString()); + setState(() { + cityController.text = locations.first; + suggestionsController.suggestions = locations; + }); + } + setState(() { + isLoading = false; + }); + } + : null, + child: Ink( + height: 40, + width: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Color(0xFFEAF2FD), + ), + child: !isLoading + ? Icon( + Icons.near_me_rounded, + color: appTheme.blueA400, + ) + : CupertinoActivityIndicator(), + ), + ), + ], ), ], ), diff --git a/lib/features/registration/repositories/registration_repository.dart b/lib/features/registration/repositories/registration_repository.dart index a96a32f3..d609ee12 100644 --- a/lib/features/registration/repositories/registration_repository.dart +++ b/lib/features/registration/repositories/registration_repository.dart @@ -22,4 +22,12 @@ class RegistrationRepository { rethrow; } } + + Future getLocationsListByCoordinates(String latitude, String longitude) async { + try { + return (await registrationService.getLocationsByCoordinate(latitude: latitude, longitude: longitude)); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/registration/services/registration_service.dart b/lib/features/registration/services/registration_service.dart index 08a23a0e..200c2f6f 100644 --- a/lib/features/registration/services/registration_service.dart +++ b/lib/features/registration/services/registration_service.dart @@ -18,7 +18,7 @@ abstract class RegistrationService { @Query('user_address') required String query, }); - @GET('/api/api/location_by_coordinate') + @GET('/api/location_by_coordinate') Future getLocationsByCoordinate({ @Query('coordinate[position][latitude]') required String latitude, @Query('coordinate[position][longitude]') required String longitude, diff --git a/lib/features/registration/services/registration_service.g.dart b/lib/features/registration/services/registration_service.g.dart index 9cd29f32..9820fd33 100644 --- a/lib/features/registration/services/registration_service.g.dart +++ b/lib/features/registration/services/registration_service.g.dart @@ -94,7 +94,7 @@ class _RegistrationService implements RegistrationService { ) .compose( _dio.options, - '/api/api/location_by_coordinate', + '/api/location_by_coordinate', queryParameters: queryParameters, data: _data, ) diff --git a/pubspec.yaml b/pubspec.yaml index 2cdbe598..110e8fca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: lazy_load_scrollview: ^1.3.0 badges: ^3.1.2 google_fonts: ^6.2.1 + location: ^7.0.0 dev_dependencies: build_runner: ^2.4.11 From bdfa671c18374c35f8c25e6f15f39b4c6e5b874f Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 31 Jul 2024 13:10:07 +0300 Subject: [PATCH 8/8] feat(blogPost): add routing --- lib/app_router.dart | 40 +++++------ .../presentation/pages/blog_post_screen.dart | 2 +- .../pages/blog_post_create_screen.dart | 9 ++- .../feed/presentation/pages/feed_screen.dart | 2 +- .../presentation/pages/feed_screen_new.dart | 68 ++++++++++--------- .../widgets/blog_post_widget.dart | 16 +++-- .../subscribers_screen.dart | 2 +- 7 files changed, 74 insertions(+), 65 deletions(-) diff --git a/lib/app_router.dart b/lib/app_router.dart index 4cddc6ec..605e247a 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -65,28 +65,6 @@ class AppRouter { path: '/feed-screen', name: 'feedScreen', builder: (context, state) => FeedScreenNew(), - routes: [ - GoRoute( - path: 'blog-post-create-screen', - name: 'blogPostCreateScreen', - builder: (context, state) => BlogPostCreateScreen.builder(context), - ), - GoRoute( - path: 'blog-post-screen', - name: 'blogPostScreen', - builder: (context, state) => BlogPostScreen(blogPostID: state.extra as String), - routes: [ - GoRoute( - path: 'blog-post-edit-screen', - name: 'blogPostEditScreen', - builder: (context, state) => BlogPostEditScreen.builder( - context, - state.extra, - ), - ), - ], - ), - ], ), GoRoute( path: '/diary-screen', @@ -100,6 +78,24 @@ class AppRouter { ), ], ), + GoRoute( + path: '/blog-post-create-screen', + name: 'blogPostCreateScreen', + builder: (context, state) => BlogPostCreateScreen.builder(context), + ), + GoRoute( + path: '/blog-post-screen', + name: 'blogPostScreen', + builder: (context, state) => BlogPostScreen(blogPostID: state.extra as String), + ), + GoRoute( + path: '/blog-post-edit-screen', + name: 'blogPostEditScreen', + builder: (context, state) => BlogPostEditScreen.builder( + context, + state.extra, + ), + ), GoRoute( path: '/chat-screen', name: 'chatScreen', diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index c46c8b15..8bb66303 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -294,7 +294,7 @@ class BlogPostScreen extends StatelessWidget { void tapMenu(String value, BuildContext context, BlogPostStateLoaded state) { if (value == 'edit') { - GoRouter.of(context).goNamed( + GoRouter.of(context).pushNamed( 'blogPostEditScreen', extra: state.blogPost.id.toString(), ); diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index 39c3f4e4..d8295147 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -9,7 +9,7 @@ import 'package:image_picker/image_picker.dart'; import '../../../../presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart'; import '../../../../presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart'; -class BlogPostCreateScreen extends StatelessWidget { +class BlogPostCreateScreen extends StatefulWidget { const BlogPostCreateScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { @@ -21,6 +21,11 @@ class BlogPostCreateScreen extends StatelessWidget { ); } + @override + State createState() => _BlogPostCreateScreenState(); +} + +class _BlogPostCreateScreenState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { @@ -115,7 +120,7 @@ class BlogPostCreateScreen extends StatelessWidget { } } -_submitForm(BuildContext context, String title, String description, String youtubeURL) { +_submitForm(BuildContext context, String title, String description, String youtubeURL) async { String? titleError = _validateTitle(title); String? descriptionError = _validateDescription(description); String? youtubeURLError = _validateYouTubeLink(youtubeURL); diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index e97b39e1..93697ebc 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -108,7 +108,7 @@ class FeedScreen extends StatelessWidget { flex: 1, child: CustomOutlinedButton( text: "+", - onPressed: () => GoRouter.of(context).goNamed('blogPostCreateScreen'), + onPressed: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, ), ), diff --git a/lib/features/feed/presentation/pages/feed_screen_new.dart b/lib/features/feed/presentation/pages/feed_screen_new.dart index bdd2476a..6b5e1835 100644 --- a/lib/features/feed/presentation/pages/feed_screen_new.dart +++ b/lib/features/feed/presentation/pages/feed_screen_new.dart @@ -6,6 +6,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; class FeedScreenNew extends StatefulWidget { const FeedScreenNew({super.key}); @@ -61,38 +62,41 @@ class _FeedScreenNewState extends State { } _buildAllBlogPosts() { - return BlocBuilder( - builder: (BuildContext context, FeedState state) { - if (state is FeedStateLoaded) { - return ListView.separated( - padding: EdgeInsets.symmetric(vertical: 8), - shrinkWrap: true, - physics: BouncingScrollPhysics(), - itemBuilder: (context, index) { - return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); - }, - separatorBuilder: (context, index) { - return SizedBox(height: 8); - }, - itemCount: state.feedBlogPostList.length, - ); - } else if (state is FeedStateError) { - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage), + return RefreshIndicator( + onRefresh: () => context.read().loadFeedBlogPostList(), + child: BlocBuilder( + builder: (BuildContext context, FeedState state) { + if (state is FeedStateLoaded) { + return ListView.separated( + padding: EdgeInsets.symmetric(vertical: 8), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemBuilder: (context, index) { + return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 8); + }, + itemCount: state.feedBlogPostList.length, + ); + } else if (state is FeedStateError) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Text(state.errorMessage), + ), ), - ), - ); - } else { - return Scaffold( - body: Center( - child: CupertinoActivityIndicator(), - ), - ); - } - }, + ); + } else { + return Scaffold( + body: Center( + child: CupertinoActivityIndicator(), + ), + ); + } + }, + ), ); } @@ -124,7 +128,7 @@ class _FeedScreenNewState extends State { _addBlogPostButton() { return InkWell( - onTap: () {}, + onTap: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'), borderRadius: BorderRadius.circular(64), child: Ink( decoration: BoxDecoration( diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart index b7d26df1..6bf0f7ec 100644 --- a/lib/features/feed/presentation/widgets/blog_post_widget.dart +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class BlogPostWidget extends StatefulWidget { final FeedBlogPostModel feedBlogPostModel; @@ -17,8 +18,11 @@ class BlogPostWidget extends StatefulWidget { class _BlogPostWidgetState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () {}, + return InkWell( + onTap: () => GoRouter.of(context).pushNamed( + 'blogPostScreen', + extra: widget.feedBlogPostModel.id.toString(), + ), child: Ink( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), @@ -45,17 +49,17 @@ class _BlogPostWidgetState extends State { ], ), ), - SizedBox(height: 12), + if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text(widget.feedBlogPostModel.title), ), - SizedBox(height: 8), + if (widget.feedBlogPostModel.title.isNotEmpty && widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text(widget.feedBlogPostModel.description), ), - SizedBox(height: 8), + if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8), GestureDetector( onTap: () { context.pushTransparentRoute( @@ -73,7 +77,7 @@ class _BlogPostWidgetState extends State { ), ), ), - SizedBox(height: 8), + SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( diff --git a/lib/presentation/users/subscribers_screen/subscribers_screen.dart b/lib/presentation/users/subscribers_screen/subscribers_screen.dart index 01fb9a7f..bb968f23 100644 --- a/lib/presentation/users/subscribers_screen/subscribers_screen.dart +++ b/lib/presentation/users/subscribers_screen/subscribers_screen.dart @@ -139,7 +139,7 @@ class SubscribersScreen extends StatelessWidget { flex: 1, child: CustomOutlinedButton( text: "+", - onPressed: () => GoRouter.of(context).goNamed('blogPostCreateScreen'), + onPressed: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, ), ),