Update: 2026-05-07 03:06:15
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/payment_receipt_controller.dart';
|
||||
|
||||
class PaymentReceiptView extends StatelessWidget {
|
||||
const PaymentReceiptView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(PaymentReceiptController());
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA),
|
||||
appBar: AppBar(
|
||||
title: const Text('إتمام الدفع', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
backgroundColor: isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Payment Info Card
|
||||
Obx(() => Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text('تفاصيل التحويل المطلوب', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('الاسم المستعار (CliQ)', controller.payment['cliq_alias'] ?? '', isDark, isHighlight: true),
|
||||
const Divider(height: 24),
|
||||
_buildInfoRow('المبلغ المطلوب', '${controller.payment['amount_jod'] ?? 0} JOD', isDark),
|
||||
],
|
||||
),
|
||||
)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
Text('الخطوة التالية:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: isDark ? Colors.white : Colors.black87)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'قم بتحويل المبلغ عبر تطبيق البنك الخاص بك إلى الاسم المستعار المذكور أعلاه (CliQ). بعد إتمام الحوالة بنجاح، ستصلك رسالة أو إشعار من البنك يحتوي على "رقم المرجع" للعملية. انسخه والصقه هنا.',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.grey.shade700, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Reference Number Input Area
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('رقم المرجع (Reference Number)', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: controller.referenceController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'مثال: 1234567890',
|
||||
hintStyle: TextStyle(color: isDark ? Colors.white38 : Colors.grey),
|
||||
filled: true,
|
||||
fillColor: isDark ? const Color(0xFF1A1A2E) : const Color(0xFFF1F5F9),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Submit Button
|
||||
SizedBox(
|
||||
height: 52,
|
||||
child: Obx(() => ElevatedButton.icon(
|
||||
onPressed: controller.isUploading.value
|
||||
? null
|
||||
: () => controller.submitReference(),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
disabledBackgroundColor: isDark ? Colors.white10 : Colors.grey.shade300,
|
||||
),
|
||||
icon: controller.isUploading.value
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.check_circle_outline),
|
||||
label: Text(
|
||||
controller.isUploading.value ? 'جاري التحقق...' : 'تأكيد الدفع',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value, bool isDark, {bool isHighlight = false}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.grey.shade600)),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: isHighlight ? 12 : 0, vertical: isHighlight ? 6 : 0),
|
||||
decoration: isHighlight ? BoxDecoration(
|
||||
color: const Color(0xFF0F4C81).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
) : null,
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
color: isHighlight ? const Color(0xFF0F4C81) : (isDark ? Colors.white : Colors.black87),
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../controllers/subscription_controller.dart';
|
||||
import '../../../core/utils/app_snackbar.dart';
|
||||
|
||||
class SubscriptionView extends StatelessWidget {
|
||||
const SubscriptionView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(SubscriptionController());
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA),
|
||||
appBar: AppBar(
|
||||
title: const Text('الاشتراكات', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
backgroundColor: isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
body: Obx(() {
|
||||
if (controller.isLoading.value) {
|
||||
return const Center(child: CircularProgressIndicator(color: Color(0xFF0F4C81)));
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => controller.loadAll(),
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Current Subscription Status
|
||||
if (controller.currentSubscription.value != null)
|
||||
_buildCurrentPlan(controller.currentSubscription.value!, isDark),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Active Payment Request Banner
|
||||
if (controller.activePaymentRequest.value != null)
|
||||
_buildActivePaymentBanner(controller.activePaymentRequest.value!, isDark),
|
||||
|
||||
// Plans Header
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.diamond_rounded, color: Color(0xFFD4AF37), size: 22),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'اختر باقتك',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : const Color(0xFF0F172A),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'ادفع عبر CliQ — بدون عمولة!',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Plans Grid
|
||||
...controller.plans.map((plan) => _buildPlanCard(plan, controller, isDark)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Payment History
|
||||
if (controller.myPayments.isNotEmpty) ...[
|
||||
const Text('سجل المدفوعات', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 12),
|
||||
...controller.myPayments.map((p) => _buildPaymentHistoryItem(p, isDark)),
|
||||
],
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCurrentPlan(Map<String, dynamic> sub, bool isDark) {
|
||||
final planName = sub['plan_name'] ?? sub['plan_id'] ?? 'مجانية';
|
||||
final daysLeft = sub['days_remaining'] ?? 0;
|
||||
final used = sub['invoices_used'] ?? 0;
|
||||
final limit = sub['invoices_limit'] ?? 0;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF0F4C81), Color(0xFF1A6BB5)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.verified, color: Color(0xFFD4AF37), size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'باقتك الحالية: $planName',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
_buildSubInfoChip(Icons.timer_outlined, '$daysLeft يوم متبقي'),
|
||||
const SizedBox(width: 12),
|
||||
_buildSubInfoChip(Icons.receipt_long, '$used/$limit فاتورة'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubInfoChip(IconData icon, String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: Colors.white70, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(text, style: const TextStyle(color: Colors.white, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivePaymentBanner(Map<String, dynamic> payment, bool isDark) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF7ED),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFFED7AA)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.pending_actions, color: Color(0xFFF59E0B), size: 20),
|
||||
SizedBox(width: 8),
|
||||
Text('لديك طلب دفع قائم', style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFF92400E))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('رقم المرجع: ${payment['reference_number']}', style: const TextStyle(fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
Text('المبلغ: ${payment['amount_jod']} JOD', style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.upload_file, size: 18),
|
||||
label: const Text('رفع وصل الدفع'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFF59E0B),
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
// Navigate to receipt upload screen
|
||||
Get.toNamed('/payment-receipt', arguments: payment);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlanCard(Map<String, dynamic> plan, SubscriptionController ctrl, bool isDark) {
|
||||
final isPopular = plan['is_popular'] == true;
|
||||
final price = (plan['price_jod'] ?? 0).toString();
|
||||
final features = (plan['features'] as List?)?.cast<String>() ?? [];
|
||||
final nameAr = plan['name_ar'] ?? plan['name_en'] ?? 'باقة';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isPopular ? const Color(0xFFD4AF37) : (isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
width: isPopular ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Popular Badge
|
||||
if (isPopular)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD4AF37),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(14), topRight: Radius.circular(14)),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('⭐ الأكثر شعبية', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(nameAr, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: isDark ? Colors.white : const Color(0xFF0F172A))),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(text: price, style: TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: isDark ? const Color(0xFF5EEAD4) : const Color(0xFF0F4C81))),
|
||||
TextSpan(text: ' JOD', style: TextStyle(fontSize: 12, color: isDark ? Colors.white38 : Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
plan['description_ar'] ?? '',
|
||||
style: TextStyle(fontSize: 13, color: isDark ? Colors.white38 : Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Features
|
||||
...features.map((f) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Color(0xFF10B981), size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(f, style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.black87))),
|
||||
],
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Stats Row
|
||||
Row(
|
||||
children: [
|
||||
_buildPlanStat(Icons.business, '${plan['max_companies'] ?? 0} شركات'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPlanStat(Icons.receipt_long, '${plan['max_invoices_month'] ?? 0} فاتورة/شهر'),
|
||||
const SizedBox(width: 8),
|
||||
_buildPlanStat(Icons.people, '${plan['max_users'] ?? 0} مستخدمين'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Upgrade Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: Obx(() => ElevatedButton(
|
||||
onPressed: ctrl.isCreatingPayment.value ? null : () async {
|
||||
final result = await ctrl.createPaymentRequest(plan['id'].toString());
|
||||
if (result != null) {
|
||||
AppSnackbar.showSuccess('تم إنشاء طلب الدفع', 'قم بالتحويل عبر CliQ ثم ارفع وصل الدفع');
|
||||
} else {
|
||||
AppSnackbar.showError('خطأ', 'فشل إنشاء طلب الدفع');
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isPopular ? const Color(0xFFD4AF37) : const Color(0xFF0F4C81),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: ctrl.isCreatingPayment.value
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Text('ترقية الآن', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPlanStat(IconData icon, String text) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F4C81).withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, size: 16, color: const Color(0xFF0F4C81)),
|
||||
const SizedBox(height: 2),
|
||||
Text(text, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w600), textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentHistoryItem(Map<String, dynamic> payment, bool isDark) {
|
||||
final status = payment['status'] ?? '';
|
||||
Color statusColor;
|
||||
String statusText;
|
||||
|
||||
switch (status) {
|
||||
case 'approved': statusColor = const Color(0xFF10B981); statusText = 'تم الاعتماد'; break;
|
||||
case 'pending': statusColor = const Color(0xFFF59E0B); statusText = 'قيد الانتظار'; break;
|
||||
case 'uploaded': statusColor = const Color(0xFF3B82F6); statusText = 'تحت المراجعة'; break;
|
||||
case 'rejected': statusColor = const Color(0xFFEF4444); statusText = 'مرفوض'; break;
|
||||
default: statusColor = Colors.grey; statusText = status;
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
elevation: 0,
|
||||
color: isDark ? const Color(0xFF1E1E2E) : Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: isDark ? Colors.white10 : Colors.grey.shade200),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(Icons.payment_rounded, color: statusColor, size: 20),
|
||||
),
|
||||
title: Text(payment['plan_name'] ?? 'باقة', style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14)),
|
||||
subtitle: Text('${payment['amount_jod']} JOD • ${payment['reference_number']}', style: const TextStyle(fontSize: 11, fontFamily: 'monospace')),
|
||||
trailing: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(statusText, style: TextStyle(color: statusColor, fontSize: 11, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user