diff --git a/siro_driver/lib/controller/home/captin/order_request_controller.dart b/siro_driver/lib/controller/home/captin/order_request_controller.dart index 1ca23b15..4758e97b 100755 --- a/siro_driver/lib/controller/home/captin/order_request_controller.dart +++ b/siro_driver/lib/controller/home/captin/order_request_controller.dart @@ -65,7 +65,7 @@ class OrderRequestController extends GetxController }; } - /// معلومات الحجز المسبق إن كانت الرحلة مؤجلة. + /// معلومات الحجز المسبق إن كانت الرحلة مجدولة. /// /// يصل الحقلان من buildMarketPayload عبر السوكِت (خريطة)، أو من الفهرسين /// 39/40 عبر FCM (قائمة). _getValueAt يوحّد الشكلين. diff --git a/siro_driver/lib/models/model/order_data.dart b/siro_driver/lib/models/model/order_data.dart index 5530fe1a..a0836852 100755 --- a/siro_driver/lib/models/model/order_data.dart +++ b/siro_driver/lib/models/model/order_data.dart @@ -35,9 +35,17 @@ class OrderData { /// ثواني السبق الحصري كما حددها الخادم (DISPATCH_HEAD_START_SECONDS). final int offerExpiresIn; + /// رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري. + final bool isScheduled; + + /// موعد الانطلاق كما حجزه الراكب. فارغ في الرحلات العادية. + final String scheduledAt; + OrderData({ this.offerType, this.offerExpiresIn = 0, + this.isScheduled = false, + this.scheduledAt = '', required this.customerName, required this.customerToken, required this.tripDistanceKm, @@ -151,6 +159,11 @@ class OrderData { : null, offerExpiresIn: 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, offerType: str('offer_type').isNotEmpty ? str('offer_type') : null, offerExpiresIn: int.tryParse(str('offer_expires_in', '0')) ?? 0, + isScheduled: str('is_scheduled') == '1' || str('is_scheduled') == 'true', + scheduledAt: str('scheduled_at'), ); } diff --git a/siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart b/siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart index c6f9f027..3766a1f3 100755 --- a/siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart +++ b/siro_driver/lib/views/home/Captin/orderCaptin/order_over_lay.dart @@ -475,21 +475,36 @@ class _OrderOverlayState extends State Row( children: [ Icon( - isExclusive ? Icons.star_rounded : Icons.drive_eta_rounded, - color: isExclusive ? const Color(0xFFFFD54F) : AppColors.white, + orderData?.isScheduled == true + ? 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, ), const SizedBox(width: 8), // ‏العنوان يحمل حالة العرض: خاص خلال السبق، عام بعده، و"طلب // ‏جديد" في البثّ الحر حيث لا معنى للتمييز. Text( - orderData?.offerType == null - ? "طلب جديد".tr - : (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr), + orderData?.isScheduled == true + // ‏الأولوية للمجدولة: معلومة دائمة عن الرحلة تسبق حالة + // ‏العرض المؤقتة، والسائق يحتاجها ليقرر إن كان يستطيع + // ‏الالتزام بالموعد. + ? "🗓️ رحلة مجدولة".tr + : orderData?.offerType == null + ? "طلب جديد".tr + : (isExclusive ? "⭐ طلب خاص لك".tr : "🌐 طلب عام".tr), style: TextStyle( - color: isExclusive - ? const Color(0xFFFFD54F) - : AppColors.white, + color: orderData?.isScheduled == true + ? const Color(0xFFB39DDB) + : isExclusive + ? const Color(0xFFFFD54F) + : AppColors.white, fontSize: 18, fontWeight: FontWeight.w600), ), diff --git a/siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart b/siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart index 929bd8ab..ba24d980 100755 --- a/siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart +++ b/siro_driver/lib/views/home/Captin/orderCaptin/order_request_page.dart @@ -116,7 +116,7 @@ class OrderRequestPage extends StatelessWidget { OfferTypeBadge(rideInfo: controller.offerInfo!), const SizedBox(width: 10), ], - // شارة الرحلة المؤجلة — موعد مضبوط لا طلب فوري + // شارة الرحلة المجدولة — موعد مضبوط لا طلب فوري if (ScheduledRideBadge.isScheduled( controller.scheduleInfo)) ...[ ScheduledRideBadge( diff --git a/siro_driver/lib/views/notification/available_rides_page.dart b/siro_driver/lib/views/notification/available_rides_page.dart index 0199b6f3..34412440 100755 --- a/siro_driver/lib/views/notification/available_rides_page.dart +++ b/siro_driver/lib/views/notification/available_rides_page.dart @@ -189,7 +189,7 @@ class RideAvailableCard extends StatelessWidget { .copyWith(color: AppColor.greenColor, fontSize: 13), ), ), - // شارة الرحلة المؤجلة — معلومة دائمة عن الرحلة، تظهر هنا + // شارة الرحلة المجدولة — معلومة دائمة عن الرحلة، تظهر هنا // وفي نافذة الطلب والأوفرلي معاً. if (ScheduledRideBadge.isScheduled(rideInfo)) ...[ const SizedBox(height: 8), diff --git a/siro_driver/lib/views/widgets/scheduled_ride_badge.dart b/siro_driver/lib/views/widgets/scheduled_ride_badge.dart index 29b2beb4..be780efa 100644 --- a/siro_driver/lib/views/widgets/scheduled_ride_badge.dart +++ b/siro_driver/lib/views/widgets/scheduled_ride_badge.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -/// شارة «رحلة مؤجلة». +/// شارة «رحلة مجدولة». /// /// السائق يجب أن يعرف أن هذه ليست رحلة لحظية بل موعد مضبوط: الراكب /// ينتظره في وقت محدد، والتأخر عليه يختلف عن التأخر على طلب فوري. @@ -73,7 +73,7 @@ class ScheduledRideBadge extends StatelessWidget { Icon(Icons.schedule, color: color, size: compact ? 14 : 16), const SizedBox(width: 4), Text( - t.isEmpty ? 'رحلة مؤجلة'.tr : '${'رحلة مؤجلة'.tr} · $t', + t.isEmpty ? 'رحلة مجدولة'.tr : '${'رحلة مجدولة'.tr} · $t', style: TextStyle( color: color, fontSize: compact ? 11 : 13, diff --git a/siro_driver/trip_overlay_plugin/android/src/main/kotlin/com/siro_driver/trip_overlay_plugin/TripzOverlayService.kt b/siro_driver/trip_overlay_plugin/android/src/main/kotlin/com/siro_driver/trip_overlay_plugin/TripzOverlayService.kt index f5c589b3..430163c3 100644 --- a/siro_driver/trip_overlay_plugin/android/src/main/kotlin/com/siro_driver/trip_overlay_plugin/TripzOverlayService.kt +++ b/siro_driver/trip_overlay_plugin/android/src/main/kotlin/com/siro_driver/trip_overlay_plugin/TripzOverlayService.kt @@ -581,6 +581,35 @@ class TripOverlayService : Service() { } 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. شريط الوقت ── val countdownLabel = TextView(ctx).apply { @@ -925,7 +954,9 @@ class TripOverlayService : Service() { pickupLat = obj.getDouble("pickupLat"), pickupLng = obj.getDouble("pickupLng"), 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) { null @@ -944,6 +975,9 @@ class TripOverlayService : Service() { val pickupLng: Double, // ‏"exclusive" خلال السبق الحصري، "public" بعده، وفارغ في البثّ الحر val offerType: String = "", - val offerExpiresIn: Int = 0 + val offerExpiresIn: Int = 0, + // ‏رحلة محجوزة مسبقاً — موعد مضبوط لا طلب فوري + val isScheduled: Boolean = false, + val scheduledAt: String = "" ) } diff --git a/siro_driver/trip_overlay_plugin/lib/trip_overlay_plugin.dart b/siro_driver/trip_overlay_plugin/lib/trip_overlay_plugin.dart index f76aa658..771cdcc3 100644 --- a/siro_driver/trip_overlay_plugin/lib/trip_overlay_plugin.dart +++ b/siro_driver/trip_overlay_plugin/lib/trip_overlay_plugin.dart @@ -25,6 +25,10 @@ class TripData { /// من نفسها — النافذة لا تُغلق، فالسائق يظل قادراً على القبول. final int offerExpiresIn; + /// رحلة محجوزة مسبقاً وموعدها. السائق يجب أن يعرف أنها ليست طلباً فورياً. + final bool isScheduled; + final String scheduledAt; + TripData({ required this.tripId, required this.passengerName, @@ -38,6 +42,8 @@ class TripData { this.passengerAvatarUrl, this.offerType, this.offerExpiresIn = 0, + this.isScheduled = false, + this.scheduledAt = '', }); Map toMap() => { @@ -53,6 +59,8 @@ class TripData { 'passengerAvatarUrl': passengerAvatarUrl ?? '', 'offerType': offerType ?? '', 'offerExpiresIn': offerExpiresIn, + 'isScheduled': isScheduled, + 'scheduledAt': scheduledAt, }; factory TripData.fromMap(Map map) => TripData( @@ -67,6 +75,8 @@ class TripData { ? null : map['offerType'].toString(), offerExpiresIn: int.tryParse('${map['offerExpiresIn'] ?? 0}') ?? 0, + isScheduled: map['isScheduled'] == true || '${map['isScheduled']}' == '1', + scheduledAt: (map['scheduledAt'] ?? '').toString(), pickupLat: (map['pickupLat'] ?? 0.0).toDouble(), pickupLng: (map['pickupLng'] ?? 0.0).toDouble(), passengerAvatarUrl: map['passengerAvatarUrl'], diff --git a/siro_rider/lib/constant/links.dart b/siro_rider/lib/constant/links.dart index 060b1eef..ca93c1ad 100644 --- a/siro_rider/lib/constant/links.dart +++ b/siro_rider/lib/constant/links.dart @@ -416,6 +416,11 @@ class AppLink { /// needsReview في getRideStatusFromStartApp محصور بنافذة ساعة مقصودة /// لاستئناف رحلة قائمة، فمن يغلق التطبيق بعد نزوله لا يُطلب منه تقييم. 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 getPassengerRate => "$server/ride/rate/getPassengerRate.php"; diff --git a/siro_rider/lib/controller/scheduled/scheduled_rides_controller.dart b/siro_rider/lib/controller/scheduled/scheduled_rides_controller.dart new file mode 100644 index 00000000..e04f30d0 --- /dev/null +++ b/siro_rider/lib/controller/scheduled/scheduled_rides_controller.dart @@ -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 = >[].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 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.from(e as Map)).toList()); + } catch (e) { + mySnackbarError('تعذّر جلب الحجوزات'); + } finally { + isLoading.value = false; + } + } + + /// ينشئ حجزاً. يرجع true عند النجاح. + /// + /// [when] موعد الانطلاق كما اختاره الراكب. + Future 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 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? _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.from(data) : {}; + } +} diff --git a/siro_rider/lib/views/scheduled/scheduled_rides_page.dart b/siro_rider/lib/views/scheduled/scheduled_rides_page.dart new file mode 100644 index 00000000..82bbf148 --- /dev/null +++ b/siro_rider/lib/views/scheduled/scheduled_rides_page.dart @@ -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 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))), + ); + } +}