Files
musadaq-saas/musadaq-app/lib/features/subscription/views/subscription_view.dart
2026-05-16 01:40:56 +03:00

486 lines
20 KiB
Dart

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(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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: 20),
// Toggle
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: isDark ? Colors.white.withOpacity(0.05) : Colors.black.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => controller.isAnnual.value = false,
child: Obx(() => Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: !controller.isAnnual.value ? const Color(0xFF0F4C81) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
'دفع شهري',
style: TextStyle(
color: !controller.isAnnual.value ? Colors.white : (isDark ? Colors.white60 : Colors.black54),
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
),
)),
),
),
Expanded(
child: GestureDetector(
onTap: () => controller.isAnnual.value = true,
child: Obx(() => Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: controller.isAnnual.value ? const Color(0xFF0F4C81) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
'دفع سنوي (توفير ✨)',
style: TextStyle(
color: controller.isAnnual.value ? Colors.white : (isDark ? Colors.white60 : Colors.black54),
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
),
)),
),
),
],
),
),
const SizedBox(height: 20),
// 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_name_en'] ?? sub['plan_id'] ?? 'مجانية';
final daysLeft = sub['days_remaining'] ?? 0;
final invoices = sub['invoices'] as Map<String, dynamic>?;
final used = invoices?['used'] ?? 0;
final limit = 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),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.upload_file, size: 18),
label: const Text('رفع الوصل'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF59E0B),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: () {
Get.toNamed('/payment-receipt', arguments: payment);
},
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.close, size: 18),
label: const Text('إلغاء الطلب'),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red,
side: const BorderSide(color: Colors.red),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: () {
Get.dialog(
AlertDialog(
title: const Text('إلغاء الطلب'),
content: const Text('هل أنت متأكد من إلغاء طلب الدفع هذا؟'),
actions: [
TextButton(onPressed: () => Get.back(), child: const Text('تراجع')),
TextButton(
onPressed: () {
Get.back();
final ctrl = Get.find<SubscriptionController>();
ctrl.cancelPaymentRequest(payment['id'].toString());
},
child: const Text('نعم، إلغاء', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
),
],
),
],
),
);
}
Widget _buildPlanCard(Map<String, dynamic> plan, SubscriptionController ctrl, bool isDark) {
final isPopular = plan['is_popular'] == true;
final nameAr = plan['name_ar'] ?? plan['name_en'] ?? 'باقة';
return Obx(() {
final bool annual = ctrl.isAnnual.value;
final price = annual
? (plan['price_annual_jod'] ?? (plan['price_jod'] * 10)).toString()
: (plan['price_monthly_jod'] ?? plan['price_jod']).toString();
final features = (plan['features'] as List?)?.cast<String>() ?? [];
final invoiceLimit = annual
? (plan['max_invoices_month'] * 12).toString()
: (plan['max_invoices_month']).toString();
final cycleText = annual ? 'سنة' : 'شهر';
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 / $cycleText', 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, '$invoiceLimit فاتورة/$cycleText'),
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 ثم أدخل رقم المرجع');
Get.toNamed('/payment-receipt', arguments: result);
}
},
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)),
),
),
);
}
}