Update codebase
This commit is contained in:
@@ -11,16 +11,26 @@ import '../../../../core/tenant/feature_gate.dart';
|
||||
import '../../../../core/ui/tripz_button.dart';
|
||||
import '../../cubit/duty_cubit.dart';
|
||||
import '../../data/models/trip.dart';
|
||||
import '../../data/models/route_info.dart';
|
||||
import 'fare_summary.dart';
|
||||
import 'nav_eta_strip.dart';
|
||||
|
||||
/// الرحلة من طرف السائق: **زر واحد** يدفع آلة الحالات خطوة واحدة.
|
||||
/// السائق لا يختار الحالة التالية — الكيوبت يشتقّها (docs/38 §4).
|
||||
class ActiveDutySheet extends StatelessWidget {
|
||||
const ActiveDutySheet({super.key, required this.trip, required this.busy});
|
||||
const ActiveDutySheet({
|
||||
super.key,
|
||||
required this.trip,
|
||||
required this.busy,
|
||||
this.navRoute,
|
||||
});
|
||||
|
||||
final Trip trip;
|
||||
final bool busy;
|
||||
|
||||
/// مسار القيادة إلى الهدف الحالي — منه شريط الوقت والمسافة.
|
||||
final RouteInfo? navRoute;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -31,6 +41,13 @@ class ActiveDutySheet extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(_statusText(context), style: context.texts.titleMedium),
|
||||
if (navRoute != null) ...[
|
||||
const SizedBox(height: Space.sm),
|
||||
NavEtaStrip(
|
||||
minutes: navRoute!.durationMin,
|
||||
km: navRoute!.distanceKm,
|
||||
),
|
||||
],
|
||||
if (trip.priceForDriver != null) ...[
|
||||
const SizedBox(height: Space.sm),
|
||||
// السائق يرى **أرباحه** لا أجرة الراكب: حقلان منفصلان والفرق عمولة.
|
||||
@@ -42,7 +59,7 @@ class ActiveDutySheet extends StatelessWidget {
|
||||
label: l10n.chatTitle,
|
||||
icon: Icons.forum_outlined,
|
||||
expanded: true,
|
||||
onPressed: () => context.push('${Routes.chat}?trip=\${trip.id}'),
|
||||
onPressed: () => context.push('${Routes.chat}?trip=${trip.id}'),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: Space.md),
|
||||
|
||||
@@ -4,6 +4,7 @@ import '../../../../core/design/tokens.dart';
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
import '../../../../core/design/typography.dart';
|
||||
import '../../data/models/trip.dart';
|
||||
import 'vehicle_color.dart';
|
||||
|
||||
/// بطاقة السائق ولوحة المركبة — أهم ما يبحث عنه الراكب في الشارع، فيأخذ
|
||||
/// أوضح موضع وأكبر تباين.
|
||||
@@ -15,25 +16,35 @@ class DriverBadge extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (trip.driverName == null) return const SizedBox.shrink();
|
||||
|
||||
// اللون **يُرسم لا يُكتب فقط**: الراكب يمسح الشارع بعينه بحثاً عن سيارة
|
||||
// بيضاء، لا يقرأ كلمة «أبيض». نمط سيرو (`apply_order_widget.dart:333`)
|
||||
// يلوّن رمز السيارة بلون المركبة الفعلي — وهذا مقابله.
|
||||
final carColor = VehicleColor.resolve(
|
||||
hex: trip.vehicleColorHex,
|
||||
name: trip.vehicleColor,
|
||||
);
|
||||
|
||||
// سطر المركبة: الموديل واللون معاً بفاصل — أحدهما قد يغيب.
|
||||
final vehicleLine = [
|
||||
if (trip.vehicleModel != null && trip.vehicleModel!.isNotEmpty)
|
||||
trip.vehicleModel!,
|
||||
if (trip.vehicleColor != null && trip.vehicleColor!.isNotEmpty)
|
||||
trip.vehicleColor!,
|
||||
].join(' • ');
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: context.colors.primaryContainer,
|
||||
child: Icon(
|
||||
Icons.person_rounded,
|
||||
color: context.colors.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
_CarAvatar(color: carColor),
|
||||
const SizedBox(width: Space.sm),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(trip.driverName!, style: context.texts.titleSmall),
|
||||
if (trip.vehicleModel != null)
|
||||
if (vehicleLine.isNotEmpty)
|
||||
Text(
|
||||
trip.vehicleModel!,
|
||||
vehicleLine,
|
||||
style: context.texts.bodySmall?.copyWith(
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
@@ -62,6 +73,43 @@ class DriverBadge extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// رمز سيارة ملوّن بلون المركبة الحقيقي.
|
||||
///
|
||||
/// اللون الفاتح (أبيض/بيج) يختفي على سطح فاتح، فتُرسم حلقة حدّ حوله دائماً —
|
||||
/// وبها تبقى السيارة البيضاء مقروءة في الوضع النهاري.
|
||||
class _CarAvatar extends StatelessWidget {
|
||||
const _CarAvatar({this.color});
|
||||
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resolved = color ?? context.colors.onSurfaceVariant;
|
||||
return Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
// 10% من لون السيارة خلفيةً — يربط الرمز بلونه بلا صراخ.
|
||||
color: color?.withValues(alpha: 0.12) ??
|
||||
context.colors.surfaceContainerHighest,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: context.colors.outlineVariant),
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.directions_car_filled_rounded,
|
||||
size: Sizes.icon,
|
||||
color: resolved,
|
||||
// ظلّ خفيف كي لا تذوب السيارة البيضاء في الخلفية الفاتحة.
|
||||
shadows: const [
|
||||
Shadow(color: Color(0x33000000), blurRadius: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// لوحة مرسومة لا نصّ عادي — تُقرأ من بعيد ومن زاوية.
|
||||
class _Plate extends StatelessWidget {
|
||||
const _Plate({required this.plate});
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/design/tokens.dart';
|
||||
import '../../../../core/design/tripz_colors.dart';
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
|
||||
/// «كم بقي للهدف» — الوقت والمسافة من مسار القيادة الحيّ.
|
||||
///
|
||||
/// الهدف يتبدّل مع آلة الحالات: الراكب أثناء الاقتراب، ثم وجهته بعد بدء
|
||||
/// الرحلة (`DutyState.navTarget`). فالشريط نفسه يخدم المرحلتين.
|
||||
class NavEtaStrip extends StatelessWidget {
|
||||
const NavEtaStrip({super.key, required this.minutes, required this.km});
|
||||
|
||||
final double minutes;
|
||||
final double km;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Space.md,
|
||||
vertical: Space.sm,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: context.tripzColors.info.withValues(alpha: 0.10),
|
||||
borderRadius: Radii.card_,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.navigation_outlined,
|
||||
size: Sizes.iconSm,
|
||||
color: context.tripzColors.info,
|
||||
),
|
||||
const SizedBox(width: Space.xs),
|
||||
Text(
|
||||
// أقلّ من دقيقة تُعرض «1»: «0 دقيقة» تعني «وصلتُ» وهي كذبة.
|
||||
l10n.rideEta(minutes < 1 ? '1' : minutes.round().toString()),
|
||||
style: context.texts.titleSmall,
|
||||
),
|
||||
const SizedBox(width: Space.sm),
|
||||
Text('·', style: context.texts.titleSmall),
|
||||
const SizedBox(width: Space.sm),
|
||||
Text(
|
||||
l10n.rideDistanceKm(km.toStringAsFixed(1)),
|
||||
style: context.texts.bodyMedium?.copyWith(
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer' as dev;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart' as im;
|
||||
|
||||
@@ -83,6 +85,29 @@ class _RideMapState extends State<RideMap> {
|
||||
|
||||
static const _fallback = im.LatLng(31.9539, 35.9106); // عمّان
|
||||
|
||||
/// حارس الإحداثيات — **بلا هذا يسقط التطبيق أصلاً لا استثناءً في دارت**.
|
||||
///
|
||||
/// `std::domain_error` الذي رأيناه (2026-08-05) يأتي من `mbgl::LatLng`
|
||||
/// في MapLibre الأصلي، وهو يرمي حرفياً عند: `latitude must not be NaN` ·
|
||||
/// `longitude must not be NaN` · `latitude must be between -90 and 90` ·
|
||||
/// `longitude must not be infinite`. (السلاسل الأربع موجودة في ثنائي
|
||||
/// `MapLibre.framework`، وهي مصدر `std::domain_error` الوحيد فيه — أمّا
|
||||
/// الستايل فبريء: الملفّات الخمسة كلها اجتازت `validateStyleMin` بصفر
|
||||
/// أخطاء.)
|
||||
///
|
||||
/// وهو استثناء **C++ يعبر حدّ المنصّة**، فلا `try/catch` في دارت يمسكه:
|
||||
/// المنع قبل التمرير هو العلاج الوحيد.
|
||||
static im.LatLng? _safe(GeoPoint? p, String tag) {
|
||||
if (p == null) return null;
|
||||
final ok = p.lat.isFinite && p.lng.isFinite && p.lat.abs() <= 90;
|
||||
if (!ok) {
|
||||
dev.log('إحداثية غير صالحة رُفضت [$tag]: ${p.lat}, ${p.lng}',
|
||||
name: 'RideMap');
|
||||
return null;
|
||||
}
|
||||
return im.LatLng(p.lat, p.lng);
|
||||
}
|
||||
|
||||
/// الستايلان مرفقان في `assets/` — منقولان من سيرو الميداني.
|
||||
static const _lightStyleAsset = 'assets/style.json';
|
||||
static const _darkStyleAsset = 'assets/style_dark.json';
|
||||
@@ -123,9 +148,43 @@ class _RideMapState extends State<RideMap> {
|
||||
}
|
||||
|
||||
void _moveTo(GeoPoint p) {
|
||||
_controller?.animateCamera(
|
||||
im.CameraUpdate.newLatLngZoom(im.LatLng(p.lat, p.lng), 15.5),
|
||||
);
|
||||
final target = _safe(p, 'moveTo');
|
||||
if (target == null) return;
|
||||
_controller?.animateCamera(im.CameraUpdate.newLatLngZoom(target, 15.5));
|
||||
}
|
||||
|
||||
/// يفكّ ترميز المسار **مصفّىً**: مسار مشوّه من الخادم (أو سلسلة مقطوعة)
|
||||
/// يعطي خطوط عرض خارج ±90، وتلك تصل `mbgl::LatLng` فتُسقط التطبيق أصلاً.
|
||||
static List<im.LatLng> _decodeRoute(String encoded) {
|
||||
final all = im.PolylineUtils.decode(encoded);
|
||||
final good = all
|
||||
.where((p) =>
|
||||
p.latitude.isFinite &&
|
||||
p.longitude.isFinite &&
|
||||
p.latitude.abs() <= 90)
|
||||
.toList();
|
||||
if (good.length != all.length) {
|
||||
dev.log('نقاط مسار غير صالحة رُفضت: ${all.length - good.length}',
|
||||
name: 'RideMap');
|
||||
}
|
||||
return good;
|
||||
}
|
||||
|
||||
/// المسار كطبقة `Polyline` — مجموعة فارغة ما لم يكن الستايل جاهزاً
|
||||
/// والنقاط صالحة.
|
||||
Set<im.Polyline> _routePolylines(Color color) {
|
||||
final encoded = widget.route?.encodedPoints;
|
||||
if (!_styleReady || encoded == null || encoded.isEmpty) return const {};
|
||||
final points = _decodeRoute(encoded);
|
||||
if (points.length < 2) return const {};
|
||||
return {
|
||||
im.Polyline(
|
||||
polylineId: const im.PolylineId('route'),
|
||||
points: points,
|
||||
color: color,
|
||||
width: 5,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// يضبط الكاميرا على المسار كاملاً بدل نقطة واحدة.
|
||||
@@ -135,7 +194,7 @@ class _RideMapState extends State<RideMap> {
|
||||
void _fitRoute() {
|
||||
final encoded = widget.route?.encodedPoints;
|
||||
if (encoded == null || encoded.isEmpty) return;
|
||||
final points = im.PolylineUtils.decode(encoded);
|
||||
final points = _decodeRoute(encoded);
|
||||
if (points.length < 2) return;
|
||||
|
||||
var minLat = points.first.latitude, maxLat = points.first.latitude;
|
||||
@@ -147,6 +206,10 @@ class _RideMapState extends State<RideMap> {
|
||||
if (p.longitude > maxLng) maxLng = p.longitude;
|
||||
}
|
||||
|
||||
// `mbgl::EdgeInsets` يرمي هو الآخر على NaN (`bottom must not be NaN`)،
|
||||
// و`bottomInset` مشتقّ من `MediaQuery` فقد يصل صفراً أو NaN في أوّل إطار.
|
||||
final bottom = widget.bottomInset.isFinite ? widget.bottomInset : 280.0;
|
||||
|
||||
_controller?.animateCamera(
|
||||
im.CameraUpdate.newLatLngBounds(
|
||||
im.LatLngBounds(
|
||||
@@ -156,7 +219,7 @@ class _RideMapState extends State<RideMap> {
|
||||
left: 48,
|
||||
right: 48,
|
||||
top: 120,
|
||||
bottom: widget.bottomInset,
|
||||
bottom: bottom,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -167,19 +230,19 @@ class _RideMapState extends State<RideMap> {
|
||||
if (controller == null || _syncing) return;
|
||||
_syncing = true;
|
||||
|
||||
Future<Offset?> px(GeoPoint? p) async {
|
||||
if (p == null) return null;
|
||||
final point = await controller.getScreenCoordinate(
|
||||
im.LatLng(p.lat, p.lng),
|
||||
);
|
||||
// `getScreenCoordinate` يعبر إلى `mbgl::LatLng` هو الآخر — نفس الحارس.
|
||||
Future<Offset?> px(GeoPoint? p, String tag) async {
|
||||
final target = _safe(p, tag);
|
||||
if (target == null) return null;
|
||||
final point = await controller.getScreenCoordinate(target);
|
||||
return Offset(point.x.toDouble(), point.y.toDouble());
|
||||
}
|
||||
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
px(widget.origin),
|
||||
px(widget.destination),
|
||||
px(widget.driver),
|
||||
px(widget.origin, 'origin'),
|
||||
px(widget.destination, 'destination'),
|
||||
px(widget.driver, 'driver'),
|
||||
]);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
@@ -205,22 +268,19 @@ class _RideMapState extends State<RideMap> {
|
||||
// الخريطة تبدّل ستايلها مع الثيم — خريطة نهارية في وضع ليلي تكسر
|
||||
// الغرض (docs/26 §2). وتبديله يهدم مديري التعليقات، فيُصفَّر العَلَم.
|
||||
//
|
||||
// ⚠️ **ستايل محلّي لا بعيد.** ستايل map-saas البعيد
|
||||
// (`/api/maps/style.json?theme=light`) فيه **12 طبقة `symbol` تستعمل
|
||||
// `icon-image` بلا `sprite` معرَّف** — وMapLibre الأصلي يرمي عندها
|
||||
// `std::domain_error` فيُسقط التطبيق قبل أن يظهر إطار واحد
|
||||
// (مثبت 2026-08-05). الستايلان المرفقان في `assets/` سليمان: بهما
|
||||
// `sprite` وبلاطاتهما من `tiles.intaleqapp.com` نفسها.
|
||||
//
|
||||
// وهذا حرفياً حلّ سيرو الميداني: انظر
|
||||
// **ستايل محلّي لا بعيد** — كما في سيرو الميداني حرفياً:
|
||||
// `apps/rider/lib/views/home/map_widget.dart/google_map_passenger_widget.dart`
|
||||
// — يمرّر `'assets/style.json'` / `'assets/style_dark.json'` نفسهما، وملفّاه
|
||||
// يمرّر `'assets/style.json'` / `'assets/style_dark.json'` نفسهما، وملفّاه
|
||||
// مطابقان لهذين طبقةً بطبقة (لا يختلفان إلا في اسم العلامة التجارية).
|
||||
// فلا تُعِد هذا السطر إلى `IntaleqStyles.light/obsidian` — جُرّب وأسقط
|
||||
// التطبيق مرتين.
|
||||
//
|
||||
// ومعها نكسب الإقلاع بلا انتظار شبكة — وهو ما يعنيه «الشكل حتمي من أول
|
||||
// إقلاع» (docs/26 §1).
|
||||
//
|
||||
// ملاحظة تصحيحية (2026-08-05): كان مكتوباً هنا أن ستايل map-saas البعيد
|
||||
// هو سبب `std::domain_error` لأنه بلا `sprite`. **هذا خطأ.** الستايلات
|
||||
// الخمسة (المحلّيان + البعيد + ستايلا الحزمة) اجتازت
|
||||
// `validateStyleMin` من `@maplibre/maplibre-gl-style-spec` بصفر أخطاء،
|
||||
// والسقوط تكرّر بالستايل المحلّي أيضاً. المصدر الحقيقي هو حارس
|
||||
// الإحداثيات في `mbgl::LatLng`/`mbgl::EdgeInsets` — انظر `_safe` أعلاه.
|
||||
final styleUrl = isDark ? _darkStyleAsset : _lightStyleAsset;
|
||||
if (_styleUrl != null && _styleUrl != styleUrl) _styleReady = false;
|
||||
_styleUrl = styleUrl;
|
||||
@@ -234,13 +294,17 @@ class _RideMapState extends State<RideMap> {
|
||||
// فنقفله من هنا بدل الاتّكال على ذلك.
|
||||
autoCache: false,
|
||||
initialCameraPosition: im.CameraPosition(
|
||||
target: widget.origin == null
|
||||
? _fallback
|
||||
: im.LatLng(widget.origin!.lat, widget.origin!.lng),
|
||||
target: _safe(widget.origin, 'initialCamera') ?? _fallback,
|
||||
zoom: 15,
|
||||
),
|
||||
styleUrl: styleUrl,
|
||||
myLocationEnabled: true,
|
||||
// نقطة «موقعي» تُفعَّل **بعد** أن يصلنا موقع حقيقي لا عند الإنشاء.
|
||||
// قبل ذلك يكون `MLNMapView.userLocation.coordinate` هو
|
||||
// `kCLLocationCoordinate2DInvalid` أي (-180, -180) — وخط عرض -180
|
||||
// يخترق حارس `mbgl::LatLng` نفسه (`latitude must be between -90
|
||||
// and 90`). و`origin` لا يصير غير فارغ إلا بعد منح الإذن ووصول
|
||||
// قراءة فعلية، فهو الشرط الصحيح تماماً.
|
||||
myLocationEnabled: widget.origin != null,
|
||||
compassEnabled: false,
|
||||
zoomControlsEnabled: false,
|
||||
onMapCreated: _onCreated,
|
||||
@@ -257,20 +321,9 @@ class _RideMapState extends State<RideMap> {
|
||||
}
|
||||
_syncPixels();
|
||||
},
|
||||
// **المسار وحده annotation** — لا بديل عنه، ومحروسٌ بعَلَم الستايل.
|
||||
polylines: _styleReady && widget.route != null &&
|
||||
widget.route!.encodedPoints.isNotEmpty
|
||||
? {
|
||||
im.Polyline(
|
||||
polylineId: const im.PolylineId('route'),
|
||||
points: im.PolylineUtils.decode(
|
||||
widget.route!.encodedPoints,
|
||||
),
|
||||
color: colors.mapRoute,
|
||||
width: 5,
|
||||
),
|
||||
}
|
||||
: const {},
|
||||
// **المسار وحده annotation** — لا بديل عنه، ومحروسٌ بعَلَم الستايل
|
||||
// وبتصفية الإحداثيات (نقاط المسار تعبر إلى `mbgl::LatLng` أيضاً).
|
||||
polylines: _routePolylines(colors.mapRoute),
|
||||
),
|
||||
|
||||
// ── الطبقة الفلاترية: نقاط وأسماء وسائق ──────────────────────────
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// يحوّل لون المركبة — hex أو اسماً — إلى لون يُرسم به.
|
||||
///
|
||||
/// الخادم غير متّسق عمداً: `vehicles` عندها `color_hex` بينما حدث `assigned`
|
||||
/// يرسل `color` اسماً (`trips.service.ts:427`). فالمُحوِّل يقبل الاثنين
|
||||
/// ويُقدّم الـhex لأنه دقيق.
|
||||
///
|
||||
/// الأسماء تُطابَق **بالعربية والإنجليزية معاً**: السائقون يُدخلونها بلغتهم،
|
||||
/// وقائمة إنجليزية وحدها تعني رمادياً افتراضياً لنصف الأسطول.
|
||||
class VehicleColor {
|
||||
const VehicleColor._();
|
||||
|
||||
/// اللون الذي يُرسم به رمز السيارة، أو `null` إن تعذّرت المطابقة —
|
||||
/// و`null` تعني «لا تلوّن» لا «لوّن رمادياً»، فالرمادي لونُ سيارةٍ حقيقي
|
||||
/// ولا يصحّ أن يعني «مجهول».
|
||||
static Color? resolve({String? hex, String? name}) =>
|
||||
_fromHex(hex) ?? _fromName(name);
|
||||
|
||||
static Color? _fromHex(String? hex) {
|
||||
if (hex == null) return null;
|
||||
var raw = hex.replaceFirst('#', '').trim();
|
||||
if (raw.length == 3) {
|
||||
// `#f00` → `#ff0000`: صيغة مختصرة شائعة في لوحات الإدارة.
|
||||
raw = raw.split('').map((c) => '$c$c').join();
|
||||
}
|
||||
if (raw.length == 6) raw = 'FF$raw';
|
||||
if (raw.length != 8) return null;
|
||||
final value = int.tryParse(raw, radix: 16);
|
||||
return value == null ? null : Color(value);
|
||||
}
|
||||
|
||||
static const _names = <String, Color>{
|
||||
'white': Colors.white,
|
||||
'أبيض': Colors.white,
|
||||
'black': Colors.black,
|
||||
'أسود': Colors.black,
|
||||
'silver': Color(0xFFC0C0C0),
|
||||
'فضي': Color(0xFFC0C0C0),
|
||||
'grey': Color(0xFF808080),
|
||||
'gray': Color(0xFF808080),
|
||||
'رمادي': Color(0xFF808080),
|
||||
'red': Color(0xFFD32F2F),
|
||||
'أحمر': Color(0xFFD32F2F),
|
||||
'blue': Color(0xFF1976D2),
|
||||
'أزرق': Color(0xFF1976D2),
|
||||
'green': Color(0xFF388E3C),
|
||||
'أخضر': Color(0xFF388E3C),
|
||||
'yellow': Color(0xFFFBC02D),
|
||||
'أصفر': Color(0xFFFBC02D),
|
||||
'orange': Color(0xFFF57C00),
|
||||
'برتقالي': Color(0xFFF57C00),
|
||||
'brown': Color(0xFF6D4C41),
|
||||
'بني': Color(0xFF6D4C41),
|
||||
'beige': Color(0xFFE8DCC4),
|
||||
'بيج': Color(0xFFE8DCC4),
|
||||
'gold': Color(0xFFC9A227),
|
||||
'ذهبي': Color(0xFFC9A227),
|
||||
'navy': Color(0xFF1A2340),
|
||||
'كحلي': Color(0xFF1A2340),
|
||||
};
|
||||
|
||||
static Color? _fromName(String? name) {
|
||||
if (name == null) return null;
|
||||
return _names[name.trim().toLowerCase()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user