# Cart Feature - Key Code Reference ## 1. Adding Item to Cart with Conversion ```dart // In cart_provider.dart void addToCart(Product product, {double quantity = 1.0}) { // Calculate conversion final converted = _calculateConversion(quantity); // Create cart item with conversion data final newItem = CartItemData( product: product, quantity: quantity, // User input: 10 quantityConverted: converted.convertedQuantity, // Billing: 10.08 boxes: converted.boxes, // Tiles: 28 ); // Add to cart and auto-select final updatedSelection = Map.from(state.selectedItems); updatedSelection[product.productId] = true; state = state.copyWith( items: [...state.items, newItem], selectedItems: updatedSelection, ); } // Conversion calculation (mock - replace with backend) ({double convertedQuantity, int boxes}) _calculateConversion(double quantity) { final converted = (quantity * 1.008 * 100).ceilToDouble() / 100; final boxes = (quantity * 2.8).ceil(); return (convertedQuantity: converted, boxes: boxes); } ``` ## 2. Cart Item Widget with Checkbox ```dart // In cart_item_widget.dart Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Checkbox (aligned to top) Padding( padding: const EdgeInsets.only(top: 34), child: _CustomCheckbox( value: isSelected, onChanged: (value) { ref.read(cartProvider.notifier).toggleSelection(item.product.productId); }, ), ), const SizedBox(width: 12), // Product Image ClipRRect(...), const SizedBox(width: 12), // Product Info with Conversion Expanded( child: Column( children: [ Text(item.product.name), Text('${price}/${unit}'), // Quantity Controls Row([ _QuantityButton(icon: Icons.remove, onPressed: decrement), Text(quantity), _QuantityButton(icon: Icons.add, onPressed: increment), Text(unit), ]), // Conversion Display RichText( text: TextSpan( children: [ TextSpan(text: '(Quy đổi: '), TextSpan( text: '${item.quantityConverted.toStringAsFixed(2)} m²', style: TextStyle(fontWeight: FontWeight.bold), ), TextSpan(text: ' = '), TextSpan( text: '${item.boxes} viên', style: TextStyle(fontWeight: FontWeight.bold), ), TextSpan(text: ')'), ], ), ), ], ), ), ], ) ``` ## 3. Select All Section ```dart // In cart_page.dart Container( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Left: Checkbox + Label GestureDetector( onTap: () => ref.read(cartProvider.notifier).toggleSelectAll(), child: Row( children: [ _CustomCheckbox( value: cartState.isAllSelected, onChanged: (value) => ref.read(cartProvider.notifier).toggleSelectAll(), ), const SizedBox(width: 12), Text('Chọn tất cả'), ], ), ), // Right: Selected Count Text('Đã chọn: ${cartState.selectedCount}/${cartState.itemCount}'), ], ), ) ``` ## 4. Sticky Footer ```dart // In cart_page.dart Positioned( bottom: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( color: AppColors.white, border: Border(top: BorderSide(...)), boxShadow: [...], ), child: SafeArea( child: Row( children: [ // Delete Button (48x48) InkWell( onTap: hasSelection ? deleteSelected : null, child: Container( width: 48, height: 48, decoration: BoxDecoration( border: Border.all(color: AppColors.danger, width: 2), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.delete_outline), ), ), const SizedBox(width: 16), // Total Info Expanded( child: Column( children: [ Text('Tổng tạm tính (${selectedCount} sản phẩm)'), Text(currencyFormatter.format(selectedTotal)), ], ), ), const SizedBox(width: 16), // Checkout Button ElevatedButton( onPressed: hasSelection ? checkout : null, child: Text('Tiến hành đặt hàng'), ), ], ), ), ), ) ``` ## 5. Selection Logic in Provider ```dart // Toggle single item void toggleSelection(String productId) { final updatedSelection = Map.from(state.selectedItems); updatedSelection[productId] = !(updatedSelection[productId] ?? false); state = state.copyWith(selectedItems: updatedSelection); _recalculateTotal(); } // Toggle all items void toggleSelectAll() { final allSelected = state.isAllSelected; final updatedSelection = {}; for (final item in state.items) { updatedSelection[item.product.productId] = !allSelected; } state = state.copyWith(selectedItems: updatedSelection); _recalculateTotal(); } // Delete selected void deleteSelected() { final selectedIds = state.selectedItems.entries .where((entry) => entry.value) .map((entry) => entry.key) .toSet(); final remainingItems = state.items .where((item) => !selectedIds.contains(item.product.productId)) .toList(); final updatedSelection = Map.from(state.selectedItems); for (final id in selectedIds) { updatedSelection.remove(id); } state = state.copyWith( items: remainingItems, selectedItems: updatedSelection, ); _recalculateTotal(); } ``` ## 6. Recalculate Total (Selected Items Only) ```dart void _recalculateTotal() { // Only include selected items double subtotal = 0.0; for (final item in state.items) { if (state.selectedItems[item.product.productId] == true) { subtotal += item.lineTotal; // Uses quantityConverted } } final memberDiscount = subtotal * (state.memberDiscountPercent / 100); const shippingFee = 0.0; final total = subtotal - memberDiscount + shippingFee; state = state.copyWith( subtotal: subtotal, memberDiscount: memberDiscount, shippingFee: shippingFee, total: total, ); } ``` ## 7. Payment Method Options ```dart // Full Payment Radio( value: 'full_payment', groupValue: paymentMethod.value, onChanged: (value) => paymentMethod.value = value!, ), const Column( children: [ Text('Thanh toán hoàn toàn'), Text('Thanh toán qua tài khoản ngân hàng'), ], ), // Partial Payment Radio( value: 'partial_payment', groupValue: paymentMethod.value, onChanged: (value) => paymentMethod.value = value!, ), const Column( children: [ Text('Thanh toán một phần'), Text('Trả trước(≥20%), còn lại thanh toán trong vòng 30 ngày'), ], ), ``` ## 8. Order Summary with Conversion ```dart // Item display in checkout Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Line 1: Product name Text(item['name']), // Line 2: Conversion (muted) Text( '$quantityM2 m² ($boxes viên / ${quantityConverted.toStringAsFixed(2)} m²)', style: TextStyle(color: AppColors.grey500), ), ], ), ), // Price (using converted quantity) Text(_formatCurrency(price * quantityConverted)), ], ) ``` ## 9. Custom Checkbox Widget ```dart class _CustomCheckbox extends StatelessWidget { final bool value; final ValueChanged? onChanged; @override Widget build(BuildContext context) { return GestureDetector( onTap: () => onChanged?.call(!value), child: Container( width: 22, height: 22, decoration: BoxDecoration( color: value ? AppColors.primaryBlue : AppColors.white, border: Border.all( color: value ? AppColors.primaryBlue : Color(0xFFCBD5E1), width: 2, ), borderRadius: BorderRadius.circular(6), ), child: value ? Icon(Icons.check, size: 16, color: AppColors.white) : null, ), ); } } ``` ## 10. Delete Confirmation Dialog ```dart void _showDeleteConfirmation(BuildContext context, WidgetRef ref, CartState cartState) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Xóa sản phẩm'), content: Text('Bạn có chắc muốn xóa ${cartState.selectedCount} sản phẩm đã chọn?'), actions: [ TextButton( onPressed: () => context.pop(), child: const Text('Hủy'), ), ElevatedButton( onPressed: () { ref.read(cartProvider.notifier).deleteSelected(); context.pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Đã xóa sản phẩm khỏi giỏ hàng'), backgroundColor: AppColors.success, ), ); }, style: ElevatedButton.styleFrom(backgroundColor: AppColors.danger), child: const Text('Xóa'), ), ], ), ); } ``` ## CSS/Flutter Equivalents ### HTML Checkbox Styles → Flutter ```css /* HTML */ .checkmark { height: 22px; width: 22px; border: 2px solid #cbd5e1; border-radius: 6px; } .checkbox-container input:checked ~ .checkmark { background-color: #005B9A; border-color: #005B9A; } ``` ```dart // Flutter Container( width: 22, height: 22, decoration: BoxDecoration( color: value ? AppColors.primaryBlue : AppColors.white, border: Border.all( color: value ? AppColors.primaryBlue : Color(0xFFCBD5E1), width: 2, ), borderRadius: BorderRadius.circular(6), ), child: value ? Icon(Icons.check, size: 16, color: AppColors.white) : null, ) ``` ### HTML Sticky Footer → Flutter ```css /* HTML */ .cart-footer { position: fixed; bottom: 0; background: white; border-top: 2px solid #f0f0f0; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.08); z-index: 100; } ``` ```dart // Flutter Positioned( bottom: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( color: AppColors.white, border: Border(top: BorderSide(color: Color(0xFFF0F0F0), width: 2)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.08), blurRadius: 10, offset: Offset(0, -2), ), ], ), child: SafeArea(child: /* footer content */), ), ) ```