// ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/widgets/category_tab_bar.dart'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import '../../../core/assets/assets.gen.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; import '../widgets/home_title.dart'; import '../widgets/order_menu.dart'; import '../widgets/product_card.dart'; class HomePage extends StatefulWidget { final bool isTable; final TableModel? table; final List items; const HomePage({ super.key, required this.isTable, this.table, required this.items, }); @override State createState() => _HomePageState(); } class _HomePageState extends State { final searchController = TextEditingController(); final ScrollController scrollController = ScrollController(); String searchQuery = ''; test() async { // await AuthLocalDataSource().removeAuthData(); } @override void initState() { test(); // First sync products from API, then load local products _syncAndLoadProducts(); super.initState(); } @override void dispose() { // Properly dispose controllers searchController.dispose(); scrollController.dispose(); super.dispose(); } void _syncAndLoadProducts() { // Trigger sync from API first // context.read().add(const SyncProductEvent.syncProduct()); // Also load local products initially in case sync fails or takes time // context // .read() // .add(const LocalProductEvent.getLocalProduct()); context .read() .add(const ProductLoaderEvent.getProduct()); // Initialize checkout with tax and service charge settings context.read().add(CheckoutEvent.started(widget.items)); // Get Category context.read().add(CategoryLoaderEvent.get()); // Get Outlets context.read().add(CurrentOutletEvent.currentOutlet()); } void onCategoryTap(int index) { searchController.clear(); setState(() { searchQuery = ''; }); } List _filterProducts(List products) { if (searchQuery.isEmpty) { return products; } return products.where((product) { final productName = product.name?.toLowerCase() ?? ''; final queryLower = searchQuery.toLowerCase(); return productName.contains(queryLower); }).toList(); } bool _handleScrollNotification( ScrollNotification notification, String? categoryId) { // Check if the ScrollController is attached before accessing position if (!scrollController.hasClients) { return false; } if (notification is ScrollEndNotification && scrollController.position.extentAfter == 0) { context.read().add( ProductLoaderEvent.loadMore( categoryId: categoryId, search: searchQuery, ), ); return true; } return false; } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, loading: () {}, success: () { Future.delayed(Duration(milliseconds: 300), () { AppFlushbar.showSuccess(context, 'Outlet berhasil diubah'); context .read() .add(CurrentOutletEvent.currentOutlet()); }); }, error: (message) => AppFlushbar.showError(context, message), ); }, child: Hero( tag: 'confirmation_screen', child: Scaffold( backgroundColor: AppColors.white, body: Row( children: [ Expanded( flex: 3, child: Align( alignment: AlignmentDirectional.topStart, child: BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => Center( child: CircularProgressIndicator(), ), loaded: (categories, categoryId) => Column( mainAxisAlignment: MainAxisAlignment.start, children: [ HomeTitle( controller: searchController, onChanged: (value) { setState(() { searchQuery = value; }); Future.delayed(Duration(milliseconds: 600), () { context.read().add( ProductLoaderEvent.getProduct( categoryId: categoryId, search: value, ), ); }); }, ), BlocBuilder( builder: (context, state) { return NotificationListener( onNotification: (notification) { return state.maybeWhen( orElse: () => false, loaded: (products, hasReachedMax, currentPage, isLoadingMore) { return _handleScrollNotification( notification, categoryId); }, ); }, child: Expanded( child: CategoryTabBar( categories: categories, tabViews: categories.map((category) { return SizedBox( child: state.maybeWhen(orElse: () { return const Center( child: CircularProgressIndicator(), ); }, loading: () { return const Center( child: CircularProgressIndicator(), ); }, loaded: (products, hashasReachedMax, currentPage, isLoadingMore) { final filteredProducts = _filterProducts(products); if (filteredProducts.isEmpty) { return Center( child: Column( children: [ Text('No Items Found'), SpaceHeight(20), Button.filled( width: 120, onPressed: () { context .read< ProductLoaderBloc>() .add(const ProductLoaderEvent .getProduct()); }, label: 'Retry', ), ], ), ); } return GridView.builder( itemCount: filteredProducts.length, controller: scrollController, padding: const EdgeInsets.all(16), gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 180, mainAxisSpacing: 30, crossAxisSpacing: 30, childAspectRatio: 180 / 240, ), itemBuilder: (context, index) => ProductCard( data: filteredProducts[index], onCartButton: () {}, ), ); }), ); }).toList(), ), ), ); }, ), ], ), ); }, ), ), ), Expanded( flex: 2, child: Align( alignment: Alignment.topCenter, child: Material( color: Colors.white, child: Column( children: [ HomeRightTitle( table: widget.table, ), Padding( padding: const EdgeInsets.all(16.0) .copyWith(bottom: 0, top: 27), child: Column( children: [ const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Item', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox( width: 130, ), SizedBox( width: 50.0, child: Text( 'Qty', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), SizedBox( child: Text( 'Price', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ], ), const SpaceHeight(8), const Divider(), ], ), ), Expanded( child: BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( child: Text('No Items'), ), loaded: ( products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType, deliveryType, ) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } return ListView.separated( shrinkWrap: true, padding: const EdgeInsets.symmetric( horizontal: 16), itemBuilder: (context, index) => OrderMenu(data: products[index]), separatorBuilder: (context, index) => const SpaceHeight(1.0), itemCount: products.length, ); }, ); }, ), ), Padding( padding: const EdgeInsets.all(16.0).copyWith(top: 0), child: Column( children: [ const Divider(), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Pajak', style: TextStyle( color: AppColors.black, fontWeight: FontWeight.bold, ), ), BlocBuilder( builder: (context, state) { final tax = state.maybeWhen( orElse: () => 0, loaded: ( products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType, deliveryType, ) { if (products.isEmpty) { return 0; } return tax; }); return Text( '$tax %', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Sub total', style: TextStyle( color: AppColors.black, fontWeight: FontWeight.bold, ), ), BlocBuilder( builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType, deliveryType) { if (products.isEmpty) { return 0; } return products .map((e) => (e.product.price! * e.quantity) + (e.variant?.priceModifier ?? 0)) .reduce((value, element) => value + element); }); return Text( price.currencyFormatRp, style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w900, ), ); }, ), ], ), SpaceHeight(16.0), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => Align( alignment: Alignment.bottomCenter, child: Button.filled( borderRadius: 12, elevation: 1, disabled: true, onPressed: () { context.push(ConfirmPaymentPage( isTable: widget.table == null ? false : true, table: widget.table, )); }, label: 'Lanjutkan Pembayaran', ), ), loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType, deliveryType) => Align( alignment: Alignment.bottomCenter, child: Button.filled( borderRadius: 12, elevation: 1, disabled: items.isEmpty, onPressed: () { if (orderType.name == 'dineIn' && widget.table == null) { AppFlushbar.showError(context, 'Mohon pilih meja terlebih dahulu'); return; } if (orderType.name == 'delivery' && deliveryType == null) { AppFlushbar.showError(context, 'Mohon pilih pengiriman terlebih dahulu'); return; } context.push(ConfirmPaymentPage( isTable: widget.table == null ? false : true, table: widget.table, )); }, label: 'Lanjutkan Pembayaran', ), ), ); }, ), ], ), ), ], ), ), ), ), ], ), ), ), ); } } // ignore: unused_element class _IsEmpty extends StatelessWidget { const _IsEmpty(); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SpaceHeight(40), Assets.icons.noProduct.svg(), const SizedBox(height: 40.0), const Text( 'Belum Ada Produk', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), ], ); } }