Update: 2026-08-07 06:00:56

This commit is contained in:
Hamza-Ayed
2026-08-07 06:00:56 +03:00
parent 67465df000
commit 92a6aa5c0f
11 changed files with 443 additions and 15 deletions
@@ -65,7 +65,7 @@ class OrderRequestController extends GetxController
}; };
} }
/// معلومات الحجز المسبق إن كانت الرحلة مؤجلة. /// معلومات الحجز المسبق إن كانت الرحلة مجدولة.
/// ///
/// يصل الحقلان من buildMarketPayload عبر السوكِت (خريطة)، أو من الفهرسين /// يصل الحقلان من buildMarketPayload عبر السوكِت (خريطة)، أو من الفهرسين
/// 39/40 عبر FCM (قائمة). _getValueAt يوحّد الشكلين. /// 39/40 عبر FCM (قائمة). _getValueAt يوحّد الشكلين.
@@ -35,9 +35,17 @@ class OrderData {
/// ثواني السبق الحصري كما حددها الخادم (DISPATCH_HEAD_START_SECONDS). /// ثواني السبق الحصري كما حددها الخادم (DISPATCH_HEAD_START_SECONDS).
final int offerExpiresIn; final int offerExpiresIn;
/// رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري.
final bool isScheduled;
/// موعد الانطلاق كما حجزه الراكب. فارغ في الرحلات العادية.
final String scheduledAt;
OrderData({ OrderData({
this.offerType, this.offerType,
this.offerExpiresIn = 0, this.offerExpiresIn = 0,
this.isScheduled = false,
this.scheduledAt = '',
required this.customerName, required this.customerName,
required this.customerToken, required this.customerToken,
required this.tripDistanceKm, required this.tripDistanceKm,
@@ -151,6 +159,11 @@ class OrderData {
: null, : null,
offerExpiresIn: offerExpiresIn:
list.length > 38 ? (int.tryParse(list[38].toString()) ?? 0) : 0, list.length > 38 ? (int.tryParse(list[38].toString()) ?? 0) : 0,
// ‏الفهرسان 39/40 — يضيفهما add_ride.php للرحلات المحجوزة.
isScheduled: list.length > 39 &&
(list[39].toString() == '1' || list[39].toString() == 'true'),
scheduledAt: list.length > 40 ? list[40].toString() : '',
); );
} }
@@ -197,6 +210,8 @@ class OrderData {
: null, : null,
offerType: str('offer_type').isNotEmpty ? str('offer_type') : null, offerType: str('offer_type').isNotEmpty ? str('offer_type') : null,
offerExpiresIn: int.tryParse(str('offer_expires_in', '0')) ?? 0, offerExpiresIn: int.tryParse(str('offer_expires_in', '0')) ?? 0,
isScheduled: str('is_scheduled') == '1' || str('is_scheduled') == 'true',
scheduledAt: str('scheduled_at'),
); );
} }
@@ -475,19 +475,34 @@ class _OrderOverlayState extends State<OrderOverlay>
Row( Row(
children: [ children: [
Icon( Icon(
isExclusive ? Icons.star_rounded : Icons.drive_eta_rounded, orderData?.isScheduled == true
color: isExclusive ? const Color(0xFFFFD54F) : AppColors.white, ? Icons.schedule
: isExclusive
? Icons.star_rounded
: Icons.drive_eta_rounded,
color: orderData?.isScheduled == true
? const Color(0xFFB39DDB)
: isExclusive
? const Color(0xFFFFD54F)
: AppColors.white,
size: 24, size: 24,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
// ‏العنوان يحمل حالة العرض: خاص خلال السبق، عام بعده، و"طلب // ‏العنوان يحمل حالة العرض: خاص خلال السبق، عام بعده، و"طلب
// ‏جديد" في البثّ الحر حيث لا معنى للتمييز. // ‏جديد" في البثّ الحر حيث لا معنى للتمييز.
Text( Text(
orderData?.offerType == null orderData?.isScheduled == true
// ‏الأولوية للمجدولة: معلومة دائمة عن الرحلة تسبق حالة
// ‏العرض المؤقتة، والسائق يحتاجها ليقرر إن كان يستطيع
// ‏الالتزام بالموعد.
? "🗓️ رحلة مجدولة".tr
: orderData?.offerType == null
? "طلب جديد".tr ? "طلب جديد".tr
: (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr), : (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr),
style: TextStyle( style: TextStyle(
color: isExclusive color: orderData?.isScheduled == true
? const Color(0xFFB39DDB)
: isExclusive
? const Color(0xFFFFD54F) ? const Color(0xFFFFD54F)
: AppColors.white, : AppColors.white,
fontSize: 18, fontSize: 18,
@@ -116,7 +116,7 @@ class OrderRequestPage extends StatelessWidget {
OfferTypeBadge(rideInfo: controller.offerInfo!), OfferTypeBadge(rideInfo: controller.offerInfo!),
const SizedBox(width: 10), const SizedBox(width: 10),
], ],
// شارة الرحلة المؤجلة — موعد مضبوط لا طلب فوري // شارة الرحلة المجدولة — موعد مضبوط لا طلب فوري
if (ScheduledRideBadge.isScheduled( if (ScheduledRideBadge.isScheduled(
controller.scheduleInfo)) ...[ controller.scheduleInfo)) ...[
ScheduledRideBadge( ScheduledRideBadge(
@@ -189,7 +189,7 @@ class RideAvailableCard extends StatelessWidget {
.copyWith(color: AppColor.greenColor, fontSize: 13), .copyWith(color: AppColor.greenColor, fontSize: 13),
), ),
), ),
// شارة الرحلة المؤجلة — معلومة دائمة عن الرحلة، تظهر هنا // شارة الرحلة المجدولة — معلومة دائمة عن الرحلة، تظهر هنا
// وفي نافذة الطلب والأوفرلي معاً. // وفي نافذة الطلب والأوفرلي معاً.
if (ScheduledRideBadge.isScheduled(rideInfo)) ...[ if (ScheduledRideBadge.isScheduled(rideInfo)) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
/// شارة «رحلة مؤجلة». /// شارة «رحلة مجدولة».
/// ///
/// السائق يجب أن يعرف أن هذه ليست رحلة لحظية بل موعد مضبوط: الراكب /// السائق يجب أن يعرف أن هذه ليست رحلة لحظية بل موعد مضبوط: الراكب
/// ينتظره في وقت محدد، والتأخر عليه يختلف عن التأخر على طلب فوري. /// ينتظره في وقت محدد، والتأخر عليه يختلف عن التأخر على طلب فوري.
@@ -73,7 +73,7 @@ class ScheduledRideBadge extends StatelessWidget {
Icon(Icons.schedule, color: color, size: compact ? 14 : 16), Icon(Icons.schedule, color: color, size: compact ? 14 : 16),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
t.isEmpty ? 'رحلة مؤجلة'.tr : '${'رحلة مؤجلة'.tr} · $t', t.isEmpty ? 'رحلة مجدولة'.tr : '${'رحلة مجدولة'.tr} · $t',
style: TextStyle( style: TextStyle(
color: color, color: color,
fontSize: compact ? 11 : 13, fontSize: compact ? 11 : 13,
@@ -581,6 +581,35 @@ class TripOverlayService : Service() {
} }
detailsContainer.addView(offerBadge) detailsContainer.addView(offerBadge)
// ‏شارة الرحلة المجدولة — تظهر مع شارة نوع الطلب لا بدلاً منها:
// ‏الأولى تصف الرحلة والثانية تصف العرض، ومعلومتان مختلفتان.
if (trip.isScheduled) {
val scheduledBadge =
TextView(ctx).apply {
val color = Color.parseColor("#6A5ACD")
text = if (trip.scheduledAt.isEmpty()) "🗓️ رحلة مجدولة"
else "🗓️ رحلة مجدولة · " + trip.scheduledAt
gravity = Gravity.CENTER
textSize = 13f
typeface = android.graphics.Typeface.DEFAULT_BOLD
setPadding(24, 12, 24, 12)
setTextColor(color)
background =
android.graphics.drawable.GradientDrawable().apply {
setColor(Color.parseColor("#F0EDFB"))
cornerRadius = 40f
setStroke(2, color)
}
layoutParams =
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
.apply { bottomMargin = 24 }
}
detailsContainer.addView(scheduledBadge)
}
// ── 4. شريط الوقت ── // ── 4. شريط الوقت ──
val countdownLabel = val countdownLabel =
TextView(ctx).apply { TextView(ctx).apply {
@@ -925,7 +954,9 @@ class TripOverlayService : Service() {
pickupLat = obj.getDouble("pickupLat"), pickupLat = obj.getDouble("pickupLat"),
pickupLng = obj.getDouble("pickupLng"), pickupLng = obj.getDouble("pickupLng"),
offerType = obj.optString("offerType", ""), offerType = obj.optString("offerType", ""),
offerExpiresIn = obj.optInt("offerExpiresIn", 0) offerExpiresIn = obj.optInt("offerExpiresIn", 0),
isScheduled = obj.optBoolean("isScheduled", false),
scheduledAt = obj.optString("scheduledAt", "")
) )
} catch (e: Exception) { } catch (e: Exception) {
null null
@@ -944,6 +975,9 @@ class TripOverlayService : Service() {
val pickupLng: Double, val pickupLng: Double,
// ‏"exclusive" خلال السبق الحصري، "public" بعده، وفارغ في البثّ الحر // ‏"exclusive" خلال السبق الحصري، "public" بعده، وفارغ في البثّ الحر
val offerType: String = "", val offerType: String = "",
val offerExpiresIn: Int = 0 val offerExpiresIn: Int = 0,
// ‏رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري
val isScheduled: Boolean = false,
val scheduledAt: String = ""
) )
} }
@@ -25,6 +25,10 @@ class TripData {
/// من نفسها — النافذة لا تُغلق، فالسائق يظل قادراً على القبول. /// من نفسها — النافذة لا تُغلق، فالسائق يظل قادراً على القبول.
final int offerExpiresIn; final int offerExpiresIn;
/// رحلة محجوزة مسبقاً وموعدها. السائق يجب أن يعرف أنها ليست طلباً فورياً.
final bool isScheduled;
final String scheduledAt;
TripData({ TripData({
required this.tripId, required this.tripId,
required this.passengerName, required this.passengerName,
@@ -38,6 +42,8 @@ class TripData {
this.passengerAvatarUrl, this.passengerAvatarUrl,
this.offerType, this.offerType,
this.offerExpiresIn = 0, this.offerExpiresIn = 0,
this.isScheduled = false,
this.scheduledAt = '',
}); });
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
@@ -53,6 +59,8 @@ class TripData {
'passengerAvatarUrl': passengerAvatarUrl ?? '', 'passengerAvatarUrl': passengerAvatarUrl ?? '',
'offerType': offerType ?? '', 'offerType': offerType ?? '',
'offerExpiresIn': offerExpiresIn, 'offerExpiresIn': offerExpiresIn,
'isScheduled': isScheduled,
'scheduledAt': scheduledAt,
}; };
factory TripData.fromMap(Map<String, dynamic> map) => TripData( factory TripData.fromMap(Map<String, dynamic> map) => TripData(
@@ -67,6 +75,8 @@ class TripData {
? null ? null
: map['offerType'].toString(), : map['offerType'].toString(),
offerExpiresIn: int.tryParse('${map['offerExpiresIn'] ?? 0}') ?? 0, offerExpiresIn: int.tryParse('${map['offerExpiresIn'] ?? 0}') ?? 0,
isScheduled: map['isScheduled'] == true || '${map['isScheduled']}' == '1',
scheduledAt: (map['scheduledAt'] ?? '').toString(),
pickupLat: (map['pickupLat'] ?? 0.0).toDouble(), pickupLat: (map['pickupLat'] ?? 0.0).toDouble(),
pickupLng: (map['pickupLng'] ?? 0.0).toDouble(), pickupLng: (map['pickupLng'] ?? 0.0).toDouble(),
passengerAvatarUrl: map['passengerAvatarUrl'], passengerAvatarUrl: map['passengerAvatarUrl'],
+5
View File
@@ -416,6 +416,11 @@ class AppLink {
/// needsReview في getRideStatusFromStartApp محصور بنافذة ساعة مقصودة /// needsReview في getRideStatusFromStartApp محصور بنافذة ساعة مقصودة
/// لاستئناف رحلة قائمة، فمن يغلق التطبيق بعد نزوله لا يُطلب منه تقييم. /// لاستئناف رحلة قائمة، فمن يغلق التطبيق بعد نزوله لا يُطلب منه تقييم.
static String get pendingRating => "$server/ride/rate/pending_rating.php"; static String get pendingRating => "$server/ride/rate/pending_rating.php";
// ── الحجز المسبق ──
static String get scheduledAdd => "$server/ride/scheduled/add.php";
static String get scheduledList => "$server/ride/scheduled/list.php";
static String get scheduledCancel => "$server/ride/scheduled/cancel.php";
static String get getDriverRate => "$server/ride/rate/getDriverRate.php"; static String get getDriverRate => "$server/ride/rate/getDriverRate.php";
static String get getPassengerRate => static String get getPassengerRate =>
"$server/ride/rate/getPassengerRate.php"; "$server/ride/rate/getPassengerRate.php";
@@ -0,0 +1,143 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/error_snakbar.dart';
import '../functions/crud.dart';
/// حجوزات الراكب المسبقة.
///
/// الحجز ليس رحلة: يسجَّل كنيّة سفر، ويحوّله الخادم لرحلة فعلية قبل
/// الموعد بهامش يشتقّه من المسافة. لذلك لا شاشة تتبّع هنا — الرحلة
/// تظهر في الخريطة كأي رحلة حين يحين وقتها.
class ScheduledRidesController extends GetxController {
final CRUD _crud = CRUD();
final bookings = <Map<String, dynamic>>[].obs;
final isLoading = false.obs;
final isSaving = false.obs;
/// حدود الخادم — مكرّرة هنا للتحقق المبكر فقط. الخادم هو من يفرضها.
static const int minLeadMinutes = 30;
static const int maxDaysAhead = 2;
DateTime get earliest =>
DateTime.now().add(const Duration(minutes: minLeadMinutes));
DateTime get latest => DateTime.now().add(const Duration(days: maxDaysAhead));
@override
void onInit() {
super.onInit();
fetch();
}
Future<void> fetch() async {
isLoading.value = true;
try {
final res = await _crud.post(
link: AppLink.scheduledList,
payload: {'scope': 'upcoming'},
);
final data = _extract(res);
final list = (data?['bookings'] as List?) ?? [];
bookings.assignAll(
list.map((e) => Map<String, dynamic>.from(e as Map)).toList());
} catch (e) {
mySnackbarError('تعذّر جلب الحجوزات');
} finally {
isLoading.value = false;
}
}
/// ينشئ حجزاً. يرجع true عند النجاح.
///
/// [when] موعد الانطلاق كما اختاره الراكب.
Future<bool> create({
required String startLocation,
required String endLocation,
required String startName,
required String endName,
required String carType,
required double distanceKm,
required int durationSeconds,
required double estimatedPrice,
required DateTime when,
String? note,
}) async {
if (isSaving.value) return false;
// ‏فحص محلي للراحة — يوفّر رحلة للخادم ويعطي رسالة أوضح.
if (when.isBefore(earliest)) {
mySnackbarError('اختر موعداً بعد $minLeadMinutes دقيقة على الأقل');
return false;
}
if (when.isAfter(latest)) {
mySnackbarError('يمكنك الحجز حتى $maxDaysAhead يومين مقدماً فقط');
return false;
}
isSaving.value = true;
try {
final res = await _crud.post(
link: AppLink.scheduledAdd,
payload: {
'start_location': startLocation,
'end_location': endLocation,
'start_name': startName,
'end_name': endName,
'car_type': carType,
'distance': distanceKm.toString(),
'duration': durationSeconds.toString(),
'estimated_price': estimatedPrice.toString(),
'scheduled_at': _fmt(when),
if (note != null && note.trim().isNotEmpty) 'note': note.trim(),
},
);
final data = _extract(res);
if (data == null) return false;
await fetch();
return true;
} catch (e) {
mySnackbarError('تعذّر الاتصال بالخادم');
return false;
} finally {
isSaving.value = false;
}
}
Future<void> cancel(int id) async {
try {
final res = await _crud.post(
link: AppLink.scheduledCancel,
payload: {'id': id.toString()},
);
if (_extract(res) == null) return;
mySnackbarSuccess('أُلغي الحجز');
await fetch();
} catch (e) {
mySnackbarError('تعذّر إلغاء الحجز');
}
}
String _fmt(DateTime d) =>
'${d.year}-${_2(d.month)}-${_2(d.day)} ${_2(d.hour)}:${_2(d.minute)}:00';
String _2(int n) => n.toString().padLeft(2, '0');
Map<String, dynamic>? _extract(dynamic res) {
final decoded = res is String ? jsonDecode(res) : res;
if (decoded is! Map) {
mySnackbarError('رد غير مفهوم من الخادم');
return null;
}
if (decoded['status'] != 'success') {
// ‏رسالة الخادم أدقّ من أي نص عام: تفرّق بين "لديك حجز قريب" و
// ‏"الموعد خارج المدى المسموح".
mySnackbarError(decoded['message']?.toString() ?? 'فشلت العملية');
return null;
}
final data = decoded['message'] ?? decoded['data'];
return data is Map ? Map<String, dynamic>.from(data) : <String, dynamic>{};
}
}
@@ -0,0 +1,206 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../controller/scheduled/scheduled_rides_controller.dart';
/// شاشة حجوزات الراكب المسبقة.
///
/// تعرض القادم فقط: الحجز الفائت لا يفيد، والراكب يفتح الشاشة ليرى ما
/// ينتظره لا ليتصفّح أرشيفه.
class ScheduledRidesPage extends StatelessWidget {
const ScheduledRidesPage({super.key});
@override
Widget build(BuildContext context) {
final c = Get.put(ScheduledRidesController());
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(title: Text('رحلاتي المحجوزة'.tr)),
body: Obx(() {
if (c.isLoading.value && c.bookings.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (c.bookings.isEmpty) {
return _Empty(maxDays: ScheduledRidesController.maxDaysAhead);
}
return RefreshIndicator(
onRefresh: c.fetch,
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: c.bookings.length,
itemBuilder: (_, i) => _BookingCard(b: c.bookings[i], c: c),
),
);
}),
),
);
}
}
class _Empty extends StatelessWidget {
const _Empty({required this.maxDays});
final int maxDays;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return ListView(
padding: const EdgeInsets.all(32),
children: [
const SizedBox(height: 80),
Icon(Icons.event_available_outlined,
size: 64, color: cs.primary.withOpacity(0.4)),
const SizedBox(height: 20),
Text('لا حجوزات قادمة'.tr,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Text(
'احجز رحلتك للمطار أو موعدك الطبي أو دوامك الصباحي'
' حتى $maxDays يومين مقدماً، ونرسل لك سائقاً في وقته.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, height: 1.6, color: cs.onSurfaceVariant),
),
],
);
}
}
class _BookingCard extends StatelessWidget {
const _BookingCard({required this.b, required this.c});
final Map<String, dynamic> b;
final ScheduledRidesController c;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final when = DateTime.tryParse('${b['scheduled_at']}');
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.schedule, size: 18, color: Color(0xFF6A5ACD)),
const SizedBox(width: 8),
Text(
when == null ? '${b['scheduled_at']}' : _when(when),
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 15),
),
const Spacer(),
if (when != null) _Countdown(when: when),
],
),
const Divider(height: 20),
_row(context, '🟢', b['start_name'] ?? 'موقع الانطلاق'),
const SizedBox(height: 6),
_row(context, '🔴', b['end_name'] ?? 'الوجهة'),
const SizedBox(height: 12),
Row(
children: [
Text('${b['car_type'] ?? ''}',
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant)),
const Spacer(),
// ‏نصرّح بأن السعر تقديري: التسعير النهائي لحظة إنشاء
// ‏الرحلة، فرقم اليوم قد لا يطابق رقم الغد.
Text(
'تقديري: ${b['estimated_price'] ?? '—'}',
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant),
),
],
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerLeft,
child: TextButton.icon(
onPressed: () => _confirmCancel(context),
icon: Icon(Icons.close, size: 16, color: cs.error),
label: Text('إلغاء الحجز'.tr,
style: TextStyle(color: cs.error, fontSize: 13)),
),
),
],
),
),
);
}
void _confirmCancel(BuildContext context) {
Get.defaultDialog(
title: 'إلغاء الحجز'.tr,
// ‏بلا رسم: لا سائق قُبِل ولا أحد تحرّك. نقولها صراحةً ليطمئن الراكب.
middleText: 'سيُلغى الحجز بلا أي رسوم — لم يُسند سائق بعد.'.tr,
textConfirm: 'تأكيد'.tr,
textCancel: 'تراجع'.tr,
onConfirm: () {
Get.back();
final id = int.tryParse('${b['id']}');
if (id != null) c.cancel(id);
},
);
}
Widget _row(BuildContext context, String dot, dynamic text) => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(dot, style: const TextStyle(fontSize: 12)),
const SizedBox(width: 8),
Expanded(
child: Text('$text',
style: const TextStyle(fontSize: 13, height: 1.4)),
),
],
);
String _when(DateTime d) {
final now = DateTime.now();
final isToday =
d.year == now.year && d.month == now.month && d.day == now.day;
final tomorrow = now.add(const Duration(days: 1));
final isTomorrow = d.year == tomorrow.year &&
d.month == tomorrow.month &&
d.day == tomorrow.day;
final hh = d.hour.toString().padLeft(2, '0');
final mm = d.minute.toString().padLeft(2, '0');
if (isToday) return 'اليوم $hh:$mm';
if (isTomorrow) return 'غداً $hh:$mm';
return '${d.day}/${d.month} — $hh:$mm';
}
}
/// كم تبقّى للموعد. يجعل الحجز محسوساً بدل تاريخ جامد.
class _Countdown extends StatelessWidget {
const _Countdown({required this.when});
final DateTime when;
@override
Widget build(BuildContext context) {
final diff = when.difference(DateTime.now());
if (diff.isNegative) return const SizedBox.shrink();
final label = diff.inHours >= 1
? 'بعد ${diff.inHours} ساعة'
: 'بعد ${diff.inMinutes} دقيقة';
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF6A5ACD).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(label,
style: const TextStyle(fontSize: 11, color: Color(0xFF6A5ACD))),
);
}
}