feature(blogPosts): refactor main screen

This commit is contained in:
mso7nueh
2024-07-29 15:55:48 +03:00
parent f96c6b53d6
commit 2b3a3a0e93
7 changed files with 386 additions and 8 deletions

View File

@@ -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',

View File

@@ -37,7 +37,6 @@ class ChatSelectScreen extends StatelessWidget {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
forceMaterialTransparency: true,
toolbarHeight: 0,
),
body: NestedScrollView(

View 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;
});
},
);
}
}

View 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,
),
),
],
),
),
);
}
}

View File

@@ -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;
}

View File

@@ -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,
});
}

View File

@@ -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,
)