feat: refactor financial wallet UI components and add offline map service support

This commit is contained in:
Hamza-Ayed
2026-04-21 00:35:30 +03:00
parent 4293d20561
commit b92db3bb39
99 changed files with 22888 additions and 27387 deletions

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/captin/behavior_controller.dart';
class BehaviorPage extends StatelessWidget {
@@ -12,6 +13,7 @@ class BehaviorPage extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.put(DriverBehaviorController());
controller.fetchDriverBehavior();
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
@@ -37,20 +39,23 @@ class BehaviorPage extends StatelessWidget {
child: Column(
children: [
Text("Overall Behavior Score".tr,
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Text(
"${controller.overallScore.value.toStringAsFixed(1)} / 100",
style: const TextStyle(
fontSize: 28, color: Colors.blue)),
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor)),
],
),
),
),
const SizedBox(height: 20),
Text("Last 10 Trips".tr,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
@@ -62,7 +67,7 @@ class BehaviorPage extends StatelessWidget {
elevation: 3,
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
backgroundColor: AppColor.primaryColor,
child: Text("${index + 1}",
style: const TextStyle(color: Colors.white)),
),

View File

@@ -41,50 +41,91 @@ class CaptainsCars extends StatelessWidget {
itemBuilder: (context, index) {
final car = controller.cars[index];
return Padding(
padding: const EdgeInsets.all(4.0),
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
child: Card(
color: car['isDefault'].toString() == '0'
? AppColor.accentColor
: AppColor.blueColor,
elevation: 2,
color: car['isDefault'].toString() == '1'
? AppColor.primaryColor
: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: car['isDefault'].toString() == '1'
? BorderSide.none
: BorderSide(color: Theme.of(context).dividerColor)),
child: ListTile(
leading: Icon(
Fontisto.car,
size: 50,
color: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
contentPadding: const EdgeInsets.all(12),
leading: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white12,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Fontisto.car,
size: 32,
color: car['isDefault'].toString() == '1'
? Colors.white
: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
),
),
title: Text(
car['make'],
style: AppStyle.title,
), // Assuming `make` is a field in each car item
subtitle: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
car['model'],
style: AppStyle.title,
),
Container(
decoration: BoxDecoration(
border: Border.all(
color: AppColor.blueColor)),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4),
child: Text(
(car['car_plate']),
style: AppStyle.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: car['isDefault'].toString() == '1'
? Colors.white
: Theme.of(context).textTheme.bodyLarge?.color,
),
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Text(
car['model'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
),
Text(
car['year'],
style: AppStyle.title,
),
],
), // Assuming `model` is a field in each car item
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: car['isDefault'].toString() == '1'
? Colors.white54
: AppColor.primaryColor,
),
),
child: Text(
car['car_plate'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: car['isDefault'].toString() == '1'
? Colors.white
: AppColor.primaryColor,
),
),
),
const Spacer(),
Text(
car['year'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
],
),
),
// trailing: IconButton(
// icon: const Icon(
// Icons.delete,

View File

@@ -99,29 +99,30 @@ class ProfileHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
CircleAvatar(
radius: 50,
backgroundColor: Get.theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: Get.theme.primaryColor),
backgroundColor: theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: theme.primaryColor),
),
const SizedBox(height: 12),
Text(
name,
style: Get.textTheme.headlineSmall
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
const Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text(
'${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)',
style: Get.textTheme.titleMedium
?.copyWith(color: Colors.grey.shade600),
style: theme.textTheme.titleMedium
?.copyWith(color: theme.hintColor),
),
],
),
@@ -130,6 +131,7 @@ class ProfileHeader extends StatelessWidget {
}
}
/// 2. ويدجت شبكة الأزرار
class ActionsGrid extends StatelessWidget {
const ActionsGrid({super.key});
@@ -187,20 +189,23 @@ void showShamCashInput() {
TextEditingController(text: existingCode);
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(25),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: Colors.black26, blurRadius: 10, offset: Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
Builder(builder: (context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- 1. المقبض العلوي ---
Center(
@@ -208,12 +213,13 @@ void showShamCashInput() {
height: 5,
width: 50,
decoration: BoxDecoration(
color: Colors.grey[300],
color: theme.dividerColor,
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.only(bottom: 20),
),
),
// --- 2. العنوان والأيقونة ---
Image.asset(
'assets/images/shamCash.png',
@@ -386,11 +392,13 @@ void showShamCashInput() {
],
),
),
),
isScrollControlled: true, // ضروري لرفع النافذة عند فتح الكيبورد
);
}),
isScrollControlled: true,
);
}
/// ويدجت داخلية لزر في الشبكة
class _ActionTile extends StatelessWidget {
final String title;
@@ -402,8 +410,9 @@ class _ActionTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: Colors.grey.shade100,
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
@@ -413,12 +422,12 @@ class _ActionTile extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Get.theme.primaryColor, size: 20),
Icon(icon, color: theme.primaryColor, size: 20),
const SizedBox(width: 8),
Flexible(
child: Text(
title,
style: Get.textTheme.labelLarge,
style: theme.textTheme.labelLarge,
textAlign: TextAlign.center,
)),
],
@@ -429,6 +438,7 @@ class _ActionTile extends StatelessWidget {
}
}
/// 3. بطاقة المعلومات الشخصية
class PersonalInfoCard extends StatelessWidget {
final Map<String, dynamic> data;
@@ -551,19 +561,21 @@ class _InfoRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(icon, color: Colors.grey.shade500, size: 20),
Icon(icon, color: theme.hintColor.withOpacity(0.6), size: 20),
const SizedBox(width: 16),
Text(label, style: Get.textTheme.bodyLarge),
Text(label, style: theme.textTheme.bodyLarge),
const Spacer(),
Flexible(
child: Text(
value,
style: Get.textTheme.bodyLarge?.copyWith(
color: Colors.grey.shade700, fontWeight: FontWeight.w500),
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
fontWeight: FontWeight.w500),
textAlign: TextAlign.end,
),
),
@@ -572,3 +584,4 @@ class _InfoRow extends StatelessWidget {
);
}
}

View File

@@ -26,77 +26,91 @@ class PromosPassengerPage extends StatelessWidget {
itemBuilder: (BuildContext context, int index) {
final rides = orderHistoryController.promoList[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: AppColor.secondaryColor,
boxShadow: [
BoxShadow(
color: AppColor.accentColor,
offset: Offset(-3, -3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer),
BoxShadow(
color: AppColor.accentColor,
offset: Offset(3, 3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer)
]),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Card(
elevation: 4,
shadowColor:
Theme.of(context).shadowColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
FlickerAnimatedText(
rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
],
isRepeatingAnimation: true,
onTap: () {},
),
Text(
rides['description'],
style: AppStyle.title,
),
],
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 30,
child: AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
WavyAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
],
repeatForever: true,
),
),
const SizedBox(height: 8),
Text(
rides['description'],
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
rides['validity_start_date'],
style: AppStyle.title,
),
Text(
rides['validity_end_date'],
style: AppStyle.title,
),
_buildDateBadge(context,
rides['validity_start_date'], true),
const SizedBox(height: 4),
_buildDateBadge(context,
rides['validity_end_date'], false),
],
),
],
),
const Divider(height: 24),
Text(
'Copy this Promo to use it in your Ride!'.tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle2
.copyWith(color: AppColor.accentColor),
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
)
],
),
@@ -109,4 +123,24 @@ class PromosPassengerPage extends StatelessWidget {
],
);
}
Widget _buildDateBadge(BuildContext context, String date, bool isStart) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isStart
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
date,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isStart ? Colors.green : Colors.red,
),
),
);
}
}

View File

@@ -5,6 +5,8 @@ import 'package:get/get.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
class TaarifPage extends StatelessWidget {
const TaarifPage({super.key});
@@ -18,47 +20,35 @@ class TaarifPage extends StatelessWidget {
// crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge,
children: [
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.symmetric(),
textBaseline: TextBaseline.alphabetic,
children: [
TableRow(
// decoration: AppStyle.boxDecoration,
children: [
Text('Minimum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title)
: Text('20 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Maximum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('200 ${'JOD'.tr}', style: AppStyle.title)
: Text('15000 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Flag-down fee'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title)
: Text('15 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km',
style: AppStyle.title)
: Text('1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km',
style: AppStyle.title),
Text('Including Tax'.tr, style: AppStyle.title),
],
),
],
_buildTariffItem(
context,
'Minimum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '1 ${'JOD'.tr}'
: '20 ${'LE'.tr}'),
_buildTariffItem(
context,
'Maximum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '200 ${'JOD'.tr}'
: '15000 ${'LE'.tr}'),
_buildTariffItem(
context,
'Flag-down fee'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.47 ${'JOD'.tr}'
: '15 ${'LE'.tr}'),
_buildTariffItem(
context,
'Rate'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km'
: '1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text('Including Tax'.tr,
style: AppStyle.subtitle
.copyWith(color: Theme.of(context).hintColor)),
),
const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2),
@@ -85,4 +75,21 @@ class TaarifPage extends StatelessWidget {
),
]);
}
Widget _buildTariffItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(label, style: AppStyle.title),
),
Text(value,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
],
),
);
}
}