Update: 2026-08-08 16:55:39

This commit is contained in:
Hamza-Ayed
2026-08-08 16:55:39 +03:00
parent dd44b84e81
commit 8c9f729ecd
27 changed files with 2030 additions and 1798 deletions
@@ -1,44 +1,59 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'colors.dart';
/// كان هذا نظام ألوان **ثانياً منافساً** لـ [AppColor]: نيفي `#0A0E21` وأزرق
/// `#3D5AFE` بدل نيفي العلامة `#1A2340` والـ Periwinkle `#8C9CF8`. النتيجة أن
/// صفحات المحفظة والأرباح والتحديات كانت تبدو من تطبيق آخر.
///
/// أُبقي الصنف باسمه حتى لا تتكسّر عشرات مواضع الاستدعاء، لكنه صار **واجهة
/// رقيقة فوق ألوان العلامة**. الجديد يُكتب بـ `SiroUi` / `Theme.of(context)`.
class FinanceDesignSystem {
// --- Colors ---
static Color get primaryDark => Get.isDarkMode ? const Color(0xFFE0E0E0) : const Color(0xFF0A0E21);
static Color get accentBlue => const Color(0xFF3D5AFE);
static Color get successGreen => const Color(0xFF00C853);
static Color get dangerRed => const Color(0xFFD50000);
static Color get backgroundColor => Get.isDarkMode ? const Color(0xFF0A0E21) : const Color(0xFFF6F8FA);
static Color get cardColor => Get.isDarkMode ? const Color(0xFF1E1E2E) : const Color(0xFFFFFFFF);
static Color get textSecondary => Get.isDarkMode ? Colors.white70 : const Color(0xFF757575);
static Color get textMuted => Get.isDarkMode ? Colors.white38 : const Color(0xFFBDBDBD);
static Color get borderColor => Get.isDarkMode ? Colors.white10 : Colors.grey.withOpacity(0.1);
static Color get primaryDark =>
Get.isDarkMode ? const Color(0xFFF1F5F9) : AppColor.primaryColor;
static Color get accentBlue =>
Get.isDarkMode ? AppColor.secondaryColorStatic : AppColor.primaryColor;
static Color get successGreen => AppColor.greenColor;
static Color get dangerRed => AppColor.redColor;
static Color get backgroundColor =>
Get.isDarkMode ? const Color(0xFF0F1424) : const Color(0xFFF6F8FA);
static Color get cardColor =>
Get.isDarkMode ? const Color(0xFF171D33) : const Color(0xFFFFFFFF);
static Color get textSecondary =>
Get.isDarkMode ? const Color(0xFF94A3B8) : const Color(0xFF64748B);
static Color get textMuted =>
Get.isDarkMode ? const Color(0xFF64748B) : const Color(0xFF94A3B8);
static Color get borderColor =>
Get.isDarkMode ? const Color(0xFF262E4A) : const Color(0xFFE6EAF0);
// --- Shadows ---
static BoxShadow get softShadow => BoxShadow(
color: Colors.black.withOpacity(Get.isDarkMode ? 0.3 : 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
);
color: Colors.black.withValues(alpha: Get.isDarkMode ? 0.35 : 0.05),
blurRadius: 18,
offset: const Offset(0, 6),
);
// --- Gradients ---
/// تدرّج العلامة: نيفي ← Periwinkle. يُستعمل في بطاقات الرصيد والرؤوس.
static LinearGradient get balanceGradient => LinearGradient(
colors: Get.isDarkMode
? [const Color(0xFF1E1E2E), const Color(0xFF2A2A3E)]
: [const Color(0xFF0A0E21), const Color(0xFF1A237E)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
colors: Get.isDarkMode
? [const Color(0xFF1E2745), const Color(0xFF2E3A63)]
: [AppColor.primaryColor, const Color(0xFF3A4674)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const LinearGradient dangerGradient = LinearGradient(
colors: [Color(0xFFD50000), Color(0xFFFF5252)],
colors: [AppColor.redColor, Color(0xFFF87171)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
// --- Radius ---
static const double mainRadius = 16.0;
static const double cardRadius = 20.0;
static const double buttonRadius = 12.0;
static const double cardRadius = 18.0;
static const double buttonRadius = 14.0;
// --- Spacing ---
static const double horizontalPadding = 16.0;
@@ -46,19 +61,19 @@ class FinanceDesignSystem {
// --- Text Styles ---
static TextStyle get balanceStyle => const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
);
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static TextStyle get headingStyle => TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryDark,
);
fontSize: 17,
fontWeight: FontWeight.bold,
color: primaryDark,
);
static TextStyle get subHeadingStyle => TextStyle(
fontSize: 14,
color: textSecondary,
);
fontSize: 13,
color: textSecondary,
);
}
@@ -2693,4 +2693,13 @@ final Map<String, String> ar_eg = {
"Destination cleared!": "تم مسح الوجهة الشخصية!",
"Move map to select destination": "حرك الخريطة لتحديد الوجهة الشخصية",
"service_unavailable_area": "هذا التطبيق لا يعمل في منطقتك حالياً",
// ── أقسام القائمة الجانبية (UI) ──
"Services": "الخدمات",
"Wallet & Earnings": "المحفظة والأرباح",
"My Trips": "رحلاتي",
"Rewards": "المكافآت",
"My Account": "حسابي",
"My Earnings": "أرباحي",
"Upcoming Scheduled Rides": "الرحلات المجدولة القادمة",
"Retry": "إعادة المحاولة",
};
@@ -2708,4 +2708,13 @@ final Map<String, String> ar_jo = {
"فشل تسجيل الحضور": "فشل تسجيل الحضور",
"لا يوجد ركاب مسجلين في هذه المحطة": "لا يوجد ركاب مسجلين في هذه المحطة",
"حاضر": "حاضر",
// ── أقسام القائمة الجانبية (UI) ──
"Services": "الخدمات",
"Wallet & Earnings": "المحفظة والأرباح",
"My Trips": "رحلاتي",
"Rewards": "المكافآت",
"My Account": "حسابي",
"My Earnings": "أرباحي",
"Upcoming Scheduled Rides": "الرحلات المجدولة القادمة",
"Retry": "إعادة المحاولة",
};
@@ -2695,4 +2695,13 @@ final Map<String, String> ar_sy = {
"💰 Pay with Wallet": "💰 ادفع من المحفظة",
"💳 Pay with Credit Card": "💳 ادفع ببطاقة ائتمان",
"service_unavailable_area": "هذا التطبيق لا يعمل في منطقتك حالياً",
// ── أقسام القائمة الجانبية (UI) ──
"Services": "الخدمات",
"Wallet & Earnings": "المحفظة والأرباح",
"My Trips": "رحلاتي",
"Rewards": "المكافآت",
"My Account": "حسابي",
"My Earnings": "أرباحي",
"Upcoming Scheduled Rides": "الرحلات المجدولة القادمة",
"Retry": "إعادة المحاولة",
};
+9
View File
@@ -2693,4 +2693,13 @@ final Map<String, String> en = {
"Destination cleared!": "Destination cleared!",
"Move map to select destination": "Move map to select destination",
"service_unavailable_area": "This service is not currently available in your area",
// ── أقسام القائمة الجانبية (UI) ──
"Services": "Services",
"Wallet & Earnings": "Wallet & Earnings",
"My Trips": "My Trips",
"Rewards": "Rewards",
"My Account": "My Account",
"My Earnings": "My Earnings",
"Upcoming Scheduled Rides": "Upcoming Scheduled Rides",
"Retry": "Retry",
};
+153 -27
View File
@@ -2,13 +2,25 @@ import 'package:flutter/material.dart';
import 'package:siro_driver/constant/style.dart';
import '../../constant/colors.dart';
/// ملاحظة مهمة: لا تستعمل هنا getters من `AppColor` التي تقرأ `Get.isDarkMode`
/// (مثل `writeColor` و`cardColor`). هذا الملف يُنفَّذ **مرة واحدة عند التحميل**،
/// فتُخبَّأ قيمة الوضع الحالي داخل الثيمين معاً ⇒ الثيم الفاتح يرث ألوان الداكن
/// (أو العكس) حسب ما كان الوضع وقت الإقلاع. الألوان هنا تُمرَّر صراحةً لكل ثيم.
ThemeData _createTheme({
required Brightness brightness,
required String fontFamily,
required Color scaffoldBackgroundColor,
required Color cardColor,
required Color dividerColor,
required Color textColor,
required Color secondaryTextColor,
}) {
final isDark = brightness == Brightness.dark;
// في الوضع الداكن يصبح النيفي الداكن غير مقروء على خلفية داكنة،
// فنستعمل لون العلامة الفاتح (Periwinkle) للعناصر التفاعلية.
final interactive =
isDark ? AppColor.secondaryColorStatic : AppColor.primaryColor;
return ThemeData(
brightness: brightness,
fontFamily: fontFamily,
@@ -16,75 +28,189 @@ ThemeData _createTheme({
cardColor: cardColor,
dividerColor: dividerColor,
primaryColor: AppColor.primaryColor,
splashFactory: InkSparkle.splashFactory,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColor.primaryColor,
brightness: brightness,
primary: AppColor.primaryColor,
primary: interactive,
onPrimary: isDark ? AppColor.primaryColor : Colors.white,
secondary: AppColor.secondaryColorStatic,
surface: cardColor,
background: scaffoldBackgroundColor,
onSurface: textColor,
error: AppColor.redColor,
),
textTheme: TextTheme(
displaySmall: AppStyle.title.copyWith(color: AppColor.writeColor),
displayLarge: AppStyle.headTitle.copyWith(color: AppColor.writeColor),
displayMedium: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
bodyLarge: AppStyle.title.copyWith(color: AppColor.writeColor),
bodyMedium: AppStyle.subtitle.copyWith(color: AppColor.writeColor),
titleLarge: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
displayLarge: AppStyle.headTitle.copyWith(color: textColor),
displayMedium: AppStyle.headTitle2.copyWith(color: textColor),
displaySmall: AppStyle.title.copyWith(color: textColor),
titleLarge: AppStyle.headTitle2.copyWith(color: textColor, fontSize: 20),
titleMedium: AppStyle.title
.copyWith(color: textColor, fontWeight: FontWeight.w600),
bodyLarge: AppStyle.title.copyWith(color: textColor),
bodyMedium: AppStyle.title.copyWith(color: textColor, fontSize: 14),
bodySmall: AppStyle.title.copyWith(color: secondaryTextColor, fontSize: 13),
labelLarge: AppStyle.title
.copyWith(color: textColor, fontWeight: FontWeight.w600, fontSize: 15),
labelMedium: AppStyle.subtitle.copyWith(color: secondaryTextColor),
),
appBarTheme: AppBarTheme(
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: scaffoldBackgroundColor,
foregroundColor: textColor,
centerTitle: true,
iconTheme: const IconThemeData(color: AppColor.primaryColor),
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
iconTheme: IconThemeData(color: textColor),
actionsIconTheme: IconThemeData(color: textColor),
titleTextStyle: AppStyle.headTitle2.copyWith(
color: textColor,
fontSize: 19,
),
),
dialogTheme: DialogThemeData(
backgroundColor: cardColor,
contentTextStyle: AppStyle.title.copyWith(color: AppColor.writeColor),
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
contentTextStyle: AppStyle.title.copyWith(color: textColor),
titleTextStyle: AppStyle.headTitle2.copyWith(color: textColor, fontSize: 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
cardTheme: CardThemeData(
color: cardColor,
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: BorderSide(color: dividerColor),
),
),
listTileTheme: ListTileThemeData(
iconColor: secondaryTextColor,
textColor: textColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
dividerTheme: DividerThemeData(
color: dividerColor,
thickness: 1,
space: 1,
),
chipTheme: ChipThemeData(
backgroundColor: interactive.withValues(alpha: 0.10),
side: BorderSide.none,
labelStyle: AppStyle.subtitle.copyWith(color: textColor),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: interactive,
foregroundColor: isDark ? AppColor.primaryColor : Colors.white,
elevation: 0,
minimumSize: const Size(0, 50),
padding: const EdgeInsets.symmetric(horizontal: 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
textStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: interactive,
minimumSize: const Size(0, 50),
side: BorderSide(color: interactive.withValues(alpha: 0.5)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
textStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 15),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: interactive,
textStyle: const TextStyle(fontWeight: FontWeight.w600),
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
progressIndicatorTheme: ProgressIndicatorThemeData(color: interactive),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: isDark ? Colors.white.withValues(alpha: 0.04) : cardColor,
hintStyle: AppStyle.title.copyWith(color: secondaryTextColor, fontSize: 14),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: dividerColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: dividerColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: interactive, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: AppColor.redColor),
),
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: cardColor,
surfaceTintColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
),
);
}
// --- ألوان الأسطح: فاتح ---
const Color _lightBg = Color(0xFFF6F8FA);
const Color _lightCard = Color(0xFFFFFFFF);
const Color _lightDivider = Color(0xFFE6EAF0);
const Color _lightText = Color(0xFF0F172A);
const Color _lightTextSecondary = Color(0xFF64748B);
// --- ألوان الأسطح: داكن ---
const Color _darkBg = Color(0xFF0F1424);
const Color _darkCard = Color(0xFF171D33);
const Color _darkDivider = Color(0xFF262E4A);
const Color _darkText = Color(0xFFF1F5F9);
const Color _darkTextSecondary = Color(0xFF94A3B8);
ThemeData lightThemeEnglish = _createTheme(
brightness: Brightness.light,
fontFamily: 'CupertinoSystemText', // Native fallback
scaffoldBackgroundColor: Colors.white,
cardColor: Colors.white,
dividerColor: Colors.black12,
scaffoldBackgroundColor: _lightBg,
cardColor: _lightCard,
dividerColor: _lightDivider,
textColor: _lightText,
secondaryTextColor: _lightTextSecondary,
);
ThemeData darkThemeEnglish = _createTheme(
brightness: Brightness.dark,
fontFamily: 'CupertinoSystemText',
scaffoldBackgroundColor: const Color(0xFF121212),
cardColor: const Color(0xFF1E1E1E),
dividerColor: Colors.white10,
scaffoldBackgroundColor: _darkBg,
cardColor: _darkCard,
dividerColor: _darkDivider,
textColor: _darkText,
secondaryTextColor: _darkTextSecondary,
);
ThemeData lightThemeArabic = _createTheme(
brightness: Brightness.light,
fontFamily: 'CupertinoSystemText',
scaffoldBackgroundColor: Colors.white,
cardColor: Colors.white,
dividerColor: Colors.black12,
scaffoldBackgroundColor: _lightBg,
cardColor: _lightCard,
dividerColor: _lightDivider,
textColor: _lightText,
secondaryTextColor: _lightTextSecondary,
);
ThemeData darkThemeArabic = _createTheme(
brightness: Brightness.dark,
fontFamily: 'CupertinoSystemText',
scaffoldBackgroundColor: const Color(0xFF121212),
cardColor: const Color(0xFF1E1E1E),
dividerColor: Colors.white10,
scaffoldBackgroundColor: _darkBg,
cardColor: _darkCard,
dividerColor: _darkDivider,
textColor: _darkText,
secondaryTextColor: _darkTextSecondary,
);
+89 -118
View File
@@ -1,119 +1,90 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../controller/rate/rate_app_controller.dart';
import '../widgets/ui/siro_ui.dart';
class RatingScreen extends StatelessWidget {
RatingScreen({super.key});
final RatingController ratingController = Get.put(RatingController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(
"Rate Our App".tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
child: SafeArea(
child: SingleChildScrollView(
child: Center(
child: Obx(() {
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// App logo or visual
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset(
'assets/images/logo.gif',
height: 120,
),
),
const SizedBox(height: 20),
// Rating Prompt
Text(
"How would you rate our app?".tr,
textAlign: TextAlign.center,
style: CupertinoTheme.of(context)
.textTheme
.navTitleTextStyle
.copyWith(fontSize: 18, fontWeight: FontWeight.w600),
),
const SizedBox(height: 20),
// Comment Box
CupertinoTextField(
controller: ratingController.comment,
placeholder: 'Write your comment here'.tr,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: CupertinoColors.systemGrey4),
borderRadius: BorderRadius.circular(12),
color: CupertinoColors.white,
),
prefix: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Icon(CupertinoIcons.pencil_ellipsis_rectangle,
color: CupertinoColors.systemGrey),
),
),
const SizedBox(height: 20),
// Star Rating Section
_buildRatingStars(),
const SizedBox(height: 30),
// Submit Button
CupertinoButton(
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 14),
borderRadius: BorderRadius.circular(12),
color: CupertinoColors.activeGreen,
onPressed: () {
if (ratingController.userRating.value > 0) {
ratingController
.submitRating(ratingController.userRating.value);
Get.snackbar(
"Thank You!".tr,
"Your rating has been submitted.".tr,
backgroundColor: CupertinoColors.systemGrey6,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 12,
);
} else {
Get.snackbar(
"Error".tr,
"Please select a rating before submitting.".tr,
backgroundColor: CupertinoColors.systemRed,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 12,
);
}
},
child: Text(
"Submit Rating".tr,
style: const TextStyle(
color: CupertinoColors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
],
return SiroPage(
title: 'Rate Our App'.tr,
padding: SiroUi.pageInsets,
body: Obx(() {
return ListView(
children: [
const SizedBox(height: 8),
Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset('assets/images/logo.gif', height: 120),
),
),
const SizedBox(height: 20),
Text(
'How would you rate our app?'.tr,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 18),
_buildRatingStars(),
const SizedBox(height: 22),
SiroCard(
padding: const EdgeInsets.all(6),
child: TextField(
controller: ratingController.comment,
maxLines: 4,
minLines: 3,
decoration: InputDecoration(
hintText: 'Write your comment here'.tr,
filled: false,
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
);
}),
),
),
),
),
),
const SizedBox(height: 22),
ElevatedButton.icon(
onPressed: () {
if (ratingController.userRating.value > 0) {
ratingController
.submitRating(ratingController.userRating.value);
Get.snackbar(
'Thank You!'.tr,
'Your rating has been submitted.'.tr,
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 14,
);
} else {
Get.snackbar(
'Error'.tr,
'Please select a rating before submitting.'.tr,
backgroundColor: AppColor.redColor,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 14,
);
}
},
icon: const Icon(Icons.send_rounded, size: 18),
label: Text('Submit Rating'.tr),
),
const SizedBox(height: 20),
],
);
}),
);
}
@@ -122,19 +93,19 @@ class RatingScreen extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (index) {
final selected = index < ratingController.userRating.value;
return GestureDetector(
onTap: () {
ratingController.userRating.value = index + 1;
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
margin: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(
CupertinoIcons.star_fill,
size: 40,
color: index < ratingController.userRating.value
? CupertinoColors.systemYellow
: CupertinoColors.systemGrey3,
onTap: () => ratingController.userRating.value = index + 1,
child: AnimatedScale(
scale: selected ? 1.0 : 0.88,
duration: const Duration(milliseconds: 200),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Icon(
selected ? Icons.star_rounded : Icons.star_outline_rounded,
size: 42,
color: selected ? AppColor.gold : Colors.grey,
),
),
),
);
+12 -23
View File
@@ -10,6 +10,7 @@ import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../constant/style.dart';
import '../../controller/rate/rate_conroller.dart';
import '../widgets/ui/siro_ui.dart';
class RatePassenger extends StatelessWidget {
final RateController controller = Get.put(RateController());
@@ -21,19 +22,14 @@ class RatePassenger extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Trip Completed'.tr),
centerTitle: true,
automaticallyImplyLeading: false,
elevation: 0,
),
return SiroPage(
title: 'Trip Completed'.tr,
showBack: false,
body: GetBuilder<RateController>(
builder: (controller) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(20.0),
padding: SiroUi.pageInsets,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -159,20 +155,13 @@ class RatePassenger extends StatelessWidget {
}
Widget _buildWalletSection(BuildContext context, RateController controller) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).dividerColor),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: controller.ispassengerWantWalletFromDriver
? _buildAmountInput(context, controller)
: _buildWalletQuery(context, controller),
),
return SiroCard(
padding: const EdgeInsets.all(20.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: controller.ispassengerWantWalletFromDriver
? _buildAmountInput(context, controller)
: _buildWalletQuery(context, controller),
),
);
}
@@ -1,100 +1,92 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/tts.dart';
import '../../../controller/home/captin/contact_us_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/ui/siro_ui.dart';
class ContactUsPage extends StatelessWidget {
ContactUsPage({super.key});
const ContactUsPage({super.key});
static const String _aboutText =
'Siro is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.';
@override
Widget build(BuildContext context) {
Get.put(ContactUsController());
return GetBuilder<ContactUsController>(builder: (controller) {
return MyScafolld(
title: "Contact Us".tr,
body: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
return SiroPage(
title: 'Contact Us'.tr,
padding: SiroUi.pageInsets,
body: ListView(
children: [
SiroCard(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Column(
children: [
Container(
decoration: AppStyle.boxDecoration1,
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.asset('assets/images/logo.gif')),
IconButton(
onPressed: () async {
Get.put(TextToSpeechController()).speakText(
'Siro is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.'
.tr);
},
icon: const Icon(Icons.headphones),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Siro is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.'
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
],
ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.asset(
'assets/images/logo.gif',
height: 150,
fit: BoxFit.contain,
),
),
const SizedBox(
height: 30,
const SizedBox(height: 12),
Text(
_aboutText.tr,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
Container(
decoration: AppStyle.boxDecoration1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"You can contact us during working hours from 10:00 - 17:00."
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
const SizedBox(height: 4),
// زرّ القراءة الصوتية كان أيقونة عارية بلا تسمية،
// فلا يُفهم غرضه قبل الضغط.
TextButton.icon(
onPressed: () => Get.put(TextToSpeechController())
.speakText(_aboutText.tr),
icon: const Icon(Icons.headphones_rounded, size: 18),
label: Text('Listen'.tr),
),
InkWell(
onTap: () => controller.showContactDialog(context),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(
Icons.phone,
color: AppColor.blueColor,
),
Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
Icon(
Icons.email,
color: AppColor.redColor,
),
],
),
),
const SizedBox(
height: 30,
)
],
),
)
),
SiroCard(
child: Row(
children: [
SiroIconBadge(Icons.schedule_rounded,
color: SiroUi.interactive(context)),
const SizedBox(width: 12),
Expanded(
child: Text(
'You can contact us during working hours from 10:00 - 17:00.'
.tr,
style: Theme.of(context).textTheme.bodyMedium,
),
),
],
),
),
SiroSectionTitle('Contact Us'.tr),
SiroCard(
padding: const EdgeInsets.symmetric(vertical: 18),
onTap: () => controller.showContactDialog(context),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SiroIconBadge(Icons.phone_rounded,
color: AppColor.primaryColor, size: 24),
SiroIconBadge(FontAwesome.whatsapp,
color: AppColor.greenColor, size: 24),
SiroIconBadge(Icons.email_rounded,
color: AppColor.redColor, size: 24),
],
),
),
],
isleading: true);
),
);
});
}
}
@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/finance_design_system.dart';
import '../../constant/colors.dart';
import '../../../controller/gamification/challenges_controller.dart';
import '../widgets/ui/siro_ui.dart';
class ChallengesPage extends StatelessWidget {
ChallengesPage({super.key});
@@ -10,24 +11,18 @@ class ChallengesPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
body: GetBuilder<ChallengesController>(builder: (cc) {
if (cc.isLoading) {
return Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark));
return const SiroLoading();
}
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
SliverAppBar(
expandedHeight: 160,
expandedHeight: 150,
pinned: true,
backgroundColor: Get.isDarkMode ? FinanceDesignSystem.primaryDark : const Color(0xFF1A237E),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
color: Colors.white, size: 20),
onPressed: () => Get.back()),
foregroundColor: Colors.white,
backgroundColor: AppColor.primaryColor,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded,
@@ -36,136 +31,98 @@ class ChallengesPage extends StatelessWidget {
],
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
titlePadding: const EdgeInsets.symmetric(
horizontal: 48, vertical: 14),
title: Text('Challenges'.tr,
style: TextStyle(
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18)),
fontSize: 17)),
background: Stack(fit: StackFit.expand, children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: Get.isDarkMode
? [FinanceDesignSystem.primaryDark, const Color(0xFF1E1E2E)]
: [const Color(0xFF0A0E21), const Color(0xFF1A237E)]
))),
Positioned(
right: -30,
gradient: SiroUi.brandGradient(context))),
PositionedDirectional(
end: -30,
top: -10,
child: Icon(Icons.bolt_rounded,
size: 160, color: Colors.white.withValues(alpha: 0.04))),
size: 160,
color: Colors.white.withValues(alpha: 0.05))),
]),
),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 40),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
sliver: SliverList(
delegate: SliverChildListDelegate([
// Daily Challenges
_sectionHeader(
context,
'Daily Challenges'.tr,
'🔥',
cc.dailyChallenges.where((c) => c.isCompleted).length,
cc.dailyChallenges.length),
const SizedBox(height: 12),
...cc.dailyChallenges.map((c) => _challengeCard(c, cc)),
const SizedBox(height: 28),
...cc.dailyChallenges
.map((c) => _challengeCard(context, c, cc)),
const SizedBox(height: 16),
// Weekly Challenges
_sectionHeader(
context,
'Weekly Challenges'.tr,
'🏆',
cc.weeklyChallenges.where((c) => c.isCompleted).length,
cc.weeklyChallenges.length),
const SizedBox(height: 12),
...cc.weeklyChallenges.map((c) => _challengeCard(c, cc)),
...cc.weeklyChallenges
.map((c) => _challengeCard(context, c, cc)),
]))),
]);
}),
);
}
Widget _sectionHeader(String title, String emoji, int done, int total) {
return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Row(children: [
Text(emoji, style: const TextStyle(fontSize: 20)),
const SizedBox(width: 8),
Text(title, style: FinanceDesignSystem.headingStyle),
]),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: FinanceDesignSystem.accentBlue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20)),
child: Text('$done/$total',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.accentBlue)),
),
]);
Widget _sectionHeader(
BuildContext context, String title, int done, int total) {
return SiroSectionTitle(
title,
trailing: SiroTag('$done/$total', color: SiroUi.interactive(context)),
);
}
Widget _challengeCard(Challenge c, ChallengesController cc) {
Widget _challengeCard(
BuildContext context, Challenge c, ChallengesController cc) {
final isAr = Get.locale?.languageCode == 'ar';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
border: c.isCompleted
? Border.all(color: c.color.withValues(alpha: 0.3), width: 1.5)
: null,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.03),
blurRadius: 10,
offset: const Offset(0, 4))
],
),
return SiroCard(
accent: c.isCompleted ? c.color : null,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: c.color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12)),
child: Icon(c.icon, color: c.color, size: 22),
),
SiroIconBadge(c.icon, color: c.color, size: 22),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(isAr ? c.titleAr : c.titleEn,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold)),
Text(isAr ? c.descriptionAr : c.descriptionEn,
style:
TextStyle(fontSize: 11, color: FinanceDesignSystem.textSecondary)),
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(fontSize: 11)),
])),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: const Color(0xFFFFD700).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12)),
child: Text('+${c.reward}',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Color(0xFFFF8F00))),
),
const SizedBox(width: 8),
SiroTag('+${c.reward}',
color: AppColor.yellowColor, icon: Icons.star_rounded),
]),
const SizedBox(height: 14),
// Progress bar
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text('${c.currentProgress}/${c.target}',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: FinanceDesignSystem.textSecondary)),
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(fontSize: 11, fontWeight: FontWeight.w600)),
Text('${(c.progress * 100).toStringAsFixed(0)}%',
style: TextStyle(
fontSize: 11, fontWeight: FontWeight.bold, color: c.color)),
@@ -175,7 +132,7 @@ class ChallengesPage extends StatelessWidget {
Container(
height: 8,
decoration: BoxDecoration(
color: FinanceDesignSystem.backgroundColor,
color: SiroUi.border(context),
borderRadius: BorderRadius.circular(4))),
LayoutBuilder(
builder: (ctx, cons) => AnimatedContainer(
@@ -207,14 +164,11 @@ class ChallengesPage extends StatelessWidget {
),
)
: const Icon(Icons.card_giftcard_rounded, size: 18),
label: Text(cc.isClaiming(c.id) ? 'Claiming...'.tr : 'Claim Reward'.tr),
label: Text(
cc.isClaiming(c.id) ? 'Claiming...'.tr : 'Claim Reward'.tr),
style: ElevatedButton.styleFrom(
backgroundColor: c.color,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
)),
],
@@ -222,14 +176,14 @@ class ChallengesPage extends StatelessWidget {
const SizedBox(height: 8),
Center(
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.check_circle_rounded,
color: FinanceDesignSystem.successGreen, size: 16),
const Icon(Icons.check_circle_rounded,
color: AppColor.greenColor, size: 16),
const SizedBox(width: 4),
Text('Claimed'.tr,
style: TextStyle(
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.successGreen)),
color: AppColor.greenColor)),
])),
],
]),
@@ -1,8 +1,9 @@
import 'package:siro_driver/constant/currency.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/finance_design_system.dart';
import '../../constant/colors.dart';
import '../../../controller/gamification/leaderboard_controller.dart';
import '../widgets/ui/siro_ui.dart';
class LeaderboardPage extends StatelessWidget {
LeaderboardPage({super.key});
@@ -11,12 +12,16 @@ class LeaderboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
body: GetBuilder<LeaderboardController>(builder: (lc) {
if (lc.isLoading) {
return Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark));
return const SiroLoading();
}
if (lc.currentLeaderboard.isEmpty) {
return SiroEmptyState(
icon: Icons.leaderboard_rounded,
title: 'Leaderboard'.tr,
message: 'No data yet'.tr,
);
}
return CustomScrollView(
physics: const BouncingScrollPhysics(),
@@ -24,32 +29,30 @@ class LeaderboardPage extends StatelessWidget {
SliverAppBar(
expandedHeight: 200,
pinned: true,
backgroundColor: Get.isDarkMode
? FinanceDesignSystem.primaryDark
: const Color(0xFF0A0E21),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
color: Colors.white, size: 20),
onPressed: () => Get.back()),
foregroundColor: Colors.white,
backgroundColor: AppColor.primaryColor,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
titlePadding: const EdgeInsets.symmetric(
horizontal: 48, vertical: 14),
title: Text('Leaderboard'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18)),
fontSize: 17)),
background: Stack(fit: StackFit.expand, children: [
// كان التدرّج نيفي ← أحمر قانٍ، وهو لون لا يوجد في
// هوية سيرو ويقرأه المستخدم إشارةَ خطر لا إنجاز.
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(colors: [
Color(0xFF0A0E21),
Color(0xFFB71C1C)
]))),
Positioned(
right: -40,
decoration: BoxDecoration(
gradient: SiroUi.brandGradient(context))),
PositionedDirectional(
end: -40,
top: -20,
child: Icon(Icons.leaderboard_rounded,
size: 180, color: Colors.white.withValues(alpha: 0.04))),
size: 180,
color: Colors.white.withValues(alpha: 0.05))),
// Top 3 podium
if (lc.currentLeaderboard.length >= 3)
Positioned(
@@ -63,25 +66,26 @@ class LeaderboardPage extends StatelessWidget {
// Tab bar
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
margin: const EdgeInsets.fromLTRB(16, 14, 16, 0),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: FinanceDesignSystem.backgroundColor.withValues(alpha: 0.5),
color: SiroUi.border(context).withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(14)),
child: Row(children: [
_tab('Trips'.tr, Icons.local_taxi_rounded, 0, lc),
_tab('Earnings'.tr, Icons.monetization_on_rounded, 1, lc),
_tab(context, 'Trips'.tr, Icons.local_taxi_rounded, 0, lc),
_tab(context, 'Earnings'.tr, Icons.monetization_on_rounded, 1,
lc),
]),
)),
// List
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 40),
padding: const EdgeInsets.fromLTRB(16, 14, 16, 32),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, i) {
if (i >= lc.currentLeaderboard.length) return null;
return _buildRow(
lc.currentLeaderboard[i], lc.selectedTab == 1);
ctx, lc.currentLeaderboard[i], lc.selectedTab == 1);
},
childCount: lc.currentLeaderboard.length,
),
@@ -98,15 +102,13 @@ class LeaderboardPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (top3.length > 1)
_podiumItem(top3[1], 2, 50, const Color(0xFFC0C0C0)),
if (top3.isNotEmpty)
_podiumItem(top3[0], 1, 70, const Color(0xFFFFD700)),
if (top3.length > 2)
_podiumItem(top3[2], 3, 35, const Color(0xFFCD7F32)),
_podiumItem(top3[1], 2, const Color(0xFFC0C0C0)),
if (top3.isNotEmpty) _podiumItem(top3[0], 1, AppColor.gold),
if (top3.length > 2) _podiumItem(top3[2], 3, AppColor.bronze),
]);
}
Widget _podiumItem(LeaderboardEntry e, int rank, double height, Color color) {
Widget _podiumItem(LeaderboardEntry e, int rank, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(mainAxisSize: MainAxisSize.min, children: [
@@ -125,8 +127,10 @@ class LeaderboardPage extends StatelessWidget {
]));
}
Widget _tab(String label, IconData icon, int idx, LeaderboardController lc) {
Widget _tab(BuildContext context, String label, IconData icon, int idx,
LeaderboardController lc) {
final selected = lc.selectedTab == idx;
final accent = SiroUi.interactive(context);
return Expanded(
child: GestureDetector(
onTap: () => lc.changeTab(idx),
@@ -134,60 +138,41 @@ class LeaderboardPage extends StatelessWidget {
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color:
selected ? FinanceDesignSystem.cardColor : Colors.transparent,
color: selected ? SiroUi.surface(context) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
boxShadow: selected
? [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05), blurRadius: 5)
]
border: selected
? Border.all(color: accent.withValues(alpha: 0.35))
: null),
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(icon,
size: 16,
color: selected ? FinanceDesignSystem.primaryDark : Colors.grey),
size: 16, color: selected ? accent : SiroUi.subtle(context)),
const SizedBox(width: 6),
Text(label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: selected
? FinanceDesignSystem.primaryDark
: Colors.grey)),
Flexible(
child: Text(label,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: selected ? accent : SiroUi.subtle(context))),
),
]),
),
));
}
Widget _buildRow(LeaderboardEntry e, bool isEarnings) {
Widget _buildRow(BuildContext context, LeaderboardEntry e, bool isEarnings) {
final accent = SiroUi.interactive(context);
final rankColor = e.rank == 1
? const Color(0xFFFFD700)
? AppColor.gold
: e.rank == 2
? const Color(0xFFC0C0C0)
: e.rank == 3
? const Color(0xFFCD7F32)
: Colors.grey.shade400;
return Container(
? AppColor.bronze
: SiroUi.subtle(context);
return SiroCard(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: e.isCurrentUser
? FinanceDesignSystem.accentBlue.withValues(alpha: 0.08)
: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(14),
border: e.isCurrentUser
? Border.all(
color: FinanceDesignSystem.accentBlue.withValues(alpha: 0.3),
width: 1.5)
: null,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.02),
blurRadius: 8,
offset: const Offset(0, 3))
],
),
padding: const EdgeInsets.all(12),
accent: e.isCurrentUser ? accent : null,
child: Row(children: [
// Rank
Container(
@@ -208,49 +193,41 @@ class LeaderboardPage extends StatelessWidget {
const SizedBox(width: 12),
// Avatar
CircleAvatar(
radius: 18,
backgroundColor: FinanceDesignSystem.backgroundColor,
radius: 17,
backgroundColor: accent.withValues(alpha: 0.12),
child: Text(e.name.isNotEmpty ? e.name[0] : '?',
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.textSecondary))),
fontWeight: FontWeight.bold, color: accent))),
const SizedBox(width: 12),
// Name
Expanded(
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Text(e.name,
style: TextStyle(
fontSize: 13,
fontWeight:
e.isCurrentUser ? FontWeight.bold : FontWeight.w500,
color: FinanceDesignSystem.primaryDark)),
Flexible(
child: Text(e.name,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: e.isCurrentUser
? FontWeight.bold
: FontWeight.w500)),
),
if (e.isCurrentUser) ...[
const SizedBox(width: 6),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: FinanceDesignSystem.accentBlue,
borderRadius: BorderRadius.circular(6)),
child: Text('You'.tr,
style: const TextStyle(
fontSize: 8,
fontWeight: FontWeight.bold,
color: Colors.white)))
SiroTag('You'.tr, color: accent),
],
]),
])),
const SizedBox(width: 8),
// Value
Text(
isEarnings
? '${e.value.toStringAsFixed(0)} ${CurrencyHelper.currency}'
: '${e.value.toInt()} ${'Rides'.tr}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w800,
color: FinanceDesignSystem.primaryDark)),
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w800)),
]),
);
}
@@ -3,12 +3,11 @@ import 'package:get/get.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/controller/profile/setting_controller.dart';
import 'package:siro_driver/views/lang/languages.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
// تأكد من صحة مسارات الاستيراد هذه
import '../../../../controller/functions/vibrate.dart'; // Controller with isVibrate
import '../../../auth/country_widget.dart';
import '../../../widgets/ui/siro_ui.dart';
import 'about_us.dart';
import 'frequantly_question.dart';
import 'using_app_page.dart';
@@ -22,15 +21,13 @@ class SettingsCaptain extends StatelessWidget {
final settingsController = Get.put(SettingController());
final homeController = Get.put(HomePageController());
return MyScafolld(
return SiroPage(
title: 'Settings'.tr,
isleading: true,
body: [
ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
padding: SiroUi.pageInsets,
body: ListView(
children: <Widget>[
// --- القسم الأول: عام ---
_buildSectionHeader('General'.tr, context),
SiroSectionTitle('General'.tr),
_buildSettingsCard(
children: [
_buildListTile(
@@ -53,10 +50,8 @@ class SettingsCaptain extends StatelessWidget {
// ),
],
),
const SizedBox(height: 20),
// --- القسم الثاني: تفضيلات التطبيق ---
_buildSectionHeader('App Preferences'.tr, context),
SiroSectionTitle('App Preferences'.tr),
_buildSettingsCard(
children: [
_buildSwitchTile(
@@ -96,10 +91,8 @@ class SettingsCaptain extends StatelessWidget {
),
],
),
const SizedBox(height: 20),
// --- القسم الثالث: المساعدة والدعم ---
_buildSectionHeader('Help & Support'.tr, context),
SiroSectionTitle('Help & Support'.tr),
_buildSettingsCard(
children: [
_buildListTile(
@@ -119,18 +112,17 @@ class SettingsCaptain extends StatelessWidget {
),
],
),
const SizedBox(height: 20),
// --- القسم الرابع: تسجيل الخروج ---
_buildSectionHeader('Account'.tr, context),
SiroSectionTitle('Account'.tr),
_buildSettingsCard(
children: [
ListTile(
leading: const Icon(Icons.logout, color: Colors.red),
leading: const SiroIconBadge(Icons.logout_rounded,
color: AppColor.redColor),
title: Text(
'Logout'.tr,
style: const TextStyle(
color: Colors.red, fontWeight: FontWeight.w500),
color: AppColor.redColor, fontWeight: FontWeight.w600),
),
onTap: () {
MyDialog().getDialog(
@@ -145,34 +137,17 @@ class SettingsCaptain extends StatelessWidget {
},
)
],
)
),
const SizedBox(height: 16),
],
),
],
);
}
// ويدجت لبناء عنوان كل قسم
Widget _buildSectionHeader(String title, BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 12.0),
child: Text(
title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Colors.grey.shade600,
fontWeight: FontWeight.bold,
),
),
);
}
// ويدجت لبناء بطاقة الإعدادات
// ويدجت لبناء بطاقة الإعدادات — تجمع عناصر القسم داخل سطح واحد.
Widget _buildSettingsCard({required List<Widget> children}) {
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
clipBehavior: Clip.antiAlias, // مهم لجعل splash effect داخل حدود البطاقة
return SiroCard(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(children: children),
);
}
@@ -184,14 +159,18 @@ class SettingsCaptain extends StatelessWidget {
String? subtitle,
required VoidCallback onTap,
}) {
return ListTile(
leading: Icon(icon, color: Colors.grey.shade700),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
subtitle: subtitle != null ? Text(subtitle) : null,
trailing:
const Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
onTap: onTap,
);
return Builder(builder: (context) {
return ListTile(
leading: SiroIconBadge(icon, color: SiroUi.interactive(context)),
title: Text(title, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: subtitle != null
? Text(subtitle, style: Theme.of(context).textTheme.bodySmall)
: null,
trailing: Icon(Icons.arrow_forward_ios,
size: 14, color: SiroUi.subtle(context)),
onTap: onTap,
);
});
}
// ويدجت لبناء عنصر يحتوي على مفتاح تفعيل/إلغاء (Switch)
@@ -207,15 +186,19 @@ class SettingsCaptain extends StatelessWidget {
return GetBuilder<T>(
init: controller,
builder: (ctrl) {
return SwitchListTile(
secondary: Icon(icon, color: color ?? Colors.grey.shade700),
title:
Text(title, style: const TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text(subtitle),
value: valueGetter(ctrl),
onChanged: (value) => onChanged(ctrl),
activeColor: AppColor.primaryColor,
);
return Builder(builder: (context) {
return SwitchListTile(
secondary: SiroIconBadge(icon,
color: color ?? SiroUi.interactive(context)),
title:
Text(title, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text(subtitle,
style: Theme.of(context).textTheme.bodySmall),
value: valueGetter(ctrl),
onChanged: (value) => onChanged(ctrl),
activeThumbColor: SiroUi.interactive(context),
);
});
},
);
}
@@ -5,77 +5,66 @@ import 'package:video_player/video_player.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import '../../../../controller/home/captin/help/video_controller.dart';
import 'package:flutter/cupertino.dart';
import '../../../widgets/ui/siro_ui.dart';
class VideoListPage extends StatelessWidget {
VideoListPage({super.key});
final VideoController videoController = Get.put(VideoController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Videos Tutorials'.tr),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0), // Outer padding around the list
child: GetBuilder<VideoController>(
builder: (videoController) {
return ListView.builder(
itemCount: videoController.videos.length,
itemBuilder: (context, index) {
final video = videoController.videos[index];
return Container(
margin: const EdgeInsets.symmetric(
vertical: 8.0), // Spacing between each card
decoration: BoxDecoration(
color: CupertinoColors.white,
borderRadius:
BorderRadius.circular(12.0), // Rounded corners
boxShadow: [
BoxShadow(
color: CupertinoColors.systemGrey.withOpacity(0.3),
offset: const Offset(
0, 4), // Offset for shadow to appear below
blurRadius: 10.0, // Blur for softer shadow effect
),
],
border: Border.all(
color: CupertinoColors.systemGrey4,
width: 0.5, // Subtle border for a refined iOS-like look
),
),
child: CupertinoListTile(
title: Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
video['title'],
style: const TextStyle(
color: CupertinoColors.black,
fontWeight: FontWeight.bold,
return SiroPage(
title: 'Videos Tutorials'.tr,
padding: SiroUi.pageInsets,
body: GetBuilder<VideoController>(
builder: (videoController) {
if (videoController.videos.isEmpty) {
return SiroEmptyState(
icon: Icons.video_library_rounded,
title: 'No videos available'.tr,
);
}
return ListView.builder(
itemCount: videoController.videos.length,
itemBuilder: (context, index) {
final video = videoController.videos[index];
return SiroCard(
onTap: () =>
Get.to(() => VideoPlayerPage1(videoUrl: video['url'])),
child: Row(
children: [
SiroIconBadge(Icons.play_arrow_rounded,
color: SiroUi.interactive(context), size: 24),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
video['title'] ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 3),
Text(
video['description'] ?? '',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
subtitle: Text(
video['description'],
style: const TextStyle(
color: CupertinoColors.systemGrey,
),
),
onTap: () {
// Navigate to video player page (iOS-style)
Get.to(() => VideoPlayerPage1(videoUrl: video['url']));
},
),
);
},
Icon(Icons.arrow_forward_ios_rounded,
size: 13, color: SiroUi.subtle(context)),
],
),
);
},
),
),
);
},
),
);
}
@@ -84,7 +73,7 @@ class VideoListPage extends StatelessWidget {
class VideoPlayerPage extends StatelessWidget {
final String videoUrl;
VideoPlayerPage({required this.videoUrl});
VideoPlayerPage({super.key, required this.videoUrl});
final VideoController videoController = Get.put(VideoController());
@@ -93,51 +82,45 @@ class VideoPlayerPage extends StatelessWidget {
// Initialize the video when the page is loaded
videoController.initializeVideo(videoUrl);
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text(AppInformation.appName),
),
child: SafeArea(
child: Center(
child: GetBuilder<VideoController>(
builder: (controller) {
if (!controller.videoPlayerController.value.isInitialized) {
return const CircularProgressIndicator(); // Show loading indicator while initializing
}
return SiroPage(
title: AppInformation.appName,
body: Center(
child: GetBuilder<VideoController>(
builder: (controller) {
if (!controller.videoPlayerController.value.isInitialized) {
return const SiroLoading();
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Video player widget
AspectRatio(
aspectRatio:
controller.videoPlayerController.value.aspectRatio,
// child: VideoPlayer(controller.videoPlayerController),
child: VideoPlayer(controller.videoPlayerController),
),
const SizedBox(height: 20),
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Video player widget
AspectRatio(
aspectRatio:
controller.videoPlayerController.value.aspectRatio,
child: VideoPlayer(controller.videoPlayerController),
),
const SizedBox(height: 20),
// Play/pause button
CupertinoButton(
onPressed: () {
if (controller.videoPlayerController.value.isPlaying) {
controller.pause();
} else {
controller.play();
}
},
child: Icon(
controller.videoPlayerController.value.isPlaying
? CupertinoIcons.pause
: CupertinoIcons.play_arrow,
color: CupertinoColors.activeBlue,
size: 30,
),
// Play/pause button
IconButton.filled(
iconSize: 32,
onPressed: () {
if (controller.videoPlayerController.value.isPlaying) {
controller.pause();
} else {
controller.play();
}
},
icon: Icon(
controller.videoPlayerController.value.isPlaying
? Icons.pause_rounded
: Icons.play_arrow_rounded,
),
],
);
},
),
),
],
);
},
),
),
);
@@ -147,15 +130,26 @@ class VideoPlayerPage extends StatelessWidget {
class VideoPlayerPage1 extends StatelessWidget {
final String videoUrl;
const VideoPlayerPage1({required this.videoUrl});
const VideoPlayerPage1({super.key, required this.videoUrl});
@override
Widget build(BuildContext context) {
// Extract the video ID from the URL
String videoId = YoutubePlayer.convertUrlToId(videoUrl)!;
final String? videoId = YoutubePlayer.convertUrlToId(videoUrl);
// Create a YoutubePlayerController
final YoutubePlayerController _controller = YoutubePlayerController(
// رابط غير صالح كان يرمي `Null check operator used on a null value`
// ويُسقط الشاشة بيضاء بدل رسالة مفهومة.
if (videoId == null) {
return SiroPage(
title: AppInformation.appName,
body: SiroEmptyState(
icon: Icons.videocam_off_rounded,
title: 'Video unavailable'.tr,
),
);
}
final YoutubePlayerController controller = YoutubePlayerController(
initialVideoId: videoId,
flags: const YoutubePlayerFlags(
autoPlay: true,
@@ -166,24 +160,28 @@ class VideoPlayerPage1 extends StatelessWidget {
);
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// Full-screen YouTube player
Positioned.fill(
child: YoutubePlayer(
controller: _controller,
controller: controller,
showVideoProgressIndicator: true,
),
),
// Overlay back button in the top left corner for exit
Positioned(
top: 40.0,
left: 16.0,
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
},
// زر الخروج — كان `left` ثابتاً فيقع تحت الإبهام الخطأ في العربية
SafeArea(
child: PositionedDirectional(
top: 8,
start: 8,
child: CircleAvatar(
backgroundColor: Colors.black45,
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
),
),
],
@@ -3,9 +3,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../constant/colors.dart';
import '../../../../controller/auth/captin/history_captain.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import '../../../widgets/ui/siro_ui.dart';
class HistoryCaptain extends StatelessWidget {
const HistoryCaptain({super.key});
@@ -15,14 +16,10 @@ class HistoryCaptain extends StatelessWidget {
Get.put(HistoryCaptainController());
return Scaffold(
backgroundColor: Colors.white,
body: GetBuilder<HistoryCaptainController>(
builder: (controller) {
if (controller.isloading) {
return Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark),
);
return const SiroLoading();
}
final List trips = controller.historyData['message'] ?? [];
@@ -30,20 +27,24 @@ class HistoryCaptain extends StatelessWidget {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// 1. Custom App Bar
// 1. رأس بتدرّج العلامة — نفس تدرّج رأس القائمة الجانبية
SliverAppBar(
expandedHeight: 180,
expandedHeight: 170,
pinned: true,
stretch: true,
backgroundColor: FinanceDesignSystem.primaryDark,
foregroundColor: Colors.white,
backgroundColor: AppColor.primaryColor,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
titlePadding:
const EdgeInsets.symmetric(horizontal: 48, vertical: 14),
title: Text(
'Ride History'.tr,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
fontSize: 17,
),
),
background: Stack(
@@ -51,11 +52,11 @@ class HistoryCaptain extends StatelessWidget {
children: [
Container(
decoration: BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
gradient: SiroUi.brandGradient(context),
),
),
Positioned(
right: -50,
PositionedDirectional(
end: -50,
top: -20,
child: Icon(
Icons.history_rounded,
@@ -66,12 +67,11 @@ class HistoryCaptain extends StatelessWidget {
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
Text(
trips.length.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontSize: 38,
fontWeight: FontWeight.w900,
),
),
@@ -79,10 +79,11 @@ class HistoryCaptain extends StatelessWidget {
'Total Rides'.tr,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 14,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 34),
],
),
],
@@ -93,46 +94,16 @@ class HistoryCaptain extends StatelessWidget {
// 2. Trips List
if (trips.isEmpty)
SliverFillRemaining(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.grey.shade50,
shape: BoxShape.circle,
),
child: Icon(
Icons.history_toggle_off_rounded,
size: 64,
color: Colors.grey.shade300,
),
),
const SizedBox(height: 24),
Text(
'No Rides Yet'.tr,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 8),
Text(
'Your completed trips will appear here'.tr,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade400,
),
),
],
),
hasScrollBody: false,
child: SiroEmptyState(
icon: Icons.history_toggle_off_rounded,
title: 'No Rides Yet'.tr,
message: 'Your completed trips will appear here'.tr,
),
)
else
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 40),
padding: const EdgeInsets.fromLTRB(16, 20, 16, 32),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
@@ -197,141 +168,99 @@ class _TripHistoryCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
final theme = Theme.of(context);
final accent = SiroUi.interactive(context);
return SiroCard(
onTap: onTap,
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
SiroIconBadge(Icons.receipt_long_rounded, color: accent),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: FinanceDesignSystem.primaryDark
.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.receipt_long_rounded,
color: FinanceDesignSystem.primaryDark,
size: 20,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'OrderId'.tr} #${trip['order_id']}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
),
Text(
_formatDateToSyria(trip['created_at']),
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
),
],
),
],
Text(
'${'OrderId'.tr} #${trip['order_id']}',
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
),
Text(
_formatDateToSyria(trip['created_at']),
style: theme.textTheme.bodySmall?.copyWith(fontSize: 11),
),
_buildStatusChip(trip['status']),
],
),
const SizedBox(height: 20),
const Divider(height: 1),
const SizedBox(height: 20),
Row(
),
const SizedBox(width: 8),
_buildStatusChip(trip['status']),
],
),
const SizedBox(height: 14),
const Divider(height: 1),
const SizedBox(height: 14),
Row(
children: [
Column(
children: [
Icon(Icons.circle, size: 11, color: accent),
Container(
width: 2,
height: 20,
color: SiroUi.border(context),
),
const Icon(Icons.location_on_rounded,
size: 14, color: AppColor.redColor),
],
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Icon(Icons.circle,
size: 12, color: FinanceDesignSystem.accentBlue),
Container(
width: 2,
height: 20,
color: Colors.grey.shade200,
),
Icon(Icons.location_on_rounded,
size: 14, color: FinanceDesignSystem.dangerRed),
],
Text(
trip['start_name'] ?? 'Pickup Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall
?.copyWith(fontWeight: FontWeight.w500),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
trip['start_name'] ?? 'Pickup Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
Text(
trip['end_name'] ?? 'Destination Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
],
const SizedBox(height: 12),
Text(
trip['end_name'] ?? 'Destination Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall
?.copyWith(fontWeight: FontWeight.w500),
),
],
),
),
if (trip['price'] != null) ...[
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${trip['price']} ${CurrencyHelper.currency}',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
color: AppColor.greenColor,
),
),
if (trip['price'] != null)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${trip['price']} ${CurrencyHelper.currency}',
style: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 16,
color: FinanceDesignSystem.primaryDark,
),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 12, color: Colors.grey),
],
),
Icon(Icons.arrow_forward_ios_rounded,
size: 11, color: SiroUi.subtle(context)),
],
),
],
),
],
),
),
],
),
);
}
@@ -340,73 +269,33 @@ class _TripHistoryCard extends StatelessWidget {
Color color;
IconData icon;
String label = status ?? 'Unknown';
List<Color> gradientColors;
switch (status) {
case 'Apply':
color = FinanceDesignSystem.successGreen;
color = AppColor.greenColor;
icon = Icons.check_circle_rounded;
label = 'Completed'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Refused':
color = FinanceDesignSystem.dangerRed;
color = AppColor.redColor;
icon = Icons.cancel_rounded;
label = 'Refused'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Cancel':
color = Colors.orange;
color = AppColor.yellowColor;
icon = Icons.info_rounded;
label = 'Cancelled'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'CancelByPassenger':
color = const Color(0xFF6B4EFF);
color = AppColor.secondaryColorStatic;
icon = Icons.person_off_rounded;
label = 'Cancelled by Passenger'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
default:
color = Colors.grey;
icon = Icons.help_rounded;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradientColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: color,
fontSize: 11,
fontWeight: FontWeight.w800,
letterSpacing: 0.3,
),
),
],
),
);
return SiroTag(label, color: color, icon: icon);
}
}
@@ -26,6 +26,7 @@ import 'package:siro_driver/views/home/profile/profile_captain.dart';
import 'package:siro_driver/views/notification/notification_captain.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../constant/colors.dart';
import '../../../widgets/ui/siro_ui.dart';
import '../../../transit/transit_driver_home_page.dart';
import '../About Us/video_page.dart';
import '../../../food_delivery/food_delivery_home_page.dart';
@@ -46,188 +47,224 @@ class DrawerItem {
required this.onTap});
}
/// مجموعة عناصر تحت عنوان واحد. القائمة كانت ٢٢ عنصراً مسطّحاً بـ ٢٢ لوناً
/// مختلفاً، فيصعب العثور على أي بند. التجميع يقصّر زمن البحث بلا تغيير أي وجهة.
class DrawerSection {
final String title;
final List<DrawerItem> items;
DrawerSection({required this.title, required this.items});
}
// --- الويدجت الرئيسية للقائمة الجانبية ---
class AppDrawer extends StatelessWidget {
AppDrawer({super.key});
final ImageController imageController = Get.put(ImageController());
// 2. تعريف بيانات القائمة بشكل مركزي ومنظم
final List<DrawerItem> drawerItems = [
DrawerItem(
title: 'Mawasalati'.tr,
icon: Icons.directions_bus_filled_rounded,
color: Colors.teal,
onTap: () => Get.to(() => const TransitDriverHomePage())),
DrawerItem(
title: 'Delivery'.tr,
icon: Icons.delivery_dining_rounded,
color: Colors.deepOrange,
onTap: () => Get.to(() => const FoodDeliveryHomePage())),
DrawerItem(
title: 'Balance'.tr,
icon: Icons.account_balance_wallet,
color: Colors.green,
onTap: () => Get.to(() => const WalletCaptainRefactored())),
DrawerItem(
title: 'أرباحي',
icon: Icons.payments_rounded,
color: Colors.teal,
onTap: () => Get.to(() => EarningsPage())),
DrawerItem(
title: 'Challenges'.tr,
icon: Icons.bolt_rounded,
color: Colors.amber,
onTap: () => Get.to(() => ChallengesPage())),
DrawerItem(
title: 'My Schedule'.tr,
icon: Icons.calendar_today_rounded,
color: Colors.teal,
onTap: () => Get.to(() => SchedulePage())),
DrawerItem(
title: 'الرحلات المجدولة القادمة'.tr, // or 'Reserved Rides'
icon: Icons.edit_calendar_rounded,
color: Colors.orangeAccent,
onTap: () => Get.to(() => const DriverScheduledRidesPage())),
DrawerItem(
title: 'Insurance'.tr,
icon: Icons.health_and_safety_rounded,
color: Colors.indigo,
onTap: () => Get.to(() => const DriverInsurancePage())),
DrawerItem(
title: 'Leaderboard'.tr,
icon: Icons.leaderboard_rounded,
color: Colors.red,
onTap: () => Get.to(() => LeaderboardPage())),
DrawerItem(
title: 'Profile'.tr,
icon: Icons.person,
color: Colors.blue,
onTap: () => Get.to(() => ProfileCaptain())),
DrawerItem(
title: 'History of Trip'.tr,
icon: Icons.history,
color: Colors.orange,
onTap: () => Get.to(() => const HistoryCaptain())),
DrawerItem(
title: 'Available for rides'.tr,
icon: Icons.drive_eta,
color: Colors.teal,
onTap: () => Get.to(() => const AvailableRidesPage())),
DrawerItem(
title: 'Notifications'.tr,
icon: Icons.notifications,
color: Colors.purple,
onTap: () => Get.to(() => const NotificationCaptain())),
DrawerItem(
title: 'Helping Center'.tr,
icon: Icons.help_center,
color: Colors.cyan,
onTap: () => Get.to(() => HelpCaptain())),
DrawerItem(
title: 'Invite Driver'.tr,
icon: Icons.person_add_rounded,
color: Colors.indigo,
onTap: () => Get.to(() => InviteScreen())),
// DrawerItem(
// title: 'Maintenance Center'.tr,
// icon: Icons.build,
// color: Colors.brown,
// onTap: () => Get.to(() => MaintainCenterPage())),
// DrawerItem(
// title: 'Health Insurance'.tr,
// icon: Icons.favorite,
// color: Colors.pink,
// onTap: () => Get.to(() => AssuranceHealthPage())),
DrawerItem(
title: 'Contact Us'.tr,
icon: Icons.email,
color: Colors.blueGrey,
onTap: () => Get.to(() => ContactUsPage())),
DrawerItem(
title: 'Videos Tutorials'.tr,
icon: Icons.video_library,
color: Colors.redAccent,
onTap: () => Get.to(() => VideoListPage())),
DrawerItem(
title: 'Rate Our App'.tr,
icon: Icons.star,
color: Colors.amber,
onTap: () => Get.to(() => RatingScreen())),
DrawerItem(
title: 'Is device compatible'.tr,
icon: Icons.memory,
color: Colors.greenAccent,
onTap: () => Get.to(() => DeviceCompatibilityPage())),
DrawerItem(
title: 'Privacy Policy'.tr,
icon: Icons.memory,
color: Colors.greenAccent,
onTap: () =>
launchUrl(Uri.parse('${AppLink.server}/privacy_policy.php'))),
DrawerItem(
title: 'Settings'.tr,
icon: Icons.settings,
color: Colors.grey.shade600,
onTap: () => Get.to(() => const SettingsCaptain())),
// 2. تعريف بيانات القائمة مجمَّعة حسب الغرض.
// الوجهات لم تتغيّر — التغيير في الترتيب واللون فقط.
// اللون صار دلالياً: أخضر = مال، كهرماني = مكافآت، أحمر = خروج،
// وما عداه لون العلامة. الطيف العشوائي السابق كان يشتّت لا يوجّه.
final List<DrawerSection> sections = [
DrawerSection(title: 'Services'.tr, items: [
DrawerItem(
title: 'Available for rides'.tr,
icon: Icons.drive_eta_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const AvailableRidesPage())),
DrawerItem(
title: 'Mawasalati'.tr,
icon: Icons.directions_bus_filled_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const TransitDriverHomePage())),
DrawerItem(
title: 'Delivery'.tr,
icon: Icons.delivery_dining_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const FoodDeliveryHomePage())),
]),
DrawerSection(title: 'Wallet & Earnings'.tr, items: [
DrawerItem(
title: 'Balance'.tr,
icon: Icons.account_balance_wallet_rounded,
color: AppColor.greenColor,
onTap: () => Get.to(() => const WalletCaptainRefactored())),
DrawerItem(
title: 'My Earnings'.tr,
icon: Icons.payments_rounded,
color: AppColor.greenColor,
onTap: () => Get.to(() => EarningsPage())),
DrawerItem(
title: 'Insurance'.tr,
icon: Icons.health_and_safety_rounded,
color: AppColor.greenColor,
onTap: () => Get.to(() => const DriverInsurancePage())),
]),
DrawerSection(title: 'My Trips'.tr, items: [
DrawerItem(
title: 'My Schedule'.tr,
icon: Icons.calendar_today_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => SchedulePage())),
DrawerItem(
title: 'Upcoming Scheduled Rides'.tr,
icon: Icons.edit_calendar_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const DriverScheduledRidesPage())),
DrawerItem(
title: 'History of Trip'.tr,
icon: Icons.history_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const HistoryCaptain())),
]),
DrawerSection(title: 'Rewards'.tr, items: [
DrawerItem(
title: 'Challenges'.tr,
icon: Icons.bolt_rounded,
color: AppColor.yellowColor,
onTap: () => Get.to(() => ChallengesPage())),
DrawerItem(
title: 'Leaderboard'.tr,
icon: Icons.leaderboard_rounded,
color: AppColor.yellowColor,
onTap: () => Get.to(() => LeaderboardPage())),
DrawerItem(
title: 'Invite Driver'.tr,
icon: Icons.person_add_alt_1_rounded,
color: AppColor.yellowColor,
onTap: () => Get.to(() => InviteScreen())),
]),
DrawerSection(title: 'My Account'.tr, items: [
DrawerItem(
title: 'Profile'.tr,
icon: Icons.person_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => ProfileCaptain())),
DrawerItem(
title: 'Notifications'.tr,
icon: Icons.notifications_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const NotificationCaptain())),
DrawerItem(
title: 'Settings'.tr,
icon: Icons.settings_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => const SettingsCaptain())),
]),
DrawerSection(title: 'Support'.tr, items: [
DrawerItem(
title: 'Helping Center'.tr,
icon: Icons.help_center_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => HelpCaptain())),
DrawerItem(
title: 'Contact Us'.tr,
icon: Icons.email_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => ContactUsPage())),
DrawerItem(
title: 'Videos Tutorials'.tr,
icon: Icons.play_circle_fill_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => VideoListPage())),
DrawerItem(
title: 'Rate Our App'.tr,
icon: Icons.star_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => RatingScreen())),
DrawerItem(
title: 'Is device compatible'.tr,
icon: Icons.memory_rounded,
color: AppColor.primaryColor,
onTap: () => Get.to(() => DeviceCompatibilityPage())),
DrawerItem(
title: 'Privacy Policy'.tr,
icon: Icons.privacy_tip_rounded,
color: AppColor.primaryColor,
onTap: () =>
launchUrl(Uri.parse('${AppLink.server}/privacy_policy.php'))),
]),
];
@override
Widget build(BuildContext context) {
// نبني قائمة مسطّحة من العناوين والعناصر لتبقى الحركة المتدرّجة تعمل.
final rows = <Widget>[];
for (final section in sections) {
rows.add(_DrawerSectionTitle(section.title));
rows.addAll(section.items.map((e) => _DrawerItemTile(item: e)));
}
return Drawer(
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
children: [
// --- الجزء العلوي من القائمة (بيانات المستخدم) ---
UserHeader(), // استخدمنا الويدجت المحسنة بالأسفل
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
child: Column(
children: [
// --- الجزء العلوي من القائمة (بيانات المستخدم) ---
UserHeader(),
// --- قائمة العناصر المتحركة ---
Expanded(
child: AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
itemCount: drawerItems.length + 1, // +1 لزر تسجيل الخروج
itemBuilder: (BuildContext context, int index) {
// --- زر تسجيل الخروج في النهاية ---
if (index == drawerItems.length) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _DrawerItemTile(
item: DrawerItem(
title: 'Sign Out'.tr,
icon: Icons.logout,
color: Colors.red,
onTap: () =>
Get.to(() => const LogoutCaptain())),
),
),
),
);
}
// --- بقية العناصر ---
final item = drawerItems[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _DrawerItemTile(item: item),
),
),
);
},
),
// --- قائمة العناصر المتحركة ---
Expanded(
child: AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.fromLTRB(10, 8, 10, 8),
itemCount: rows.length,
itemBuilder: (BuildContext context, int index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 40.0,
child: FadeInAnimation(child: rows[index]),
),
);
},
),
),
],
),
),
// --- تسجيل الخروج مثبَّت أسفل القائمة ---
// كان آخر عنصر في قائمة من ٢٢ بنداً، فيتطلّب تمريراً كاملاً للوصول إليه.
SafeArea(
top: false,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Theme.of(context).dividerColor)),
),
padding: const EdgeInsets.fromLTRB(10, 6, 10, 6),
child: _DrawerItemTile(
item: DrawerItem(
title: 'Sign Out'.tr,
icon: Icons.logout_rounded,
color: AppColor.redColor,
onTap: () => Get.to(() => const LogoutCaptain())),
),
),
),
],
),
);
}
}
// --- عنوان مجموعة داخل القائمة ---
class _DrawerSectionTitle extends StatelessWidget {
final String title;
const _DrawerSectionTitle(this.title);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.fromSTEB(14, 16, 14, 6),
child: Text(
title.toUpperCase(),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
fontWeight: FontWeight.bold,
letterSpacing: 0.8,
),
),
);
}
@@ -240,22 +277,26 @@ class _DrawerItemTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
// النيفي الداكن لا يُقرأ على خلفية ليلية، فنستبدله بلون العلامة الفاتح.
final isDark = Theme.of(context).brightness == Brightness.dark;
final color = item.color == AppColor.primaryColor && isDark
? AppColor.secondaryColorStatic
: item.color;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: ListTile(
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: item.color.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(item.icon, color: item.color, size: 24),
),
dense: true,
visualDensity: const VisualDensity(vertical: -1),
horizontalTitleGap: 12,
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
leading: SiroIconBadge(item.icon, color: color, size: 20),
title: Text(
item.title,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.titleMedium
.bodyMedium
?.copyWith(fontWeight: FontWeight.w500),
),
onTap: () {
@@ -265,7 +306,7 @@ class _DrawerItemTile extends StatelessWidget {
});
},
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
splashColor: item.color.withValues(alpha: 0.2),
splashColor: color.withValues(alpha: 0.15),
),
);
}
@@ -398,31 +439,31 @@ class UserHeader extends StatelessWidget {
),
),
otherAccountsPictures: [
SizedBox(
width: 60,
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(999),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star_rounded, color: AppColor.gold, size: 15),
const SizedBox(width: 3),
Text(
homeCaptainController.rating.toString(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14),
fontSize: 13),
),
const SizedBox(width: 2),
const Icon(Icons.star, color: Colors.amber, size: 16),
],
),
),
],
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Theme.of(context).primaryColor, Colors.blue.shade700],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
// تدرّج العلامة نفسه المستعمل في رؤوس الصفحات الداخلية
// (كان `Colors.blue.shade700` وهو أزرق لا علاقة له بهوية سيرو).
decoration: BoxDecoration(gradient: SiroUi.brandGradient(context)),
);
}
}
@@ -3,50 +3,41 @@ import 'package:get/get.dart';
import 'package:siro_driver/controller/home/captin/help/help_controller.dart';
import 'package:siro_driver/views/home/Captin/home_captain/help_details_replay_page.dart';
import 'package:flutter/cupertino.dart';
import '../../../../constant/colors.dart';
import '../../../../controller/functions/encrypt_decrypt.dart';
import '../../../widgets/my_scafold.dart';
import '../../../widgets/ui/siro_ui.dart';
class HelpCaptain extends StatelessWidget {
HelpCaptain({super.key});
const HelpCaptain({super.key});
@override
Widget build(BuildContext context) {
Get.put(HelpController());
final theme = Theme.of(context);
return MyScafolld(
return SiroPage(
title: 'Helping Page'.tr,
isleading: true,
body: [
SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
padding: SiroUi.pageInsets,
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding: const EdgeInsets.all(18.0),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(15.0),
border: Border.all(color: theme.dividerColor),
),
child: Text(
'If you need any help or have questions, this is the right place to do that. You are welcome!'
.tr,
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
SiroCard(
child: Row(
children: [
SiroIconBadge(Icons.support_agent_rounded,
color: SiroUi.interactive(context)),
const SizedBox(width: 12),
Expanded(
child: Text(
'If you need any help or have questions, this is the right place to do that. You are welcome!'
.tr,
style: theme.textTheme.bodyMedium,
),
),
],
),
),
const SizedBox(height: 24),
Text(
'Submit Your Question'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
SiroSectionTitle('Submit Your Question'.tr),
GetBuilder<HelpController>(
builder: (helpController) => Form(
key: helpController.formKey,
@@ -58,8 +49,6 @@ class HelpCaptain extends StatelessWidget {
hintText: 'Enter your Question here'.tr,
prefixIcon:
const Icon(Icons.question_answer_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
),
maxLines: 3,
validator: (value) {
@@ -71,10 +60,10 @@ class HelpCaptain extends StatelessWidget {
),
const SizedBox(height: 16),
helpController.isLoading
? const Center(child: CircularProgressIndicator())
? const SiroLoading()
: SizedBox(
width: double.infinity,
child: ElevatedButton(
child: ElevatedButton.icon(
onPressed: () {
if (helpController.formKey.currentState!
.validate()) {
@@ -83,53 +72,36 @@ class HelpCaptain extends StatelessWidget {
.clear();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text('Submit Question'.tr),
icon: const Icon(Icons.send_rounded, size: 18),
label: Text('Submit Question'.tr),
),
),
],
),
),
),
const SizedBox(height: 32),
Text(
'Your Questions'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
SiroSectionTitle('Your Questions'.tr),
GetBuilder<HelpController>(
builder: (helpController) {
final questions = helpController.helpQuestionDate['message'];
if (questions == null || questions.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text('No questions asked yet.'.tr),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: SiroEmptyState(
icon: Icons.forum_outlined,
title: 'No questions asked yet.'.tr,
),
);
}
return ListView.separated(
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: questions.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
var list = questions[index];
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: theme.dividerColor),
),
return SiroCard(
padding: EdgeInsets.zero,
child: ListTile(
title: Text(
EncryptionHelper.instance
@@ -142,8 +114,8 @@ class HelpCaptain extends StatelessWidget {
list['datecreated'],
style: theme.textTheme.bodySmall,
),
trailing:
const Icon(Icons.arrow_forward_ios, size: 16),
trailing: Icon(Icons.arrow_forward_ios,
size: 14, color: SiroUi.subtle(context)),
onTap: () {
helpController.getIndex(
int.parse(EncryptionHelper.instance
@@ -159,173 +131,10 @@ class HelpCaptain extends StatelessWidget {
);
},
),
const SizedBox(height: 16),
],
),
),
],
);
}
}
// class HelpCaptain extends StatelessWidget {
// HelpCaptain({super.key});
// @override
// Widget build(BuildContext context) {
// Get.put(HelpController());
// return CupertinoPageScaffold(
// navigationBar: CupertinoNavigationBar(
// middle: Text('Helping Page'.tr),
// leading: CupertinoButton(
// padding: EdgeInsets.zero,
// child: Icon(CupertinoIcons.back),
// onPressed: () => Navigator.pop(context),
// ),
// ),
// child: SafeArea(
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// children: [
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 12.0),
// child: Container(
// padding: const EdgeInsets.all(16.0),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12.0),
// border: Border.all(
// color: CupertinoColors.systemGrey2,
// ),
// ),
// child: Text(
// 'If you need any help or have questions, this is the right place to do that. You are welcome!'
// .tr,
// style: CupertinoTheme.of(context).textTheme.textStyle,
// textAlign: TextAlign.center,
// ),
// ),
// ),
// Card(
// elevation: 3,
// color: CupertinoColors.systemGrey6,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12.0),
// ),
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: GetBuilder<HelpController>(
// builder: (helpController) => Form(
// key: helpController.formKey,
// child: Column(
// children: [
// CupertinoTextField(
// controller: helpController.helpQuestionController,
// placeholder: 'Enter your Question here'.tr,
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12),
// border: Border.all(
// color: CupertinoColors.systemGrey,
// ),
// ),
// padding: const EdgeInsets.all(16),
// clearButtonMode: OverlayVisibilityMode.editing,
// ),
// const SizedBox(height: 20),
// helpController.isLoading
// ? const CupertinoActivityIndicator()
// : CupertinoButton.filled(
// onPressed: () {
// if (helpController.formKey.currentState!
// .validate()) {
// helpController.addHelpQuestion();
// // Clear the feedback form
// helpController.formKey.currentState!
// .reset();
// }
// },
// child: Text('Submit Question'.tr),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// const SizedBox(height: 20),
// Expanded(
// child: GetBuilder<HelpController>(
// builder: (helpController) => Padding(
// padding: const EdgeInsets.all(10),
// child: Container(
// decoration: BoxDecoration(
// border: Border.all(
// color: CupertinoColors.systemGrey2,
// ),
// borderRadius: BorderRadius.circular(12.0),
// ),
// child: ListView.builder(
// itemCount: helpController.helpQuestionDate['message'] !=
// null
// ? helpController.helpQuestionDate['message'].length
// : 0,
// itemBuilder: (BuildContext context, int index) {
// var list =
// helpController.helpQuestionDate['message'][index];
// return Padding(
// padding: const EdgeInsets.all(3),
// child: Container(
// padding: const EdgeInsets.symmetric(
// vertical: 12, horizontal: 16),
// decoration: BoxDecoration(
// border: Border.all(
// color: CupertinoColors.activeGreen,
// width: 2,
// ),
// borderRadius: BorderRadius.circular(12),
// ),
// child: GestureDetector(
// onTap: () {
// helpController.getIndex(
// list['id'], list['helpQuestion']);
// helpController
// .getHelpRepley(list['id'].toString());
// Get.to(() => const HelpDetailsReplayPage());
// },
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// Expanded(
// child: Text(
// list['helpQuestion'],
// style: CupertinoTheme.of(context)
// .textTheme
// .textStyle,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// Text(
// list['datecreated'],
// style: CupertinoTheme.of(context)
// .textTheme
// .tabLabelTextStyle,
// ),
// ],
// ),
// ),
// ),
// );
// },
// ),
// ),
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
// }
@@ -79,7 +79,7 @@ class OrderRequestPage extends StatelessWidget {
zoom: 13.0,
),
markers: controller.markers,
mapType: Get.isDarkMode
mapType: Theme.of(context).brightness == Brightness.dark
? IntaleqMapType.normal
: IntaleqMapType.light,
polylines: controller.polylines,
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../controller/insurance/driver_insurance_controller.dart';
import '../../widgets/ui/siro_ui.dart';
/// شاشة التأمين: الوثيقة النشطة ودفترها، أو الخطط المتاحة.
///
@@ -16,18 +17,20 @@ class DriverInsurancePage extends StatelessWidget {
Widget build(BuildContext context) {
final c = Get.put(DriverInsuranceController());
return Scaffold(
appBar: AppBar(title: Text('Insurance'.tr), centerTitle: true),
return SiroPage(
title: 'Insurance'.tr,
body: Obx(() {
if (c.isLoading.value) {
return const Center(child: CircularProgressIndicator());
return const SiroLoading();
}
return RefreshIndicator(
onRefresh: c.refreshAll,
color: SiroUi.interactive(context),
child: ListView(
padding: const EdgeInsets.all(16),
children: c.hasPolicy ? _policyView(context, c) : _plansView(context, c),
padding: SiroUi.pageInsets,
children:
c.hasPolicy ? _policyView(context, c) : _plansView(context, c),
),
);
}),
@@ -39,29 +42,34 @@ class DriverInsurancePage extends StatelessWidget {
final p = c.policy.value!;
return [
Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
SiroCard(
accent: AppColor.greenColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.verified_user, color: Colors.green),
const SizedBox(width: 8),
const SiroIconBadge(Icons.verified_user_rounded,
color: AppColor.greenColor),
const SizedBox(width: 10),
Expanded(
child: Text(
'${p['plan'] ?? ''}',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${p['plan'] ?? ''}',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
Text('${p['provider'] ?? ''}',
style: Theme.of(context).textTheme.bodySmall),
],
),
),
],
),
const SizedBox(height: 4),
Text('${p['provider'] ?? ''}',
style: TextStyle(color: Colors.grey.shade600)),
const Divider(height: 24),
_row('Premium'.tr,
'${p['premium']} ${p['currency']} / '
@@ -72,28 +80,25 @@ class DriverInsurancePage extends StatelessWidget {
if ((p['coverage'] ?? '').toString().isNotEmpty) ...[
const SizedBox(height: 12),
Text('${p['coverage']}',
style: TextStyle(color: Colors.grey.shade700)),
style: Theme.of(context).textTheme.bodySmall),
],
],
),
),
),
const SizedBox(height: 16),
// ── دفتر الأقساط ──
// يظهر في نفس الشاشة لا في أخرى: الخصم الذي لا يرى السائق مقابله
// مفاجأة تُفقد الثقة، مهما كان مشروحاً في مكان آخر.
Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
SiroCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Premium ledger'.tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
_row('Outstanding'.tr,
'${c.pendingTotal.toStringAsFixed(2)} ${c.currency}'),
_row('Settled'.tr,
@@ -106,13 +111,13 @@ class DriverInsurancePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${e['charge_date']}',
style: TextStyle(color: Colors.grey.shade700)),
style: Theme.of(context).textTheme.bodySmall),
Text('${e['amount']} ${e['currency']}',
style: TextStyle(
color: e['status'] == 'settled'
? Colors.green
: Colors.orange.shade800,
fontWeight: FontWeight.w600,
? AppColor.greenColor
: AppColor.yellowColor,
fontWeight: FontWeight.w700,
)),
],
),
@@ -120,20 +125,21 @@ class DriverInsurancePage extends StatelessWidget {
],
],
),
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: c.isSubmitting.value ? null : () => _confirmCancel(context, c),
icon: const Icon(Icons.cancel_outlined, color: Colors.red),
label: Text('Cancel policy'.tr,
style: const TextStyle(color: Colors.red)),
onPressed:
c.isSubmitting.value ? null : () => _confirmCancel(context, c),
icon: const Icon(Icons.cancel_outlined, size: 18),
label: Text('Cancel policy'.tr),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.red),
foregroundColor: AppColor.redColor,
side: const BorderSide(color: AppColor.redColor),
minimumSize: const Size.fromHeight(48),
),
),
const SizedBox(height: 16),
];
}
@@ -144,34 +150,15 @@ class DriverInsurancePage extends StatelessWidget {
// ‏الحالة الحقيقية: لا خطط مُفعّلة بعد لدى الشركاء — لا خطأ ولا
// ‏نقص أهلية — ونقولها صراحة حتى لا يظن الكابتن أن التطبيق خربان.
return [
const SizedBox(height: 72),
Icon(Icons.health_and_safety_outlined,
size: 80, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text('No insurance plans available yet'.tr,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.grey)),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text(
'Insurance plans have not been launched in your area yet. Once a partner plan is available it will appear here automatically.'
.tr,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14, height: 1.5, color: Colors.grey.shade600),
),
),
const SizedBox(height: 20),
Center(
child: TextButton.icon(
onPressed: c.isLoading.value ? null : () => c.refreshAll(),
icon: const Icon(Icons.refresh_rounded, size: 18),
label: Text('Refresh'.tr),
),
const SizedBox(height: 48),
SiroEmptyState(
icon: Icons.health_and_safety_outlined,
title: 'No insurance plans available yet'.tr,
message:
'Insurance plans have not been launched in your area yet. Once a partner plan is available it will appear here automatically.'
.tr,
actionLabel: 'Refresh'.tr,
onAction: c.isLoading.value ? null : () => c.refreshAll(),
),
];
}
@@ -182,46 +169,58 @@ class DriverInsurancePage extends StatelessWidget {
final progress = (plan['progress_rides'] ?? 0) as num;
final required = (plan['required_rides'] ?? 0) as num;
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
return SiroCard(
accent: eligible ? AppColor.greenColor : null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${plan['name'] ?? ''}',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child: Text('${plan['name'] ?? ''}',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
),
if (eligible)
SiroTag('Eligible'.tr,
color: AppColor.greenColor,
icon: Icons.check_circle_rounded),
],
),
Text('${plan['provider'] ?? ''}',
style: TextStyle(color: Colors.grey.shade600)),
style: Theme.of(context).textTheme.bodySmall),
const SizedBox(height: 8),
Text(
'${plan['premium']} ${plan['currency']} / '
'${plan['billing_cycle'] == 'monthly' ? 'Monthly'.tr : 'Daily'.tr}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: SiroUi.interactive(context),
),
),
if ((plan['coverage'] ?? '').toString().isNotEmpty) ...[
const SizedBox(height: 8),
Text('${plan['coverage']}',
style: TextStyle(color: Colors.grey.shade700)),
style: Theme.of(context).textTheme.bodySmall),
],
// شريط التقدّم نحو الشرط — الرقم وحده لا يحرّك أحداً،
// ورؤية القرب من الهدف تفعل.
if (!eligible && required > 0) ...[
const SizedBox(height: 12),
LinearProgressIndicator(
value: required == 0 ? 0 : (progress / required).clamp(0, 1),
backgroundColor: Colors.grey.shade300,
ClipRRect(
borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator(
minHeight: 7,
value: required == 0 ? 0 : (progress / required).clamp(0, 1),
backgroundColor: SiroUi.border(context),
),
),
const SizedBox(height: 6),
Text('$progress / $required ${'rides'.tr}',
style: TextStyle(
fontSize: 12, color: Colors.grey.shade600)),
style: Theme.of(context).textTheme.bodySmall),
],
if (!eligible && missing.isNotEmpty) ...[
@@ -230,33 +229,31 @@ class DriverInsurancePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(Icons.lock_outline,
size: 14, color: Colors.orange),
size: 14, color: AppColor.yellowColor),
const SizedBox(width: 6),
Expanded(
child: Text('$m',
style: TextStyle(
style: const TextStyle(
fontSize: 12,
color: Colors.orange.shade900)),
color: AppColor.yellowColor)),
),
],
)),
],
const SizedBox(height: 12),
ElevatedButton(
onPressed: (!eligible || c.isSubmitting.value)
? null
: () => c.subscribe(plan['id'] as int),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(44),
const SizedBox(height: 14),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: (!eligible || c.isSubmitting.value)
? null
: () => c.subscribe(plan['id'] as int),
child:
Text(eligible ? 'Subscribe'.tr : 'Not eligible yet'.tr),
),
child: Text(eligible ? 'Subscribe'.tr : 'Not eligible yet'.tr),
),
],
),
),
);
}).toList();
}
@@ -283,7 +280,7 @@ class DriverInsurancePage extends StatelessWidget {
c.cancel();
},
child: Text('Confirm'.tr,
style: const TextStyle(color: Colors.red)),
style: const TextStyle(color: AppColor.redColor)),
),
],
),
@@ -293,18 +290,6 @@ class DriverInsurancePage extends StatelessWidget {
static String ledgerNum(dynamic v) =>
(double.tryParse('${v ?? 0}') ?? 0).toStringAsFixed(2);
Widget _row(String label, String value) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: Colors.grey.shade700)),
Flexible(
child: Text(value,
textAlign: TextAlign.end,
style: const TextStyle(fontWeight: FontWeight.w600)),
),
],
),
);
Widget _row(String label, String value) =>
SiroDetailRow(label: label, value: value);
}
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/scheduled/driver_scheduled_rides_controller.dart';
import '../../../constant/colors.dart';
import '../../widgets/ui/siro_ui.dart';
class DriverScheduledRidesPage extends StatelessWidget {
const DriverScheduledRidesPage({super.key});
@@ -10,75 +11,35 @@ class DriverScheduledRidesPage extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.put(DriverScheduledRidesController());
return Scaffold(
appBar: AppBar(
title: Text('Upcoming Scheduled Rides'.tr),
centerTitle: true,
),
return SiroPage(
title: 'Upcoming Scheduled Rides'.tr,
body: Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
return const SiroLoading();
}
// ‏تعذّر الجلب: نقولها بدل ادّعاء "لا حجوزات" — الفرق بينهما
// ‏هو الفرق بين كابتن يطمئن وكابتن يفوته عمل مؤكد.
if (controller.hasError.value) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.cloud_off_rounded,
size: 80, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text(
'Could not load scheduled rides'.tr,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.grey),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Please check your connection and try again.'.tr,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14, color: Colors.grey.shade600, height: 1.5),
),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: controller.fetch,
icon: const Icon(Icons.refresh_rounded, size: 18),
label: Text('Retry'.tr),
),
],
),
return SiroErrorState(
message: '${'Could not load scheduled rides'.tr}\n'
'${'Please check your connection and try again.'.tr}',
onRetry: controller.fetch,
);
}
if (controller.bookings.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.calendar_month,
size: 80, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text(
'You have no upcoming scheduled rides'.tr,
style: const TextStyle(fontSize: 18, color: Colors.grey),
),
],
),
return SiroEmptyState(
icon: Icons.calendar_month_rounded,
title: 'You have no upcoming scheduled rides'.tr,
);
}
return RefreshIndicator(
onRefresh: controller.fetch,
color: SiroUi.interactive(context),
child: ListView.builder(
padding: const EdgeInsets.all(12),
padding: SiroUi.pageInsets,
itemCount: controller.bookings.length,
itemBuilder: (context, index) {
final booking = controller.bookings[index];
@@ -91,75 +52,65 @@ class DriverScheduledRidesPage extends StatelessWidget {
}
Widget _buildBookingCard(Map<String, dynamic> booking, BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
const Icon(Icons.access_time,
size: 16, color: AppColor.primaryColor),
const SizedBox(width: 6),
Text(
booking['scheduled_at'] ?? '',
style: const TextStyle(
color: AppColor.primaryColor,
fontWeight: FontWeight.bold),
),
],
return SiroCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Align(
alignment: AlignmentDirectional.centerStart,
child: SiroTag(
booking['scheduled_at'] ?? '',
color: SiroUi.interactive(context),
icon: Icons.access_time_rounded,
),
),
Text(
'${booking['estimated_price']} ${'Currency'.tr}',
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(width: 8),
Text(
'${booking['estimated_price']} ${'Currency'.tr}',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.greenColor,
),
),
],
),
const SizedBox(height: 14),
Row(
children: [
const Icon(Icons.my_location_rounded,
color: AppColor.greenColor, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
booking['start_name'] ?? 'Starting Point'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 16),
Row(
children: [
const Icon(Icons.my_location, color: Colors.green, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
booking['start_name'] ?? 'Starting Point'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.location_on_rounded,
color: AppColor.redColor, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
booking['end_name'] ?? 'Destination'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.location_on, color: Colors.red, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
booking['end_name'] ?? 'Destination'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
],
),
],
),
);
}
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/finance_design_system.dart';
import '../../../controller/home/journal/schedule_controller.dart';
import '../../widgets/ui/siro_ui.dart';
class SchedulePage extends StatelessWidget {
SchedulePage({super.key});
@@ -9,71 +9,34 @@ class SchedulePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('My Schedule'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back()),
),
return SiroPage(
title: 'My Schedule'.tr,
padding: SiroUi.pageInsets,
body: GetBuilder<ScheduleController>(builder: (sc) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
// Summary Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
borderRadius:
BorderRadius.circular(FinanceDesignSystem.cardRadius),
),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Weekly Plan'.tr,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 14)),
const SizedBox(height: 8),
Text('${sc.totalWeeklyHours.toStringAsFixed(1)}h',
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w900,
color: Colors.white)),
Text('${sc.activeDays} ${'Days'.tr}',
style: TextStyle(
color: Colors.white.withValues(alpha: 0.6),
fontSize: 13)),
])),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SiroHeroHeader(
label: 'Weekly Plan'.tr,
value: '${sc.totalWeeklyHours.toStringAsFixed(1)}h',
caption: '${sc.activeDays} ${'Days'.tr}',
icon: Icons.calendar_today_rounded,
trailing: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(14)),
child: const Icon(Icons.calendar_today_rounded,
color: Colors.white, size: 28),
color: Colors.white.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(Icons.calendar_month_rounded,
color: Colors.white, size: 26),
),
]),
),
const SizedBox(height: 24),
Text('Work Days'.tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 12),
...sc.schedule.map((slot) => _buildDayCard(context, slot, sc)),
]),
),
SiroSectionTitle('Work Days'.tr),
...sc.schedule.map((slot) => _buildDayCard(context, slot, sc)),
const SizedBox(height: 16),
],
),
);
}),
);
@@ -82,76 +45,72 @@ class SchedulePage extends StatelessWidget {
Widget _buildDayCard(
BuildContext context, WorkSlot slot, ScheduleController sc) {
final isAr = Get.locale?.languageCode == 'ar';
return Container(
final accent = SiroUi.interactive(context);
final off = SiroUi.subtle(context);
return SiroCard(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: slot.isActive ? Colors.white : Colors.grey.shade50,
borderRadius: BorderRadius.circular(14),
boxShadow: slot.isActive
? [
BoxShadow(
color: Colors.black.withValues(alpha: 0.03),
blurRadius: 8,
offset: const Offset(0, 3))
]
: null,
),
accent: slot.isActive ? accent : null,
child: Row(children: [
// Toggle
GestureDetector(
onTap: () => sc.toggleDay(slot.dayOfWeek),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 44,
height: 44,
width: 42,
height: 42,
decoration: BoxDecoration(
color: slot.isActive
? FinanceDesignSystem.accentBlue.withValues(alpha: 0.1)
: Colors.grey.shade200,
? accent.withValues(alpha: 0.12)
: off.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
isAr ? slot.dayNameAr.substring(0, 2) : slot.dayName,
style: TextStyle(
child: Text(
isAr ? slot.dayNameAr.substring(0, 2) : slot.dayName,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: slot.isActive
? FinanceDesignSystem.accentBlue
: Colors.grey.shade400),
)),
color: slot.isActive ? accent : off,
),
),
),
),
),
const SizedBox(width: 14),
const SizedBox(width: 12),
// Day name
Expanded(
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(isAr ? slot.dayNameAr : slot.dayName.tr,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: slot.isActive
? FinanceDesignSystem.primaryDark
: Colors.grey.shade400)),
if (slot.isActive)
Text(slot.timeRange,
style: TextStyle(fontSize: 12, color: Colors.grey.shade500)),
if (!slot.isActive)
Text('Day Off'.tr,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade400,
fontStyle: FontStyle.italic)),
])),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isAr ? slot.dayNameAr : slot.dayName.tr,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: slot.isActive ? null : off,
),
),
Text(
slot.isActive ? slot.timeRange : 'Day Off'.tr,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 12,
fontStyle:
slot.isActive ? FontStyle.normal : FontStyle.italic,
),
),
],
),
),
// Time pickers
if (slot.isActive) ...[
_timePicker(context, slot.startTime,
(t) => sc.updateStartTime(slot.dayOfWeek, t)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text('-', style: TextStyle(color: Colors.grey.shade400))),
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Text('-', style: TextStyle(color: off)),
),
_timePicker(context, slot.endTime,
(t) => sc.updateEndTime(slot.dayOfWeek, t)),
],
@@ -159,7 +118,7 @@ class SchedulePage extends StatelessWidget {
Switch(
value: slot.isActive,
onChanged: (_) => sc.toggleDay(slot.dayOfWeek),
activeThumbColor: FinanceDesignSystem.accentBlue,
activeThumbColor: accent,
),
]),
);
@@ -174,16 +133,19 @@ class SchedulePage extends StatelessWidget {
if (picked != null) onChanged(picked);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8)),
color: SiroUi.interactive(context).withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: FinanceDesignSystem.primaryDark)),
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: SiroUi.interactive(context),
),
),
),
);
}
@@ -4,6 +4,7 @@ import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import '../../../controller/payment/smsPaymnet/pay_out_syria_controller.dart';
import '../../widgets/ui/siro_ui.dart';
class PayoutScreen extends StatefulWidget {
// استقبال كل البيانات المطلوبة جاهزة
@@ -128,10 +129,10 @@ class _PayoutScreenState extends State<PayoutScreen> {
// حساب المبلغ الإجمالي المخصوم
final totalDeducted = widget.amountToWithdraw + PayoutService.payoutFee;
return Scaffold(
appBar: AppBar(title: const Text("تأكيد سحب الأموال")),
return SiroPage(
title: "تأكيد سحب الأموال",
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
padding: SiroUi.pageInsets,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -164,13 +165,10 @@ class _PayoutScreenState extends State<PayoutScreen> {
}
Widget _buildSummaryCard(double totalDeducted) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
return SiroCard(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
_summaryRow("المبلغ المسحوب:",
"${widget.amountToWithdraw.toStringAsFixed(2)} ل.س"),
const Divider(),
@@ -187,7 +185,6 @@ class _PayoutScreenState extends State<PayoutScreen> {
_summaryRow("عبر محفظة:", widget.walletType),
],
),
),
);
}
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:siro_driver/constant/finance_design_system.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
import '../../widgets/ui/siro_ui.dart';
class PaymentHistoryDriverPage extends StatelessWidget {
const PaymentHistoryDriverPage({super.key});
@@ -13,26 +13,12 @@ class PaymentHistoryDriverPage extends StatelessWidget {
Widget build(BuildContext context) {
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Payment History'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
),
return SiroPage(
title: 'Payment History'.tr,
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
return const SiroLoading();
}
if (controller.archive.isEmpty) {
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/finance_design_system.dart';
import 'package:siro_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/my_textField.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
@@ -25,6 +24,7 @@ import 'widgets/quick_actions.dart';
import 'widgets/financial_summary_card.dart';
import 'widgets/promo_gamification_card.dart';
import 'widgets/transaction_preview_item.dart';
import '../../widgets/ui/siro_ui.dart';
class WalletCaptainRefactored extends StatefulWidget {
const WalletCaptainRefactored({super.key});
@@ -49,34 +49,20 @@ class _WalletCaptainRefactoredState extends State<WalletCaptainRefactored> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Driver Balance'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
// شريط العنوان من الثيم — لا ألوان مبنيّة يدوياً هنا.
return SiroPage(
title: 'Driver Balance'.tr,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () => controller.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
),
actions: [
IconButton(
icon: Icon(Icons.refresh_rounded,
color: FinanceDesignSystem.primaryDark),
onPressed: () => controller.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
),
],
),
],
body: GetBuilder<CaptainWalletController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
return const SiroLoading();
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(
@@ -145,7 +131,7 @@ class _WalletCaptainRefactoredState extends State<WalletCaptainRefactored> {
Text('Recharge Balance'.tr,
style: FinanceDesignSystem.headingStyle),
Icon(Icons.info_outline_rounded,
size: 18, color: Colors.grey.shade400),
size: 18, color: FinanceDesignSystem.textMuted),
],
),
const SizedBox(height: 12),
@@ -233,7 +219,7 @@ class _WalletCaptainRefactoredState extends State<WalletCaptainRefactored> {
padding: const EdgeInsets.symmetric(vertical: 20),
child: Center(
child: Text("No transactions yet".tr,
style: TextStyle(color: Colors.grey.shade400))),
style: TextStyle(color: FinanceDesignSystem.textMuted))),
);
}
// Show only last 3
@@ -270,8 +256,9 @@ class _WalletCaptainRefactoredState extends State<WalletCaptainRefactored> {
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(24),
// كان `Colors.white` ثابتاً ⇒ ورقة بيضاء بنص فاتح في الوضع الليلي.
decoration: BoxDecoration(
color: Colors.white,
color: FinanceDesignSystem.cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/finance_design_system.dart';
import 'package:siro_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
import '../../widgets/ui/siro_ui.dart';
class WeeklyPaymentPage extends StatelessWidget {
const WeeklyPaymentPage({super.key});
@@ -14,26 +14,12 @@ class WeeklyPaymentPage extends StatelessWidget {
Widget build(BuildContext context) {
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Weekly Summary'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
),
return SiroPage(
title: 'Weekly Summary'.tr,
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
return const SiroLoading();
}
return Column(
@@ -13,6 +13,7 @@ import 'package:siro_driver/controller/gamification/gamification_controller.dart
import 'package:siro_driver/controller/home/statistics/earnings_controller.dart';
import 'package:siro_driver/controller/home/statistics/earnings_models.dart';
import '../../widgets/ui/siro_ui.dart';
import 'widgets/behavior_and_achievements.dart';
import 'widgets/daily_goal_widget.dart';
import 'widgets/level_progress_widget.dart';
@@ -28,26 +29,14 @@ class EarningsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// شريط العنوان يأتي من الثيم — كان يبني ألوانه يدوياً في كل صفحة،
// فيختلف ارتفاعه ووزن خطه من شاشة لأخرى.
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: Text('أرباحي',
style: TextStyle(
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
),
appBar: AppBar(title: Text('My Earnings'.tr)),
body: GetBuilder<EarningsController>(
builder: (ctl) {
if (ctl.isLoading && ctl.summary == null) {
return const Center(child: CircularProgressIndicator());
return const SiroLoading();
}
// ‏لا بيانات ولا كاش: نعرض الخطأ صريحاً مع زر إعادة. الشاشة
@@ -1,84 +1,108 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/controller/notification/notification_captain_controller.dart';
import 'package:flutter/cupertino.dart';
import '../widgets/ui/siro_ui.dart';
class NotificationCaptain extends StatelessWidget {
const NotificationCaptain({Key? key}) : super(key: key);
const NotificationCaptain({super.key});
@override
Widget build(BuildContext context) {
Get.put(NotificationCaptainController());
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Notifications'.tr),
leading: CupertinoNavigationBarBackButton(
onPressed: () => Get.back(),
),
),
child: SafeArea(
child: GetBuilder<NotificationCaptainController>(
builder: (notificationCaptainController) =>
notificationCaptainController.isLoading
? const Center(child: CupertinoActivityIndicator())
: ListView.builder(
itemCount: notificationCaptainController
.notificationData['message'].length,
itemBuilder: (BuildContext context, int index) {
if (notificationCaptainController
.notificationData['message'] ==
"No notification data found") {
_showCupertinoDialog(context, 'No Notifications',
'There are no notifications at this time.');
return const SizedBox.shrink();
}
var res = notificationCaptainController
.notificationData['message'][index];
return CupertinoListTile(
leading: const Icon(CupertinoIcons.bell_fill),
title: Text(
res['title'],
style:
CupertinoTheme.of(context).textTheme.textStyle,
),
subtitle: Text(
res['body'],
style: CupertinoTheme.of(context)
return SiroPage(
title: 'Notifications'.tr,
body: GetBuilder<NotificationCaptainController>(
builder: (controller) {
if (controller.isLoading) return const SiroLoading();
final data = controller.notificationData['message'];
// الردّ قد يكون نصاً ("No notification data found") لا قائمة.
// كان الكود يقرأ `.length` عليه ثم يفتح حواراً من داخل `itemBuilder`
// — أي أثناء البناء، وهو ما يرمي استثناءً بدل عرض حالة فراغ.
final List items = data is List ? data : const [];
if (items.isEmpty) {
return SiroEmptyState(
icon: Icons.notifications_off_rounded,
title: 'No Notifications'.tr,
message: 'There are no notifications at this time.'.tr,
);
}
return ListView.builder(
padding: SiroUi.pageInsets,
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
final res = items[index];
return SiroCard(
onTap: () => _showDetails(
context,
res['title']?.toString() ?? '',
res['body']?.toString() ?? '',
() {
controller.updateNotification(res['id'].toString());
Navigator.of(context).pop();
},
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SiroIconBadge(Icons.notifications_rounded,
color: SiroUi.interactive(context)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
res['title']?.toString() ?? '',
style: Theme.of(context)
.textTheme
.tabLabelTextStyle,
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
),
onTap: () {
_showCupertinoDialog(
context,
res['title'],
res['body'],
onConfirm: () {
notificationCaptainController
.updateNotification(res['id'].toString());
Navigator.of(context).pop();
},
);
},
);
},
const SizedBox(height: 4),
Text(
res['body']?.toString() ?? '',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
),
],
),
);
},
);
},
),
);
}
void _showCupertinoDialog(BuildContext context, String title, String content,
{VoidCallback? onConfirm}) {
showCupertinoDialog(
void _showDetails(BuildContext context, String title, String content,
VoidCallback onConfirm) {
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
child: const Text('OK'),
onPressed: onConfirm ?? () => Navigator.of(context).pop(),
builder: (BuildContext ctx) => AlertDialog(
icon: SiroIconBadge(Icons.notifications_active_rounded,
color: SiroUi.interactive(ctx), size: 22),
title: Text(title, textAlign: TextAlign.center),
content: Text(content, textAlign: TextAlign.center),
actionsAlignment: MainAxisAlignment.center,
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('Back'.tr,
style: TextStyle(color: SiroUi.subtle(ctx))),
),
ElevatedButton(
onPressed: onConfirm,
child: Text('OK'.tr),
),
],
),
@@ -0,0 +1,585 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
/// عدّة الواجهة الموحّدة لتطبيق السائق.
///
/// كل صفحات القائمة الجانبية تُبنى من هذه القطع، فالشكل واحد والثيم الليلي
/// والنهاري يتبعان `Theme.of(context)` بلا ألوان مثبّتة داخل الصفحات.
///
/// **لا تضع منطقاً هنا** — القطع كلها عرض صرف بلا حالة ولا كنترولرات.
class SiroUi {
const SiroUi._();
// --- المسافات والانحناءات (مقياس واحد لكل التطبيق) ---
static const double gap = 12;
static const double pagePadding = 16;
static const double cardRadius = 18;
static const double controlRadius = 14;
static const EdgeInsets pageInsets =
EdgeInsets.symmetric(horizontal: pagePadding, vertical: 12);
/// لون العنصر التفاعلي حسب الوضع: النيفي يختفي على الخلفية الداكنة.
static Color interactive(BuildContext context) =>
Theme.of(context).colorScheme.primary;
static Color subtle(BuildContext context) =>
Theme.of(context).textTheme.bodySmall?.color ?? AppColor.grayColor;
static Color border(BuildContext context) => Theme.of(context).dividerColor;
static Color surface(BuildContext context) => Theme.of(context).cardColor;
/// تدرّج العلامة — يُستعمل في الرؤوس والبطاقات البارزة فقط، لا كخلفية صفحة.
static LinearGradient brandGradient(BuildContext context) {
final dark = Theme.of(context).brightness == Brightness.dark;
return LinearGradient(
colors: dark
? const [Color(0xFF1E2745), Color(0xFF2E3A63)]
: const [AppColor.primaryColor, Color(0xFF3A4674)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}
}
/// الهيكل الموحّد لكل صفحة داخلية.
///
/// يستبدل الخليط الحالي: `Scaffold` عادي و`CupertinoPageScaffold` و`SliverAppBar`
/// وخلفيات `Colors.white` المثبّتة (التي كانت تُنتج صفحة بيضاء بنص أبيض في
/// الوضع الليلي — كما في سجل الرحلات).
class SiroPage extends StatelessWidget {
final String title;
final Widget body;
final List<Widget>? actions;
final Widget? floatingActionButton;
final Widget? bottomBar;
final PreferredSizeWidget? appBarBottom;
final bool showBack;
final Future<void> Function()? onRefresh;
final EdgeInsetsGeometry? padding;
const SiroPage({
super.key,
required this.title,
required this.body,
this.actions,
this.floatingActionButton,
this.bottomBar,
this.appBarBottom,
this.showBack = true,
this.onRefresh,
this.padding,
});
@override
Widget build(BuildContext context) {
Widget content = padding == null
? body
: Padding(padding: padding!, child: body);
if (onRefresh != null) {
content = RefreshIndicator(
onRefresh: onRefresh!,
color: SiroUi.interactive(context),
child: content,
);
}
return Scaffold(
appBar: AppBar(
title: Text(title, overflow: TextOverflow.ellipsis),
automaticallyImplyLeading: showBack,
actions: actions,
bottom: appBarBottom,
),
body: SafeArea(child: content),
floatingActionButton: floatingActionButton,
bottomNavigationBar: bottomBar == null
? null
: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
child: bottomBar,
),
),
);
}
}
/// البطاقة الموحّدة: سطح + حدّ رفيع + انحناء واحد. بلا ظلال ثقيلة.
class SiroCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry? margin;
final VoidCallback? onTap;
final Color? accent;
const SiroCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(16),
this.margin,
this.onTap,
this.accent,
});
@override
Widget build(BuildContext context) {
final radius = BorderRadius.circular(SiroUi.cardRadius);
return Padding(
padding: margin ?? const EdgeInsets.only(bottom: SiroUi.gap),
child: Material(
color: SiroUi.surface(context),
borderRadius: radius,
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Container(
padding: padding,
decoration: BoxDecoration(
borderRadius: radius,
border: Border.all(
color: accent?.withValues(alpha: 0.35) ?? SiroUi.border(context),
),
),
child: child,
),
),
),
);
}
}
/// عنوان قسم داخل الصفحة، بخط جانبي بلون العلامة.
class SiroSectionTitle extends StatelessWidget {
final String title;
final Widget? trailing;
const SiroSectionTitle(this.title, {super.key, this.trailing});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 10),
child: Row(
children: [
Container(
width: 3,
height: 16,
decoration: BoxDecoration(
color: SiroUi.interactive(context),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
),
if (trailing != null) trailing!,
],
),
);
}
}
/// أيقونة داخل دائرة ملوّنة خفيفة — نفس نمط أيقونات القائمة الجانبية.
class SiroIconBadge extends StatelessWidget {
final IconData icon;
final Color color;
final double size;
const SiroIconBadge(this.icon,
{super.key, required this.color, this.size = 20});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(size * 0.45),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: size),
);
}
}
/// شارة نصية صغيرة (حالة رحلة، نوع عرض، ...).
class SiroTag extends StatelessWidget {
final String label;
final Color color;
final IconData? icon;
const SiroTag(this.label, {super.key, required this.color, this.icon});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(999),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 13, color: color),
const SizedBox(width: 4),
],
Text(
label,
style: TextStyle(
color: color, fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
);
}
}
/// خانة إحصاء (قيمة كبيرة + تسمية) — تُستعمل في الأرباح والمحفظة والملف.
class SiroStat extends StatelessWidget {
final String value;
final String label;
final IconData? icon;
final Color? color;
const SiroStat({
super.key,
required this.value,
required this.label,
this.icon,
this.color,
});
@override
Widget build(BuildContext context) {
final c = color ?? SiroUi.interactive(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
SiroIconBadge(icon!, color: c, size: 18),
const SizedBox(height: 8),
],
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
value,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: c,
),
),
),
const SizedBox(height: 2),
Text(
label,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}
/// صفّ إحصاءات متساوية داخل بطاقة واحدة، بفواصل رأسية.
class SiroStatRow extends StatelessWidget {
final List<SiroStat> stats;
const SiroStatRow(this.stats, {super.key});
@override
Widget build(BuildContext context) {
final children = <Widget>[];
for (var i = 0; i < stats.length; i++) {
if (i > 0) {
children.add(Container(
width: 1,
height: 40,
color: SiroUi.border(context),
));
}
children.add(Expanded(child: stats[i]));
}
return SiroCard(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
}
}
/// حالة الفراغ. **منفصلة عمداً عن [SiroErrorState]**: عرض «لا يوجد» عند فشل
/// الشبكة يُضلّل الكابتن ويجعله يظن أن لا عمل لديه.
class SiroEmptyState extends StatelessWidget {
final IconData icon;
final String title;
final String? message;
final String? actionLabel;
final VoidCallback? onAction;
const SiroEmptyState({
super.key,
required this.icon,
required this.title,
this.message,
this.actionLabel,
this.onAction,
});
@override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
color: SiroUi.interactive(context).withValues(alpha: 0.08),
shape: BoxShape.circle,
),
child: Icon(icon, size: 40, color: SiroUi.interactive(context)),
),
const SizedBox(height: 18),
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
if (message != null) ...[
const SizedBox(height: 8),
Text(
message!,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
),
],
if (onAction != null && actionLabel != null) ...[
const SizedBox(height: 20),
OutlinedButton(onPressed: onAction, child: Text(actionLabel!)),
],
],
),
),
);
}
}
/// حالة الخطأ — بلون تحذيري وبزر إعادة محاولة صريح.
class SiroErrorState extends StatelessWidget {
final String message;
final VoidCallback? onRetry;
const SiroErrorState({super.key, required this.message, this.onRetry});
@override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
color: AppColor.redColor.withValues(alpha: 0.10),
shape: BoxShape.circle,
),
child: const Icon(Icons.cloud_off_rounded,
size: 40, color: AppColor.redColor),
),
const SizedBox(height: 18),
Text(
message,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
if (onRetry != null) ...[
const SizedBox(height: 20),
OutlinedButton.icon(
onPressed: onRetry,
icon: const Icon(Icons.refresh_rounded, size: 18),
label: Text('Retry'.tr),
),
],
],
),
),
);
}
}
/// مؤشّر التحميل الموحّد.
class SiroLoading extends StatelessWidget {
final String? label;
const SiroLoading({super.key, this.label});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
strokeWidth: 2.6,
color: SiroUi.interactive(context),
),
if (label != null) ...[
const SizedBox(height: 14),
Text(label!, style: Theme.of(context).textTheme.bodySmall),
],
],
),
);
}
}
/// صفّ «تسمية ← قيمة» داخل بطاقة تفاصيل.
class SiroDetailRow extends StatelessWidget {
final String label;
final String value;
final IconData? icon;
final Color? valueColor;
const SiroDetailRow({
super.key,
required this.label,
required this.value,
this.icon,
this.valueColor,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 7),
child: Row(
children: [
if (icon != null) ...[
Icon(icon, size: 17, color: SiroUi.subtle(context)),
const SizedBox(width: 8),
],
Expanded(
child: Text(label, style: Theme.of(context).textTheme.bodySmall),
),
const SizedBox(width: 12),
Flexible(
child: Text(
value,
textAlign: TextAlign.end,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
color: valueColor,
),
),
),
],
),
);
}
}
/// رأس بارز بتدرّج العلامة — بديل الـ `SliverAppBar` الملوّن يدوياً في كل صفحة.
class SiroHeroHeader extends StatelessWidget {
final String label;
final String value;
final String? caption;
final IconData? icon;
final Widget? trailing;
const SiroHeroHeader({
super.key,
required this.label,
required this.value,
this.caption,
this.icon,
this.trailing,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: SiroUi.gap),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: SiroUi.brandGradient(context),
borderRadius: BorderRadius.circular(SiroUi.cardRadius),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
if (icon != null) ...[
Icon(icon, color: Colors.white70, size: 16),
const SizedBox(width: 6),
],
Flexible(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white70,
fontSize: 13,
fontWeight: FontWeight.w500),
),
),
],
),
const SizedBox(height: 8),
FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold),
),
),
if (caption != null) ...[
const SizedBox(height: 4),
Text(
caption!,
style: const TextStyle(color: Colors.white60, fontSize: 12),
),
],
],
),
),
if (trailing != null) trailing!,
],
),
);
}
}