Update: 2026-08-08 17:42:04

This commit is contained in:
Hamza-Ayed
2026-08-08 17:42:04 +03:00
parent e63f990aea
commit e52afa1863
3 changed files with 176 additions and 45 deletions
@@ -23,7 +23,8 @@ class PaymentScreenCliq extends StatefulWidget {
State<PaymentScreenCliq> createState() => _PaymentScreenCliqState();
}
class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTickerProviderStateMixin {
class _PaymentScreenCliqState extends State<PaymentScreenCliq>
with SingleTickerProviderStateMixin {
Timer? _pollingTimer;
String _status = 'waiting'; // waiting, uploading, verifying, success, error
final TextEditingController _proofController = TextEditingController();
@@ -35,9 +36,14 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
@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));
_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();
}
@@ -54,8 +60,12 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
_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') {
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');
}
@@ -65,13 +75,15 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
Future<void> _submitProof() async {
if (_proofController.text.trim().isEmpty) {
Get.snackbar('Error'.tr, 'Please paste the transfer message'.tr, backgroundColor: Colors.red);
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: {
final res =
await CRUD().postWallet(link: AppLink.uploadCliqProof, payload: {
'invoice_number': widget.invoiceNumber,
'proof_text': _proofController.text.trim(),
});
@@ -80,11 +92,14 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
if (mounted) setState(() => _status = 'success');
} else {
if (mounted) setState(() => _status = 'error');
Get.defaultDialog(title: 'Failed'.tr, content: Text(res['message']?.toString() ?? 'Verification failed'));
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));
Get.defaultDialog(
title: 'Error'.tr, content: Text('Error uploading proof'.tr));
}
}
@@ -110,40 +125,117 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
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)),
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 Text("المبلغ المطلوب",
style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 5),
Text('${widget.amount.toStringAsFixed(widget.amount == widget.amount.roundToDouble() ? 0 : 1)} JOD',
style: const TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: 1)),
Text(
'${widget.amount.toStringAsFixed(widget.amount == widget.amount.roundToDouble() ? 0 : 1)} JOD',
style: const TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.w700,
letterSpacing: 1)),
],
),
),
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)]),
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 Text(
"يرجى تحويل المبلغ إلى الاسم المستعار التالي (Alias):",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.amber.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.amber.shade200)),
child: Column(
children: [
const Text(
"⚠️ هام جداً: يجب إدخال رقم الفاتورة التالي في (ملاحظات التحويل) عند الدفع:",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.brown,
fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.invoiceNumber,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black)),
IconButton(
icon: const Icon(Icons.copy,
color: Colors.brown, size: 20),
onPressed: () {
Clipboard.setData(ClipboardData(
text: widget.invoiceNumber));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم الفاتورة ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.amber));
},
),
],
),
],
),
),
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));
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)),
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)),
Text(widget.cliqAlias,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
letterSpacing: 2.0)),
const Icon(Icons.copy, color: Colors.blue),
],
),
@@ -154,16 +246,19 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
);
},
),
const SizedBox(height: 30),
const Text("بعد إتمام التحويل، يرجى نسخ رسالة البنك النصية ولصقها هنا للتحقق التلقائي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14)),
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)),
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
filled: true,
fillColor: Colors.white,
),
@@ -173,15 +268,23 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue.shade800, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
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 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)),
const Text("جاري فحص الفاتورة تلقائياً كل 5 ثوانٍ...",
style: TextStyle(color: Colors.grey, fontSize: 12)),
],
),
);
@@ -193,13 +296,20 @@ class _PaymentScreenCliqState extends State<PaymentScreenCliq> with SingleTicker
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 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(); },
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)),
),
),
@@ -131,6 +131,30 @@ class _PaymentScreenMtnState extends State<PaymentScreenMtn> with SingleTickerPr
children: [
const Text("يرجى تحويل المبلغ إلى الرقم التالي:", textAlign: TextAlign.center, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.amber.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.amber.shade200)),
child: Column(
children: [
const Text("⚠️ هام جداً: يجب إدخال رقم الفاتورة التالي في (ملاحظات التحويل) عند الدفع:", textAlign: TextAlign.center, style: TextStyle(fontSize: 14, color: Colors.brown, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.invoiceNumber, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black)),
IconButton(
icon: const Icon(Icons.copy, color: Colors.brown, size: 20),
onPressed: () {
Clipboard.setData(ClipboardData(text: widget.invoiceNumber));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("تم نسخ رقم الفاتورة ✅", textAlign: TextAlign.center), backgroundColor: Colors.amber));
},
),
],
),
],
),
),
const SizedBox(height: 15),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: widget.mtnNumber));
@@ -190,13 +190,7 @@ class PointsCaptain extends StatelessWidget {
title: 'CliQ'.tr,
subtitle: 'Pay via CliQ (Alias: siroapp)'.tr,
color: Colors.purple,
onTap: () {
Get.back();
MyDialog().getDialog(
'CliQ Payment'.tr,
'Please transfer the amount to alias: siroapp'.tr,
() => Get.back());
},
onTap: () => _showPhoneInputDialog(context, 'CliQ'),
),
],
if (box.read(BoxName.countryCode) == 'Egypt') ...[
@@ -302,7 +296,7 @@ class PointsCaptain extends StatelessWidget {
child: MyTextForm(
controller: paymentController.walletphoneController,
label: 'Phone Number'.tr,
hint: provider == 'Syriatel' ? '963991234567' : '963941234567',
hint: provider == 'Syriatel' ? '963991234567' : provider == 'CliQ' ? '0791234567' : '963941234567',
type: TextInputType.phone,
),
),
@@ -316,6 +310,9 @@ class PointsCaptain extends StatelessWidget {
if (provider == 'Syriatel') {
await payWithSyriaTelWallet(
context, pricePoint.toString(), CurrencyHelper.currency);
} else if (provider == 'CliQ') {
captainWalletController.phoneWallet.text = paymentController.walletphoneController.text;
await captainWalletController.payWithClickWallet(context, pricePoint.toString(), CurrencyHelper.currency);
} else {
await payWithMTNWallet(
context, pricePoint.toString(), CurrencyHelper.currency);