Update: 2026-05-08 23:18:10
This commit is contained in:
@@ -98,7 +98,7 @@ function detectAudioMimeType(string $path, string $fallback, string $fileName =
|
|||||||
|
|
||||||
function extractIntentFromAudio(string $base64Audio, string $mimeType, string $apiKey): array
|
function extractIntentFromAudio(string $base64Audio, string $mimeType, string $apiKey): array
|
||||||
{
|
{
|
||||||
$model = env('GEMINI_VOICE_MODEL', 'gemini-1.5-flash');
|
$model = env('GEMINI_MODEL', 'gemini-flash-lite-latest');
|
||||||
|
|
||||||
$systemPrompt = <<<PROMPT
|
$systemPrompt = <<<PROMPT
|
||||||
أنت مساعد أوامر صوتية عربي لمنصة "مُصادَق" للفوترة الأردنية.
|
أنت مساعد أوامر صوتية عربي لمنصة "مُصادَق" للفوترة الأردنية.
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ android {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
android.useLibSizedPageAlignment=true
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ PODS:
|
|||||||
- flutter_secure_storage_darwin (10.0.0):
|
- flutter_secure_storage_darwin (10.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- freerasp (1.0.0):
|
||||||
|
- Flutter
|
||||||
- GoogleDataTransport (10.1.0):
|
- GoogleDataTransport (10.1.0):
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
@@ -147,6 +149,7 @@ DEPENDENCIES:
|
|||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
||||||
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||||
|
- freerasp (from `.symlinks/plugins/freerasp/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
|
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
|
||||||
@@ -198,6 +201,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
|
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
|
||||||
flutter_secure_storage_darwin:
|
flutter_secure_storage_darwin:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||||
|
freerasp:
|
||||||
|
:path: ".symlinks/plugins/freerasp/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
@@ -240,6 +245,7 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
||||||
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||||
|
freerasp: d77275f774facb901f52e9608e5bd34768728363
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:freerasp/freerasp.dart';
|
import 'package:freerasp/freerasp.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
/// Device Security Service
|
/// Device Security Service
|
||||||
/// Enterprise-grade jailbreak/root/tamper detection for Musadaq using freeRASP.
|
/// Enterprise-grade jailbreak/root/tamper detection for Musadaq using freeRASP.
|
||||||
@@ -71,6 +72,14 @@ class DeviceSecurityService extends GetxService {
|
|||||||
/// Handle threat detection
|
/// Handle threat detection
|
||||||
void _onThreatDetected(String threat) {
|
void _onThreatDetected(String threat) {
|
||||||
debugPrint('[Security] ⚠️ THREAT: $threat');
|
debugPrint('[Security] ⚠️ THREAT: $threat');
|
||||||
|
|
||||||
|
// RELAXED SECURITY FOR DEBUGGING
|
||||||
|
// Only block the app in release mode or if explicitly requested.
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint('[Security] Skipping block due to kDebugMode');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
threatDetails.add(threat);
|
threatDetails.add(threat);
|
||||||
isCompromised.value = true;
|
isCompromised.value = true;
|
||||||
|
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ class HomeWidgetService extends GetxService {
|
|||||||
/// Refresh widget data from API
|
/// Refresh widget data from API
|
||||||
Future<void> refreshWidgetData() async {
|
Future<void> refreshWidgetData() async {
|
||||||
try {
|
try {
|
||||||
final res = await DioClient().client.get('/v1/dashboard/stats');
|
final res = await DioClient().client.get('dashboard/stats');
|
||||||
if (res.data['success'] == true) {
|
if (res.data['success'] == true) {
|
||||||
final data = res.data['data'];
|
final data = res.data['data'];
|
||||||
totalInvoices.value = data['invoices']?['total'] ?? 0;
|
totalInvoices.value = data['invoices']?['total'] ?? 0;
|
||||||
pendingInvoices.value = data['invoices']?['pending'] ?? 0;
|
pendingInvoices.value = data['invoices']?['pending'] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
final subRes = await DioClient().client.get('/v1/subscriptions/current');
|
final subRes = await DioClient().client.get('subscriptions/current');
|
||||||
if (subRes.data['success'] == true) {
|
if (subRes.data['success'] == true) {
|
||||||
final sub = subRes.data['data'];
|
final sub = subRes.data['data'];
|
||||||
quotaUsed.value = sub['invoices']?['used'] ?? 0;
|
quotaUsed.value = sub['invoices']?['used'] ?? 0;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class AiUsageView extends StatelessWidget {
|
|||||||
Widget _buildHeroCard(Map<String, dynamic> overall, bool isDark) {
|
Widget _buildHeroCard(Map<String, dynamic> overall, bool isDark) {
|
||||||
final totalRequests = overall['total_requests'] ?? 0;
|
final totalRequests = overall['total_requests'] ?? 0;
|
||||||
final totalTokens = overall['total_tokens'] ?? 0;
|
final totalTokens = overall['total_tokens'] ?? 0;
|
||||||
final totalCostJod = (overall['total_cost_jod'] ?? 0).toDouble();
|
final totalCostJod = double.tryParse(overall['total_cost_jod']?.toString() ?? '0') ?? 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
@@ -122,7 +122,7 @@ class AiUsageView extends StatelessWidget {
|
|||||||
Widget _buildPeriodCard(String title, Map<String, dynamic> data, IconData icon, Color color, bool isDark) {
|
Widget _buildPeriodCard(String title, Map<String, dynamic> data, IconData icon, Color color, bool isDark) {
|
||||||
final requests = data['requests'] ?? 0;
|
final requests = data['requests'] ?? 0;
|
||||||
final tokens = data['tokens'] ?? 0;
|
final tokens = data['tokens'] ?? 0;
|
||||||
final costJod = (data['cost_jod'] ?? 0).toDouble();
|
final costJod = double.tryParse(data['cost_jod']?.toString() ?? '0') ?? 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -153,8 +153,8 @@ class AiUsageView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCostPerInvoiceCard(Map<String, dynamic> overall, bool isDark) {
|
Widget _buildCostPerInvoiceCard(Map<String, dynamic> overall, bool isDark) {
|
||||||
final avgCost = (overall['avg_cost_per_invoice_jod'] ?? 0).toDouble();
|
final avgCost = double.tryParse(overall['avg_cost_per_invoice_jod']?.toString() ?? '0') ?? 0.0;
|
||||||
final avgTokens = (overall['avg_tokens_per_invoice'] ?? 0).toDouble();
|
final avgTokens = double.tryParse(overall['avg_tokens_per_invoice']?.toString() ?? '0') ?? 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
@@ -238,7 +238,7 @@ class AiUsageView extends StatelessWidget {
|
|||||||
final date = d['date'] ?? '';
|
final date = d['date'] ?? '';
|
||||||
final requests = d['requests'] ?? 0;
|
final requests = d['requests'] ?? 0;
|
||||||
final tokens = d['tokens'] ?? 0;
|
final tokens = d['tokens'] ?? 0;
|
||||||
final costJod = (d['cost_jod'] ?? 0).toDouble();
|
final costJod = double.tryParse(d['cost_jod']?.toString() ?? '0') ?? 0.0;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
|||||||
@@ -98,4 +98,22 @@ class SubscriptionController extends GetxController {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> cancelPaymentRequest(String paymentId) async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
final res = await DioClient().client.post('payments/cancel', data: {'payment_id': paymentId});
|
||||||
|
if (res.data['success'] == true) {
|
||||||
|
AppSnackbar.showSuccess('تم الإلغاء', 'تم إلغاء طلب الدفع بنجاح');
|
||||||
|
await loadAll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
AppLogger.error('Failed to cancel payment', e);
|
||||||
|
AppSnackbar.showError('خطأ', 'فشل إلغاء طلب الدفع');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,21 +169,57 @@ class SubscriptionView extends StatelessWidget {
|
|||||||
Text('رقم المرجع: ${payment['reference_number']}', style: const TextStyle(fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.bold)),
|
Text('رقم المرجع: ${payment['reference_number']}', style: const TextStyle(fontFamily: 'monospace', fontSize: 14, fontWeight: FontWeight.bold)),
|
||||||
Text('المبلغ: ${payment['amount_jod']} JOD', style: const TextStyle(fontSize: 13)),
|
Text('المبلغ: ${payment['amount_jod']} JOD', style: const TextStyle(fontSize: 13)),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
const SizedBox(height: 12),
|
||||||
width: double.infinity,
|
Row(
|
||||||
child: ElevatedButton.icon(
|
children: [
|
||||||
icon: const Icon(Icons.upload_file, size: 18),
|
Expanded(
|
||||||
label: const Text('رفع وصل الدفع'),
|
child: ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
icon: const Icon(Icons.upload_file, size: 18),
|
||||||
backgroundColor: const Color(0xFFF59E0B),
|
label: const Text('رفع الوصل'),
|
||||||
foregroundColor: Colors.white,
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
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);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
const SizedBox(width: 8),
|
||||||
// Navigate to receipt upload screen
|
Expanded(
|
||||||
Get.toNamed('/payment-receipt', arguments: payment);
|
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)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: musadaq_app
|
name: musadaq_app
|
||||||
description: Jordanian E-Invoicing Automation SaaS
|
description: Jordanian E-Invoicing Automation SaaS
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.0.0+1
|
version: 1.0.2+2
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.2.0 <4.0.0'
|
sdk: '>=3.2.0 <4.0.0'
|
||||||
|
|||||||
Reference in New Issue
Block a user