feat: refactor financial wallet UI components and add offline map service support
This commit is contained in:
@@ -2,30 +2,35 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart'; // Assuming this has your text styles
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart'; // Assuming this is your loading widget
|
||||
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/constant/finance_design_system.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
import 'widgets/transaction_preview_item.dart';
|
||||
|
||||
class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
const PaymentHistoryDriverPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Initialize your controller
|
||||
Get.put(DriverWalletHistoryController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: FinanceDesignSystem.backgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text('Payment History'.tr),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
title: Text('Payment History'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.grey[100],
|
||||
body: GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) {
|
||||
if (controller.isLoading) {
|
||||
// Using your custom loading indicator
|
||||
return const Center(child: MyCircularProgressIndicator());
|
||||
}
|
||||
|
||||
@@ -34,16 +39,10 @@ class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.account_balance_wallet_outlined,
|
||||
size: 80, color: Colors.grey[400]),
|
||||
Icon(Icons.history_rounded, size: 80, color: Colors.grey.shade300),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No transactions yet'.tr,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: Colors.grey[600]),
|
||||
),
|
||||
Text('No transactions yet'.tr,
|
||||
style: TextStyle(color: Colors.grey.shade400, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -51,18 +50,26 @@ class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
|
||||
return AnimationLimiter(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
itemCount: controller.archive.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var transaction = controller.archive[index];
|
||||
|
||||
final tx = controller.archive[index];
|
||||
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
|
||||
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
child: FadeInAnimation(
|
||||
child: _TransactionCard(transaction: transaction),
|
||||
child: TransactionPreviewItem(
|
||||
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
|
||||
subtitle: tx['created_at'] ?? '',
|
||||
amount: amount.abs().toStringAsFixed(0),
|
||||
date: tx['created_at']?.split(' ')[0] ?? '',
|
||||
type: amount >= 0 ? 'credit' : 'debit',
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -74,71 +81,3 @@ class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// A dedicated widget for displaying a single transaction with a modern UI.
|
||||
class _TransactionCard extends StatelessWidget {
|
||||
final Map<String, dynamic> transaction;
|
||||
|
||||
const _TransactionCard({required this.transaction});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Safely parse the amount to avoid errors
|
||||
final double amount =
|
||||
double.tryParse(transaction['amount']?.toString() ?? '0') ?? 0;
|
||||
|
||||
final bool isCredit = amount >= 0;
|
||||
|
||||
final Color indicatorColor =
|
||||
isCredit ? AppColor.greenColor : AppColor.redColor;
|
||||
final IconData iconData =
|
||||
isCredit ? Icons.arrow_upward_rounded : Icons.arrow_downward_rounded;
|
||||
final String transactionType = (isCredit ? 'Credit'.tr : 'Debit'.tr).tr;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.05),
|
||||
margin: const EdgeInsets.only(bottom: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
clipBehavior: Clip.antiAlias, // Ensures the color bar is clipped neatly
|
||||
child: IntrinsicHeight(
|
||||
// Ensures the color bar and content have the same height
|
||||
child: Row(
|
||||
children: [
|
||||
// Left-side color indicator bar
|
||||
Container(width: 6, color: indicatorColor),
|
||||
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
leading: Icon(iconData, color: indicatorColor, size: 30),
|
||||
title: Text(
|
||||
// Use .abs() to remove the negative sign from the display
|
||||
'${amount.abs().toStringAsFixed(2)} ${'SYP'.tr}',
|
||||
style: AppStyle.title.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
transaction['created_at'] ?? 'No date',
|
||||
style: AppStyle.title.copyWith(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
trailing: Text(
|
||||
transactionType,
|
||||
style: AppStyle.title.copyWith(
|
||||
fontSize: 14,
|
||||
color: indicatorColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:sefer_driver/controller/payment/smsPaymnet/payment_services.dart
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/finance_design_system.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart';
|
||||
@@ -21,8 +22,8 @@ import '../../widgets/my_textField.dart';
|
||||
import 'ecash.dart';
|
||||
|
||||
class PointsCaptain extends StatelessWidget {
|
||||
PaymentController paymentController = Get.put(PaymentController());
|
||||
CaptainWalletController captainWalletController =
|
||||
final PaymentController paymentController = Get.put(PaymentController());
|
||||
final CaptainWalletController captainWalletController =
|
||||
Get.put(CaptainWalletController());
|
||||
|
||||
PointsCaptain({
|
||||
@@ -31,263 +32,67 @@ class PointsCaptain extends StatelessWidget {
|
||||
required this.countPoint,
|
||||
required this.pricePoint,
|
||||
});
|
||||
|
||||
final Color kolor;
|
||||
final String countPoint;
|
||||
double pricePoint;
|
||||
final double pricePoint;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
Get.defaultDialog(
|
||||
title: 'Which method you will pay'.tr,
|
||||
titleStyle: AppStyle.title,
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 12, bottom: 4),
|
||||
child: Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
elevation: 4,
|
||||
shadowColor: kolor.withOpacity(0.3),
|
||||
child: InkWell(
|
||||
onTap: () => _showPaymentOptions(context),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
width: 130,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
kolor.withOpacity(0.05),
|
||||
Colors.white,
|
||||
],
|
||||
),
|
||||
border: Border.all(color: kolor.withOpacity(0.2), width: 1.5),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}',
|
||||
style: AppStyle.title,
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: kolor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.account_balance_wallet_rounded,
|
||||
color: kolor, size: 24),
|
||||
),
|
||||
|
||||
// Add some spacing between buttons
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
payWithEcashDriver(context, pricePoint.toString());
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Pay with Debit Card'.tr),
|
||||
const SizedBox(width: 10),
|
||||
Icon(Icons.credit_card_sharp,
|
||||
color: AppColor.blueColor, size: 70),
|
||||
],
|
||||
)),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// Get.back();
|
||||
// Get.defaultDialog(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Insert Wallet phone number'.tr,
|
||||
// content: Form(
|
||||
// key: paymentController.formKey,
|
||||
// child: MyTextForm(
|
||||
// controller:
|
||||
// paymentController.walletphoneController,
|
||||
// label: 'Insert Wallet phone number'.tr,
|
||||
// hint: '963941234567',
|
||||
// type: TextInputType.phone)),
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'OK'.tr,
|
||||
// onPressed: () async {
|
||||
// Get.back();
|
||||
// if (paymentController.formKey.currentState!
|
||||
// .validate()) {
|
||||
// box.write(
|
||||
// BoxName.phoneWallet,
|
||||
// paymentController
|
||||
// .walletphoneController.text);
|
||||
// await payWithMTNWallet(
|
||||
// context, pricePoint.toString(), 'SYP');
|
||||
// }
|
||||
// }));
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text('Pay by MTN Wallet'.tr),
|
||||
// const SizedBox(width: 10),
|
||||
// Image.asset(
|
||||
// 'assets/images/cashMTN.png',
|
||||
// width: 70,
|
||||
// height: 70,
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller:
|
||||
paymentController.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963991234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (paymentController.formKey.currentState!
|
||||
.validate()) {
|
||||
box.write(
|
||||
BoxName.phoneWallet,
|
||||
paymentController
|
||||
.walletphoneController.text);
|
||||
await payWithSyriaTelWallet(
|
||||
context, pricePoint.toString(), 'SYP');
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Pay by Syriatel Wallet'.tr),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/syriatel.jpeg',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// التحقق بالبصمة قبل أي شيء
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication().authenticate(
|
||||
localizedReason:
|
||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
|
||||
if (!didAuthenticate) {
|
||||
print("❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||
Get.to(
|
||||
() => PaymentScreenSmsProvider(amount: pricePoint));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Pay by Sham Cash'.tr),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/shamCash.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
)),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// Get.back();
|
||||
// Get.defaultDialog(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Insert Wallet phone number'.tr,
|
||||
// content: Form(
|
||||
// key: paymentController.formKey,
|
||||
// child: MyTextForm(
|
||||
// controller:
|
||||
// paymentController.walletphoneController,
|
||||
// label: 'Insert Wallet phone number'.tr,
|
||||
// hint: '963941234567',
|
||||
// type: TextInputType.phone)),
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'OK'.tr,
|
||||
// onPressed: () async {
|
||||
// Get.back();
|
||||
// if (paymentController.formKey.currentState!
|
||||
// .validate()) {
|
||||
// box.write(
|
||||
// BoxName.phoneWallet,
|
||||
// paymentController
|
||||
// .walletphoneController.text);
|
||||
// // await payWithSyriaTelWallet(
|
||||
// // context, pricePoint.toString(), 'SYP');
|
||||
// bool isAuthSupported =
|
||||
// await LocalAuthentication()
|
||||
// .isDeviceSupported();
|
||||
// if (isAuthSupported) {
|
||||
// bool didAuthenticate =
|
||||
// await LocalAuthentication()
|
||||
// .authenticate(
|
||||
// localizedReason:
|
||||
// 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
// );
|
||||
// if (!didAuthenticate) {
|
||||
// if (Get.isDialogOpen ?? false) Get.back();
|
||||
// print(
|
||||
// "❌ User did not authenticate with biometrics");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// Get.to(() => PaymentScreenMtn(
|
||||
// amount: pricePoint,
|
||||
// userType: 'Driver',
|
||||
// ));
|
||||
// }
|
||||
// }));
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text('Pay by MTN Wallet'.tr),
|
||||
// const SizedBox(width: 10),
|
||||
// Image.asset(
|
||||
// 'assets/images/cashMTN.png',
|
||||
// width: 70,
|
||||
// height: 70,
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
],
|
||||
));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
|
||||
child: Container(
|
||||
width: Get.width * .22,
|
||||
height: Get.width * .22,
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
kolor.withOpacity(0.3),
|
||||
kolor,
|
||||
kolor.withOpacity(0.7),
|
||||
kolor,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
border: Border.all(color: AppColor.accentColor),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'$countPoint ${'L.S'.tr}',
|
||||
style: AppStyle.subtitle
|
||||
.copyWith(color: AppColor.secondaryColor),
|
||||
'$countPoint ${'SYP'.tr}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 15,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$pricePoint ${'L.S'.tr}',
|
||||
style:
|
||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
textAlign: TextAlign.center,
|
||||
'${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${'SYP'.tr}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -296,6 +101,183 @@ class PointsCaptain extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPaymentOptions(BuildContext context) {
|
||||
Get.bottomSheet(
|
||||
isScrollControlled: true,
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text("Select Payment Method".tr,
|
||||
style: FinanceDesignSystem.headingStyle),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close_rounded, color: Colors.grey),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"${'Amount to charge:'.tr} $countPoint ${'SYP'.tr}",
|
||||
style: FinanceDesignSystem.subHeadingStyle),
|
||||
const SizedBox(height: 24),
|
||||
_buildPaymentMethodTile(
|
||||
icon: Icons.credit_card_rounded,
|
||||
title: 'Debit Card'.tr,
|
||||
subtitle: 'E-Cash payment gateway'.tr,
|
||||
color: Colors.blue,
|
||||
onTap: () {
|
||||
Get.back();
|
||||
payWithEcashDriver(context, pricePoint.toString());
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPaymentMethodTile(
|
||||
image: 'assets/images/syriatel.jpeg',
|
||||
title: 'Syriatel Cash'.tr,
|
||||
subtitle: 'Pay using Syriatel mobile wallet'.tr,
|
||||
color: Colors.red,
|
||||
onTap: () => _showPhoneInputDialog(context, 'Syriatel'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPaymentMethodTile(
|
||||
image: 'assets/images/shamCash.png',
|
||||
title: 'Sham Cash'.tr,
|
||||
subtitle: 'Pay using Sham Cash wallet'.tr,
|
||||
color: Colors.orange,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication().authenticate(
|
||||
localizedReason: 'Confirm payment with biometrics'.tr,
|
||||
);
|
||||
if (!didAuthenticate) return;
|
||||
}
|
||||
Get.to(() => PaymentScreenSmsProvider(amount: pricePoint));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentMethodTile({
|
||||
IconData? icon,
|
||||
String? image,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required Color color,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.grey.shade200, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: image != null
|
||||
? Image.asset(image, fit: BoxFit.contain)
|
||||
: Icon(icon, color: color, size: 30),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: FinanceDesignSystem.primaryDark)),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitle,
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios_rounded,
|
||||
size: 14, color: Colors.grey.shade400),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPhoneInputDialog(BuildContext context, String provider) {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
title: 'Wallet Phone Number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller: paymentController.walletphoneController,
|
||||
label: 'Phone Number'.tr,
|
||||
hint: provider == 'Syriatel' ? '963991234567' : '963941234567',
|
||||
type: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Confirm'.tr,
|
||||
onPressed: () async {
|
||||
if (paymentController.formKey.currentState!.validate()) {
|
||||
Get.back();
|
||||
box.write(BoxName.phoneWallet,
|
||||
paymentController.walletphoneController.text);
|
||||
if (provider == 'Syriatel') {
|
||||
await payWithSyriaTelWallet(
|
||||
context, pricePoint.toString(), 'SYP');
|
||||
} else {
|
||||
await payWithMTNWallet(context, pricePoint.toString(), 'SYP');
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentScreen extends StatefulWidget {
|
||||
|
||||
@@ -1,521 +1,370 @@
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/controller/functions/tts.dart';
|
||||
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/info.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/constant/finance_design_system.dart';
|
||||
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_textField.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
|
||||
import 'package:sefer_driver/controller/payment/driver_payment_controller.dart';
|
||||
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import 'card_wallet_widget.dart';
|
||||
// Import new widgets
|
||||
import 'points_captain.dart';
|
||||
import 'transfer_budget_page.dart';
|
||||
import 'weekly_payment_page.dart';
|
||||
import 'widgets/balance_card.dart';
|
||||
import 'widgets/quick_actions.dart';
|
||||
import 'widgets/financial_summary_card.dart';
|
||||
import 'widgets/promo_gamification_card.dart';
|
||||
import 'widgets/transaction_preview_item.dart';
|
||||
|
||||
class WalletCaptainRefactored extends StatelessWidget {
|
||||
WalletCaptainRefactored({super.key});
|
||||
|
||||
final CaptainWalletController captainWalletController =
|
||||
Get.put(CaptainWalletController());
|
||||
|
||||
// دالة مساعدة لتحديد لون خلفية النقاط
|
||||
Color _getPointsColor(String pointsStr) {
|
||||
final points = double.tryParse(pointsStr) ?? 0.0;
|
||||
if (points < -30000) {
|
||||
return AppColor.redColor;
|
||||
} else if (points < 0 && points >= -30000) {
|
||||
return AppColor.yellowColor;
|
||||
} else {
|
||||
return AppColor.greenColor;
|
||||
}
|
||||
}
|
||||
final CaptainWalletController controller = Get.put(CaptainWalletController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
captainWalletController.refreshCaptainWallet();
|
||||
return MyScafolld(
|
||||
title: 'Driver Balance'.tr,
|
||||
isleading: true,
|
||||
action: IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () => captainWalletController.refreshCaptainWallet(),
|
||||
tooltip: 'Refresh'.tr,
|
||||
controller.refreshCaptainWallet();
|
||||
return Scaffold(
|
||||
backgroundColor: FinanceDesignSystem.backgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text('Driver Balance'.tr,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: FinanceDesignSystem.primaryDark)),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded,
|
||||
color: FinanceDesignSystem.primaryDark, size: 20),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh_rounded,
|
||||
color: FinanceDesignSystem.primaryDark),
|
||||
onPressed: () => controller.refreshCaptainWallet(),
|
||||
tooltip: 'Refresh'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: [
|
||||
GetBuilder<CaptainWalletController>(
|
||||
builder: (controller) {
|
||||
if (controller.isLoading) {
|
||||
return const MyCircularProgressIndicator();
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildTotalPointsSection(context, controller),
|
||||
const SizedBox(height: 16),
|
||||
const CardSeferWalletDriver(), // This can be redesigned if needed
|
||||
const SizedBox(height: 16),
|
||||
_buildWalletDetailsCard(context, controller),
|
||||
const SizedBox(height: 24),
|
||||
_buildPromoSection(controller),
|
||||
const SizedBox(height: 24),
|
||||
_buildNavigationButtons(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// القسم العلوي لعرض النقاط الإجمالية
|
||||
Widget _buildTotalPointsSection(
|
||||
BuildContext context, CaptainWalletController controller) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
color: _getPointsColor(controller.totalPoints.toString()),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) => CupertinoAlertDialog(
|
||||
title: Text('Info'.tr),
|
||||
content: Text(
|
||||
'The 30000 points equal 30000 S.P for you \nSo go and gain your money'
|
||||
.tr),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
child: Text("OK".tr),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
body: GetBuilder<CaptainWalletController>(
|
||||
builder: (controller) {
|
||||
if (controller.isLoading) {
|
||||
return const Center(child: MyCircularProgressIndicator());
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: FinanceDesignSystem.horizontalPadding,
|
||||
vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 1. Header / Balance
|
||||
BalanceCard(
|
||||
balance: controller.totalPoints.toString(),
|
||||
isNegative:
|
||||
double.tryParse(controller.totalPoints.toString()) !=
|
||||
null &&
|
||||
double.parse(controller.totalPoints.toString()) <
|
||||
-30000,
|
||||
lastUpdated: "Just now".tr,
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: FinanceDesignSystem.verticalSectionPadding),
|
||||
|
||||
// 2. Quick Actions
|
||||
QuickActionsGrid(
|
||||
onAddBalance: () =>
|
||||
_showAddBalanceOptions(context, controller),
|
||||
onWithdraw: () => addSyrianPaymentMethod(controller),
|
||||
onTransfer: () => Get.to(() => TransferBudgetPage()),
|
||||
onHistory: () async {
|
||||
await Get.put(DriverWalletHistoryController())
|
||||
.getArchivePayment();
|
||||
Get.to(() => const PaymentHistoryDriverPage());
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: FinanceDesignSystem.verticalSectionPadding),
|
||||
|
||||
// 3. Earnings Summary
|
||||
FinancialSummaryCard(
|
||||
title: 'Earnings Summary'.tr,
|
||||
subtitle: 'ملخص الأرباح'.tr,
|
||||
items: [
|
||||
SummaryItem(
|
||||
icon: Icons.money_rounded,
|
||||
label: 'Cash Earnings'.tr,
|
||||
amount: controller.totalAmount,
|
||||
color: FinanceDesignSystem.successGreen,
|
||||
),
|
||||
SummaryItem(
|
||||
icon: Icons.credit_card_rounded,
|
||||
label: 'Card Earnings'.tr,
|
||||
amount: controller.totalAmountVisa,
|
||||
color: FinanceDesignSystem.accentBlue,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: FinanceDesignSystem.verticalSectionPadding),
|
||||
|
||||
// 3. Recharge Balance Packages
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Recharge Balance'.tr,
|
||||
style: FinanceDesignSystem.headingStyle),
|
||||
Icon(Icons.info_outline_rounded,
|
||||
size: 18, color: Colors.grey.shade400),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 140, // Increased height for modern cards
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
PointsCaptain(
|
||||
kolor: Colors.blueGrey,
|
||||
pricePoint: 100,
|
||||
countPoint: '100'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.brown,
|
||||
pricePoint: 200,
|
||||
countPoint: '210'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.amber,
|
||||
pricePoint: 400,
|
||||
countPoint: '450'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.orange,
|
||||
pricePoint: 1000,
|
||||
countPoint: '1100'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: FinanceDesignSystem.verticalSectionPadding),
|
||||
|
||||
// 5. Promotions
|
||||
Text('Promotions'.tr, style: FinanceDesignSystem.headingStyle),
|
||||
const SizedBox(height: 12),
|
||||
PromoGamificationCard(
|
||||
title: 'Morning Promo'.tr,
|
||||
subtitle: "from 7:00am to 10:00am".tr,
|
||||
currentProgress: controller.walletDate['message']?[0]
|
||||
?['morning_count'] ??
|
||||
0,
|
||||
targetProgress: 5,
|
||||
reward: "+50 SYP",
|
||||
onTap: () =>
|
||||
controller.addDriverWalletFromPromo('Morning Promo', 50),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
PromoGamificationCard(
|
||||
title: 'Afternoon Promo'.tr,
|
||||
subtitle: "from 3:00pm to 6:00 pm".tr,
|
||||
currentProgress: controller.walletDate['message']?[0]
|
||||
?['afternoon_count'] ??
|
||||
0,
|
||||
targetProgress: 5,
|
||||
reward: "+50 SYP",
|
||||
onTap: () => controller.addDriverWalletFromPromo(
|
||||
'Afternoon Promo', 50),
|
||||
),
|
||||
|
||||
const SizedBox(
|
||||
height: FinanceDesignSystem.verticalSectionPadding),
|
||||
|
||||
// 6. Transactions Preview
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Recent Transactions'.tr,
|
||||
style: FinanceDesignSystem.headingStyle),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Get.put(DriverWalletHistoryController())
|
||||
.getArchivePayment();
|
||||
Get.to(() => const PaymentHistoryDriverPage());
|
||||
},
|
||||
child: Text('View All'.tr,
|
||||
style: const TextStyle(
|
||||
color: FinanceDesignSystem.accentBlue,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
],
|
||||
),
|
||||
GetBuilder<DriverWalletHistoryController>(
|
||||
init: DriverWalletHistoryController(),
|
||||
builder: (historyController) {
|
||||
if (historyController.archive.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Center(
|
||||
child: Text("No transactions yet".tr,
|
||||
style: TextStyle(color: Colors.grey.shade400))),
|
||||
);
|
||||
}
|
||||
// Show only last 3
|
||||
final lastThree =
|
||||
historyController.archive.take(3).toList();
|
||||
return Column(
|
||||
children: lastThree.map((tx) {
|
||||
final double amount =
|
||||
double.tryParse(tx['amount']?.toString() ?? '0') ??
|
||||
0;
|
||||
return TransactionPreviewItem(
|
||||
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
|
||||
subtitle: tx['created_at'] ?? '',
|
||||
amount: amount.abs().toStringAsFixed(0),
|
||||
date: tx['created_at']?.split(' ')[0] ?? '',
|
||||
type: amount >= 0 ? 'credit' : 'debit',
|
||||
onTap: () {},
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'رصيد التشغيل 💎',
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
controller.totalPoints.toString(),
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Colors.white,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w900),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (double.parse(controller.totalPoints.toString()) < -30000)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: CupertinoButton(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
onPressed: () {
|
||||
// Add your charge account logic here
|
||||
},
|
||||
child: Text(
|
||||
'Charge your Account'.tr,
|
||||
style: TextStyle(
|
||||
color: AppColor.redColor,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// بطاقة لعرض تفاصيل المحفظة وخيارات الشراء
|
||||
Widget _buildWalletDetailsCard(
|
||||
void _showAddBalanceOptions(
|
||||
BuildContext context, CaptainWalletController controller) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_BudgetInfoRow(
|
||||
title: 'Total Budget from trips is '.tr,
|
||||
amount: controller.totalAmount,
|
||||
onTap: () {
|
||||
Get.snackbar(
|
||||
icon: InkWell(
|
||||
onTap: () async => await Get.put(TextToSpeechController())
|
||||
.speakText(
|
||||
'This amount for all trip I get from Passengers'
|
||||
.tr),
|
||||
child: const Icon(Icons.headphones)),
|
||||
'${'Total Amount:'.tr} ${controller.totalAmount} ${'SYP'.tr}',
|
||||
'This amount for all trip I get from Passengers'.tr,
|
||||
duration: const Duration(seconds: 6),
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(height: 32),
|
||||
_BudgetInfoRow(
|
||||
title: 'Total Budget from trips by\nCredit card is '.tr,
|
||||
amount: controller.totalAmountVisa,
|
||||
onTap: () {
|
||||
Get.snackbar(
|
||||
icon: InkWell(
|
||||
onTap: () async => await Get.find<TextToSpeechController>()
|
||||
.speakText(
|
||||
'This amount for all trip I get from Passengers and Collected For me in'
|
||||
.tr +
|
||||
' Intaleq Wallet'.tr),
|
||||
child: const Icon(Icons.headphones),
|
||||
),
|
||||
'${'Total Amount:'.tr} ${controller.totalAmountVisa} ${'SYP'.tr}',
|
||||
'This amount for all trip I get from Passengers and Collected For me in'
|
||||
.tr +
|
||||
' ${AppInformation.appName} Wallet'.tr,
|
||||
duration: const Duration(seconds: 6),
|
||||
backgroundColor: AppColor.redColor,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_buildBuyPointsButton(controller),
|
||||
const SizedBox(height: 16),
|
||||
// _buildTransferBudgetButton(controller), // Uncomment if needed
|
||||
_buildPurchaseInstructions(),
|
||||
Text("Add Balance".tr, style: FinanceDesignSystem.headingStyle),
|
||||
const SizedBox(height: 8),
|
||||
_buildPointsOptions(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// قسم العروض الترويجية
|
||||
Widget _buildPromoSection(CaptainWalletController controller) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Today's Promo".tr, style: AppStyle.headTitle),
|
||||
const SizedBox(height: 10),
|
||||
_PromoProgressCard(
|
||||
title: 'Morning Promo'.tr,
|
||||
timePromo: 'Morning Promo',
|
||||
count: (controller.walletDate['message'][0]['morning_count']),
|
||||
maxCount: 5,
|
||||
description:
|
||||
"this is count of your all trips in the morning promo today from 7:00am to 10:00am"
|
||||
.tr,
|
||||
controller: controller,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_PromoProgressCard(
|
||||
title: 'Afternoon Promo'.tr,
|
||||
timePromo: 'Afternoon Promo',
|
||||
count: (controller.walletDate['message'][0]['afternoon_count']),
|
||||
maxCount: 5,
|
||||
description:
|
||||
"this is count of your all trips in the Afternoon promo today from 3:00pm to 6:00 pm"
|
||||
.tr,
|
||||
controller: controller,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// أزرار التنقل السفلية
|
||||
Widget _buildNavigationButtons() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyElevatedButton(
|
||||
kolor: AppColor.blueColor,
|
||||
title: 'Payment History'.tr,
|
||||
onPressed: () async {
|
||||
await Get.put(DriverWalletHistoryController())
|
||||
.getArchivePayment();
|
||||
Get.to(() => const PaymentHistoryDriverPage(),
|
||||
transition: Transition.size);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: MyElevatedButton(
|
||||
kolor: AppColor.blueColor,
|
||||
title: 'Weekly Budget'.tr,
|
||||
onPressed: () async {
|
||||
await Get.put(DriverWalletHistoryController())
|
||||
.getWeekllyArchivePayment();
|
||||
Get.to(() => const WeeklyPaymentPage(),
|
||||
transition: Transition.size);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// --- حافظت على هذه الدوال كما هي لأنها تحتوي على منطق مهم ---
|
||||
Widget _buildBuyPointsButton(CaptainWalletController controller) {
|
||||
return MyElevatedButton(
|
||||
title: 'You can buy points from your budget'.tr,
|
||||
onPressed: () {
|
||||
Get.defaultDialog(
|
||||
title: 'Pay from my budget'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.amountFromBudgetController,
|
||||
label:
|
||||
'${'You have in account'.tr} ${controller.totalAmountVisa}',
|
||||
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
|
||||
type: TextInputType.number,
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Pay'.tr,
|
||||
onPressed: () async {
|
||||
bool isAvailable =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
if (isAvailable) {
|
||||
// Authenticate the user
|
||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
||||
localizedReason:
|
||||
'Use Touch ID or Face ID to confirm payment'.tr,
|
||||
// options: const AuthenticationOptions(
|
||||
biometricOnly: true,
|
||||
sensitiveTransaction: true,
|
||||
);
|
||||
if (didAuthenticate) {
|
||||
if (double.parse(controller.amountFromBudgetController.text) <
|
||||
double.parse(controller.totalAmountVisa)) {
|
||||
await controller.payFromBudget();
|
||||
} else {
|
||||
Get.back();
|
||||
|
||||
mySnackeBarError('Your Budget less than needed'.tr);
|
||||
}
|
||||
} else {
|
||||
// Authentication failed, handle accordingly
|
||||
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
|
||||
Get.back();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
MyDialog().getDialog('Biometric Authentication'.tr,
|
||||
'You should use Touch ID or Face ID to confirm payment'.tr,
|
||||
() {
|
||||
Get.back();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPurchaseInstructions() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.accentColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColor.accentColor.withOpacity(0.3)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"You can purchase a budget to enable online access through the options listed below"
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title.copyWith(fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPointsOptions() {
|
||||
return SizedBox(
|
||||
height: Get.height * 0.19,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
PointsCaptain(
|
||||
kolor: AppColor.greyColor, pricePoint: 100, countPoint: '100'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.bronze, pricePoint: 200, countPoint: '210'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.goldenBronze, pricePoint: 400, countPoint: '450'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.gold, pricePoint: 1000, countPoint: '1100'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ويدجت مُحسّن لعرض صف معلومات الرصيد
|
||||
class _BudgetInfoRow extends StatelessWidget {
|
||||
final String title;
|
||||
final String amount;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _BudgetInfoRow({
|
||||
required this.title,
|
||||
required this.amount,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
Text("Select how you want to charge your account".tr,
|
||||
style: FinanceDesignSystem.subHeadingStyle),
|
||||
const SizedBox(height: 24),
|
||||
ListTile(
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: FinanceDesignSystem.accentBlue.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12)),
|
||||
child: const Icon(Icons.account_balance_wallet_rounded,
|
||||
color: FinanceDesignSystem.accentBlue),
|
||||
),
|
||||
title: Text("Pay from my budget".tr),
|
||||
subtitle: Text(
|
||||
"${'You have in account'.tr} ${controller.totalAmountVisa}"),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_showPayFromBudgetDialog(controller);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.accentColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Text(
|
||||
'$amount ${'S.P'.tr}',
|
||||
style:
|
||||
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ويدجت مُحسّن لعرض بطاقة العرض الترويجي
|
||||
class _PromoProgressCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String timePromo;
|
||||
final int count;
|
||||
final int maxCount;
|
||||
final String description;
|
||||
final CaptainWalletController controller;
|
||||
|
||||
const _PromoProgressCard({
|
||||
required this.title,
|
||||
required this.timePromo,
|
||||
required this.count,
|
||||
required this.maxCount,
|
||||
required this.description,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
MyDialog().getDialog(title, description, () async {
|
||||
if (count >= maxCount) {
|
||||
controller.addDriverWalletFromPromo(timePromo, 50);
|
||||
}
|
||||
Get.back();
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text("Recharge Balance Packages".tr,
|
||||
style: FinanceDesignSystem.subHeadingStyle
|
||||
.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 140,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
Text(title,
|
||||
style:
|
||||
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'$count / $maxCount',
|
||||
style: AppStyle.title.copyWith(color: AppColor.blueColor),
|
||||
),
|
||||
PointsCaptain(
|
||||
kolor: Colors.blueGrey,
|
||||
pricePoint: 100,
|
||||
countPoint: '100'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.brown, pricePoint: 200, countPoint: '210'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.amber, pricePoint: 400, countPoint: '450'),
|
||||
PointsCaptain(
|
||||
kolor: Colors.orange,
|
||||
pricePoint: 1000,
|
||||
countPoint: '1100'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 12,
|
||||
value: count / maxCount,
|
||||
backgroundColor: AppColor.accentColor.withOpacity(0.2),
|
||||
color: count >= maxCount
|
||||
? AppColor.greenColor
|
||||
: AppColor.blueColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showPayFromBudgetDialog(CaptainWalletController controller) {
|
||||
Get.defaultDialog(
|
||||
title: 'Pay from my budget'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.amountFromBudgetController,
|
||||
label: '${'You have in account'.tr} ${controller.totalAmountVisa}',
|
||||
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
|
||||
type: TextInputType.number,
|
||||
),
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Pay'.tr,
|
||||
onPressed: () async {
|
||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
||||
if (isAvailable) {
|
||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
||||
localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr,
|
||||
biometricOnly: true,
|
||||
sensitiveTransaction: true,
|
||||
);
|
||||
if (didAuthenticate) {
|
||||
if (double.parse(controller.amountFromBudgetController.text) <
|
||||
double.parse(controller.totalAmountVisa)) {
|
||||
await controller.payFromBudget();
|
||||
} else {
|
||||
Get.back();
|
||||
mySnackeBarError('Your Budget less than needed'.tr);
|
||||
}
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'Authentication failed'.tr, ''.tr, () => Get.back());
|
||||
}
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'Biometric Authentication'.tr,
|
||||
'You should use Touch ID or Face ID to confirm payment'.tr,
|
||||
() => Get.back());
|
||||
}
|
||||
},
|
||||
),
|
||||
cancel: MyElevatedButton(title: 'Cancel'.tr, onPressed: () => Get.back()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- الدوال والويدجتس الخاصة بالدفع في سوريا تبقى كما هي ---
|
||||
// This function is a placeholder for adding Syrian payment methods.
|
||||
// You would implement the UI and logic for mobile wallets or other local options here.
|
||||
Future<dynamic> addSyrianPaymentMethod(
|
||||
CaptainWalletController captainWalletController) {
|
||||
return Get.defaultDialog(
|
||||
@@ -528,15 +377,12 @@ Future<dynamic> addSyrianPaymentMethod(
|
||||
"Insert your mobile wallet details to receive your money weekly"
|
||||
.tr),
|
||||
MyTextForm(
|
||||
controller: captainWalletController
|
||||
.cardBank, // Re-using for mobile number
|
||||
controller: captainWalletController.cardBank,
|
||||
label: "Insert mobile wallet number".tr,
|
||||
hint: '0912 345 678',
|
||||
type: TextInputType.phone),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
MyDropDownSyria() // Dropdown for Syrian providers
|
||||
const SizedBox(height: 10),
|
||||
MyDropDownSyria()
|
||||
],
|
||||
)),
|
||||
confirm: MyElevatedButton(
|
||||
@@ -545,7 +391,6 @@ Future<dynamic> addSyrianPaymentMethod(
|
||||
if (captainWalletController.formKeyAccount.currentState!
|
||||
.validate()) {
|
||||
Get.back();
|
||||
// Replace with your actual API endpoint and payload for Syria
|
||||
var res =
|
||||
await CRUD().post(link: AppLink.updateAccountBank, payload: {
|
||||
"paymentProvider":
|
||||
@@ -554,7 +399,6 @@ Future<dynamic> addSyrianPaymentMethod(
|
||||
captainWalletController.cardBank.text.toString(),
|
||||
"id": box.read(BoxName.driverID).toString()
|
||||
});
|
||||
print('res: $res');
|
||||
if (res != 'failure') {
|
||||
mySnackbarSuccess('Payment details added successfully'.tr);
|
||||
}
|
||||
@@ -562,10 +406,8 @@ Future<dynamic> addSyrianPaymentMethod(
|
||||
}));
|
||||
}
|
||||
|
||||
// A new GetX controller for the Syrian payout dropdown
|
||||
class SyrianPayoutController extends GetxController {
|
||||
String dropdownValue = 'syriatel';
|
||||
|
||||
void changeValue(String? newValue) {
|
||||
if (newValue != null) {
|
||||
dropdownValue = newValue;
|
||||
@@ -574,33 +416,25 @@ class SyrianPayoutController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
// A new Dropdown widget for Syrian mobile wallet providers
|
||||
class MyDropDownSyria extends StatelessWidget {
|
||||
const MyDropDownSyria({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(SyrianPayoutController());
|
||||
final theme = Theme.of(context);
|
||||
return GetBuilder<SyrianPayoutController>(builder: (controller) {
|
||||
return DropdownButton<String>(
|
||||
value: controller.dropdownValue,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
elevation: 16,
|
||||
isExpanded: true,
|
||||
style: const TextStyle(color: Colors.deepPurple),
|
||||
underline: Container(
|
||||
height: 2,
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
onChanged: (String? newValue) {
|
||||
controller.changeValue(newValue);
|
||||
},
|
||||
dropdownColor: theme.cardColor,
|
||||
style: TextStyle(color: theme.textTheme.bodyLarge?.color),
|
||||
underline: Container(height: 2, color: theme.primaryColor),
|
||||
onChanged: (String? newValue) => controller.changeValue(newValue),
|
||||
items: <String>['syriatel', 'mtn']
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value.tr),
|
||||
);
|
||||
return DropdownMenuItem<String>(value: value, child: Text(value.tr));
|
||||
}).toList(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,11 +2,12 @@ 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 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/constant/finance_design_system.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
import 'widgets/transaction_preview_item.dart';
|
||||
|
||||
class WeeklyPaymentPage extends StatelessWidget {
|
||||
const WeeklyPaymentPage({super.key});
|
||||
@@ -16,12 +17,18 @@ class WeeklyPaymentPage extends StatelessWidget {
|
||||
Get.put(DriverWalletHistoryController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: FinanceDesignSystem.backgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text('Weekly Summary'.tr),
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
title: Text('Weekly Summary'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.grey[100],
|
||||
body: GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) {
|
||||
if (controller.isLoading) {
|
||||
@@ -30,45 +37,46 @@ class WeeklyPaymentPage extends StatelessWidget {
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// 1. Prominent Summary Card at the top
|
||||
_buildSummaryCard(controller),
|
||||
|
||||
// 2. A title for the transactions list
|
||||
_buildWeeklyStatsHeader(controller),
|
||||
const SizedBox(height: 16),
|
||||
_buildWeekSelector(),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.list_alt, color: Colors.grey[600]),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Transactions this week'.tr,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
Text('Transactions this week'.tr, style: FinanceDesignSystem.headingStyle),
|
||||
const Spacer(),
|
||||
Icon(Icons.list_rounded, color: Colors.grey.shade400, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 3. The animated list of transactions
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: controller.weeklyList.isEmpty
|
||||
? _buildEmptyState(context)
|
||||
: AnimationLimiter(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
|
||||
itemCount: controller.weeklyList.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var transaction = controller.weeklyList[index];
|
||||
final tx = controller.weeklyList[index];
|
||||
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
|
||||
return AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 375),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 50.0,
|
||||
verticalOffset: 25,
|
||||
child: FadeInAnimation(
|
||||
child: _TransactionListItem(
|
||||
transaction: transaction),
|
||||
child: TransactionPreviewItem(
|
||||
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
|
||||
subtitle: tx['dateUpdated'] ?? '',
|
||||
amount: amount.abs().toStringAsFixed(0),
|
||||
date: tx['dateUpdated']?.split(' ')[0] ?? '',
|
||||
type: amount >= 0 ? 'credit' : 'debit',
|
||||
method: tx['paymentMethod'],
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -83,92 +91,87 @@ class WeeklyPaymentPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// A widget for the top summary card.
|
||||
Widget _buildSummaryCard(DriverWalletHistoryController controller) {
|
||||
final String totalAmount = controller.weeklyList.isEmpty
|
||||
Widget _buildWeeklyStatsHeader(DriverWalletHistoryController controller) {
|
||||
final totalAmount = controller.weeklyList.isEmpty
|
||||
? '0.00'
|
||||
: controller.weeklyList[0]['totalAmount']?.toString() ?? '0.00';
|
||||
|
||||
final double earnings = double.tryParse(totalAmount) ?? 0;
|
||||
final int trips = controller.weeklyList.length;
|
||||
final double commission = earnings * 0.15; // Example 15%
|
||||
final double netProfit = earnings - commission;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(16.0),
|
||||
elevation: 4,
|
||||
shadowColor: AppColor.primaryColor.withOpacity(0.2),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.primaryColor, AppColor.greenColor],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
gradient: FinanceDesignSystem.balanceGradient,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: FinanceDesignSystem.primaryDark.withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.account_balance_wallet,
|
||||
color: Colors.white, size: 40),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Total Weekly Earnings'.tr,
|
||||
style: AppStyle.title
|
||||
.copyWith(color: Colors.white70, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'$totalAmount ${'SYP'.tr}',
|
||||
style: AppStyle.number
|
||||
.copyWith(color: Colors.white, fontSize: 32),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Total Weekly Earnings'.tr,
|
||||
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
|
||||
const SizedBox(height: 8),
|
||||
Text('${earnings.toStringAsFixed(0)} SYP',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildStatItem('Total Trips'.tr, trips.toString(), Icons.directions_car_rounded),
|
||||
_buildStatItem('Commission'.tr, '${commission.toStringAsFixed(0)}', Icons.percent_rounded),
|
||||
_buildStatItem('Net Profit'.tr, '${netProfit.toStringAsFixed(0)}', Icons.account_balance_rounded),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// A dedicated widget for the list item.
|
||||
Widget _TransactionListItem({required Map<String, dynamic> transaction}) {
|
||||
final String paymentMethod = transaction['paymentMethod'] ?? 'Unknown';
|
||||
final String amount = transaction['amount']?.toString() ?? '0';
|
||||
final DateTime? date = DateTime.tryParse(transaction['dateUpdated'] ?? '');
|
||||
Widget _buildStatItem(String label, String value, IconData icon) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(10)),
|
||||
child: Icon(icon, color: Colors.white, size: 18),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
Text(label, style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 10)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.05),
|
||||
margin: const EdgeInsets.only(bottom: 12.0),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
|
||||
child: Icon(_getPaymentIcon(paymentMethod),
|
||||
color: AppColor.primaryColor, size: 22),
|
||||
Widget _buildWeekSelector() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10)],
|
||||
),
|
||||
title: Text(
|
||||
'$amount ${'SYP'.tr}',
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
date != null
|
||||
? DateFormat('EEEE, hh:mm a', Get.locale?.toString())
|
||||
.format(date) // e.g., Tuesday, 10:11 AM
|
||||
: 'Invalid Date',
|
||||
style: AppStyle.title.copyWith(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
trailing: Chip(
|
||||
label: Text(
|
||||
_getTranslatedPaymentMethod(paymentMethod),
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
side: BorderSide.none,
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(icon: const Icon(Icons.chevron_left_rounded), onPressed: () {}),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text('Dec 15 - Dec 21, 2024'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: FinanceDesignSystem.primaryDark)),
|
||||
),
|
||||
),
|
||||
IconButton(icon: const Icon(Icons.chevron_right_rounded), onPressed: () {}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -179,45 +182,11 @@ class WeeklyPaymentPage extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.receipt_long_outlined, size: 80, color: Colors.grey[400]),
|
||||
Icon(Icons.bar_chart_rounded, size: 64, color: Colors.grey.shade300),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No transactions this week'.tr,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: Colors.grey[600]),
|
||||
),
|
||||
Text('No transactions this week'.tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to get a specific icon for each payment method.
|
||||
IconData _getPaymentIcon(String paymentMethod) {
|
||||
switch (paymentMethod.toLowerCase()) {
|
||||
case 'visa':
|
||||
return Icons.credit_card;
|
||||
case 'frombudget':
|
||||
return Icons.account_balance_wallet_outlined;
|
||||
case 'remainder':
|
||||
return Icons.receipt_long;
|
||||
case 'cash':
|
||||
return Icons.money;
|
||||
default:
|
||||
return Icons.payment;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get translated or formatted payment method names.
|
||||
String _getTranslatedPaymentMethod(String paymentMethod) {
|
||||
switch (paymentMethod) {
|
||||
case 'Remainder':
|
||||
return 'Remainder'.tr;
|
||||
case 'fromBudget':
|
||||
return 'From Budget'.tr;
|
||||
default:
|
||||
return paymentMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
124
lib/views/home/my_wallet/widgets/balance_card.dart
Normal file
124
lib/views/home/my_wallet/widgets/balance_card.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/finance_design_system.dart';
|
||||
|
||||
class BalanceCard extends StatelessWidget {
|
||||
final String balance;
|
||||
final bool isNegative;
|
||||
final String lastUpdated;
|
||||
|
||||
const BalanceCard({
|
||||
super.key,
|
||||
required this.balance,
|
||||
required this.isNegative,
|
||||
this.lastUpdated = "Just now",
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
|
||||
gradient: isNegative
|
||||
? FinanceDesignSystem.dangerGradient
|
||||
: FinanceDesignSystem.balanceGradient,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (isNegative
|
||||
? FinanceDesignSystem.dangerRed
|
||||
: FinanceDesignSystem.primaryDark)
|
||||
.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Available Balance".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"الرصيد المتاح".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isNegative
|
||||
? Icons.warning_rounded
|
||||
: Icons.account_balance_wallet_rounded,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(
|
||||
balance,
|
||||
style: FinanceDesignSystem.balanceStyle,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"SYP".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Divider(color: Colors.white.withOpacity(0.15)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.history_rounded,
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
size: 14,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
"${'Last updated:'.tr} $lastUpdated",
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
142
lib/views/home/my_wallet/widgets/financial_summary_card.dart
Normal file
142
lib/views/home/my_wallet/widgets/financial_summary_card.dart
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/finance_design_system.dart';
|
||||
|
||||
class FinancialSummaryCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final List<SummaryItem> items;
|
||||
|
||||
const FinancialSummaryCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: FinanceDesignSystem.headingStyle),
|
||||
if (subtitle != null)
|
||||
Text(subtitle!, style: FinanceDesignSystem.subHeadingStyle),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.03),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: items.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
Divider(color: Colors.grey.withOpacity(0.1), height: 24),
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: item.color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(item.icon, color: item.color, size: 20),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
if (item.subLabel != null)
|
||||
Text(
|
||||
item.subLabel!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${item.amount} ${'SYP'.tr}",
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
if (item.trend != null)
|
||||
Text(
|
||||
item.trend!,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: item.trendColor ??
|
||||
FinanceDesignSystem.successGreen,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SummaryItem {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String? subLabel;
|
||||
final String amount;
|
||||
final Color color;
|
||||
final String? trend;
|
||||
final Color? trendColor;
|
||||
|
||||
SummaryItem({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
this.subLabel,
|
||||
required this.amount,
|
||||
required this.color,
|
||||
this.trend,
|
||||
this.trendColor,
|
||||
});
|
||||
}
|
||||
163
lib/views/home/my_wallet/widgets/promo_gamification_card.dart
Normal file
163
lib/views/home/my_wallet/widgets/promo_gamification_card.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/finance_design_system.dart';
|
||||
|
||||
class PromoGamificationCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final int currentProgress;
|
||||
final int targetProgress;
|
||||
final String reward;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const PromoGamificationCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.currentProgress,
|
||||
required this.targetProgress,
|
||||
required this.reward,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double progress = (currentProgress / targetProgress).clamp(0.0, 1.0);
|
||||
final bool isCompleted = progress >= 1.0;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.03),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: (isCompleted
|
||||
? FinanceDesignSystem.successGreen
|
||||
: FinanceDesignSystem.accentBlue)
|
||||
.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
reward,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isCompleted
|
||||
? FinanceDesignSystem.successGreen
|
||||
: FinanceDesignSystem.accentBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${'Progress:'.tr} $currentProgress / $targetProgress",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
if (isCompleted)
|
||||
Icon(Icons.check_circle_rounded,
|
||||
color: FinanceDesignSystem.successGreen, size: 16),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: 10,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
height: 10,
|
||||
width: MediaQuery.of(context).size.width *
|
||||
progress, // Simplified for now
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: isCompleted
|
||||
? [
|
||||
FinanceDesignSystem.successGreen,
|
||||
Colors.lightGreenAccent
|
||||
]
|
||||
: [FinanceDesignSystem.accentBlue, Colors.blueAccent],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isCompleted && onTap != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: onTap,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: FinanceDesignSystem.successGreen,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
|
||||
),
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: Text("Claim Reward".tr),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
120
lib/views/home/my_wallet/widgets/quick_actions.dart
Normal file
120
lib/views/home/my_wallet/widgets/quick_actions.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/finance_design_system.dart';
|
||||
|
||||
class QuickActionsGrid extends StatelessWidget {
|
||||
final VoidCallback onAddBalance;
|
||||
final VoidCallback onWithdraw;
|
||||
final VoidCallback onTransfer;
|
||||
final VoidCallback onHistory;
|
||||
|
||||
const QuickActionsGrid({
|
||||
super.key,
|
||||
required this.onAddBalance,
|
||||
required this.onWithdraw,
|
||||
required this.onTransfer,
|
||||
required this.onHistory,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 1.5,
|
||||
children: [
|
||||
_ActionItem(
|
||||
icon: Icons.add_circle_outline_rounded,
|
||||
label: "Add Balance".tr,
|
||||
subLabel: "شحن",
|
||||
color: FinanceDesignSystem.accentBlue,
|
||||
onTap: onAddBalance,
|
||||
),
|
||||
_ActionItem(
|
||||
icon: Icons.file_download_outlined,
|
||||
label: "Withdraw".tr,
|
||||
subLabel: "سحب",
|
||||
color: FinanceDesignSystem.successGreen,
|
||||
onTap: onWithdraw,
|
||||
),
|
||||
_ActionItem(
|
||||
icon: Icons.swap_horiz_rounded,
|
||||
label: "Transfer".tr,
|
||||
subLabel: "تحويل",
|
||||
color: Colors.orange,
|
||||
onTap: onTransfer,
|
||||
),
|
||||
_ActionItem(
|
||||
icon: Icons.history_rounded,
|
||||
label: "History".tr,
|
||||
subLabel: "السجل",
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
onTap: onHistory,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String subLabel;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _ActionItem({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.subLabel,
|
||||
required this.color,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||
borderRadius:
|
||||
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
subLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
128
lib/views/home/my_wallet/widgets/transaction_preview_item.dart
Normal file
128
lib/views/home/my_wallet/widgets/transaction_preview_item.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../../../constant/finance_design_system.dart';
|
||||
|
||||
class TransactionPreviewItem extends StatelessWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final String amount;
|
||||
final String date;
|
||||
final String type; // 'credit' or 'debit'
|
||||
final String? method;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const TransactionPreviewItem({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.amount,
|
||||
required this.date,
|
||||
required this.type,
|
||||
this.method,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isCredit = type.toLowerCase() == 'credit';
|
||||
final Color statusColor = isCredit
|
||||
? FinanceDesignSystem.successGreen
|
||||
: FinanceDesignSystem.dangerRed;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(FinanceDesignSystem.mainRadius),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isCredit
|
||||
? Icons.arrow_downward_rounded
|
||||
: Icons.arrow_upward_rounded,
|
||||
color: statusColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: FinanceDesignSystem.primaryDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
date,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
if (method != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 3,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade400,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
method!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
"${isCredit ? '+' : '-'}$amount ${'SYP'.tr}",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user