fix(apps): إصلاحات من أول تجربة حقيقية على الجهاز
## تعذّر حساب الأجرة — الخطأ في التطبيق لا في جدول الكازان الخادم يبذر التعرفة بـ city: 'default' (tenants.service.ts:156) ويستعمل dto.city ?? 'default' عند إنشاء الرحلة، وكنت أرسل city: '' فيرجّع 404 «No active tariff for /economy». تحقّق حيّ: city='default' → total 1.55 JOD ✅ city='' → 404 ❌ city='amman' → 404 ❌ لا تغيير مطلوب على السيرفر. أُضيف AppConfig.defaultCity موثّقاً بالسبب. ## التسعير مرة واحدة لكل الأنواع كان نداء شبكة عند كل ضغطة نوع. صار _priceAll يسعّر كل الأنواع بالتوازي بعد اكتمال المسار — المسافة والمدة ثابتتان والفرق في التعرفة وحدها. الراكب يرى سعر الاقتصادي والمريح معاً تحت كل خيار ويقارن بنظرة، وتبديل النوع بلا نداء. ## الكاميرا لا تتبع الموقع سببان: الموقع يصل قبل إنشاء الخريطة فيضيع التحريك، ولا فحص للإذن/الخدمة قبل getCurrentPosition فيُبتلع الخطأ صامتاً. - _onCreated يلحق بما فات عند جهوز الخريطة - LocationService.ensureReady يفحص الخدمة والإذن أولاً - didChangeAppLifecycleState يعيد المحاولة عند العودة من إعدادات النظام - حالة noLocation تُعرض للمستخدم مع زر إعادة بدل خريطة صامتة ## الخريطة والتنقّل - إطار المسار من حدوده الفعلية مع حشوة سفلية بمقدار ما تغطّيه الورقة - علامتا البداية والنهاية دائرتان ملوّنتان تطابقان ألوان الخط الزمني - زر رجوع في ورقة التأكيد + زر إعادة حساب عند فشل التسعيرة: كان المستخدم يعلق في الشاشة بلا مخرج ## المحفظة — على أي أساس يُشحن POST /wallet/topup شحنٌ تجريبي بلا مزوّد دفع خلفه (الخادم نفسه يصفه كذلك). أُضيف PaymentChannel مشتقّاً من countryPack المستأجر لا من لغة الجهاز: jo → CliQ · sy → سيرياتيل/MTN/شام كاش · eg → PayMob. ويُعرض تنبيه صريح بأن الشحن بلا تحصيل فعلي — إخفاؤه يجعل المستخدم يظنّ أنه دفع. وسحب السائق صار يستعمل القناة المختارة بدل 'cliq' المكتوبة في الكود. flutter analyze نظيف في التطبيقين. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
29f375b122
commit
8976821a93
@@ -24,6 +24,7 @@ class RideMap extends StatefulWidget {
|
||||
this.route,
|
||||
this.cameraTarget,
|
||||
this.onCameraIdle,
|
||||
this.bottomInset = 280,
|
||||
});
|
||||
|
||||
final GeoPoint? origin;
|
||||
@@ -35,6 +36,9 @@ class RideMap extends StatefulWidget {
|
||||
/// مركز الخريطة عند استقرارها — يغذّي اختيار النقطة بالدبّوس الثابت.
|
||||
final ValueChanged<GeoPoint>? onCameraIdle;
|
||||
|
||||
/// ارتفاع ما تغطّيه الورقة السفلية — يُحسب منه إطار المسار.
|
||||
final double bottomInset;
|
||||
|
||||
@override
|
||||
State<RideMap> createState() => _RideMapState();
|
||||
}
|
||||
@@ -48,12 +52,62 @@ class _RideMapState extends State<RideMap> {
|
||||
@override
|
||||
void didUpdateWidget(RideMap old) {
|
||||
super.didUpdateWidget(old);
|
||||
final target = widget.cameraTarget;
|
||||
if (target != null && target != old.cameraTarget) {
|
||||
_controller?.animateCamera(
|
||||
im.CameraUpdate.newLatLng(im.LatLng(target.lat, target.lng)),
|
||||
);
|
||||
if (widget.route != null && widget.route != old.route) {
|
||||
_fitRoute();
|
||||
return;
|
||||
}
|
||||
final target = widget.cameraTarget;
|
||||
if (target != null && target != old.cameraTarget) _moveTo(target);
|
||||
}
|
||||
|
||||
/// **يُنادى عند جهوز الخريطة**: الموقع الحالي غالباً يصل *قبل* إنشاء
|
||||
/// الخريطة، فالتحريك في `didUpdateWidget` وحده يضيع و تبقى الكاميرا على
|
||||
/// الاحتياطي. هنا نلحق بما فات.
|
||||
void _onCreated(im.IntaleqMapController controller) {
|
||||
_controller = controller;
|
||||
final target = widget.cameraTarget ?? widget.origin;
|
||||
if (widget.route != null) {
|
||||
_fitRoute();
|
||||
} else if (target != null) {
|
||||
_moveTo(target);
|
||||
}
|
||||
}
|
||||
|
||||
void _moveTo(GeoPoint p) => _controller?.animateCamera(
|
||||
im.CameraUpdate.newLatLngZoom(im.LatLng(p.lat, p.lng), 15.5),
|
||||
);
|
||||
|
||||
/// يضبط الكاميرا على المسار كاملاً بدل نقطة واحدة.
|
||||
///
|
||||
/// الحشوة السفلية أكبر لأن الورقة السفلية تغطّي نحو ثلث الشاشة — بلا ذلك
|
||||
/// يختفي نصف المسار تحتها.
|
||||
void _fitRoute() {
|
||||
final encoded = widget.route?.encodedPoints;
|
||||
if (encoded == null || encoded.isEmpty) return;
|
||||
final points = im.PolylineUtils.decode(encoded);
|
||||
if (points.length < 2) return;
|
||||
|
||||
var minLat = points.first.latitude, maxLat = points.first.latitude;
|
||||
var minLng = points.first.longitude, maxLng = points.first.longitude;
|
||||
for (final p in points) {
|
||||
if (p.latitude < minLat) minLat = p.latitude;
|
||||
if (p.latitude > maxLat) maxLat = p.latitude;
|
||||
if (p.longitude < minLng) minLng = p.longitude;
|
||||
if (p.longitude > maxLng) maxLng = p.longitude;
|
||||
}
|
||||
|
||||
_controller?.animateCamera(
|
||||
im.CameraUpdate.newLatLngBounds(
|
||||
im.LatLngBounds(
|
||||
southwest: im.LatLng(minLat, minLng),
|
||||
northeast: im.LatLng(maxLat, maxLng),
|
||||
),
|
||||
left: 48,
|
||||
right: 48,
|
||||
top: 120,
|
||||
bottom: widget.bottomInset,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -77,17 +131,21 @@ class _RideMapState extends State<RideMap> {
|
||||
myLocationEnabled: true,
|
||||
compassEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
onMapCreated: (c) => _controller = c,
|
||||
onMapCreated: _onCreated,
|
||||
onCameraMove: (position) => _center = position.target,
|
||||
onCameraIdle: () {
|
||||
final c = _center;
|
||||
if (c != null) widget.onCameraIdle?.call(GeoPoint(c.latitude, c.longitude));
|
||||
},
|
||||
markers: {
|
||||
// العلامات دوائر ملوّنة تطابق ألوان الخط الزمني في الورقة السفلية،
|
||||
// فيربط المستخدم بين «من» و«إلى» وما يراه على الخريطة بلا شرح.
|
||||
circles: {
|
||||
if (widget.origin != null)
|
||||
_marker('origin', widget.origin!),
|
||||
_dot('origin', widget.origin!, colors.mapOrigin),
|
||||
if (widget.destination != null)
|
||||
_marker('destination', widget.destination!),
|
||||
_dot('destination', widget.destination!, colors.mapDestination),
|
||||
},
|
||||
markers: {
|
||||
if (widget.driver != null) _marker('driver', widget.driver!),
|
||||
},
|
||||
polylines: {
|
||||
@@ -106,4 +164,13 @@ class _RideMapState extends State<RideMap> {
|
||||
markerId: im.MarkerId(id),
|
||||
position: im.LatLng(p.lat, p.lng),
|
||||
);
|
||||
|
||||
im.Circle _dot(String id, GeoPoint p, Color color) => im.Circle(
|
||||
circleId: im.CircleId(id),
|
||||
center: im.LatLng(p.lat, p.lng),
|
||||
radius: 18,
|
||||
fillColor: color,
|
||||
strokeColor: Colors.white,
|
||||
strokeWidth: 3,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user