440 lines
14 KiB
Dart
440 lines
14 KiB
Dart
// food_offer_page.dart — شاشة استقبال عرض التوصيل (مستقلة عن شاشة عرض الرحلة)
|
|
//
|
|
// شاشة كاملة مقصودة لا بطاقة صغيرة: مهلة العرض 20 ثانية، والسائق يحتاج يقرأ
|
|
// المطعم والأجرة وهل التحصيل نقدي قبل أن يقرر. تفتح تلقائياً عند وصول العرض
|
|
// من سوكيت الطعام، وتُغلق نفسها إذا انتهت مهلة كل العروض.
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import '../../constant/colors.dart';
|
|
import '../../controller/food_delivery/food_delivery_controller.dart';
|
|
import '../../controller/food_delivery/food_delivery_models.dart';
|
|
import '../../controller/functions/location_controller.dart';
|
|
|
|
class FoodOfferPage extends StatelessWidget {
|
|
const FoodOfferPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GetBuilder<FoodDeliveryController>(
|
|
builder: (c) {
|
|
if (c.pendingOffers.isEmpty) return const _NoOfferView();
|
|
|
|
final offer = c.pendingOffers.first;
|
|
final isResponding = c.isRespondingToOffer(offer.orderId);
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFF12171F),
|
|
body: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
_Header(offer: offer, queued: c.pendingOffers.length),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
_FeeBadge(offer: offer),
|
|
const SizedBox(height: 20),
|
|
_RouteCard(offer: offer),
|
|
const SizedBox(height: 14),
|
|
_MetaRow(offer: offer),
|
|
if (offer.isCash) ...[
|
|
const SizedBox(height: 14),
|
|
_CashWarning(amount: offer.cashToCollect),
|
|
],
|
|
const SizedBox(height: 24),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
_Actions(
|
|
isResponding: isResponding,
|
|
onAccept: () => c.respondToOffer(offer.orderId, true),
|
|
onReject: () => c.respondToOffer(offer.orderId, false),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NoOfferView extends StatelessWidget {
|
|
const _NoOfferView();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFF12171F),
|
|
body: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(Icons.timer_off_rounded, size: 64, color: Colors.white38),
|
|
const SizedBox(height: 12),
|
|
const Text('انتهت مهلة العرض',
|
|
style: TextStyle(color: Colors.white70, fontSize: 16)),
|
|
const SizedBox(height: 20),
|
|
TextButton(onPressed: Get.back, child: const Text('رجوع')),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Header extends StatelessWidget {
|
|
final FoodDeliveryOffer offer;
|
|
final int queued;
|
|
const _Header({required this.offer, required this.queued});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final remaining = offer.secondsRemaining;
|
|
final progress = offer.ttlSeconds == 0 ? 0.0 : remaining / offer.ttlSeconds;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 8),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 62,
|
|
height: 62,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
SizedBox(
|
|
width: 62,
|
|
height: 62,
|
|
child: CircularProgressIndicator(
|
|
value: progress,
|
|
strokeWidth: 5,
|
|
backgroundColor: Colors.white12,
|
|
valueColor: AlwaysStoppedAnimation(
|
|
remaining <= 5 ? const Color(0xFFE53935) : const Color(0xFFFF6B35),
|
|
),
|
|
),
|
|
),
|
|
Text('$remaining',
|
|
style: const TextStyle(
|
|
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20)),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text('🍔 عرض توصيل طلب طعام',
|
|
style: TextStyle(
|
|
color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
|
Text(
|
|
queued > 1 ? 'لديك $queued عروض بالانتظار' : 'وافق قبل انتهاء المهلة',
|
|
style: const TextStyle(color: Colors.white54, fontSize: 13),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FeeBadge extends StatelessWidget {
|
|
final FoodDeliveryOffer offer;
|
|
const _FeeBadge({required this.offer});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 18),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFFFF6B35), Color(0xFFF7931E)],
|
|
),
|
|
borderRadius: BorderRadius.circular(18),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
const Text('أجرة التوصيل',
|
|
style: TextStyle(color: Colors.white70, fontSize: 13)),
|
|
const SizedBox(height: 4),
|
|
Text(foodFormatPrice(offer.deliveryFee),
|
|
style: const TextStyle(
|
|
color: Colors.white, fontSize: 30, fontWeight: FontWeight.bold)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RouteCard extends StatelessWidget {
|
|
final FoodDeliveryOffer offer;
|
|
const _RouteCard({required this.offer});
|
|
|
|
// مسافات تقريبية بخط مستقيم — كافية لقرار القبول خلال ثوانٍ، ولا نستدعي
|
|
// خدمة مسارات هنا حتى لا نضيف زمناً على مهلة عرض قصيرة أصلاً.
|
|
double? _distanceKm(double? aLat, double? aLng, double bLat, double bLng) {
|
|
if (aLat == null || aLng == null || bLat == 0 || bLng == 0) return null;
|
|
return Geolocator.distanceBetween(aLat, aLng, bLat, bLng) / 1000;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
double? driverLat;
|
|
double? driverLng;
|
|
if (Get.isRegistered<LocationController>()) {
|
|
final loc = Get.find<LocationController>().myLocation;
|
|
if (loc.latitude != 0) {
|
|
driverLat = loc.latitude;
|
|
driverLng = loc.longitude;
|
|
}
|
|
}
|
|
|
|
final toMerchant =
|
|
_distanceKm(driverLat, driverLng, offer.merchantLat ?? 0, offer.merchantLng ?? 0);
|
|
final merchantToCustomer =
|
|
_distanceKm(offer.merchantLat, offer.merchantLng, offer.deliveryLat, offer.deliveryLng);
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.06),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: Colors.white12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
_RouteRow(
|
|
icon: Icons.storefront_rounded,
|
|
iconColor: const Color(0xFFFF6B35),
|
|
title: offer.merchantNameAr,
|
|
subtitle: offer.merchantAddress ?? 'الاستلام من المطعم',
|
|
trailing: toMerchant == null ? null : '${toMerchant.toStringAsFixed(1)} كم',
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
const SizedBox(width: 18),
|
|
Container(width: 2, height: 22, color: Colors.white24),
|
|
],
|
|
),
|
|
),
|
|
_RouteRow(
|
|
icon: Icons.location_on_rounded,
|
|
iconColor: const Color(0xFF4CAF50),
|
|
title: 'التسليم للزبون',
|
|
// العنوان النصّي لا يصل في العرض — يظهر بعد القبول فقط (حماية بيانات الزبون)
|
|
subtitle: 'يظهر العنوان الكامل بعد قبول الطلب',
|
|
trailing: merchantToCustomer == null
|
|
? null
|
|
: '${merchantToCustomer.toStringAsFixed(1)} كم',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RouteRow extends StatelessWidget {
|
|
final IconData icon;
|
|
final Color iconColor;
|
|
final String title;
|
|
final String subtitle;
|
|
final String? trailing;
|
|
|
|
const _RouteRow({
|
|
required this.icon,
|
|
required this.iconColor,
|
|
required this.title,
|
|
required this.subtitle,
|
|
this.trailing,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, color: iconColor, size: 24),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title,
|
|
style: const TextStyle(
|
|
color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis),
|
|
Text(subtitle,
|
|
style: const TextStyle(color: Colors.white54, fontSize: 12),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis),
|
|
],
|
|
),
|
|
),
|
|
if (trailing != null)
|
|
Text(trailing!,
|
|
style: const TextStyle(
|
|
color: Colors.white70, fontSize: 13, fontWeight: FontWeight.bold)),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MetaRow extends StatelessWidget {
|
|
final FoodDeliveryOffer offer;
|
|
const _MetaRow({required this.offer});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
children: [
|
|
Expanded(
|
|
child: _MetaTile(
|
|
icon: Icons.shopping_bag_rounded,
|
|
label: 'عدد الأصناف',
|
|
value: offer.itemsCount > 0 ? '${offer.itemsCount}' : '—',
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _MetaTile(
|
|
icon: offer.isCash ? Icons.payments_rounded : Icons.account_balance_wallet_rounded,
|
|
label: 'طريقة الدفع',
|
|
value: offer.isCash ? 'نقداً' : 'محفظة',
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MetaTile extends StatelessWidget {
|
|
final IconData icon;
|
|
final String label;
|
|
final String value;
|
|
const _MetaTile({required this.icon, required this.label, required this.value});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.06),
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(icon, color: Colors.white70, size: 20),
|
|
const SizedBox(height: 6),
|
|
Text(label, style: const TextStyle(color: Colors.white54, fontSize: 11)),
|
|
Text(value,
|
|
style: const TextStyle(
|
|
color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CashWarning extends StatelessWidget {
|
|
final int amount;
|
|
const _CashWarning({required this.amount});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(14),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF29900).withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(14),
|
|
border: Border.all(color: const Color(0xFFF29900).withOpacity(0.5)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.info_rounded, color: Color(0xFFF29900)),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
'طلب نقدي — ستحصّل ${foodFormatPrice(amount)} من الزبون عند التسليم',
|
|
style: const TextStyle(color: Color(0xFFFFD08A), fontSize: 13),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Actions extends StatelessWidget {
|
|
final bool isResponding;
|
|
final VoidCallback onAccept;
|
|
final VoidCallback onReject;
|
|
|
|
const _Actions({
|
|
required this.isResponding,
|
|
required this.onAccept,
|
|
required this.onReject,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (isResponding) {
|
|
return const Padding(
|
|
padding: EdgeInsets.all(24),
|
|
child: Center(child: CircularProgressIndicator(color: Color(0xFFFF6B35))),
|
|
);
|
|
}
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 56,
|
|
child: OutlinedButton(
|
|
style: OutlinedButton.styleFrom(
|
|
side: const BorderSide(color: Colors.white24),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
|
),
|
|
onPressed: onReject,
|
|
child: const Text('رفض',
|
|
style: TextStyle(color: Colors.white70, fontSize: 16)),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
flex: 2,
|
|
child: SizedBox(
|
|
height: 56,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColor.greenColor,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
|
),
|
|
onPressed: onAccept,
|
|
child: const Text('قبول الطلب',
|
|
style: TextStyle(
|
|
color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold)),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|