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/app_router.dart b/lib/app_router.dart index 2f9d3845..93a75c47 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,22 +1,25 @@ import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart'; +import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart'; +import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart'; +import 'package:eatvio_app/features/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'; import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart'; import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; -import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart'; -import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart'; import 'package:eatvio_app/presentation/glossary/glossary_screen/glossary_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; -import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -63,24 +66,7 @@ class AppRouter { GoRoute( path: '/feed-screen', name: 'feedScreen', - builder: (context, state) => FeedScreen(), - routes: [ - 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, - ), - ), - ], - ), - ], + builder: (context, state) => FeedScreenNew(), ), GoRoute( path: '/diary-screen', @@ -94,6 +80,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/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/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/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 b49b45c2..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 @@ -1,27 +1,31 @@ -/* -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'; -import 'package:eatvio_app/core/app_export.dart'; -import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:eatvio_app/widgets/custom_text_form_field.dart'; -import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'dart:io'; -class BlogPostCreateScreen extends StatelessWidget { +import 'package:eatvio_app/core/app_export.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: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 StatefulWidget { const BlogPostCreateScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { final blog_postID = ModalRoute.of(context)!.settings.arguments; return BlocProvider( - create: (context) => BlogPostCreateBloc(BlogPostCreateState( - blog_postModelObj: - BlogPostCreateModel(blog_postID: blog_postID.toString()))) + create: (context) => BlogPostCreateBloc(BlogPostCreateState(blog_postModelObj: BlogPostCreateModel(blog_postID: blog_postID.toString()))) ..add(BlogPostCreateInitialEvent()), child: BlogPostCreateScreen(), ); } + @override + State createState() => _BlogPostCreateScreenState(); +} + +class _BlogPostCreateScreenState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { @@ -45,7 +49,6 @@ class BlogPostCreateScreen extends StatelessWidget { } _buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) { - print(state.image); return Scaffold( resizeToAvoidBottomInset: false, body: Container( @@ -78,15 +81,13 @@ class BlogPostCreateScreen extends StatelessWidget { children: [ Column( children: [ - Text("Заголовок новости", - style: theme.textTheme.bodyLarge), + Text("Заголовок новости", style: theme.textTheme.bodyLarge), CustomTextFormField( controller: state.title, validator: _validateTitle, ), SizedBox(height: 8.0), - Text("Ссылка на YouTube", - style: theme.textTheme.bodyLarge), + Text("Ссылка на YouTube", style: theme.textTheme.bodyLarge), CustomTextFormField( controller: state.youtubeURL, validator: _validateYouTubeLink, @@ -103,8 +104,7 @@ class BlogPostCreateScreen extends StatelessWidget { SizedBox(height: 16.0), // Add widget for image upload here CustomOutlinedButton( - onPressed: () => _submitForm(context, state.title!.text, - state.description!.text, state.youtubeURL!.text), + onPressed: () => _submitForm(context, state.title!.text, state.description!.text, state.youtubeURL!.text), buttonStyle: CustomButtonStyles.outlinePrimaryTL15, text: 'Создать', ), @@ -120,20 +120,16 @@ 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); - if (titleError != null || - descriptionError != null || - youtubeURLError != null) { + if (titleError != null || descriptionError != null || youtubeURLError != null) { // Display error messages or handle invalid input as needed // For example, show a snackbar with the error message ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(titleError ?? descriptionError ?? youtubeURLError!)), + SnackBar(content: Text(titleError ?? descriptionError ?? youtubeURLError!)), ); } else { // Proceed with form submission @@ -200,11 +196,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)); } }; } @@ -234,8 +228,6 @@ String? _validateYouTubeLink(value) { } bool _isValidUrl(String url) { - final RegExp urlRegExp = - new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$"); + final RegExp urlRegExp = new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$"); return urlRegExp.hasMatch(url); } -*/ \ No newline at end of file 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..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 @@ -37,7 +37,6 @@ class ChatSelectScreen extends StatelessWidget { return Scaffold( appBar: AppBar( backgroundColor: Colors.white, - forceMaterialTransparency: true, toolbarHeight: 0, ), body: NestedScrollView( @@ -48,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/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/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 new file mode 100644 index 00000000..6b5e1835 --- /dev/null +++ b/lib/features/feed/presentation/pages/feed_screen_new.dart @@ -0,0 +1,214 @@ +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'; +import 'package:go_router/go_router.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, + scrolledUnderElevation: 0, + bottom: _chipGroup(), + title: Row( + children: [ + _searchBar(), + SizedBox(width: 12), + _addBlogPostButton(), + ], + ), + ), + ]; + }, + body: _buildAllBlogPosts(), + ), + ); + } + + _buildAllBlogPosts() { + 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(), + ), + ); + } + }, + ), + ); + } + + _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: () => GoRouter.of(context).pushNamed('blogPostCreateScreen'), + 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..6bf0f7ec --- /dev/null +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -0,0 +1,133 @@ +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'; +import 'package:go_router/go_router.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 InkWell( + onTap: () => GoRouter.of(context).pushNamed( + 'blogPostScreen', + extra: widget.feedBlogPostModel.id.toString(), + ), + 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('Время поста'), + ], + ), + ], + ), + ), + if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text(widget.feedBlogPostModel.title), + ), + if (widget.feedBlogPostModel.title.isNotEmpty && widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text(widget.feedBlogPostModel.description), + ), + if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) 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: 12), + 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/models/location_model.dart b/lib/features/registration/models/location_model.dart new file mode 100644 index 00000000..cc6b107e --- /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 List locations, + @JsonKey(name: 'response_body') required String responseBody, + }) = _LocationModel; + + factory LocationModel.fromJson(Map json) => _$LocationModelFromJson(json); +} 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..f640ca2e --- /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 { + List 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( + {List 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 List, + 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( + {List 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 List, + responseBody: null == responseBody + ? _value.responseBody + : responseBody // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$LocationModelImpl implements _LocationModel { + const _$LocationModelImpl( + {required final List locations, + @JsonKey(name: 'response_body') required this.responseBody}) + : _locations = locations; + + factory _$LocationModelImpl.fromJson(Map json) => + _$$LocationModelImplFromJson(json); + + final List _locations; + @override + List get locations { + if (_locations is EqualUnmodifiableListView) return _locations; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_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 List locations, + @JsonKey(name: 'response_body') required final String responseBody}) = + _$LocationModelImpl; + + factory _LocationModel.fromJson(Map json) = + _$LocationModelImpl.fromJson; + + @override + List 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..449630a4 --- /dev/null +++ b/lib/features/registration/models/location_model.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LocationModelImpl _$$LocationModelImplFromJson(Map json) => + _$LocationModelImpl( + locations: + (json['locations'] as List).map((e) => e as String).toList(), + 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..dcad0306 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,24 @@ 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; + } catch (e) { + 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 02e5c65e..808c0ccc 100644 --- a/lib/features/registration/presentation/pages/city_of_living_screen.dart +++ b/lib/features/registration/presentation/pages/city_of_living_screen.dart @@ -1,11 +1,11 @@ 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/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; @@ -23,20 +23,43 @@ class _CityOfLivingScreenState extends State { late SelectionPopupModel selectedItem; final cityController = TextEditingController(); + final suggestionsController = SuggestionsController(); + bool isLoading = false; @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.location.isNotEmpty) { + cityController.text = widget.sheet.location; } - if (widget.sheet.city.isNotEmpty) { - cityController.text = widget.sheet.city; + } + + 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 @@ -46,61 +69,91 @@ class _CityOfLivingScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("country".tr), + Text("Местоположение"), 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("Населенный пункт"), - SizedBox(height: 4), - TypeAheadField( - debounceDuration: Duration.zero, - controller: cityController, - builder: (context, controller, focus) => CustomTextFormField( - controller: cityController, - focusNode: focus, - 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(city: item)); - }, - suggestionsCallback: (query) { - return citiesList.where((country) => country.toLowerCase().contains(query.toLowerCase())).toList(); - }, + 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/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..d609ee12 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,20 @@ class RegistrationRepository { rethrow; } } + + Future getLocationsListByQuery(String query) async { + try { + return (await registrationService.getLocationsByAddress(query: query)); + } catch (e) { + 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 fefca6ad..200c2f6f 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,15 @@ abstract class RegistrationService { Future> register({ @Body() required Map sheet, }); + + @GET('/api/location_by_address') + Future getLocationsByAddress({ + @Query('user_address') required String query, + }); + + @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 269d16cc..9820fd33 100644 --- a/lib/features/registration/services/registration_service.g.dart +++ b/lib/features/registration/services/registration_service.g.dart @@ -47,6 +47,66 @@ class _RegistrationService implements RegistrationService { return _value; } + @override + Future getLocationsByAddress({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/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/location_by_coordinate', + 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 624a1ef4..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 @@ -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,28 @@ 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}', - ); + print('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, - }; + '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 +99,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_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]; } 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/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, ), ), 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, diff --git a/pubspec.yaml b/pubspec.yaml index a44d77ab..5a8d7e6d 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 syncfusion_flutter_charts: ^26.1.42 dev_dependencies: