Update: 2026-06-11 18:22:57
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:siro_rider/constant/currency.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -153,7 +154,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
.copyWith(color: Colors.white.withOpacity(0.7)),
|
||||
),
|
||||
Text(
|
||||
'${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${'SYP'.tr}',
|
||||
'${box.read(BoxName.passengerWalletTotal) ?? '0.0'} ${CurrencyHelper.currency}',
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Colors.white,
|
||||
fontSize: 28,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:siro_rider/constant/currency.dart';
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'package:siro_rider/constant/style.dart';
|
||||
import 'package:siro_rider/controller/functions/encrypt_decrypt.dart';
|
||||
// import 'package:siro_rider/controller/functions/encrypt_decrypt.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -42,7 +43,7 @@ class PassengerWalletDialog extends StatelessWidget {
|
||||
child: Text(
|
||||
box.read(BoxName.countryCode) == 'Syria'
|
||||
? '10000 ${'LE'.tr}'
|
||||
: '10 ${'SYP'.tr}',
|
||||
: '10 ${CurrencyHelper.currency}',
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
@@ -55,7 +56,7 @@ class PassengerWalletDialog extends StatelessWidget {
|
||||
child: Text(
|
||||
box.read(BoxName.countryCode) == 'Syria'
|
||||
? '20000 ${'LE'.tr} = 2050 ${'LE'.tr}'
|
||||
: '20 ${'SYP'.tr}',
|
||||
: '20 ${CurrencyHelper.currency}',
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
@@ -68,7 +69,7 @@ class PassengerWalletDialog extends StatelessWidget {
|
||||
child: Text(
|
||||
box.read(BoxName.countryCode) == 'Syria'
|
||||
? '40000 ${'LE'.tr} = 4150 ${'LE'.tr}'
|
||||
: '40 ${'SYP'.tr}',
|
||||
: '40 ${CurrencyHelper.currency}',
|
||||
),
|
||||
),
|
||||
CupertinoActionSheetAction(
|
||||
@@ -81,7 +82,7 @@ class PassengerWalletDialog extends StatelessWidget {
|
||||
child: Text(
|
||||
box.read(BoxName.countryCode) == 'Syria'
|
||||
? '100000 ${'LE'.tr} = 11000 ${'LE'.tr}'
|
||||
: '50 ${'SYP'.tr}',
|
||||
: '50 ${CurrencyHelper.currency}',
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -130,10 +131,10 @@ void showPaymentBottomSheet(BuildContext context) {
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
Get.back();
|
||||
return false;
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
@@ -154,7 +155,7 @@ void showPaymentBottomSheet(BuildContext context) {
|
||||
controller: controller,
|
||||
amount: 500,
|
||||
bonusAmount: 30,
|
||||
currency: 'SYP'.tr,
|
||||
currency: CurrencyHelper.currency,
|
||||
),
|
||||
|
||||
const SizedBox(height: 8.0),
|
||||
@@ -163,7 +164,7 @@ void showPaymentBottomSheet(BuildContext context) {
|
||||
controller: controller,
|
||||
amount: 1000,
|
||||
bonusAmount: 70,
|
||||
currency: 'SYP'.tr,
|
||||
currency: CurrencyHelper.currency,
|
||||
),
|
||||
|
||||
const SizedBox(height: 8.0),
|
||||
@@ -172,7 +173,7 @@ void showPaymentBottomSheet(BuildContext context) {
|
||||
controller: controller,
|
||||
amount: 2000,
|
||||
bonusAmount: 180,
|
||||
currency: 'SYP'.tr,
|
||||
currency: CurrencyHelper.currency,
|
||||
),
|
||||
|
||||
const SizedBox(height: 8.0),
|
||||
@@ -181,7 +182,7 @@ void showPaymentBottomSheet(BuildContext context) {
|
||||
controller: controller,
|
||||
amount: 5000,
|
||||
bonusAmount: 700,
|
||||
currency: 'SYP'.tr,
|
||||
currency: CurrencyHelper.currency,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16.0),
|
||||
@@ -266,7 +267,7 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
|
||||
// controller.payWithMTNWallet(
|
||||
// context,
|
||||
// controller.selectedAmount.toString(),
|
||||
// 'SYP',
|
||||
// CurrencyHelper.currency,
|
||||
// );
|
||||
// await controller.getPassengerWallet();
|
||||
// controller.isLoading = false;
|
||||
@@ -331,148 +332,215 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
|
||||
// Get.back();
|
||||
// Get.defaultDialog(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Insert Wallet phone number'.tr,
|
||||
// content: Form(
|
||||
// key: controller.formKey,
|
||||
// child: MyTextForm(
|
||||
// controller: controller.walletphoneController,
|
||||
// label: 'Insert Wallet phone number'.tr,
|
||||
// hint: '963941234567',
|
||||
// type: TextInputType.phone)),
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'OK'.tr,
|
||||
// onPressed: () async {
|
||||
// Get.back();
|
||||
// if (controller.formKey.currentState!.validate()) {
|
||||
// if (controller.selectedAmount != 0) {
|
||||
// controller.isLoading = true;
|
||||
// controller.update();
|
||||
// box.write(BoxName.phoneWallet,
|
||||
// (controller.walletphoneController.text));
|
||||
// Get.back();
|
||||
// await controller.payWithMTNWallet(
|
||||
// context,
|
||||
// controller.selectedAmount.toString(),
|
||||
// 'SYP',
|
||||
// );
|
||||
// await controller.getPassengerWallet();
|
||||
|
||||
// controller.isLoading = false;
|
||||
// controller.update();
|
||||
// } else {
|
||||
// Toast.show(
|
||||
// context,
|
||||
// '⚠️ You need to choose an amount!'.tr,
|
||||
// AppColor.redColor,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }));
|
||||
// },
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text(
|
||||
// 'Pay by MTN Wallet'.tr,
|
||||
// style: AppStyle.title,
|
||||
// ),
|
||||
// const SizedBox(width: 10),
|
||||
// Image.asset(
|
||||
// 'assets/images/cashMTN.png',
|
||||
// width: 70,
|
||||
// height: 70,
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// )),
|
||||
if (box.read(BoxName.countryCode) == 'Syria')
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (controller.formKey.currentState!.validate()) {
|
||||
if (controller.selectedAmount != 0) {
|
||||
controller.isLoading = true;
|
||||
controller.update();
|
||||
box.write(BoxName.phoneWallet,
|
||||
(controller.walletphoneController.text));
|
||||
await controller.payWithMTNWallet(
|
||||
context,
|
||||
controller.selectedAmount.toString(),
|
||||
CurrencyHelper.currency,
|
||||
);
|
||||
await controller.getPassengerWallet();
|
||||
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (controller.formKey.currentState!.validate()) {
|
||||
box.write(BoxName.phoneWallet,
|
||||
controller.walletphoneController.text);
|
||||
await controller.payWithSyriaTelWallet(
|
||||
controller.selectedAmount.toString(), 'SYP');
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pay by Syriatel Wallet'.tr,
|
||||
controller.isLoading = false;
|
||||
controller.update();
|
||||
} else {
|
||||
Toast.show(
|
||||
context,
|
||||
'⚠️ You need to choose an amount!'.tr,
|
||||
AppColor.redColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pay by MTN Wallet'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/cashMTN.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
|
||||
if (box.read(BoxName.countryCode) == 'Jordan')
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '962791234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (controller.formKey.currentState!.validate()) {
|
||||
if (controller.selectedAmount != 0) {
|
||||
controller.isLoading = true;
|
||||
controller.update();
|
||||
box.write(BoxName.phoneWallet,
|
||||
(controller.walletphoneController.text));
|
||||
await controller.payWithClickWallet(
|
||||
context,
|
||||
controller.selectedAmount.toString(),
|
||||
CurrencyHelper.currency,
|
||||
);
|
||||
await controller.getPassengerWallet();
|
||||
|
||||
controller.isLoading = false;
|
||||
controller.update();
|
||||
} else {
|
||||
Toast.show(
|
||||
context,
|
||||
'⚠️ You need to choose an amount!'.tr,
|
||||
AppColor.redColor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
'Pay by Cliq'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/syriatel.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// التحقق بالبصمة قبل أي شيء
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
trailing: const Icon(Icons.payment, size: 40, color: Colors.green),
|
||||
),
|
||||
)), if (box.read(BoxName.countryCode) == 'Syria')
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: controller.formKey,
|
||||
child: MyTextForm(
|
||||
controller: controller.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (controller.formKey.currentState!.validate()) {
|
||||
box.write(BoxName.phoneWallet,
|
||||
controller.walletphoneController.text);
|
||||
await controller.payWithSyriaTelWallet(
|
||||
controller.selectedAmount.toString(),
|
||||
CurrencyHelper.currency);
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pay by Syriatel Wallet'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/syriatel.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
if (box.read(BoxName.countryCode) == 'Syria')
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// التحقق بالبصمة قبل أي شيء
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate = await LocalAuthentication().authenticate(
|
||||
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication().authenticate(
|
||||
localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
|
||||
if (!didAuthenticate) {
|
||||
Log.print("❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
if (!didAuthenticate) {
|
||||
Log.print("❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||
Get.to(() => PaymentScreenSmsProvider(
|
||||
amount: double.parse(controller.selectedAmount.toString())));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pay by Sham Cash'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/shamCash.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||
Get.to(() => PaymentScreenSmsProvider(
|
||||
amount:
|
||||
double.parse(controller.selectedAmount.toString())));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Pay by Sham Cash'.tr,
|
||||
style: AppStyle.title,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/shamCash.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
child: Text('Cancel'.tr),
|
||||
@@ -483,4 +551,4 @@ void showPaymentOptions(BuildContext context, PaymentController controller) {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
208
siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart
Normal file
208
siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
|
||||
class PaymentScreenCliq extends StatefulWidget {
|
||||
final double amount;
|
||||
final String invoiceNumber;
|
||||
final String cliqAlias;
|
||||
|
||||
const PaymentScreenCliq({
|
||||
Key? key,
|
||||
required this.amount,
|
||||
required this.invoiceNumber,
|
||||
required this.cliqAlias,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PaymentScreenCliq> createState() => _PaymentScreenCliqState();
|
||||
}
|
||||
|
||||
class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTickerProviderStateMixin {
|
||||
Timer? _pollingTimer;
|
||||
String _status = 'waiting'; // waiting, uploading, verifying, success, error
|
||||
final TextEditingController _proofController = TextEditingController();
|
||||
|
||||
late AnimationController _blinkController;
|
||||
late Animation<Color?> _colorAnimation;
|
||||
late Animation<double> _shadowAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_blinkController = AnimationController(duration: const Duration(milliseconds: 800), vsync: this)..repeat(reverse: true);
|
||||
_colorAnimation = ColorTween(begin: Colors.red.shade700, end: Colors.red.shade100).animate(_blinkController);
|
||||
_shadowAnimation = Tween<double>(begin: 2.0, end: 15.0).animate(CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut));
|
||||
|
||||
_startPolling();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pollingTimer?.cancel();
|
||||
_blinkController.dispose();
|
||||
_proofController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startPolling() {
|
||||
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
if (_status == 'success' || _status == 'verifying') return;
|
||||
try {
|
||||
final res = await CRUD().postWallet(link: AppLink.checkCliqStatus, payload: {'invoice_number': widget.invoiceNumber});
|
||||
if (res != 'failure' && res['status'] == 'success' && res['invoice_status'] == 'completed') {
|
||||
timer.cancel();
|
||||
if (mounted) setState(() => _status = 'success');
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _submitProof() async {
|
||||
if (_proofController.text.trim().isEmpty) {
|
||||
Get.snackbar('Error'.tr, 'Please paste the transfer message'.tr, backgroundColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
setState(() => _status = 'verifying');
|
||||
|
||||
try {
|
||||
final res = await CRUD().postWallet(link: AppLink.uploadCliqProof, payload: {
|
||||
'invoice_number': widget.invoiceNumber,
|
||||
'proof_text': _proofController.text.trim(),
|
||||
});
|
||||
|
||||
if (res != 'failure' && res['status'] == 'success') {
|
||||
if (mounted) setState(() => _status = 'success');
|
||||
} else {
|
||||
if (mounted) setState(() => _status = 'error');
|
||||
Get.defaultDialog(title: 'Failed'.tr, content: Text(res['message']?.toString() ?? 'Verification failed'));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) setState(() => _status = 'error');
|
||||
Get.defaultDialog(title: 'Error'.tr, content: Text('Error uploading proof'.tr));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(title: const Text("Cliq Payment"), centerTitle: true, backgroundColor: Colors.white, foregroundColor: Colors.black),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: _status == 'success' ? _buildSuccessUI() : _buildWaitingUI(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWaitingUI() {
|
||||
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
decoration: BoxDecoration(gradient: LinearGradient(colors: [Colors.blue.shade800, Colors.blue.shade600]), borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("المبلغ المطلوب", style: TextStyle(color: Colors.white70, fontSize: 14)),
|
||||
const SizedBox(height: 5),
|
||||
Text("${currencyFormat.format(widget.amount)} ل.س", style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
AnimatedBuilder(
|
||||
animation: _blinkController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: _colorAnimation.value ?? Colors.red, width: 3.0), boxShadow: [BoxShadow(color: (_colorAnimation.value ?? Colors.red).withOpacity(0.4), blurRadius: _shadowAnimation.value, spreadRadius: 2)]),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("يرجى تحويل المبلغ إلى الاسم المستعار التالي (Alias):", textAlign: TextAlign.center, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 15),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: widget.cliqAlias));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text("تم نسخ الاسم ✅", textAlign: TextAlign.center), backgroundColor: Colors.green.shade600));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(widget.cliqAlias, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2.0)),
|
||||
const Icon(Icons.copy, color: Colors.blue),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
const Text("بعد إتمام التحويل، يرجى نسخ رسالة البنك النصية ولصقها هنا للتحقق التلقائي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _proofController,
|
||||
maxLines: 4,
|
||||
decoration: InputDecoration(
|
||||
hintText: "قم بلصق نص رسالة التحويل هنا...",
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue.shade800, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
|
||||
onPressed: _status == 'verifying' ? null : _submitProof,
|
||||
child: _status == 'verifying'
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text("تحقق من الدفع", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text("جاري فحص الفاتورة تلقائياً كل 5 ثوانٍ...", style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSuccessUI() {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.verified_rounded, color: Colors.green, size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text("تم الدفع بنجاح!", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () { Get.back(); Get.back(); },
|
||||
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
208
siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart
Normal file
208
siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../controller/functions/crud.dart';
|
||||
|
||||
class PaymentScreenMtn extends StatefulWidget {
|
||||
final double amount;
|
||||
final String invoiceNumber;
|
||||
final String mtnNumber;
|
||||
|
||||
const PaymentScreenMtn({
|
||||
Key? key,
|
||||
required this.amount,
|
||||
required this.invoiceNumber,
|
||||
required this.mtnNumber,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<PaymentScreenMtn> createState() => _PaymentScreenMtnState();
|
||||
}
|
||||
|
||||
class _PaymentScreenMtnState extends State<PaymentScreenMtn> with SingleTickerProviderStateMixin {
|
||||
Timer? _pollingTimer;
|
||||
String _status = 'waiting'; // waiting, uploading, verifying, success, error
|
||||
final TextEditingController _proofController = TextEditingController();
|
||||
|
||||
late AnimationController _blinkController;
|
||||
late Animation<Color?> _colorAnimation;
|
||||
late Animation<double> _shadowAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_blinkController = AnimationController(duration: const Duration(milliseconds: 800), vsync: this)..repeat(reverse: true);
|
||||
_colorAnimation = ColorTween(begin: Colors.red.shade700, end: Colors.red.shade100).animate(_blinkController);
|
||||
_shadowAnimation = Tween<double>(begin: 2.0, end: 15.0).animate(CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut));
|
||||
|
||||
_startPolling();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pollingTimer?.cancel();
|
||||
_blinkController.dispose();
|
||||
_proofController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startPolling() {
|
||||
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
if (_status == 'success' || _status == 'verifying') return;
|
||||
try {
|
||||
final res = await CRUD().postWallet(link: AppLink.checkMtnStatus, payload: {'invoice_number': widget.invoiceNumber});
|
||||
if (res != 'failure' && res['status'] == 'success' && res['invoice_status'] == 'completed') {
|
||||
timer.cancel();
|
||||
if (mounted) setState(() => _status = 'success');
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _submitProof() async {
|
||||
if (_proofController.text.trim().isEmpty) {
|
||||
Get.snackbar('Error'.tr, 'Please paste the transfer message'.tr, backgroundColor: Colors.red);
|
||||
return;
|
||||
}
|
||||
setState(() => _status = 'verifying');
|
||||
|
||||
try {
|
||||
final res = await CRUD().postWallet(link: AppLink.uploadMtnProof, payload: {
|
||||
'invoice_number': widget.invoiceNumber,
|
||||
'proof_text': _proofController.text.trim(),
|
||||
});
|
||||
|
||||
if (res != 'failure' && res['status'] == 'success') {
|
||||
if (mounted) setState(() => _status = 'success');
|
||||
} else {
|
||||
if (mounted) setState(() => _status = 'error');
|
||||
Get.defaultDialog(title: 'Failed'.tr, content: Text(res['message']?.toString() ?? 'Verification failed'));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) setState(() => _status = 'error');
|
||||
Get.defaultDialog(title: 'Error'.tr, content: Text('Error uploading proof'.tr));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[50],
|
||||
appBar: AppBar(title: const Text("MTN Payment"), centerTitle: true, backgroundColor: Colors.white, foregroundColor: Colors.black),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: _status == 'success' ? _buildSuccessUI() : _buildWaitingUI(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWaitingUI() {
|
||||
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15),
|
||||
decoration: BoxDecoration(gradient: LinearGradient(colors: [Colors.blue.shade800, Colors.blue.shade600]), borderRadius: BorderRadius.circular(16)),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("المبلغ المطلوب", style: TextStyle(color: Colors.white70, fontSize: 14)),
|
||||
const SizedBox(height: 5),
|
||||
Text("${currencyFormat.format(widget.amount)} ل.س", style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
AnimatedBuilder(
|
||||
animation: _blinkController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: _colorAnimation.value ?? Colors.red, width: 3.0), boxShadow: [BoxShadow(color: (_colorAnimation.value ?? Colors.red).withOpacity(0.4), blurRadius: _shadowAnimation.value, spreadRadius: 2)]),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text("يرجى تحويل المبلغ إلى الرقم التالي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 15),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: widget.mtnNumber));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: const Text("تم نسخ الرقم ✅", textAlign: TextAlign.center), backgroundColor: Colors.green.shade600));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(widget.mtnNumber, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2.0)),
|
||||
const Icon(Icons.copy, color: Colors.blue),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
const Text("بعد إتمام التحويل، يرجى نسخ رسالة البنك النصية ولصقها هنا للتحقق التلقائي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _proofController,
|
||||
maxLines: 4,
|
||||
decoration: InputDecoration(
|
||||
hintText: "قم بلصق نص رسالة التحويل هنا...",
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue.shade800, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
|
||||
onPressed: _status == 'verifying' ? null : _submitProof,
|
||||
child: _status == 'verifying'
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text("تحقق من الدفع", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text("جاري فحص الفاتورة تلقائياً كل 5 ثوانٍ...", style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSuccessUI() {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.verified_rounded, color: Colors.green, size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text("تم الدفع بنجاح!", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.green, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16)),
|
||||
onPressed: () { Get.back(); Get.back(); },
|
||||
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user