// food_merchant_page.dart — قائمة مطعم واحد + إضافة أصناف للسلة import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constant/box_name.dart'; import '../../constant/colors.dart'; import '../../constant/style.dart'; import '../../main.dart'; import '../../views/widgets/error_snakbar.dart'; import '../../controller/food/food_controller.dart'; import '../../controller/food/food_models.dart'; import '../widgets/my_scafold.dart'; import 'food_cart_page.dart'; class FoodMerchantPage extends StatelessWidget { final int merchantId; const FoodMerchantPage({super.key, required this.merchantId}); bool get _isAr => box.read(BoxName.lang) == 'ar'; @override Widget build(BuildContext context) { final c = Get.find(); c.openMerchant(merchantId); return GetBuilder( builder: (c) { final merchant = c.selectedMerchant; return MyScafolld( title: merchant?.nameAr ?? (_isAr ? 'المطعم' : 'Restaurant'), isleading: true, action: Stack( clipBehavior: Clip.none, children: [ IconButton( icon: Icon(Icons.shopping_cart_rounded, color: AppColor.primaryColor), onPressed: () => Get.to(() => const FoodCartPage()), ), if (c.cartItemsCount > 0) Positioned( right: 4, top: 4, child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration(color: AppColor.redColor, shape: BoxShape.circle), constraints: const BoxConstraints(minWidth: 18, minHeight: 18), child: Text(c.cartItemsCount.toString(), textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold)), ), ), ], ), body: [ c.isLoadingMenu ? const Center(child: CircularProgressIndicator()) : merchant == null ? Center(child: Text(_isAr ? 'تعذر تحميل المطعم' : 'Failed to load restaurant')) : _content(c, merchant), ], ); }, ); } Widget _content(FoodController c, FoodMerchant merchant) { return ListView( padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), children: [ if (!merchant.isOpen) Container( width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: AppColor.redColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( _isAr ? 'هذا المطعم مغلق حالياً' : 'This restaurant is currently closed', style: TextStyle(color: AppColor.redColor, fontWeight: FontWeight.bold), ), ), if (merchant.descriptionAr != null && merchant.descriptionAr!.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 12), child: Text(merchant.descriptionAr!, style: AppStyle.subtitle), ), Text( _isAr ? 'الحد الأدنى للطلب: ${(merchant.minOrderAmount / 1000).toStringAsFixed(3)} د.أ' : 'Minimum order: ${(merchant.minOrderAmount / 1000).toStringAsFixed(3)} JOD', style: AppStyle.subtitle.copyWith(color: AppColor.grayColor), ), const SizedBox(height: 16), ...c.categories.map((cat) => _categorySection(c, merchant, cat)), ], ); } Widget _categorySection(FoodController c, FoodMerchant merchant, FoodMenuCategory cat) { if (cat.items.isEmpty) return const SizedBox.shrink(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(cat.nameAr, style: AppStyle.headTitle2.copyWith(fontSize: 18)), const SizedBox(height: 8), ...cat.items.map((item) => _itemTile(c, merchant, item)), const SizedBox(height: 16), ], ); } Widget _itemTile(FoodController c, FoodMerchant merchant, FoodMenuItem item) { return Card( margin: const EdgeInsets.only(bottom: 10), color: AppColor.cardColor, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: BorderSide(color: AppColor.borderColor), ), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: !item.isAvailable || !merchant.isOpen ? null : () => _openItemSheet(c, merchant, item), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.nameAr, style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)), if (item.descriptionAr != null && item.descriptionAr!.isNotEmpty) ...[ const SizedBox(height: 4), Text(item.descriptionAr!, style: AppStyle.subtitle, maxLines: 2, overflow: TextOverflow.ellipsis), ], const SizedBox(height: 6), Text(foodFormatPrice(item.price), style: AppStyle.title.copyWith(color: AppColor.accentColor, fontWeight: FontWeight.bold)), ], ), ), const SizedBox(width: 12), if (item.imageUrl != null && item.imageUrl!.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.network(item.imageUrl!, width: 72, height: 72, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container(width: 72, height: 72, color: AppColor.borderColor)), ), if (!item.isAvailable) Padding( padding: const EdgeInsets.only(left: 8), child: Text(_isAr ? 'غير متاح' : 'Unavailable', style: TextStyle(color: AppColor.redColor, fontSize: 12, fontWeight: FontWeight.bold)), ), ], ), ), ), ); } void _openItemSheet(FoodController c, FoodMerchant merchant, FoodMenuItem item) { final Map> selected = {}; int quantity = 1; Get.bottomSheet( isScrollControlled: true, StatefulBuilder( builder: (context, setState) { int unitPrice = item.price; for (final group in item.options) { for (final choiceId in (selected[group.id] ?? [])) { final choice = group.choices.firstWhere((ch) => ch.id == choiceId, orElse: () => FoodOptionChoice(id: '', labelAr: '', price: 0)); unitPrice += choice.price; } } bool canSubmit = true; for (final group in item.options) { if (group.isRequired && (selected[group.id] ?? []).isEmpty) canSubmit = false; } return Container( constraints: BoxConstraints(maxHeight: Get.height * 0.85), decoration: BoxDecoration( color: AppColor.secondaryColor, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 12), Container(width: 40, height: 4, decoration: BoxDecoration(color: Colors.grey.withOpacity(0.3), borderRadius: BorderRadius.circular(2))), Expanded( child: ListView( padding: const EdgeInsets.all(20), children: [ Text(item.nameAr, style: AppStyle.headTitle2.copyWith(fontSize: 20)), if (item.descriptionAr != null && item.descriptionAr!.isNotEmpty) ...[ const SizedBox(height: 8), Text(item.descriptionAr!, style: AppStyle.subtitle), ], const SizedBox(height: 16), ...item.options.map((group) => _optionGroupWidget(group, selected, setState)), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: const Icon(Icons.remove_circle_outline_rounded), onPressed: quantity > 1 ? () => setState(() => quantity--) : null, ), Text(quantity.toString(), style: AppStyle.headTitle2.copyWith(fontSize: 20)), IconButton( icon: const Icon(Icons.add_circle_outline_rounded), onPressed: () => setState(() => quantity++), ), ], ), ], ), ), Padding( padding: EdgeInsets.fromLTRB(20, 8, 20, MediaQuery.of(context).padding.bottom + 16), child: SizedBox( width: double.infinity, height: 52, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: canSubmit ? AppColor.primaryColor : AppColor.grayColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), onPressed: !canSubmit ? null : () { final ok = c.addToCartForMerchant(merchant.id, item, quantity: quantity, options: selected); Get.back(); if (ok) { mySnackbarSuccess(_isAr ? 'أُضيف إلى السلة' : 'Added to cart'); } }, child: Text( '${_isAr ? "إضافة" : "Add"} · ${foodFormatPrice(unitPrice * quantity)}', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16), ), ), ), ), ], ), ); }, ), ); } Widget _optionGroupWidget( FoodItemOptionGroup group, Map> selected, StateSetter setState) { final isSingle = group.maxSelect <= 1; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(group.groupNameAr, style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)), if (group.isRequired) ...[ const SizedBox(width: 6), Text(_isAr ? '(مطلوب)' : '(required)', style: TextStyle(color: AppColor.redColor, fontSize: 12)), ], ], ), ...group.choices.map((choice) { final isChecked = (selected[group.id] ?? []).contains(choice.id); return CheckboxListTile( contentPadding: EdgeInsets.zero, dense: true, value: isChecked, controlAffinity: ListTileControlAffinity.leading, title: Text(choice.labelAr), secondary: choice.price > 0 ? Text('+${foodFormatPrice(choice.price)}') : null, onChanged: (checked) { setState(() { final list = List.from(selected[group.id] ?? []); if (checked == true) { if (isSingle) list.clear(); if (!isSingle && list.length >= group.maxSelect) return; list.add(choice.id); } else { list.remove(choice.id); } selected[group.id] = list; }); }, ); }), ], ), ); } }