import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:niogu_ecommerce_v1/core/constant/app_font_size.dart'; import 'package:niogu_ecommerce_v1/core/enums/stock_type.dart'; import 'package:niogu_ecommerce_v1/core/errors/exceptions.dart'; import 'package:niogu_ecommerce_v1/core/providers/app_provider.dart'; import 'package:niogu_ecommerce_v1/core/router/app_route.dart'; import 'package:niogu_ecommerce_v1/core/system/system_setting.dart'; import 'package:niogu_ecommerce_v1/core/utils/currency_format.dart'; import 'package:niogu_ecommerce_v1/core/utils/extenstion_format.dart'; import 'package:niogu_ecommerce_v1/core/utils/log_message.dart'; import 'package:niogu_ecommerce_v1/core/widgets/custom_empty_screen.dart'; import 'package:niogu_ecommerce_v1/core/widgets/custom_snackbar.dart'; import 'package:niogu_ecommerce_v1/features/account/presentation/widgets/login_auth_modal.dart'; import 'package:niogu_ecommerce_v1/features/cart/domain/entities/cart.dart'; import 'package:niogu_ecommerce_v1/features/cart/presentation/providers/cart_provider.dart'; import 'package:niogu_ecommerce_v1/features/favorite/domain/entities/favorite.dart'; import 'package:niogu_ecommerce_v1/features/favorite/presentation/providers/favorite_provider.dart'; import 'package:niogu_ecommerce_v1/features/home/domain/entities/home.dart'; import 'package:niogu_ecommerce_v1/features/home/presentation/providers/home_provider.dart'; import 'package:niogu_ecommerce_v1/features/product/presentation/providers/product_provider.dart'; import 'package:shimmer/shimmer.dart'; import 'package:sizer/sizer.dart'; import 'package:niogu_ecommerce_v1/core/constant/app_color.dart'; class CartScreen extends ConsumerStatefulWidget { const CartScreen({super.key}); @override ConsumerState createState() => _CartScreenState(); } class _CartScreenState extends ConsumerState { @override void initState() { // TODO: implement initState super.initState(); } @override void dispose() { // TODO: implement dispose super.dispose(); } double _calculateTotalOrder(List cartItems) { return cartItems.fold(0.0, (sum, cartItem) { return sum + (cartItem.sellingPrice * cartItem.quantity); }); } Future _processCheckout() async { final isLoggedIn = ref.read(currentStatusLoginProvider); if (!isLoggedIn) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, constraints: BoxConstraints(maxWidth: double.infinity), builder: (context) => const LoginAuthModal(), ); return; } final cartItems = ref.read(cartItemProvider); await SystemSetting.saveCartItemByOutlet(cartItems); context.pushNamed(AppRoute.checkoutScreen); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final productState = ref.watch(productBestSellerControllerProvider); final currentOutletName = ref.watch(currentOutletNameProvider); final cartItemState = ref.watch(cartItemProvider); final currentOutletId = ref.watch(currentOutletIdProvider); final favoriteState = ref.watch(favoriteControllerProvider); return SafeArea( top: false, bottom: true, right: false, left: false, child: Scaffold( backgroundColor: Colors.white, resizeToAvoidBottomInset: false, appBar: AppBar( backgroundColor: Colors.white, elevation: 0, centerTitle: true, leading: IconButton( icon: Icon( Icons.arrow_back, size: 7.w, color: AppColor.primaryColor, ), onPressed: () => context.goNamed(AppRoute.homeScreen), ), title: Text( "Keranjang Belanja", style: TextStyle( color: Colors.black, fontSize: AppFontSize.medium.sp, fontWeight: FontWeight.bold, ), ), ), body: productState.when( data: (products) { if (cartItemState.isEmpty) { return _buildEmptyCart(); } return RefreshIndicator( onRefresh: () async { await ref .read(productBestSellerControllerProvider.notifier) .refresh(); }, color: AppColor.primaryColor, backgroundColor: Colors.white, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( children: [ _buildOutletInfoSection(currentOutletName!), SizedBox(height: 2.h), _buildProductListSection(cartItemState), SizedBox(height: 3.h), _buildAddOtherProductSection( products: products, currentOutletId: currentOutletId!, favorites: favoriteState, ), SizedBox(height: 5.h), ], ), ), ); }, error: (error, stackTrace) => CustomEmptyScreen( icon: Icons.cloud_off_outlined, title: "Terjadi Kesalahan Koneksi", subtitle: "Tarik ke bawah untuk mencoba lagi", height: 40.h, ), loading: () => _buildCartLoading(), ), bottomNavigationBar: cartItemState.isEmpty ? null : _buildBottomCartAction(cartItemState), ), ); }, ); } Widget _buildOutletInfoSection(String outletName) { return Container( width: double.infinity, margin: EdgeInsets.all(4.w), padding: EdgeInsets.all(4.w), decoration: BoxDecoration( color: const Color(0xFFFFF9E5), borderRadius: BorderRadius.circular(2.5.w), border: Border.all(color: const Color(0xFFFFEBB0)), ), child: Row( children: [ Container( padding: EdgeInsets.all(2.5.w), decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: Icon( Icons.storefront, color: AppColor.primaryColor, size: 5.w, ), ), SizedBox(width: 4.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Pengiriman dari :", style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, color: Colors.grey.shade700, ), ), Text( outletName, style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, fontWeight: FontWeight.bold, ), ), ], ), ), /** Text( "Ubah", style: TextStyle( color: AppColor.primaryColor, fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, ), ), */ ], ), ); } Widget _buildProductListSection(List cartItems) { return Container( margin: EdgeInsets.symmetric(horizontal: 4.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5), ), ], ), child: Column( children: List.generate(cartItems.length, (index) { final cartItem = cartItems[index]; return Column( children: [ Padding( padding: EdgeInsets.all(4.w), child: Row( children: [ CachedNetworkImage( imageUrl: cartItem.image ?? 'error', imageBuilder: (context, imageProvider) { return Container( width: 18.w, height: 18.w, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(2.w), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ); }, placeholder: (context, url) { return Shimmer.fromColors( baseColor: Colors.grey.shade300, highlightColor: Colors.grey.shade100, child: Container( height: 18.w, width: 18.w, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.w), ), ), ); }, errorWidget: (context, url, error) { return Container( width: 18.w, height: 18.w, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(2.w), ), child: Icon(Icons.image, color: Colors.grey.shade300), ); }, ), SizedBox(width: 3.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( cartItem.name, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, ), ), if (cartItem.isProductVariant) ...[ SizedBox(height: 0.5.h), Text( cartItem.variantName, style: TextStyle( color: Colors.grey.shade700, fontSize: (AppFontSize.small - 1.25).sp, ), ), ], if (cartItem.stockType == StockType.fixed.type) ...[ SizedBox(height: 0.5.h), Text( "Stok: ${cartItem.stock.toStringWithoutTrailingZero()} ${cartItem.unit}", style: TextStyle( color: Colors.grey.shade700, fontSize: (AppFontSize.small - 1.25).sp, ), ), ], SizedBox(height: 1.h), Text( CurrencyFormat.formatToIdr( cartItem.sellingPrice, 0, ), style: TextStyle( fontWeight: FontWeight.bold, color: AppColor.primaryColor, fontSize: (AppFontSize.small - 1.25).sp, ), ), ], ), ), Row( children: [ _buildCounterBtn( Icons.remove, isMinus: true, onTap: () async { ref .read(cartItemControllerProvider.notifier) .decrement(cartItem.outletId, cartItem.id); final cartItems = ref.read(cartItemProvider); await SystemSetting.saveCartItemByOutlet(cartItems); }, ), Padding( padding: EdgeInsets.symmetric(horizontal: 3.5.w), child: Text( cartItem.quantity.toString(), style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.small.sp, ), ), ), _buildCounterBtn( Icons.add, onTap: () async { if (cartItem.stockType == StockType.fixed.type && cartItem.quantity >= cartItem.stock) { CustomSnackbar.showError( context, "Kuantitas mencapai batas maksimum", ); return; } ref .read(cartItemControllerProvider.notifier) .increment(cartItem); final cartItems = ref.read(cartItemProvider); await SystemSetting.saveCartItemByOutlet(cartItems); }, ), ], ), ], ), ), if (index != 2) Divider( height: 1, thickness: 1, color: Colors.grey.shade100, indent: 4.w, endIndent: 4.w, ), ], ); }), ), ); } Widget _buildCounterBtn( IconData icon, { bool isMinus = false, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.all(1.w), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.grey.shade300), color: isMinus ? Colors.white : AppColor.primaryColor, ), child: Icon( icon, size: 4.w, color: isMinus ? Colors.grey : Colors.white, ), ), ); } Widget _buildAddOtherProductSection({ required List products, required String currentOutletId, required Map favorites, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 4.w), child: Text( "Produk Terlaris", style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.small.sp, ), ), ), SizedBox(height: 1.5.h), SizedBox( height: 25.h, child: ListView.builder( scrollDirection: Axis.horizontal, padding: EdgeInsets.only(left: 4.w), itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return _buildProductCard( product, currentOutletId, favorites.containsKey("$currentOutletId-${product.id}"), ); }, ), ), ], ); } Widget _buildProductCard( ProductItem product, String currentOutletId, bool isFavorite, ) { return GestureDetector( onTap: () async { try { final productDetail = await ref .read(productRepositoryProvider) .fetchProductById(product.id); if (productDetail == null) { CustomSnackbar.showError(context, "Produk tidak ditemukan"); ref.read(homeControllerProvider.notifier).refresh(); return; } context.pushNamed(AppRoute.productDetailScreen, extra: productDetail); } on ServerException catch (e, st) { LogMessage.log.i(e.toString(), error: e, stackTrace: st); } }, child: Container( width: 45.w, margin: EdgeInsets.only(right: 3.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 3, child: CachedNetworkImage( imageUrl: product.image ?? 'error', imageBuilder: (context, imageProvider) { return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.vertical( top: Radius.circular(2.5.w), ), image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), child: Stack( children: [ Positioned( top: 1.w, right: 1.w, child: GestureDetector( onTap: () { final currentOutletId = ref.read( currentOutletIdProvider, ); ref .read(favoriteControllerProvider.notifier) .toggle(product, currentOutletId!); }, child: CircleAvatar( radius: 14, backgroundColor: Colors.white.withOpacity(0.9), child: Icon( isFavorite ? Icons.favorite : Icons.favorite_outline, size: 5.w, color: isFavorite ? Colors.red : Colors.grey, ), ), ), ), ], ), ); }, errorWidget: (context, url, error) { return Container( width: double.infinity, decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.vertical( top: Radius.circular(2.5.w), ), ), child: Stack( children: [ Center( child: Icon( Icons.image_outlined, color: Colors.grey.shade300, size: 10.w, ), ), Positioned( top: 1.w, right: 1.w, child: GestureDetector( onTap: () { ref .read(favoriteControllerProvider.notifier) .toggle(product, currentOutletId); }, child: CircleAvatar( radius: 14, backgroundColor: Colors.white.withOpacity(0.9), child: Icon( isFavorite ? Icons.favorite : Icons.favorite_outline, size: 5.w, color: isFavorite ? Colors.red : Colors.grey, ), ), ), ), ], ), ); }, ), ), Expanded( flex: 2, child: Padding( padding: EdgeInsets.all(3.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( product.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, color: const Color(0xFF102851), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${product.totalSold.toCompact} Terjual", style: TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, fontSize: (AppFontSize.small - 1.25).sp, ), ), Row( children: [ Icon(Icons.star, color: Colors.orange, size: 3.5.w), SizedBox(width: 1.w), Text( product.averageRating.toStringAsFixed(1), style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, color: Colors.grey.shade600, ), ), ], ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( CurrencyFormat.formatToIdr(product.sellingPrice, 0), style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.small.sp, color: AppColor.primaryColor, ), ), Row( children: [ Icon( Icons.thumb_up, color: Colors.red, size: 3.5.w, ), SizedBox(width: 1.w), Text( product.likes.toString(), style: TextStyle( fontSize: (AppFontSize.small - 1.25).sp, color: Colors.red, ), ), ], ), ], ), ], ), ), ), ], ), ), ); } Widget _buildBottomCartAction(List cartItems) { return Container( height: 10.h, padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.5.h), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -5), ), ], ), child: Row( children: [ Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Total Pesanan", style: TextStyle( fontSize: AppFontSize.small.sp, color: Colors.grey.shade700, ), ), Text( CurrencyFormat.formatToIdr( _calculateTotalOrder(cartItems), 0, ), style: TextStyle( fontSize: AppFontSize.small.sp, fontWeight: FontWeight.bold, color: AppColor.primaryColor, ), ), ], ), ), ElevatedButton( onPressed: _processCheckout, style: ElevatedButton.styleFrom( backgroundColor: AppColor.primaryColor, padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 1.5.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.w), ), elevation: 0, ), child: Text( "Checkout Sekarang", style: TextStyle( fontSize: AppFontSize.small.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], ), ); } Widget _buildEmptyCart() { return Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 10.w), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 60.w, height: 60.w, decoration: BoxDecoration( color: AppColor.primaryColor.withOpacity(0.05), shape: BoxShape.circle, ), child: Icon( Icons.shopping_cart_outlined, size: 20.w, color: AppColor.primaryColor.withOpacity(0.5), ), ), SizedBox(height: 4.h), Text( "Keranjangmu Kosong", style: TextStyle( fontSize: AppFontSize.small.sp, fontWeight: FontWeight.bold, color: Colors.black87, ), ), SizedBox(height: 1.5.h), Text( "Sepertinya kamu belum menambahkan produk apa pun ke keranjang belanjaanmu.", textAlign: TextAlign.center, style: TextStyle( fontSize: AppFontSize.small.sp, color: Colors.grey.shade600, height: 1.5, ), ), SizedBox(height: 5.h), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => context.goNamed(AppRoute.homeScreen), style: ElevatedButton.styleFrom( backgroundColor: AppColor.primaryColor, padding: EdgeInsets.symmetric(vertical: 1.8.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.5.w), ), elevation: 0, ), child: Text( "Mulai Belanja", style: TextStyle( fontSize: AppFontSize.small.sp, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ); } Widget _buildCartLoading() { return Shimmer.fromColors( baseColor: Colors.grey.shade300, highlightColor: Colors.grey.shade100, child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: Column( children: [ Container( width: double.infinity, height: 10.h, margin: EdgeInsets.all(4.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), ), ), Container( margin: EdgeInsets.symmetric(horizontal: 4.w), padding: EdgeInsets.all(4.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), ), child: Column( children: List.generate( 2, (index) => Padding( padding: EdgeInsets.only(bottom: index == 0 ? 2.h : 0), child: Row( children: [ Container( width: 18.w, height: 18.w, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.w), ), ), SizedBox(width: 3.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 40.w, height: 2.h, color: Colors.white, ), SizedBox(height: 1.h), Container( width: 20.w, height: 2.h, color: Colors.white, ), ], ), ), Container( width: 20.w, height: 4.h, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), ), ], ), ), ), ), ), SizedBox(height: 3.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 4.w), child: Container( width: 35.w, height: 2.h, color: Colors.white, ), ), SizedBox(height: 1.5.h), SizedBox( height: 25.h, child: ListView.builder( scrollDirection: Axis.horizontal, padding: EdgeInsets.only(left: 4.w), itemCount: 3, itemBuilder: (_, __) => Container( width: 45.w, margin: EdgeInsets.only(right: 3.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), ), ), ), ), ], ), ], ), ), ); } }