fix(rider): تعطيل أنواع الرحلات بلا تعرفة نشطة
فحص حيّ كشف أن `GET /ride-types` يرجّع ستّة أنواع للمستأجر siro بينما ثلاثة فقط لها تعرفة نشطة: economy 1.55 · comfort 1.85 · electric 1.75 ✅ family_van · women · scooter ❌ 404 أي أن الراكب يرى ستّة خيارات، وثلاثة منها لو اختارها يفشل طلب الرحلة بـ «No pricing available» بعد أن يكون قد أكمل تحديد وجهته. المعالجة في التطبيق: النوع بلا تعرفة يظهر **معطّلاً مكتوباً عليه «غير متاح»** لا مخفياً — إخفاؤه يترك المستخدم يتساءل أين ذهب الخيار، وإظهاره قابلاً للاختيار يوصله إلى فشل بعد خطوتين. وإن كان المختار بلا تعرفة يُنتقل تلقائياً لأول نوع مسعَّر، فلا يبقى زرّ التأكيد معطّلاً بلا سبب ظاهر. هذا علاج للعَرَض؛ السبب بيانات على الخادم: كتالوج ride-types والتعرفات غير متزامنين. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8976821a93
commit
3a8caf7b27
@@ -200,5 +200,6 @@
|
||||
"rideFrom": "من",
|
||||
"rideTo": "إلى",
|
||||
"walletChannel": "طريقة الدفع",
|
||||
"walletTopupNote": "الشحن التجريبي حالياً بلا تحصيل فعلي — ربط البوابة قادم"
|
||||
"walletTopupNote": "الشحن التجريبي حالياً بلا تحصيل فعلي — ربط البوابة قادم",
|
||||
"rideTypeUnavailable": "غير متاح"
|
||||
}
|
||||
@@ -200,5 +200,6 @@
|
||||
"rideFrom": "From",
|
||||
"rideTo": "To",
|
||||
"walletChannel": "Payment method",
|
||||
"walletTopupNote": "Top-up is currently a test cash-in with no real charge — gateway wiring is next"
|
||||
"walletTopupNote": "Top-up is currently a test cash-in with no real charge — gateway wiring is next",
|
||||
"rideTypeUnavailable": "Unavailable"
|
||||
}
|
||||
@@ -283,10 +283,16 @@ class RideCubit extends Cubit<RideState> {
|
||||
final quotes = Map<String, FareQuote>.fromEntries(
|
||||
results.whereType<MapEntry<String, FareQuote>>(),
|
||||
);
|
||||
final selected = state.selectedRideType;
|
||||
// إن كان المختار بلا تعرفة، ننتقل لأول نوع مسعَّر: تركُه مختاراً يعني
|
||||
// زرَّ تأكيد معطّلاً بلا سبب ظاهر.
|
||||
var selected = state.selectedRideType;
|
||||
if (selected == null || !quotes.containsKey(selected.code)) {
|
||||
selected = types.where((t) => quotes.containsKey(t.code)).firstOrNull;
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
quotes: quotes,
|
||||
selectedRideType: selected,
|
||||
quote: selected == null ? null : quotes[selected.code],
|
||||
busy: false,
|
||||
error: quotes.isEmpty ? RideError.quoteFailed : RideError.none,
|
||||
|
||||
@@ -4,6 +4,7 @@ import '../../../../core/design/tokens.dart';
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
import '../../../../core/design/typography.dart';
|
||||
import '../../../../core/format/money.dart';
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
import '../../data/models/fare_quote.dart';
|
||||
import '../../data/models/ride_type.dart';
|
||||
|
||||
@@ -41,11 +42,16 @@ class RideTypePicker extends StatelessWidget {
|
||||
separatorBuilder: (context, i) => const SizedBox(width: Space.xs),
|
||||
itemBuilder: (context, i) {
|
||||
final type = types[i];
|
||||
final quote = quotes[type.code];
|
||||
return _Tile(
|
||||
type: type,
|
||||
quote: quotes[type.code],
|
||||
quote: quote,
|
||||
selected: type.id == selected?.id,
|
||||
label: type.nameFor(lang),
|
||||
// نوعٌ بلا تعرفة نشطة **لا يُختار**: اختياره يُفشل طلب الرحلة
|
||||
// بـ«No pricing available». يبقى ظاهراً معطّلاً كي يعرف المستخدم
|
||||
// أن الخيار موجود لكنه غير متاح الآن — لا أن يختفي بلا تفسير.
|
||||
enabled: quote != null,
|
||||
onTap: () => onSelect(type),
|
||||
);
|
||||
},
|
||||
@@ -60,6 +66,7 @@ class _Tile extends StatelessWidget {
|
||||
required this.quote,
|
||||
required this.selected,
|
||||
required this.label,
|
||||
required this.enabled,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@@ -67,12 +74,14 @@ class _Tile extends StatelessWidget {
|
||||
final FareQuote? quote;
|
||||
final bool selected;
|
||||
final String label;
|
||||
final bool enabled;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = context.colors;
|
||||
final fare = quote;
|
||||
final dim = scheme.onSurfaceVariant;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: Motion.tap,
|
||||
@@ -88,7 +97,7 @@ class _Tile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onTap: enabled ? onTap : null,
|
||||
borderRadius: Radii.card_,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Space.xs),
|
||||
@@ -102,7 +111,11 @@ class _Tile extends StatelessWidget {
|
||||
? Icons.two_wheeler_rounded
|
||||
: Icons.directions_car_rounded,
|
||||
size: Sizes.icon,
|
||||
color: selected ? scheme.onPrimaryContainer : scheme.onSurface,
|
||||
color: !enabled
|
||||
? dim
|
||||
: selected
|
||||
? scheme.onPrimaryContainer
|
||||
: scheme.onSurface,
|
||||
),
|
||||
const SizedBox(height: Space.xxs),
|
||||
Text(
|
||||
@@ -110,17 +123,29 @@ class _Tile extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.texts.labelMedium?.copyWith(
|
||||
color: selected ? scheme.onPrimaryContainer : null,
|
||||
color: !enabled
|
||||
? dim
|
||||
: selected
|
||||
? scheme.onPrimaryContainer
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Space.xxs),
|
||||
// نوعٌ بلا تعرفة نشطة يظهر بشرطة بدل أن يُخفى: إخفاؤه يترك
|
||||
// المستخدم يتساءل أين ذهب الخيار.
|
||||
if (fare == null)
|
||||
Text(
|
||||
fare == null ? '—' : Money.formatNum(fare.total!, fare.currency),
|
||||
context.l10n.rideTypeUnavailable,
|
||||
maxLines: 1,
|
||||
style: context.texts.labelSmall?.copyWith(color: dim),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
Money.formatNum(fare.total!, fare.currency),
|
||||
maxLines: 1,
|
||||
style: numericStyle(context.texts.titleSmall).copyWith(
|
||||
color: selected ? scheme.onPrimaryContainer : scheme.primary,
|
||||
color:
|
||||
selected ? scheme.onPrimaryContainer : scheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user