Update: 2026-08-02 17:52:28
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
// food_home_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 '../../controller/food/food_controller.dart';
|
||||
import '../../controller/food/food_models.dart';
|
||||
import '../widgets/my_scafold.dart';
|
||||
import 'food_merchant_page.dart';
|
||||
import 'food_cart_page.dart';
|
||||
import 'food_order_history_page.dart';
|
||||
|
||||
class FoodHomePage extends StatelessWidget {
|
||||
const FoodHomePage({super.key});
|
||||
|
||||
bool get _isAr => box.read(BoxName.lang) == 'ar';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = Get.put(FoodController());
|
||||
c.fetchMerchants();
|
||||
|
||||
return GetBuilder<FoodController>(
|
||||
builder: (c) => MyScafolld(
|
||||
title: _isAr ? 'طعام' : 'Food',
|
||||
isleading: true,
|
||||
action: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.history_rounded, color: AppColor.primaryColor),
|
||||
onPressed: () => Get.to(() => const FoodOrderHistoryPage()),
|
||||
),
|
||||
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: [
|
||||
Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: _citySearchBar(c),
|
||||
),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => c.fetchMerchants(),
|
||||
child: c.isLoadingMerchants
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: c.merchants.isEmpty
|
||||
? _emptyState()
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 24),
|
||||
itemCount: c.merchants.length,
|
||||
itemBuilder: (_, i) => _merchantCard(c.merchants[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _citySearchBar(FoodController c) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => _showCityPicker(c),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderColor),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.location_on_rounded, color: AppColor.accentColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(c.city, style: AppStyle.title, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
Icon(Icons.keyboard_arrow_down_rounded, color: AppColor.grayColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
InkWell(
|
||||
onTap: () => _showSearchDialog(c),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.cardColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderColor),
|
||||
),
|
||||
child: Icon(Icons.search_rounded, color: AppColor.primaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showCityPicker(FoodController c) {
|
||||
final controller = TextEditingController(text: c.city);
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: Text(_isAr ? 'اختر المدينة' : 'Select City'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(hintText: _isAr ? 'اسم المدينة' : 'City name'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Get.back(), child: Text(_isAr ? 'إلغاء' : 'Cancel')),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
c.setCity(controller.text);
|
||||
},
|
||||
child: Text(_isAr ? 'تم' : 'Done'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSearchDialog(FoodController c) {
|
||||
final controller = TextEditingController();
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: Text(_isAr ? 'بحث عن مطعم أو صنف' : 'Search restaurant or dish'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(hintText: _isAr ? 'اكتب هنا...' : 'Type here...'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Get.back(), child: Text(_isAr ? 'إلغاء' : 'Cancel')),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
c.searchMerchants(controller.text);
|
||||
},
|
||||
child: Text(_isAr ? 'بحث' : 'Search'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _emptyState() {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
children: [
|
||||
const SizedBox(height: 60),
|
||||
Icon(Icons.restaurant_menu_rounded, size: 72, color: AppColor.grayColor),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_isAr ? 'لا توجد مطاعم متاحة في هذه المدينة حالياً' : 'No restaurants available in this city yet',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _merchantCard(FoodMerchant m) {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
color: AppColor.cardColor,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: AppColor.borderColor),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () => Get.to(() => FoodMerchantPage(merchantId: m.id)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (m.coverUrl != null && m.coverUrl!.isNotEmpty)
|
||||
Image.network(m.coverUrl!, height: 120, width: double.infinity, fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(height: 120, color: AppColor.borderColor)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: AppColor.accentColor.withOpacity(0.15),
|
||||
backgroundImage: (m.logoUrl != null && m.logoUrl!.isNotEmpty)
|
||||
? NetworkImage(m.logoUrl!)
|
||||
: null,
|
||||
child: (m.logoUrl == null || m.logoUrl!.isEmpty)
|
||||
? Icon(Icons.restaurant_rounded, color: AppColor.accentColor)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(m.nameAr, style: AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.star_rounded, color: Color(0xFFF59E0B), size: 16),
|
||||
const SizedBox(width: 2),
|
||||
Text(m.ratingAvg.toStringAsFixed(1), style: AppStyle.subtitle),
|
||||
const SizedBox(width: 10),
|
||||
Icon(Icons.access_time_rounded, size: 14, color: AppColor.grayColor),
|
||||
const SizedBox(width: 2),
|
||||
Text('${m.avgPrepMinutes} ${_isAr ? "د" : "min"}', style: AppStyle.subtitle),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!m.isOpen)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.redColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(_isAr ? 'مغلق' : 'Closed',
|
||||
style: TextStyle(color: AppColor.redColor, fontSize: 11, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user