Files
Siro/siro_rider/lib/views/food/food_merchant_page.dart
T

313 lines
13 KiB
Dart

// 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<FoodController>();
c.openMerchant(merchantId);
return GetBuilder<FoodController>(
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<int, List<String>> 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<int, List<String>> 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<String>.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;
});
},
);
}),
],
),
);
}
}