feat(apps): الخريطة وطلب الرحلة ودورة السائق — المرحلة 4
## حسم تعارض الخرائط (docs/38 §7) لصالح القرار الأحدث 2026-07-20: كل الخرائط — البلاطات والبحث والعكسي والمسار — مباشرة إلى map-saas بترويسة x-api-key، بلا مرور بباك إند تريبز. نقاط /maps/* في الباك إند لا تُستعمل، وإحداها (geocode) معطوبة على المنشور. العقد تُحقّق حيّاً بـcurl قبل كتابة سطر. ## النواة المشتركة - core/api/antlaq_api.dart: عميل منفصل بلا AuthInterceptor — توكن تريبز لا شأن لخادم الخرائط به، وإرساله إليه تسريب بلا مقابل - core/realtime: Socket.IO واحد (trip:update · driver:location · trip:offer · trip:offer_taken) - core/location: نقطة الموقع الوحيدة بمرشّح 25 متراً - features/trip/data: سبعة نماذج + MapsRepository + TripRepository متطابق حرفياً بين التوأمين ## الراكب RideCubit بستّ مراحل + RidePage وثمانية ويدجت: الخريطة · دبّوس المنتصف · المخطّط · الخط الزمني · اختيار النوع · التأكيد · البحث عن سائق · الرحلة. ## السائق DutyCubit بأربع مراحل + DutyPage: مفتاح الاتصال · العرض بمؤقّت · الرحلة. ## قرارات تحمي الأداء والصحّة - GeoPoint مستقلّ عن حزمة الخريطة؛ intaleq_maps مستوردة في ملف واحد فقط لكل تطبيق. تبديل محرّك الخريطة لا يلمس منطقاً - الخريطة خارج BlocBuilder الورقة ولها BlocSelector خاص: نبضة موقع كل 25م لا تعيد بناء الشاشة - مهلة بحث محليّة 90ث + استطلاع كل 5ث: الخادم قد لا يُطلق expired/no_drivers أبداً (ثغرة R1)، وانتظار حدث قد لا يصل = شاشة بحث أبدية - offeredDrivers == 0 جواب نهائي فوري بلا انتظار المهلة - الأجرة لا تُعرض إلا إن كانت صالحة: /tariff/quote يرجّع 200 بقيم null صامتة - موقع السائق بمسارين عمداً: REST لمطابقة Redis والسوكت لخريطة الراكب flutter analyze نظيف · الراكب 70 ملف/5,294 سطر · السائق 67 ملف/4,771 سطر. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c52338f3bb
commit
1156299d09
@@ -1,32 +1,25 @@
|
||||
{
|
||||
"@@locale": "ar",
|
||||
|
||||
"appTitle": "Tripz",
|
||||
|
||||
"actionRetry": "أعد المحاولة",
|
||||
"actionCancel": "إلغاء",
|
||||
"actionContinue": "متابعة",
|
||||
"actionSave": "حفظ",
|
||||
"actionOpenSettings": "افتح الإعدادات",
|
||||
|
||||
"errorGeneric": "حدث خطأ غير متوقّع",
|
||||
"errorNetwork": "تعذّر الاتصال بالخادم",
|
||||
"errorTimeout": "انتهت مهلة الاتصال",
|
||||
"emptyDefault": "لا يوجد شيء هنا بعد",
|
||||
|
||||
"splashRider": "تطبيق الراكب",
|
||||
"splashDriver": "تطبيق السائق",
|
||||
|
||||
"agreementTitle": "شروط الاستخدام",
|
||||
"agreementLead": "قبل أن نبدأ، اقرأ الشروط ووافق عليها.",
|
||||
"agreementCheckbox": "قرأت الشروط وأوافق عليها",
|
||||
"agreementAccept": "أوافق وأتابع",
|
||||
|
||||
"permissionTitle": "نحتاج موقعك",
|
||||
"permissionLead": "الموقع هو ما يجعل الرحلة ممكنة: به نعرف أين أنت وأين السائق.",
|
||||
"permissionAllow": "السماح بالوصول للموقع",
|
||||
"permissionDeniedForever": "رفضتَ الإذن نهائياً. افتح الإعدادات وفعّله يدوياً ثم عُد.",
|
||||
|
||||
"phoneTitle": "أهلاً بك، كابتن",
|
||||
"phoneLead": "أدخل رقم هاتفك، ونرسل لك رمز تحقّق.",
|
||||
"phoneLabel": "رقم الهاتف",
|
||||
@@ -35,27 +28,35 @@
|
||||
"phoneErrEmpty": "أدخل رقم هاتفك",
|
||||
"phoneErrLeadingZero": "أدخل الرقم بلا الصفر في البداية",
|
||||
"phoneErrShort": "الرقم قصير جداً",
|
||||
|
||||
"otpTitle": "أدخل رمز التحقّق",
|
||||
"otpLead": "أرسلنا رمزاً من أربع خانات إلى {phone}",
|
||||
"@otpLead": { "placeholders": { "phone": { "type": "String" } } },
|
||||
"@otpLead": {
|
||||
"placeholders": {
|
||||
"phone": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otpVerify": "تحقّق وتابع",
|
||||
"otpResend": "إعادة إرسال الرمز",
|
||||
"otpResendIn": "إعادة الإرسال بعد {seconds} ثانية",
|
||||
"@otpResendIn": { "placeholders": { "seconds": { "type": "int" } } },
|
||||
"@otpResendIn": {
|
||||
"placeholders": {
|
||||
"seconds": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otpErrIncomplete": "أدخل الرمز كاملاً",
|
||||
"otpErrInvalid": "الرمز غير صحيح أو انتهت صلاحيته",
|
||||
"otpErrTooManyAttempts": "محاولات كثيرة. اطلب رمزاً جديداً.",
|
||||
"otpErrRateLimited": "طلبتَ الرمز مرات كثيرة. انتظر قليلاً ثم أعد المحاولة.",
|
||||
"otpChangeNumber": "تعديل الرقم",
|
||||
|
||||
"profileTitle": "أكمل ملفك",
|
||||
"profileLead": "اسمك يظهر للراكب عند الرحلة.",
|
||||
"profileNameLabel": "الاسم",
|
||||
"profileNameErrEmpty": "أدخل اسمك",
|
||||
|
||||
"sessionExpired": "انتهت جلستك. سجّل الدخول من جديد.",
|
||||
|
||||
"settingsTitle": "الإعدادات",
|
||||
"settingsTheme": "المظهر",
|
||||
"settingsThemeSystem": "حسب النظام",
|
||||
@@ -63,5 +64,64 @@
|
||||
"settingsThemeDark": "داكن",
|
||||
"settingsLanguage": "اللغة",
|
||||
"settingsLanguageArabic": "العربية",
|
||||
"settingsLanguageEnglish": "English"
|
||||
}
|
||||
"settingsLanguageEnglish": "English",
|
||||
"rideWhereTo": "إلى أين؟",
|
||||
"rideOrigin": "من",
|
||||
"rideDestination": "إلى",
|
||||
"rideSearchHint": "ابحث عن مكان",
|
||||
"rideAddStop": "إضافة محطة",
|
||||
"rideStop": "محطة {n}",
|
||||
"@rideStop": {
|
||||
"placeholders": {
|
||||
"n": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ridePickOnMap": "تحديد من الخريطة",
|
||||
"rideConfirmPoint": "تأكيد هذه النقطة",
|
||||
"rideConfirmTrip": "اطلب الرحلة",
|
||||
"rideSearchingTitle": "نبحث لك عن سائق",
|
||||
"rideSearchingLead": "عادةً أقل من دقيقة",
|
||||
"rideCancelSearch": "إلغاء البحث",
|
||||
"rideNoDrivers": "لا يوجد سائقون متاحون الآن. جرّب بعد قليل.",
|
||||
"rideQuoteFailed": "تعذّر حساب الأجرة",
|
||||
"rideRouteFailed": "تعذّر حساب المسار",
|
||||
"rideRequestFailed": "تعذّر إرسال الطلب",
|
||||
"rideSurge": "الطلب مرتفع الآن",
|
||||
"rideCancelTrip": "إلغاء الرحلة",
|
||||
"rideEta": "{min} دقيقة",
|
||||
"@rideEta": {
|
||||
"placeholders": {
|
||||
"min": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rideDistanceKm": "{km} كم",
|
||||
"@rideDistanceKm": {
|
||||
"placeholders": {
|
||||
"km": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tripAssigned": "السائق في الطريق إليك",
|
||||
"tripDriverArriving": "السائق يقترب",
|
||||
"tripDriverArrived": "السائق وصل وينتظرك",
|
||||
"tripInProgress": "في الطريق إلى وجهتك",
|
||||
"tripCompleted": "وصلت — رحلة موفّقة",
|
||||
"driverGoOnline": "ابدأ الاستقبال",
|
||||
"driverGoOffline": "إيقاف الاستقبال",
|
||||
"driverOnline": "متصل",
|
||||
"driverOffline": "غير متصل",
|
||||
"driverNoOffers": "لا طلبات الآن. ابقَ متصلاً.",
|
||||
"driverOfferTitle": "طلب رحلة جديد",
|
||||
"driverAccept": "قبول",
|
||||
"driverReject": "تجاهل",
|
||||
"driverOfferTaken": "سبقك سائق آخر لهذا الطلب",
|
||||
"driverArrivedAction": "وصلت لموقع الراكب",
|
||||
"driverStartTrip": "بدء الرحلة",
|
||||
"driverEndTrip": "إنهاء الرحلة",
|
||||
"driverOnTheWay": "في الطريق للراكب"
|
||||
}
|
||||
@@ -1,32 +1,25 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
|
||||
"appTitle": "Tripz",
|
||||
|
||||
"actionRetry": "Try again",
|
||||
"actionCancel": "Cancel",
|
||||
"actionContinue": "Continue",
|
||||
"actionSave": "Save",
|
||||
"actionOpenSettings": "Open settings",
|
||||
|
||||
"errorGeneric": "Something went wrong",
|
||||
"errorNetwork": "Could not reach the server",
|
||||
"errorTimeout": "The connection timed out",
|
||||
"emptyDefault": "Nothing here yet",
|
||||
|
||||
"splashRider": "Rider app",
|
||||
"splashDriver": "Driver app",
|
||||
|
||||
"agreementTitle": "Terms of use",
|
||||
"agreementLead": "Before we start, read the terms and accept them.",
|
||||
"agreementCheckbox": "I have read and accept the terms",
|
||||
"agreementAccept": "Accept and continue",
|
||||
|
||||
"permissionTitle": "We need your location",
|
||||
"permissionLead": "Location is what makes a trip possible: it tells us where you are and where your driver is.",
|
||||
"permissionAllow": "Allow location access",
|
||||
"permissionDeniedForever": "You denied the permission permanently. Open settings, enable it, then come back.",
|
||||
|
||||
"phoneTitle": "Welcome, captain",
|
||||
"phoneLead": "Enter your phone number and we'll send you a verification code.",
|
||||
"phoneLabel": "Phone number",
|
||||
@@ -35,27 +28,35 @@
|
||||
"phoneErrEmpty": "Enter your phone number",
|
||||
"phoneErrLeadingZero": "Enter the number without the leading zero",
|
||||
"phoneErrShort": "That number is too short",
|
||||
|
||||
"otpTitle": "Enter your code",
|
||||
"otpLead": "We sent a 4-digit code to {phone}",
|
||||
"@otpLead": { "placeholders": { "phone": { "type": "String" } } },
|
||||
"@otpLead": {
|
||||
"placeholders": {
|
||||
"phone": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otpVerify": "Verify and continue",
|
||||
"otpResend": "Resend the code",
|
||||
"otpResendIn": "Resend in {seconds}s",
|
||||
"@otpResendIn": { "placeholders": { "seconds": { "type": "int" } } },
|
||||
"@otpResendIn": {
|
||||
"placeholders": {
|
||||
"seconds": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"otpErrIncomplete": "Enter the full code",
|
||||
"otpErrInvalid": "That code is wrong or expired",
|
||||
"otpErrTooManyAttempts": "Too many attempts. Request a new code.",
|
||||
"otpErrRateLimited": "You requested the code too many times. Wait a moment and try again.",
|
||||
"otpChangeNumber": "Change number",
|
||||
|
||||
"profileTitle": "Complete your profile",
|
||||
"profileLead": "Your name is shown to the rider during a trip.",
|
||||
"profileNameLabel": "Name",
|
||||
"profileNameErrEmpty": "Enter your name",
|
||||
|
||||
"sessionExpired": "Your session ended. Please sign in again.",
|
||||
|
||||
"settingsTitle": "Settings",
|
||||
"settingsTheme": "Appearance",
|
||||
"settingsThemeSystem": "Follow system",
|
||||
@@ -63,5 +64,64 @@
|
||||
"settingsThemeDark": "Dark",
|
||||
"settingsLanguage": "Language",
|
||||
"settingsLanguageArabic": "العربية",
|
||||
"settingsLanguageEnglish": "English"
|
||||
}
|
||||
"settingsLanguageEnglish": "English",
|
||||
"rideWhereTo": "Where to?",
|
||||
"rideOrigin": "From",
|
||||
"rideDestination": "To",
|
||||
"rideSearchHint": "Search for a place",
|
||||
"rideAddStop": "Add a stop",
|
||||
"rideStop": "Stop {n}",
|
||||
"@rideStop": {
|
||||
"placeholders": {
|
||||
"n": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ridePickOnMap": "Pick on the map",
|
||||
"rideConfirmPoint": "Confirm this point",
|
||||
"rideConfirmTrip": "Request ride",
|
||||
"rideSearchingTitle": "Finding you a driver",
|
||||
"rideSearchingLead": "Usually under a minute",
|
||||
"rideCancelSearch": "Cancel search",
|
||||
"rideNoDrivers": "No drivers available right now. Try again shortly.",
|
||||
"rideQuoteFailed": "Could not price this trip",
|
||||
"rideRouteFailed": "Could not build the route",
|
||||
"rideRequestFailed": "Could not send the request",
|
||||
"rideSurge": "Demand is high right now",
|
||||
"rideCancelTrip": "Cancel ride",
|
||||
"rideEta": "{min} min",
|
||||
"@rideEta": {
|
||||
"placeholders": {
|
||||
"min": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rideDistanceKm": "{km} km",
|
||||
"@rideDistanceKm": {
|
||||
"placeholders": {
|
||||
"km": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tripAssigned": "Your driver is on the way",
|
||||
"tripDriverArriving": "Your driver is close",
|
||||
"tripDriverArrived": "Your driver is waiting",
|
||||
"tripInProgress": "On the way to your destination",
|
||||
"tripCompleted": "You've arrived",
|
||||
"driverGoOnline": "Go online",
|
||||
"driverGoOffline": "Go offline",
|
||||
"driverOnline": "Online",
|
||||
"driverOffline": "Offline",
|
||||
"driverNoOffers": "No requests yet. Stay online.",
|
||||
"driverOfferTitle": "New ride request",
|
||||
"driverAccept": "Accept",
|
||||
"driverReject": "Dismiss",
|
||||
"driverOfferTaken": "Another driver took this request",
|
||||
"driverArrivedAction": "I've arrived",
|
||||
"driverStartTrip": "Start trip",
|
||||
"driverEndTrip": "End trip",
|
||||
"driverOnTheWay": "Heading to rider"
|
||||
}
|
||||
Reference in New Issue
Block a user