25-7-28-2
This commit is contained in:
140
lib/views/home/my_wallet/bank_account_egypt.dart
Executable file
140
lib/views/home/my_wallet/bank_account_egypt.dart
Executable file
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class BankController extends GetxController {
|
||||
String selectedBank = '';
|
||||
|
||||
Map<String, String> bankNames = {
|
||||
'Ahli United Bank'.tr: 'AUB',
|
||||
'Citi Bank N.A. Egypt'.tr: 'CITI',
|
||||
'MIDBANK'.tr: 'MIDB',
|
||||
'Banque Du Caire'.tr: 'BDC',
|
||||
'HSBC Bank Egypt S.A.E'.tr: 'HSBC',
|
||||
'Credit Agricole Egypt S.A.E'.tr: 'ECAE',
|
||||
'Egyptian Gulf Bank'.tr: 'EGB',
|
||||
'The United Bank'.tr: 'UB',
|
||||
'Qatar National Bank Alahli'.tr: 'QNB',
|
||||
'Arab Bank PLC'.tr: 'ARAB',
|
||||
'Emirates National Bank of Dubai'.tr: 'ENBD',
|
||||
'Al Ahli Bank of Kuwait – Egypt'.tr: 'ABK',
|
||||
'National Bank of Kuwait – Egypt'.tr: 'NBK',
|
||||
'Arab Banking Corporation - Egypt S.A.E'.tr: 'EABC',
|
||||
'First Abu Dhabi Bank'.tr: 'FAB',
|
||||
'Abu Dhabi Islamic Bank – Egypt'.tr: 'ADIB',
|
||||
'Commercial International Bank - Egypt S.A.E'.tr: 'CIB',
|
||||
'Housing And Development Bank'.tr: 'HDB',
|
||||
'Banque Misr'.tr: 'MISR',
|
||||
'Arab African International Bank'.tr: 'AAIB',
|
||||
'Egyptian Arab Land Bank'.tr: 'EALB',
|
||||
'Export Development Bank of Egypt'.tr: 'EDBE',
|
||||
'Faisal Islamic Bank of Egypt'.tr: 'FAIB',
|
||||
'Blom Bank'.tr: 'BLOM',
|
||||
'Abu Dhabi Commercial Bank – Egypt'.tr: 'ADCB',
|
||||
'Alex Bank Egypt'.tr: 'BOA',
|
||||
'Societe Arabe Internationale De Banque'.tr: 'SAIB',
|
||||
'National Bank of Egypt'.tr: 'NBE',
|
||||
'Al Baraka Bank Egypt B.S.C.'.tr: 'ABRK',
|
||||
'Egypt Post'.tr: 'POST',
|
||||
'Nasser Social Bank'.tr: 'NSB',
|
||||
'Industrial Development Bank'.tr: 'IDB',
|
||||
'Suez Canal Bank'.tr: 'SCB',
|
||||
'Mashreq Bank'.tr: 'MASHA',
|
||||
'Arab Investment Bank'.tr: 'AIB',
|
||||
'General Authority For Supply Commodities'.tr: 'GASCA',
|
||||
'Arab International Bank'.tr: 'AIB',
|
||||
'Agricultural Bank of Egypt'.tr: 'PDAC',
|
||||
'National Bank of Greece'.tr: 'NBG',
|
||||
'Central Bank Of Egypt'.tr: 'CBE',
|
||||
'ATTIJARIWAFA BANK Egypt'.tr: 'BBE',
|
||||
};
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
selectedBank = bankNames.values.first;
|
||||
}
|
||||
|
||||
void updateSelectedBank(String? bankShortName) {
|
||||
selectedBank = bankShortName ?? '';
|
||||
update();
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<String>> getDropdownItems() {
|
||||
return bankNames.keys.map<DropdownMenuItem<String>>((bankFullName) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: bankNames[bankFullName],
|
||||
child: Text(bankFullName),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void showBankPicker(BuildContext context) {
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (BuildContext context) => CupertinoActionSheet(
|
||||
title: Text('Select a Bank'.tr),
|
||||
actions: bankNames.keys.map((String bankFullName) {
|
||||
return CupertinoActionSheetAction(
|
||||
child: Text(bankFullName),
|
||||
onPressed: () {
|
||||
updateSelectedBank(bankNames[bankFullName]);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
child: Text('Cancel'.tr),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BankDropdown extends StatelessWidget {
|
||||
final BankController bankController = Get.put(BankController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<BankController>(
|
||||
init: bankController,
|
||||
builder: (controller) {
|
||||
return CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () => controller.showBankPicker(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: CupertinoColors.systemGrey4),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
controller.selectedBank != null
|
||||
? controller.bankNames.keys.firstWhere(
|
||||
(key) =>
|
||||
controller.bankNames[key] ==
|
||||
controller.selectedBank,
|
||||
orElse: () => 'Select a Bank'.tr,
|
||||
)
|
||||
: 'Select a Bank'.tr,
|
||||
style: TextStyle(
|
||||
color: controller.selectedBank != null
|
||||
? CupertinoColors.black
|
||||
: CupertinoColors.systemGrey,
|
||||
),
|
||||
),
|
||||
const Icon(CupertinoIcons.chevron_down, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
299
lib/views/home/my_wallet/card_wallet_widget.dart
Executable file
299
lib/views/home/my_wallet/card_wallet_widget.dart
Executable file
@@ -0,0 +1,299 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../constant/style.dart';
|
||||
import '../../../controller/home/payment/captain_wallet_controller.dart';
|
||||
import '../../../controller/home/payment/paymob_payout.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
|
||||
// تذكير: ستحتاج إلى إضافة حزمة flutter_svg إلى ملف pubspec.yaml
|
||||
// dependencies:
|
||||
// flutter_svg: ^2.0.7
|
||||
|
||||
/// بطاقة المحفظة بتصميم سوري فاخر مستوحى من فن الأرابيسك والفسيفساء
|
||||
class CardSeferWalletDriver extends StatelessWidget {
|
||||
const CardSeferWalletDriver({super.key});
|
||||
|
||||
// SVG لنقشة أرابيسك هندسية لاستخدامها كخلفية
|
||||
final String arabesquePattern = '''
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="arabesque" patternUnits="userSpaceOnUse" width="50" height="50" patternTransform="scale(1.2)">
|
||||
<g fill="#E7C582" fill-opacity="0.1">
|
||||
<path d="M25 0 L35.35 9.65 L50 25 L35.35 40.35 L25 50 L14.65 40.35 L0 25 L14.65 9.65 Z"/>
|
||||
<path d="M50 0 L60.35 9.65 L75 25 L60.35 40.35 L50 50 L39.65 40.35 L25 25 L39.65 9.65 Z"/>
|
||||
<path d="M0 50 L9.65 39.65 L25 25 L9.65 10.35 L0 0 L-9.65 10.35 L-25 25 L-9.65 39.65 Z"/>
|
||||
</g>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#arabesque)"/>
|
||||
</svg>
|
||||
''';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: GetBuilder<CaptainWalletController>(
|
||||
builder: (captainWalletController) {
|
||||
return GestureDetector(
|
||||
onTap: () => _showCashOutDialog(context, captainWalletController),
|
||||
child: Container(
|
||||
width: Get.width * 0.9,
|
||||
height: Get.height * 0.25,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF003C43).withOpacity(0.5),
|
||||
blurRadius: 25,
|
||||
spreadRadius: -5,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: Stack(
|
||||
children: [
|
||||
// الخلفية الرئيسية
|
||||
Container(color: const Color(0xFF003C43)),
|
||||
// طبقة النقشة
|
||||
SvgPicture.string(arabesquePattern, fit: BoxFit.cover),
|
||||
// طبقة التأثير الزجاجي (Glassmorphism)
|
||||
BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
|
||||
child: Container(color: Colors.black.withOpacity(0.1)),
|
||||
),
|
||||
// محتوى البطاقة
|
||||
_buildCardContent(captainWalletController),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardContent(CaptainWalletController captainWalletController) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 20, 24, 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'محفظة انطلق',
|
||||
style: AppStyle.headTitle.copyWith(
|
||||
fontFamily: 'Amiri', // خط يوحي بالفخامة
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
// أيقونة شريحة البطاقة
|
||||
const Icon(Icons.sim_card_outlined,
|
||||
color: Color(0xFFE7C582), size: 30),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
'الرصيد الحالي'.tr,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// استخدام AnimatedSwitcher لإضافة حركة عند تحديث الرصيد
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
'${captainWalletController.totalAmountVisa} ${'ل.س'.tr}',
|
||||
key:
|
||||
ValueKey<String>(captainWalletController.totalAmountVisa),
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: const Color(0xFFE7C582), // Antique Gold
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
box.read(BoxName.nameDriver).toString().split(' ')[0],
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
fontSize: 16,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"سحب الرصيد".tr,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCashOutDialog(
|
||||
BuildContext context, CaptainWalletController captainWalletController) {
|
||||
double minAmount = 20.0; // الحد الأدنى للسحب
|
||||
if (double.parse(captainWalletController.totalAmountVisa) >= minAmount) {
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'هل تريد سحب أرباحك؟'.tr,
|
||||
titleStyle: AppStyle.title
|
||||
.copyWith(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.account_balance_wallet,
|
||||
color: AppColor.primaryColor, size: 30),
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
'${'رصيدك الإجمالي:'.tr} ${captainWalletController.totalAmountVisa} ${'ل.س'.tr}',
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'طريقة الدفع:'.tr,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const MyDropDownSyria(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Form(
|
||||
key: captainWalletController.formKey,
|
||||
child: MyTextForm(
|
||||
controller: captainWalletController.phoneWallet,
|
||||
label: "أدخل رقم محفظتك".tr,
|
||||
hint: "مثال: 0912345678".tr,
|
||||
type: TextInputType.phone,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'تأكيد'.tr,
|
||||
onPressed: () async {
|
||||
if (captainWalletController.formKey.currentState!.validate()) {
|
||||
Get.back();
|
||||
String amountAfterFee =
|
||||
(double.parse(captainWalletController.totalAmountVisa) - 5)
|
||||
.toStringAsFixed(0);
|
||||
await Get.put(PaymobPayout()).payToWalletDriverAll(
|
||||
amountAfterFee,
|
||||
Get.find<SyrianPayoutController>().dropdownValue.toString(),
|
||||
captainWalletController.phoneWallet.text.toString(),
|
||||
);
|
||||
}
|
||||
},
|
||||
kolor: AppColor.greenColor,
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'إلغاء'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
kolor: AppColor.redColor,
|
||||
));
|
||||
} else {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text("تنبيه".tr),
|
||||
content: Text(
|
||||
'${'المبلغ في محفظتك أقل من الحد الأدنى للسحب وهو'.tr} $minAmount ${'ل.س'.tr}',
|
||||
),
|
||||
actions: <Widget>[
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text("موافق".tr),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// هذا الكود من الملف الأصلي وهو ضروري لعمل الحوار
|
||||
class MyDropDownSyria extends StatelessWidget {
|
||||
const MyDropDownSyria({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(SyrianPayoutController());
|
||||
return GetBuilder<SyrianPayoutController>(builder: (controller) {
|
||||
return DropdownButton<String>(
|
||||
value: controller.dropdownValue,
|
||||
icon: const Icon(Icons.arrow_drop_down),
|
||||
elevation: 16,
|
||||
style: const TextStyle(color: Colors.deepPurple, fontSize: 16),
|
||||
underline: Container(
|
||||
height: 2,
|
||||
color: Colors.deepPurpleAccent,
|
||||
),
|
||||
onChanged: (String? newValue) {
|
||||
controller.changeValue(newValue);
|
||||
},
|
||||
items: <String>['syriatel', 'mtn']
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value.tr),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// هذا المتحكم ضروري لعمل القائمة المنسدلة
|
||||
class SyrianPayoutController extends GetxController {
|
||||
String dropdownValue = 'syriatel';
|
||||
|
||||
void changeValue(String? newValue) {
|
||||
if (newValue != null) {
|
||||
dropdownValue = newValue;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
lib/views/home/my_wallet/ecash.dart
Normal file
170
lib/views/home/my_wallet/ecash.dart
Normal file
@@ -0,0 +1,170 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../constant/style.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../main.dart';
|
||||
|
||||
// --- ملاحظات هامة ---
|
||||
// 1. تأكد من إضافة الرابط الجديد إلى ملف AppLink الخاص بك:
|
||||
// static const String payWithEcashDriver = "$server/payment/payWithEcashDriver.php";
|
||||
//
|
||||
// 2. تأكد من أنك تخزن 'driverId' في الـ box الخاص بك، مثلاً:
|
||||
// box.read(BoxName.driverID)
|
||||
|
||||
/// دالة جديدة لبدء عملية الدفع للسائق عبر ecash
|
||||
Future<void> payWithEcashDriver(BuildContext context, String amount) async {
|
||||
try {
|
||||
// يمكنك استخدام نفس طريقة التحقق بالبصمة إذا أردت
|
||||
bool isAvailable = await LocalAuthentication().isDeviceSupported();
|
||||
if (isAvailable) {
|
||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
||||
localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr,
|
||||
);
|
||||
|
||||
if (didAuthenticate) {
|
||||
// استدعاء الـ Endpoint الجديد على السيرفر الخاص بك
|
||||
var res = await CRUD().postWallet(
|
||||
link: AppLink.payWithEcashDriver,
|
||||
payload: {
|
||||
// أرسل البيانات التي يحتاجها السيرفر
|
||||
"amount": amount,
|
||||
// "driverId": box.read(BoxName.driverID), // تأكد من وجود هذا المتغير
|
||||
"driverId": box.read(BoxName.driverID), // تأكد من وجود هذا المتغير
|
||||
},
|
||||
);
|
||||
|
||||
// التأكد من أن السيرفر أعاد رابط الدفع بنجاح
|
||||
if (res != null &&
|
||||
res['status'] == 'success' &&
|
||||
res['message'] != null) {
|
||||
final String paymentUrl = res['message'];
|
||||
// الانتقال إلى شاشة الدفع الجديدة الخاصة بـ ecash للسائق
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
EcashDriverPaymentScreen(paymentUrl: paymentUrl),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// عرض رسالة خطأ في حال فشل السيرفر في إنشاء الرابط
|
||||
Get.defaultDialog(
|
||||
title: 'Error'.tr,
|
||||
content: Text(
|
||||
'Failed to initiate payment. Please try again.'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Get.defaultDialog(
|
||||
title: 'Error'.tr,
|
||||
content: Text(
|
||||
'An error occurred during the payment process.'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// شاشة جديدة ومبسطة خاصة بدفع السائقين عبر ecash
|
||||
class EcashDriverPaymentScreen extends StatefulWidget {
|
||||
final String paymentUrl;
|
||||
|
||||
const EcashDriverPaymentScreen({required this.paymentUrl, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<EcashDriverPaymentScreen> createState() =>
|
||||
_EcashDriverPaymentScreenState();
|
||||
}
|
||||
|
||||
class _EcashDriverPaymentScreenState extends State<EcashDriverPaymentScreen> {
|
||||
late final WebViewController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onPageFinished: (url) {
|
||||
print('Ecash Driver WebView URL Finished: $url');
|
||||
|
||||
// هنا نتحقق فقط من أن المستخدم عاد إلى صفحة النجاح
|
||||
// لا حاجة لاستدعاء أي API هنا، فالـ Webhook يقوم بكل العمل
|
||||
if (url.contains("success.php")) {
|
||||
showProcessingDialog();
|
||||
}
|
||||
},
|
||||
))
|
||||
..loadRequest(Uri.parse(widget.paymentUrl));
|
||||
}
|
||||
|
||||
// دالة لعرض رسالة "العملية قيد المعالجة"
|
||||
void showProcessingDialog() {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"Payment Successful".tr,
|
||||
style: TextStyle(
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
"Your payment is being processed and your wallet will be updated shortly."
|
||||
.tr,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// أغلق مربع الحوار، ثم أغلق شاشة الدفع
|
||||
Navigator.pop(context); // Close the dialog
|
||||
Navigator.pop(context); // Close the payment screen
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"OK".tr,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Complete Payment'.tr)),
|
||||
body: WebViewWidget(controller: _controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
lib/views/home/my_wallet/payment_history_driver_page.dart
Executable file
54
lib/views/home/my_wallet/payment_history_driver_page.dart
Executable file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
|
||||
class PaymentHistoryDriverPage extends StatelessWidget {
|
||||
const PaymentHistoryDriverPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(DriverWalletHistoryController());
|
||||
return MyScafolld(
|
||||
title: 'Payment History'.tr,
|
||||
body: [
|
||||
GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) => controller.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: ListView.builder(
|
||||
itemCount: controller.archive.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var list = controller.archive[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: double.parse(list['amount']) < 0
|
||||
? AppColor.redColor.withOpacity(.4)
|
||||
: AppColor.greenColor.withOpacity(.4)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
list['amount'],
|
||||
style: AppStyle.title,
|
||||
),
|
||||
Text(
|
||||
list['created_at'],
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
601
lib/views/home/my_wallet/points_captain.dart
Executable file
601
lib/views/home/my_wallet/points_captain.dart
Executable file
@@ -0,0 +1,601 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
|
||||
import 'package:sefer_driver/controller/payment/payment_controller.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import 'ecash.dart';
|
||||
|
||||
class PointsCaptain extends StatelessWidget {
|
||||
PaymentController paymentController = Get.put(PaymentController());
|
||||
CaptainWalletController captainWalletController =
|
||||
Get.put(CaptainWalletController());
|
||||
|
||||
PointsCaptain({
|
||||
super.key,
|
||||
required this.kolor,
|
||||
required this.countPoint,
|
||||
required this.pricePoint,
|
||||
});
|
||||
final Color kolor;
|
||||
final String countPoint;
|
||||
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(
|
||||
children: [
|
||||
Text(
|
||||
'${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}',
|
||||
style: AppStyle.title,
|
||||
),
|
||||
MyElevatedButton(
|
||||
title: 'Pay with Credit Card'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
payWithEcashDriver(context, pricePoint.toString());
|
||||
|
||||
// var d = jsonDecode(res);
|
||||
}, //51524
|
||||
),
|
||||
// Add some spacing between buttons
|
||||
MyElevatedButton(
|
||||
kolor: AppColor.redColor,
|
||||
title: 'Pay with Wallet'.tr,
|
||||
onPressed: () 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: 'Insert Wallet phone number'.tr,
|
||||
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: 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: [
|
||||
Text(
|
||||
'$countPoint ${'L.S'.tr}',
|
||||
style: AppStyle.subtitle
|
||||
.copyWith(color: AppColor.secondaryColor),
|
||||
),
|
||||
Text(
|
||||
'$pricePoint ${'L.S'.tr}',
|
||||
style:
|
||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentScreen extends StatefulWidget {
|
||||
final String iframeUrl;
|
||||
final String countPrice;
|
||||
|
||||
const PaymentScreen(
|
||||
{required this.iframeUrl, Key? key, required this.countPrice})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<PaymentScreen> createState() => _PaymentScreenState();
|
||||
}
|
||||
|
||||
class _PaymentScreenState extends State<PaymentScreen> {
|
||||
late final WebViewController _controller;
|
||||
final controller = Get.find<CaptainWalletController>();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onPageFinished: (url) {
|
||||
if (url.contains("success")) {
|
||||
_fetchPaymentStatus(); // ✅ استدعاء الويب هوك بعد نجاح الدفع
|
||||
} else if (url.contains("failed")) {
|
||||
showCustomDialog(
|
||||
title: "Error".tr,
|
||||
message: 'Payment Failed'.tr, // يتم جلب رسالة الخطأ من الخادم
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
))
|
||||
..loadRequest(Uri.parse(widget.iframeUrl));
|
||||
}
|
||||
|
||||
Future<void> _fetchPaymentStatus() async {
|
||||
final String userId = box.read(BoxName.phoneDriver);
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
try {
|
||||
final response = await CRUD().postWallet(
|
||||
link: AppLink.paymetVerifyDriver,
|
||||
payload: {
|
||||
'user_id': userId,
|
||||
'driverID': box.read(BoxName.driverID),
|
||||
'paymentMethod': 'visa-in',
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure' && response != 'token_expired') {
|
||||
if (response['status'] == 'success') {
|
||||
final payment = response['message'];
|
||||
final amount = payment['amount'].toString();
|
||||
final bonus = payment['bonus'].toString();
|
||||
final paymentID = payment['paymentID'].toString();
|
||||
|
||||
await controller.getCaptainWalletFromBuyPoints();
|
||||
|
||||
showCustomDialog(
|
||||
title: "payment_success".tr,
|
||||
message:
|
||||
"${"transaction_id".tr}: $paymentID\n${"amount_paid".tr}: $amount EGP\n${"bonus_added".tr}: $bonus ${"points".tr}",
|
||||
isSuccess: true,
|
||||
);
|
||||
} else {
|
||||
showCustomDialog(
|
||||
title: "transaction_failed".tr,
|
||||
message: response['message'].toString(),
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showCustomDialog(
|
||||
title: "connection_failed".tr,
|
||||
message: response.toString(),
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
showCustomDialog(
|
||||
title: "server_error".tr,
|
||||
message: "server_error_message".tr,
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showCustomDialog({
|
||||
required String title,
|
||||
required String message,
|
||||
required bool isSuccess,
|
||||
}) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check_circle : Icons.error,
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: isSuccess ? Colors.green : Colors.red,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"OK",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('إتمام الدفع')),
|
||||
body: WebViewWidget(controller: _controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentScreenWallet extends StatefulWidget {
|
||||
final String iframeUrl;
|
||||
final String countPrice;
|
||||
|
||||
const PaymentScreenWallet(
|
||||
{required this.iframeUrl, Key? key, required this.countPrice})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<PaymentScreenWallet> createState() => _PaymentScreenWalletState();
|
||||
}
|
||||
|
||||
class _PaymentScreenWalletState extends State<PaymentScreenWallet> {
|
||||
late final WebViewController _controller;
|
||||
final controller = Get.find<CaptainWalletController>();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onPageFinished: (url) {
|
||||
if (url.contains("success")) {
|
||||
_fetchPaymentStatus(); // ✅ استدعاء الويب هوك بعد نجاح الدفع
|
||||
} else if (url.contains("failed")) {
|
||||
showCustomDialog(
|
||||
title: "Error".tr,
|
||||
message: 'Payment Failed'.tr, // يتم جلب رسالة الخطأ من الخادم
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
))
|
||||
..loadRequest(Uri.parse(widget.iframeUrl));
|
||||
}
|
||||
|
||||
Future<void> _fetchPaymentStatus() async {
|
||||
final String userId = '+963' + box.read(BoxName.phoneWallet);
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
try {
|
||||
final response = await CRUD().postWallet(
|
||||
link: AppLink.paymetVerifyDriver,
|
||||
payload: {
|
||||
'user_id': userId,
|
||||
'driverID': box.read(BoxName.driverID),
|
||||
'paymentMethod': 'visa-in',
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure' && response != 'token_expired') {
|
||||
if (response['status'] == 'success') {
|
||||
final payment = response['message'];
|
||||
final amount = payment['amount'].toString();
|
||||
final bonus = payment['bonus'].toString();
|
||||
final paymentID = payment['paymentID'].toString();
|
||||
|
||||
await controller.getCaptainWalletFromBuyPoints();
|
||||
|
||||
showCustomDialog(
|
||||
title: "payment_success".tr,
|
||||
message:
|
||||
"${"transaction_id".tr}: $paymentID\n${"amount_paid".tr}: $amount EGP\n${"bonus_added".tr}: $bonus ${"points".tr}",
|
||||
isSuccess: true,
|
||||
);
|
||||
} else {
|
||||
showCustomDialog(
|
||||
title: "transaction_failed".tr,
|
||||
message: response['message'].toString(),
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showCustomDialog(
|
||||
title: "connection_failed".tr,
|
||||
message: response.toString(),
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
showCustomDialog(
|
||||
title: "server_error".tr,
|
||||
message: "server_error_message".tr,
|
||||
isSuccess: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showCustomDialog({
|
||||
required String title,
|
||||
required String message,
|
||||
required bool isSuccess,
|
||||
}) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check_circle : Icons.error,
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: isSuccess ? Colors.green : Colors.red,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"OK",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('إتمام الدفع')),
|
||||
body: WebViewWidget(controller: _controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> payWithMTNWallet(
|
||||
BuildContext context, String amount, String currency) async {
|
||||
// استخدام مؤشر تحميل لتجربة مستخدم أفضل
|
||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
barrierDismissible: false);
|
||||
|
||||
try {
|
||||
String phone = box.read(BoxName.phoneWallet) ?? '963992952235';
|
||||
String driverID = box.read(BoxName.driverID).toString();
|
||||
String formattedAmount = double.parse(amount).toStringAsFixed(0);
|
||||
|
||||
print("🚀 بدء عملية دفع MTN");
|
||||
print(
|
||||
"📦 Payload: driverID: $driverID, amount: $formattedAmount, phone: $phone");
|
||||
|
||||
// التحقق من البصمة (اختياري)
|
||||
bool isAuthSupported = await LocalAuthentication().isDeviceSupported();
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
||||
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
if (!didAuthenticate) {
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
print("❌ المستخدم لم يؤكد بالبصمة/الوجه");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 1️⃣ استدعاء mtn_start_payment.php (الملف الجديد)
|
||||
var responseData = await CRUD().postWallet(
|
||||
link: AppLink.payWithMTNStart,
|
||||
payload: {
|
||||
"amount": formattedAmount,
|
||||
"passengerId": driverID,
|
||||
"phone": phone,
|
||||
},
|
||||
);
|
||||
|
||||
print("✅ استجابة الخادم (mtn_start_payment.php):");
|
||||
print(responseData);
|
||||
|
||||
// --- بداية التعديل المهم ---
|
||||
// التحقق القوي من الاستجابة لتجنب الأخطاء
|
||||
Map<String, dynamic> startRes;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
// إذا كانت الاستجابة بالفعل Map، استخدمها مباشرة
|
||||
startRes = responseData;
|
||||
} else if (responseData is String) {
|
||||
// إذا كانت نص، حاول تحليلها كـ JSON
|
||||
try {
|
||||
startRes = json.decode(responseData);
|
||||
} catch (e) {
|
||||
throw Exception(
|
||||
"فشل في تحليل استجابة الخادم. الاستجابة: $responseData");
|
||||
}
|
||||
} else {
|
||||
// نوع غير متوقع
|
||||
throw Exception("تم استلام نوع بيانات غير متوقع من الخادم.");
|
||||
}
|
||||
|
||||
if (startRes['status'] != 'success') {
|
||||
String errorMsg = startRes['message']?.toString() ??
|
||||
"فشل بدء عملية الدفع. حاول مرة أخرى.";
|
||||
throw Exception(errorMsg);
|
||||
}
|
||||
// --- نهاية التعديل المهم ---
|
||||
|
||||
// استخراج البيانات بأمان
|
||||
final messageData = startRes["message"];
|
||||
final invoiceNumber = messageData["invoiceNumber"].toString();
|
||||
final operationNumber = messageData["operationNumber"].toString();
|
||||
final guid = messageData["guid"].toString();
|
||||
|
||||
print(
|
||||
"📄 invoiceNumber: $invoiceNumber, 🔢 operationNumber: $operationNumber, 🧭 guid: $guid");
|
||||
|
||||
if (Get.isDialogOpen ?? false)
|
||||
Get.back(); // إغلاق مؤشر التحميل قبل عرض حوار OTP
|
||||
|
||||
// 2️⃣ عرض واجهة إدخال OTP
|
||||
String? otp = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
String input = "";
|
||||
return AlertDialog(
|
||||
title: const Text("أدخل كود التحقق"),
|
||||
content: TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(hintText: "كود OTP"),
|
||||
onChanged: (val) => input = val,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text("تأكيد"),
|
||||
onPressed: () => Navigator.of(context).pop(input),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("إلغاء"),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (otp == null || otp.isEmpty) {
|
||||
print("❌ لم يتم إدخال OTP");
|
||||
return;
|
||||
}
|
||||
print("🔐 تم إدخال OTP: $otp");
|
||||
|
||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
barrierDismissible: false);
|
||||
|
||||
// 3️⃣ استدعاء mtn_confirm.php
|
||||
var confirmRes = await CRUD().postWallet(
|
||||
link: AppLink.payWithMTNConfirm,
|
||||
payload: {
|
||||
"invoiceNumber": invoiceNumber,
|
||||
"operationNumber": operationNumber,
|
||||
"guid": guid,
|
||||
"otp": otp,
|
||||
"phone": phone,
|
||||
},
|
||||
);
|
||||
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
|
||||
print("✅ استجابة mtn_confirm.php:");
|
||||
print(confirmRes);
|
||||
|
||||
if (confirmRes != null && confirmRes['status'] == 'success') {
|
||||
Get.defaultDialog(
|
||||
title: "✅ نجاح",
|
||||
content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."),
|
||||
);
|
||||
} else {
|
||||
String errorMsg =
|
||||
confirmRes?['message']?.toString() ?? "فشل في تأكيد الدفع";
|
||||
Get.defaultDialog(
|
||||
title: "❌ فشل",
|
||||
content: Text(errorMsg),
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
print("🔥 خطأ أثناء الدفع عبر MTN:");
|
||||
print(e);
|
||||
print(s);
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
Get.defaultDialog(
|
||||
title: 'حدث خطأ',
|
||||
content: Text(e.toString().replaceFirst("Exception: ", "")),
|
||||
);
|
||||
}
|
||||
}
|
||||
145
lib/views/home/my_wallet/transfer_budget_page.dart
Executable file
145
lib/views/home/my_wallet/transfer_budget_page.dart
Executable file
@@ -0,0 +1,145 @@
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_scafold.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:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../controller/home/payment/captain_wallet_controller.dart';
|
||||
|
||||
class TransferBudgetPage extends StatelessWidget {
|
||||
const TransferBudgetPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(CaptainWalletController());
|
||||
return MyScafolld(
|
||||
title: "Transfer budget".tr,
|
||||
body: [
|
||||
GetBuilder<CaptainWalletController>(
|
||||
builder: (captainWalletController) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
height: Get.height * .7,
|
||||
width: double.infinity,
|
||||
child: Form(
|
||||
key: captainWalletController.formKeyTransfer,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
MyTextForm(
|
||||
controller: captainWalletController
|
||||
.newDriverPhoneController,
|
||||
label: 'phone number of driver'.tr,
|
||||
hint: 'phone number of driver',
|
||||
type: TextInputType.phone),
|
||||
MyTextForm(
|
||||
controller: captainWalletController
|
||||
.amountFromBudgetController,
|
||||
label: 'insert amount'.tr,
|
||||
hint:
|
||||
'${'You have in account'.tr} ${captainWalletController.totalAmountVisa}',
|
||||
type: TextInputType.number),
|
||||
captainWalletController.isNewTransfer
|
||||
? const MyCircularProgressIndicator()
|
||||
: captainWalletController
|
||||
.amountToNewDriverMap.isEmpty
|
||||
? MyElevatedButton(
|
||||
title: 'Next'.tr,
|
||||
onPressed: () async {
|
||||
await captainWalletController
|
||||
.detectNewDriverFromMyBudget();
|
||||
})
|
||||
: const SizedBox(),
|
||||
captainWalletController.amountToNewDriverMap.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.maxFinite,
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Text(
|
||||
'Name :'.tr +
|
||||
captainWalletController
|
||||
.amountToNewDriverMap[0]['name']
|
||||
.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Container(
|
||||
width: double.maxFinite,
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Text(
|
||||
"${"NationalID".tr} ${captainWalletController.amountToNewDriverMap[0]['national_number']}",
|
||||
style: AppStyle.title,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Container(
|
||||
width: double.maxFinite,
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Text(
|
||||
"${"amount".tr} ${captainWalletController.amountFromBudgetController.text} ${'LE'.tr}",
|
||||
style: AppStyle.title,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
captainWalletController
|
||||
.amountToNewDriverMap.isNotEmpty
|
||||
? MyElevatedButton(
|
||||
title: 'Transfer'.tr,
|
||||
onPressed: () async {
|
||||
if (double.parse(
|
||||
captainWalletController
|
||||
.amountFromBudgetController
|
||||
.text) <
|
||||
double.parse(
|
||||
captainWalletController
|
||||
.totalAmountVisa) -
|
||||
5) {
|
||||
await captainWalletController
|
||||
.addTransferDriversWallet(
|
||||
'TransferFrom',
|
||||
'TransferTo',
|
||||
);
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
"You dont have money in your Wallet"
|
||||
.tr,
|
||||
"You dont have money in your Wallet or you should less transfer 5 LE to activate"
|
||||
.tr, () {
|
||||
Get.back();
|
||||
});
|
||||
}
|
||||
})
|
||||
: const SizedBox()
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox()
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
610
lib/views/home/my_wallet/walet_captain.dart
Executable file
610
lib/views/home/my_wallet/walet_captain.dart
Executable file
@@ -0,0 +1,610 @@
|
||||
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/controller/home/payment/captain_wallet_controller.dart';
|
||||
import 'package:sefer_driver/main.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 '../../../controller/payment/driver_payment_controller.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
import 'card_wallet_widget.dart';
|
||||
import 'points_captain.dart';
|
||||
import 'transfer_budget_page.dart';
|
||||
import 'weekly_payment_page.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;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
captainWalletController.refreshCaptainWallet();
|
||||
return MyScafolld(
|
||||
title: 'Driver Wallet'.tr,
|
||||
isleading: true,
|
||||
action: IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () => captainWalletController.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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${'Total Points is'.tr} 💎',
|
||||
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(
|
||||
BuildContext context, CaptainWalletController controller) {
|
||||
return Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
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} ${'S.P'.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 +
|
||||
' SAFAR Wallet'.tr),
|
||||
child: const Icon(Icons.headphones),
|
||||
),
|
||||
'${'Total Amount:'.tr} ${controller.totalAmountVisa} ${'S.P'.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(),
|
||||
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: 10000,
|
||||
countPoint: '10000'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.bronze, pricePoint: 20000, countPoint: '21000'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.goldenBronze,
|
||||
pricePoint: 40000,
|
||||
countPoint: '45000'),
|
||||
PointsCaptain(
|
||||
kolor: AppColor.gold, pricePoint: 100000, countPoint: '110000'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ويدجت مُحسّن لعرض صف معلومات الرصيد
|
||||
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),
|
||||
),
|
||||
),
|
||||
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,
|
||||
children: [
|
||||
Text(title,
|
||||
style:
|
||||
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
'$count / $maxCount',
|
||||
style: AppStyle.title.copyWith(color: AppColor.blueColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- الدوال والويدجتس الخاصة بالدفع في سوريا تبقى كما هي ---
|
||||
// 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(
|
||||
title: "Insert Payment Details".tr,
|
||||
content: Form(
|
||||
key: captainWalletController.formKeyAccount,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Insert your mobile wallet details to receive your money weekly"
|
||||
.tr),
|
||||
MyTextForm(
|
||||
controller: captainWalletController
|
||||
.cardBank, // Re-using for mobile number
|
||||
label: "Insert mobile wallet number".tr,
|
||||
hint: '0912 345 678',
|
||||
type: TextInputType.phone),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
MyDropDownSyria() // Dropdown for Syrian providers
|
||||
],
|
||||
)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok'.tr,
|
||||
onPressed: () async {
|
||||
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":
|
||||
Get.find<SyrianPayoutController>().dropdownValue.toString(),
|
||||
"accountNumber":
|
||||
captainWalletController.cardBank.text.toString(),
|
||||
"id": box.read(BoxName.driverID).toString()
|
||||
});
|
||||
print('res: $res');
|
||||
if (res != 'failure') {
|
||||
mySnackbarSuccess('Payment details added successfully'.tr);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 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;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
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);
|
||||
},
|
||||
items: <String>['syriatel', 'mtn']
|
||||
.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value.tr),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
141
lib/views/home/my_wallet/weekly_payment_page.dart
Executable file
141
lib/views/home/my_wallet/weekly_payment_page.dart
Executable file
@@ -0,0 +1,141 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
||||
import 'package:sefer_driver/views/widgets/mycircular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../controller/payment/driver_payment_controller.dart';
|
||||
|
||||
class WeeklyPaymentPage extends StatelessWidget {
|
||||
const WeeklyPaymentPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Get.put(DriverWalletHistoryController());
|
||||
return MyScafolld(
|
||||
title: 'Payment History'.tr,
|
||||
body: [
|
||||
GetBuilder<DriverWalletHistoryController>(
|
||||
builder: (controller) => controller.isLoading
|
||||
? const MyCircularProgressIndicator()
|
||||
: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: Get.width * .8,
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
controller.weeklyList.isEmpty
|
||||
? '0'
|
||||
: controller.weeklyList[0]
|
||||
['totalAmount']
|
||||
.toString(),
|
||||
style: AppStyle.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
' Total weekly is '.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
child: SizedBox(
|
||||
height: Get.height * .75,
|
||||
child: controller.weeklyList.isNotEmpty
|
||||
? ListView.builder(
|
||||
itemCount: controller.weeklyList.length,
|
||||
itemBuilder:
|
||||
(BuildContext context, int index) {
|
||||
var list = controller.weeklyList[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: Container(
|
||||
decoration: AppStyle.boxDecoration1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
children: [
|
||||
Card(
|
||||
elevation: 2,
|
||||
color: list['paymentMethod'] ==
|
||||
'visa'
|
||||
? AppColor.blueColor
|
||||
: AppColor.secondaryColor,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
list['paymentMethod'] ==
|
||||
'Remainder'
|
||||
? 'Remainder'.tr
|
||||
: list['paymentMethod'] ==
|
||||
'fromBudget'
|
||||
? 'fromBudget'.tr
|
||||
: list[
|
||||
'paymentMethod'],
|
||||
style: AppStyle.title,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
8.0),
|
||||
child: Text(
|
||||
list['amount'],
|
||||
style: AppStyle.number,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat(
|
||||
'yyyy-MM-dd hh:mm a')
|
||||
.format(DateTime.parse(
|
||||
list[
|
||||
'dateUpdated'])),
|
||||
style: AppStyle.number,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
isleading: true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user