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

@@ -1,6 +1,7 @@
abstract class AppLocaleKeys {
///Storage keys
static const String language = 'language';
static const String browseSearchHistory = 'browse-search-history';
static const String fontBold = "fontBold";
static const String fontMedium = "fontMedium";

View File

@@ -13,6 +13,8 @@ import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728;
import '../../feature/browse/presentation/blocs/browse_bloc/browse_bloc.dart'
as _i991;
import '../../feature/common/presentation/blocs/language_bloc/language_bloc.dart'
as _i942;
import '../../feature/home/presentation/blocs/home_bloc/home_bloc.dart'
@@ -33,9 +35,10 @@ extension GetItInjectableX on _i174.GetIt {
}) {
final gh = _i526.GetItHelper(this, environment, environmentFilter);
gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc());
gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc());
gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc());
gh.factory<_i580.MainBloc>(() => _i580.MainBloc());
gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc());
gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc());
gh.singleton<_i306.StorageService>(() => _i306.StorageService());
gh.singleton<_i152.AppRoutes>(() => _i152.AppRoutes());
gh.factory<_i942.LanguageBloc>(

View File

@@ -12,6 +12,16 @@ class StorageService {
_sharedPreference.setString(key, value);
}
void setStringList({required String key, required String value}) {
final oldList = _sharedPreference.getStringList(key);
final newList = oldList?..add(value);
_sharedPreference.setStringList(key, newList ?? []);
}
List<String> getStringList({required String key}) {
return _sharedPreference.getStringList(key) ?? [];
}
String? getString({required String key}) {
return _sharedPreference.getString(key);
}

View File

@@ -1,3 +1,10 @@
export 'presentation/pages/browse_page/browse_page.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_browse_body.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_browse_item.dart';
export "presentation/blocs/browse_bloc/browse_bloc.dart";
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_all_categories_skeletonizer.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_browse_skeletonizer.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_all_categories.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_top_categories.dart';
export 'package:food_delivery_client/feature/common/presentation/widgets/app_text_form_field.dart';
export 'package:food_delivery_client/feature/browse/presentation/pages/browse_page/widgets/w_top_categories_skeletonizer.dart';

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

View File

@@ -17,7 +17,7 @@ class AppTextFormField extends StatelessWidget {
this.obscureText = false,
required this.controller,
this.prefixIcon,
this.prefixSvgPath,
this.prefixSvgPath, this.focusNode,
});
final int? maxLines;
@@ -36,6 +36,7 @@ class AppTextFormField extends StatelessWidget {
final TextStyle? hintTextStyle;
late final Widget? prefixIcon;
final String? prefixSvgPath;
final FocusNode? focusNode;
@override
Widget build(BuildContext context) {
@@ -45,6 +46,7 @@ class AppTextFormField extends StatelessWidget {
maxLines: maxLines ?? 1,
minLines: minLines ?? 1,
onChanged: onChanged,
focusNode: focusNode,
inputFormatters: inputFormatters,
keyboardType: keyBoardType,
style: textStyle ?? AppTextStyles.size16Regular,