diff --git a/lib/app_router.dart b/lib/app_router.dart index 9c23dd0..4cddc6e 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 3df79df..32258da 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 0000000..e1f1b15 --- /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 0000000..b7d26df --- /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 31a1ff1..a96a32f 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 af4bd83..08a23a0 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 6c9ce5b..9cd29f3 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, )