feat:browse bloc done

This commit is contained in:
jahongireshonqulov
2025-10-25 17:54:56 +05:00
parent 57af573b6f
commit 97aae6e108
15 changed files with 377 additions and 37 deletions

View File

@@ -0,0 +1,48 @@
import 'package:food_delivery_client/food_delivery_client.dart';
part 'browse_event.dart';
part 'browse_state.dart';
part 'browse_bloc.freezed.dart';
@injectable
class BrowseBloc extends Bloc<BrowseEvent, BrowseState> {
BrowseBloc() : super(const BrowseState()) {
on<_GetTopCategories>(_onGetTopCategories);
on<_GetAllCategories>(_onGetAllCategories);
on<_GetSearchHistory>(_onGetSearchHistory);
on<_FocusChanged>(_onFocusChanged);
}
Future<void> _onGetTopCategories(
_GetTopCategories event,
Emitter<BrowseState> emit,
) async {
emit(state.copyWith(topCategorySt: RequestStatus.loading));
await Future.delayed(TimeDelayConst.duration3);
emit(state.copyWith(topCategorySt: RequestStatus.loaded));
}
Future<void> _onGetAllCategories(
_GetAllCategories event,
Emitter<BrowseState> emit,
) async {
emit(state.copyWith(allCategorySt: RequestStatus.loading));
await Future.delayed(TimeDelayConst.duration3);
emit(state.copyWith(allCategorySt: RequestStatus.loaded));
}
Future<void> _onGetSearchHistory(
_GetSearchHistory event,
Emitter<BrowseState> emit,
) async {
emit(state.copyWith(searchHistorySt: RequestStatus.loading));
await Future.delayed(TimeDelayConst.duration3);
emit(state.copyWith(searchHistorySt: RequestStatus.loading));
}
void _onFocusChanged(_FocusChanged event, Emitter<BrowseState> emit) {
emit(state.copyWith(isActive: event.isActive ?? !(state.isActive)));
}
}

View File

@@ -0,0 +1,14 @@
part of 'browse_bloc.dart';
@freezed
class BrowseEvent with _$BrowseEvent {
const factory BrowseEvent.started() = _Started;
const factory BrowseEvent.getTopCategories() = _GetTopCategories;
const factory BrowseEvent.getAllCategories() = _GetAllCategories;
const factory BrowseEvent.getSearchHistory() = _GetSearchHistory;
const factory BrowseEvent.focusChanged(bool? isActive) = _FocusChanged;
}

View File

@@ -0,0 +1,12 @@
part of 'browse_bloc.dart';
@freezed
abstract class BrowseState with _$BrowseState {
const factory BrowseState({
@Default(RequestStatus.initial) RequestStatus topCategorySt,
@Default(RequestStatus.initial) RequestStatus allCategorySt,
@Default(RequestStatus.initial) RequestStatus searchHistorySt,
@Default([]) List<String> searchHistory,
@Default(false) bool isActive,
}) = _BrowseState;
}

View File

@@ -6,8 +6,11 @@ class BrowsePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WLayout(
child: Scaffold(body:WBrowseBody() ),
return BlocProvider(
create: (context) => sl<BrowseBloc>()
..add(BrowseEvent.getTopCategories())
..add(BrowseEvent.getAllCategories()),
child: WLayout(child: Scaffold(body: WBrowseBody())),
);
}
}

View File

@@ -1,8 +1,25 @@
import '../../../../../../food_delivery_client.dart';
class WAllCategories extends StatelessWidget {
const WAllCategories({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<BrowseBloc, BrowseState>(
builder: (context, state) {
if (state.allCategorySt.isLoaded()) {
return WAllCategoriesBody();
}
return WAllCategoriesSkeletonizer();
},
);
}
}
class WAllCategoriesBody extends StatelessWidget {
const WAllCategoriesBody({super.key});
@override
Widget build(BuildContext context) {
return Column(

View File

@@ -0,0 +1,41 @@
import '../../../../../../food_delivery_client.dart';
class WAllCategoriesSkeletonizer extends StatelessWidget {
const WAllCategoriesSkeletonizer({
super.key,
});
@override
Widget build(BuildContext context) {
return Skeletonizer(
enabled: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
25.verticalSpace,
Text(
context.loc.allCategories,
style: AppTextStyles.size24Medium.copyWith(height: 36 / 24),
),
11.verticalSpace,
GridView.builder(
itemCount: 10,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
crossAxisSpacing: 10,
mainAxisExtent: 140,
),
itemBuilder: (context, index) {
return WBrowseSkeletonizerItem();
},
),
],
),
);
}
}

View File

@@ -1,43 +1,117 @@
import 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_all_categories.dart';
import 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_top_categories.dart';
import 'package:food_delivery_client/feature/common/presentation/widgets/app_text_form_field.dart';
import '../../../../../../food_delivery_client.dart';
class WBrowseBody extends StatelessWidget {
class WBrowseBody extends StatefulWidget {
const WBrowseBody({super.key});
@override
State<WBrowseBody> createState() => _WBrowseBodyState();
}
class _WBrowseBodyState extends State<WBrowseBody> {
late FocusNode _focusNode;
late TextEditingController _controller;
@override
void initState() {
_controller = TextEditingController();
_focusNode = FocusNode();
_focusNode.addListener(() {
context.read<BrowseBloc>().add(BrowseEvent.focusChanged(true));
});
super.initState();
}
void listener() {
_focusNode.addListener(() {
log("${_focusNode.hasFocus}");
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(color: AppColors.cFFFFFF),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
10.verticalSpace,
AppTextFormField(
controller: TextEditingController(),
prefixIcon: SvgPicture.asset(AppIcons.icSearch),
hintText: context.loc.categoriesShort,
hintTextStyle: AppTextStyles.size16Medium.copyWith(
color: AppColors.c660000
return BlocBuilder<BrowseBloc, BrowseState>(
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(color: AppColors.cFFFFFF),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
10.verticalSpace,
Row(
spacing: 15,
children: [
/*
InkWell(
onTap: () {},
borderRadius: AppUtils.kBorderRadius25,
child: Ink(
height: 44,
width: 44,
decoration: BoxDecoration(
color: AppColors.cEEEEEE,
borderRadius: AppUtils.kBorderRadius25,
),
child: SizedBox(
height: 44,
width: 44,
child: SvgPicture.asset(
AppIcons.icBack,
height: 20,
width: 20,
).paddingAll(10),
),
),
),
*/
Expanded(
child: AppTextFormField(
focusNode: _focusNode,
controller: _controller,
prefixIcon: SvgPicture.asset(AppIcons.icSearch),
hintText: context.loc.categoriesShort,
hintTextStyle: AppTextStyles.size16Medium.copyWith(
color: AppColors.c660000,
),
),
),
],
),
15.verticalSpace,
],
),
),
Expanded(
child: RefreshIndicator.adaptive(
onRefresh: () async {
context.read<BrowseBloc>()
..add(BrowseEvent.getTopCategories())
..add(BrowseEvent.getAllCategories());
},
child: SingleChildScrollView(
child: Column(
children: [
WTopCategories(),
WAllCategories(),
40.verticalSpace,
],
),
),
),
15.verticalSpace,
],
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [WTopCategories(), WAllCategories(), 40.verticalSpace],
),
),
),
],
],
);
},
).paddingSymmetric(horizontal: 16);
}
}

View File

@@ -0,0 +1,52 @@
import '../../../../../../food_delivery_client.dart';
class WBrowseSkeletonizerItem extends StatelessWidget {
const WBrowseSkeletonizerItem({
super.key,
});
@override
Widget build(BuildContext context) {
return Bounceable(
onTap: () {},
child: Ink(
decoration: BoxDecoration(
color: AppColors.cFFFFFF,
borderRadius: AppUtils.kBorderRadius15,
),
child: Column(
children: [
ClipRRect(
borderRadius: AppUtils.kBorderRadiusTop15,
child: CachedNetworkImage(
imageUrl: AppLocaleKeys.foodImageUrl,
width: context.w,
height: 96,
fit: BoxFit.cover,
),
),
SizedBox(
width: context.w,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: AppUtils.kBorderRadiusBottom15,
border: Border.all(
color: AppColors.cE8E8E8,
width: 1,
),
),
child: Text(
"Restaurant Rewards",
textAlign: TextAlign.center,
style: AppTextStyles.size16Regular.copyWith(
height: 20 / 16,
),
).paddingSymmetric(horizontal: 30),
),
),
],
),
),
);
}
}

View File

@@ -4,18 +4,34 @@ import '../../../../../../food_delivery_client.dart';
class WTopCategories extends StatelessWidget {
const WTopCategories({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<BrowseBloc, BrowseState>(
builder: (context, state) {
if (state.topCategorySt.isLoaded()) {
return WTopCategoriesBody();
}
return WTopCategoriesSkeletonizer();
},
);
}
}
class WTopCategoriesBody extends StatelessWidget {
const WTopCategoriesBody({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.loc.topCategories,
context.loc.topCategories,
style: AppTextStyles.size24Medium.copyWith(height: 36 / 24),
),
11.verticalSpace,
GridView.builder(
itemCount:6,
itemCount: 6,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),

View File

@@ -0,0 +1,40 @@
import '../../../../../../food_delivery_client.dart';
class WTopCategoriesSkeletonizer extends StatelessWidget {
const WTopCategoriesSkeletonizer({super.key});
@override
Widget build(BuildContext context) {
return Skeletonizer(
enabled: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.loc.topCategories,
style: AppTextStyles.size24Medium.copyWith(height: 36 / 24),
),
11.verticalSpace,
GridView.builder(
itemCount: 6,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
crossAxisSpacing: 10,
mainAxisExtent: 140,
),
itemBuilder: (context, index) {
return WBrowseSkeletonizerItem();
},
),
],
),
);
}
}