feature(blogPosts): refactor main screen
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -37,7 +37,6 @@ class ChatSelectScreen extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
forceMaterialTransparency: true,
|
||||
toolbarHeight: 0,
|
||||
),
|
||||
body: NestedScrollView(
|
||||
|
||||
211
lib/features/feed/presentation/pages/feed_screen_new.dart
Normal file
211
lib/features/feed/presentation/pages/feed_screen_new.dart
Normal file
@@ -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<FeedScreenNew> createState() => _FeedScreenNewState();
|
||||
}
|
||||
|
||||
class _FeedScreenNewState extends State<FeedScreenNew> {
|
||||
final List<String> 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<FeedCubit, FeedState>(
|
||||
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;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
129
lib/features/feed/presentation/widgets/blog_post_widget.dart
Normal file
129
lib/features/feed/presentation/widgets/blog_post_widget.dart
Normal file
@@ -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<BlogPostWidget> createState() => _BlogPostWidgetState();
|
||||
}
|
||||
|
||||
class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
@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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class RegistrationRepository {
|
||||
|
||||
Future<LocationModel> getLocationsListByQuery(String query) async {
|
||||
try {
|
||||
return (await registrationService.getLocations(query: query));
|
||||
return (await registrationService.getLocationsByAddress(query: query));
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,14 @@ abstract class RegistrationService {
|
||||
@Body() required Map<String, dynamic> sheet,
|
||||
});
|
||||
|
||||
@GET('/api/locations')
|
||||
Future<LocationModel> getLocations({
|
||||
@GET('/api/location_by_address')
|
||||
Future<LocationModel> getLocationsByAddress({
|
||||
@Query('user_address') required String query,
|
||||
});
|
||||
|
||||
@GET('/api/api/location_by_coordinate')
|
||||
Future<LocationModel> getLocationsByCoordinate({
|
||||
@Query('coordinate[position][latitude]') required String latitude,
|
||||
@Query('coordinate[position][longitude]') required String longitude,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class _RegistrationService implements RegistrationService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LocationModel> getLocations({required String query}) async {
|
||||
Future<LocationModel> getLocationsByAddress({required String query}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{r'user_address': query};
|
||||
final _headers = <String, dynamic>{};
|
||||
@@ -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<LocationModel> getLocationsByCoordinate({
|
||||
required String latitude,
|
||||
required String longitude,
|
||||
}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{
|
||||
r'coordinate[position][latitude]': latitude,
|
||||
r'coordinate[position][longitude]': longitude,
|
||||
};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _result = await _dio
|
||||
.fetch<Map<String, dynamic>>(_setStreamType<LocationModel>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/api/location_by_coordinate',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user