From bd7164ed23a67b184fe3bf6f2c319691bd03510f Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 7 May 2026 03:50:16 +0300 Subject: [PATCH] Update: 2026-05-07 03:50:16 --- app/modules_app/invoices/file.php | 17 +- app/modules_app/invoices/view.php | 9 +- app/modules_app/voice/grok_intent.php | 69 +++--- .../controllers/dashboard_controller.dart | 132 ++++++++++- .../dashboard/views/dashboard_view.dart | 2 +- .../invoice_detail_controller.dart | 39 +++- .../views/payment_receipt_view.dart | 205 ++++++++++++------ musadaq-app/pubspec.lock | 132 ++++++++--- musadaq-app/pubspec.yaml | 5 +- 9 files changed, 464 insertions(+), 146 deletions(-) diff --git a/app/modules_app/invoices/file.php b/app/modules_app/invoices/file.php index 249cda7..b427935 100644 --- a/app/modules_app/invoices/file.php +++ b/app/modules_app/invoices/file.php @@ -19,15 +19,14 @@ function outputErrorImage($message) { exit; } -// Extract token from header OR query string -$headers = getallheaders(); -$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? ''; -$token = ''; - -if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { - $token = $matches[1]; -} elseif (isset($_GET['token'])) { - $token = $_GET['token']; +// Extract token from header OR query string using helper +$token = input('token'); +if (!$token) { + $headers = getallheaders(); + $authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? ''; + if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $token = $matches[1]; + } } if (!$token) outputErrorImage('Forbidden: No token'); diff --git a/app/modules_app/invoices/view.php b/app/modules_app/invoices/view.php index 1f77a64..e821f53 100644 --- a/app/modules_app/invoices/view.php +++ b/app/modules_app/invoices/view.php @@ -91,8 +91,13 @@ try { $invoice['jofotara'] = null; } - // 5. Build the secure file URL using the invoice ID (file.php fetches path from DB) - $invoice['file_url'] = '/index.php?route=v1/invoices/file&id=' . urlencode($id); + // 5. Build the secure file URL with token (for Image.network compatibility) + $authHeader = getallheaders()['Authorization'] ?? getallheaders()['authorization'] ?? ''; + $token = ''; + if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $token = $matches[1]; + } + $invoice['file_url'] = '/index.php?route=v1/invoices/file&id=' . urlencode($id) . '&token=' . $token; // 6. Include local QR code from invoices table if available // (This is used as a fallback in shell.php if jofotara object is missing) diff --git a/app/modules_app/voice/grok_intent.php b/app/modules_app/voice/grok_intent.php index 1e26410..63337ea 100644 --- a/app/modules_app/voice/grok_intent.php +++ b/app/modules_app/voice/grok_intent.php @@ -54,7 +54,7 @@ $systemPrompt = << 'grok-1', // Update to the correct Grok model when available + 'model' => 'grok-beta', // Update to current xAI model name 'messages' => [ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $text] @@ -65,37 +65,42 @@ $payload = [ $url = "https://api.x.ai/v1/chat/completions"; -$ch = curl_init($url); -curl_setopt_array($ch, [ - CURLOPT_POST => true, - CURLOPT_POSTFIELDS => json_encode($payload), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => [ - 'Content-Type: application/json', - 'Authorization: Bearer ' . $apiKey - ] -]); +try { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $apiKey + ] + ]); -$response = curl_exec($ch); -$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); -$error = curl_error($ch); -curl_close($ch); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); -if ($httpCode !== 200) { - error_log("Grok Error: $response | $error"); - json_error('فشل في تحليل الأمر بواسطة Grok', 500); + if ($httpCode !== 200) { + error_log("Grok Error: $response | $error"); + json_error('فشل في تحليل الأمر بواسطة Grok. تأكد من صحة مفتاح API وصلاحية الحساب.', 500); + } + + $respData = json_decode($response, true); + if (!isset($respData['choices'][0]['message']['content'])) { + json_error('رد غير متوقع من Grok AI', 500); + } + + $jsonText = $respData['choices'][0]['message']['content']; + $parsed = json_decode($jsonText, true); + + if (!$parsed) { + json_error('فشل في تحليل الرد كـ JSON', 500); + } + + json_success($parsed, 'تم تحليل الأمر بواسطة Grok'); +} catch (\Throwable $e) { + error_log("Voice Intent Error: " . $e->getMessage()); + json_error('حدث خطأ فني أثناء تحليل الأمر صوتياً.', 500); } - -$respData = json_decode($response, true); -if (!isset($respData['choices'][0]['message']['content'])) { - json_error('رد غير متوقع من Grok AI', 500); -} - -$jsonText = $respData['choices'][0]['message']['content']; -$parsed = json_decode($jsonText, true); - -if (!$parsed) { - json_error('فشل في تحليل الرد كـ JSON', 500); -} - -json_success($parsed, 'تم تحليل الأمر بواسطة Grok'); diff --git a/musadaq-app/lib/features/dashboard/controllers/dashboard_controller.dart b/musadaq-app/lib/features/dashboard/controllers/dashboard_controller.dart index 53e4d9b..2773c29 100644 --- a/musadaq-app/lib/features/dashboard/controllers/dashboard_controller.dart +++ b/musadaq-app/lib/features/dashboard/controllers/dashboard_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:dio/dio.dart'; import '../../../core/storage/secure_storage.dart'; @@ -25,13 +26,11 @@ class DashboardController extends GetxController { _loadDashboardData(); } - - Future _loadDashboardData() async { try { isLoading.value = true; final token = await _storage.getToken(); - + if (token == null || token.isEmpty) { Get.offAllNamed(AppRoutes.PHONE_INPUT); return; @@ -71,6 +70,133 @@ class DashboardController extends GetxController { Get.offAllNamed(AppRoutes.PHONE_INPUT); } + void startVoiceAssistant() { + final textController = TextEditingController(); + + Get.bottomSheet( + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Get.isDarkMode ? const Color(0xFF1E1E2E) : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30), topRight: Radius.circular(30)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 4, + margin: const EdgeInsets.bottom(20), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.3), + borderRadius: BorderRadius.circular(2)), + ), + Row( + children: [ + const Icon(Icons.auto_awesome, color: Color(0xFF5EEAD4)), + const SizedBox(width: 12), + Text( + 'المساعد الذكي (Grok-Beta)', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Get.isDarkMode + ? Colors.white + : const Color(0xFF0F4C81)), + ), + ], + ), + const SizedBox(height: 20), + TextField( + autofocus: true, + controller: textController, + style: TextStyle( + color: Get.isDarkMode ? Colors.white : Colors.black), + decoration: InputDecoration( + hintText: 'اكتب أمرك هنا (مثلاً: أريد رؤية الفواتير)...', + hintStyle: const TextStyle(fontSize: 14, color: Colors.grey), + filled: true, + fillColor: Get.isDarkMode + ? const Color(0xFF1A1A2E) + : const Color(0xFFF1F5F9), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none), + prefixIcon: const Icon(Icons.mic_none_rounded, color: Colors.grey), + suffixIcon: IconButton( + icon: + const Icon(Icons.send_rounded, color: Color(0xFF0F4C81)), + onPressed: () => _handleVoiceCommand(textController.text), + ), + ), + onSubmitted: (val) => _handleVoiceCommand(val), + ), + const SizedBox(height: 12), + Text( + 'ملاحظة: جاري تفعيل ميزة التعرف الصوتي المباشر...', + style: + TextStyle(fontSize: 11, color: Colors.grey.withOpacity(0.7)), + ), + const SizedBox(height: 20), + ], + ), + ), + isScrollControlled: true, + ); + } + + Future _handleVoiceCommand(String text) async { + if (text.trim().isEmpty) return; + + Get.back(); // Close bottom sheet + AppSnackbar.showWarning('جاري التحليل...', 'يتم تحليل طلبك بواسطة Grok AI'); + + try { + final token = await _storage.getToken(); + final response = await _dio.post( + '/voice/parse-intent-grok', + data: {'text': text}, + options: Options(headers: {'Authorization': 'Bearer $token'}), + ); + + if (response.data['success'] == true) { + final action = response.data['data']['action']; + final params = response.data['data']['params']; + final confirmation = response.data['data']['confirmation'] ?? 'تم!'; + + AppSnackbar.showSuccess('تم!', confirmation); + _executeAction(action, params); + } else { + AppSnackbar.showError( + 'خطأ', response.data['message'] ?? 'فشل تحليل الطلب'); + } + } catch (e) { + AppLogger.error('Voice Assistant Error', e); + AppSnackbar.showError( + 'خطأ', 'حدث خطأ أثناء التواصل مع خادم الذكاء الاصطناعي'); + } + } + + void _executeAction(String action, dynamic params) { + switch (action) { + case 'list_invoices': + Get.toNamed(AppRoutes.INVOICES); + break; + case 'open_scanner': + Get.toNamed(AppRoutes.SCANNER); + break; + case 'navigate': + final screen = params['screen']?.toString().toLowerCase(); + if (screen == 'settings') Get.toNamed(AppRoutes.SETTINGS); + if (screen == 'dashboard') Get.back(); + break; + default: + AppSnackbar.showWarning( + 'تنبيه', 'الأمر مفهوم ولكن لم يتم ربط الأكشن برمجياً بعد.'); + } + } + void refreshData() { _loadDashboardData(); } diff --git a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart index f55d53c..cb14624 100644 --- a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart +++ b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart @@ -143,7 +143,7 @@ class DashboardView extends GetView { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () { - Get.snackbar('المساعد الصوتي', 'يتم تجهيز خوادم AI (Grok & Gemini) للاستماع لأوامرك...'); + controller.startVoiceAssistant(); }, ), ), diff --git a/musadaq-app/lib/features/invoices/controllers/invoice_detail_controller.dart b/musadaq-app/lib/features/invoices/controllers/invoice_detail_controller.dart index b409d10..d4f984f 100644 --- a/musadaq-app/lib/features/invoices/controllers/invoice_detail_controller.dart +++ b/musadaq-app/lib/features/invoices/controllers/invoice_detail_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../core/network/dio_client.dart'; import '../../../core/utils/app_snackbar.dart'; @@ -22,8 +23,10 @@ class InvoiceDetailController extends GetxController { Future fetchInvoiceDetails() async { try { isLoading.value = true; - final res = await DioClient().client.get('invoices/view', queryParameters: {'id': invoiceId}); - + final res = await DioClient() + .client + .get('invoices/view', queryParameters: {'id': invoiceId}); + if (res.data['success'] == true && res.data['data'] != null) { invoice.value = res.data['data']; } else { @@ -41,7 +44,9 @@ class InvoiceDetailController extends GetxController { Future approveInvoice() async { try { - final res = await DioClient().client.post('invoices/approve', data: {'invoice_id': invoiceId}); + final res = await DioClient() + .client + .post('invoices/approve', data: {'invoice_id': invoiceId}); if (res.data['success'] == true) { AppSnackbar.showSuccess('تم الاعتماد', 'تم اعتماد الفاتورة بنجاح'); // Refresh the detail view @@ -56,10 +61,30 @@ class InvoiceDetailController extends GetxController { } void viewOriginalImage() { - final imagePath = invoice['file_path']; - if (imagePath != null && imagePath.isNotEmpty) { - // In a real app, you would download/show the image. For now, just a snackbar or open URL. - AppSnackbar.showInfo('قريباً', 'سيتم عرض الصورة قريباً'); + final fileUrl = invoice['file_url']; + if (fileUrl != null && fileUrl.isNotEmpty) { + // Navigate to a dedicated image viewer or show in a dialog + final fullUrl = 'https://musadaq.intaleqapp.com/api$fileUrl'; + Get.to(() => Scaffold( + appBar: AppBar( + title: const Text('صورة الفاتورة'), + backgroundColor: const Color(0xFF0F4C81)), + body: Center( + child: InteractiveViewer( + child: Image.network( + fullUrl, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return const CircularProgressIndicator(); + }, + errorBuilder: (context, error, stackTrace) { + return const Text( + 'فشل تحميل الصورة. قد يكون الملف مفقوداً على الخادم.'); + }, + ), + ), + ), + )); } else { AppSnackbar.showWarning('عذراً', 'لا توجد صورة مرتبطة بهذه الفاتورة'); } diff --git a/musadaq-app/lib/features/subscription/views/payment_receipt_view.dart b/musadaq-app/lib/features/subscription/views/payment_receipt_view.dart index a8400cb..df307af 100644 --- a/musadaq-app/lib/features/subscription/views/payment_receipt_view.dart +++ b/musadaq-app/lib/features/subscription/views/payment_receipt_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import '../../../core/utils/app_snackbar.dart'; import '../controllers/payment_receipt_controller.dart'; class PaymentReceiptView extends StatelessWidget { @@ -11,10 +13,13 @@ class PaymentReceiptView extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( - backgroundColor: isDark ? const Color(0xFF121212) : const Color(0xFFF5F7FA), + 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), + title: const Text('إتمام الدفع', + style: TextStyle(fontWeight: FontWeight.bold)), + backgroundColor: + isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81), foregroundColor: Colors.white, elevation: 0, ), @@ -25,33 +30,51 @@ class PaymentReceiptView extends StatelessWidget { 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), - ], - ), - )), + 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)), + Text('الخطوات التالية:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: isDark ? Colors.white : Colors.black87)), const SizedBox(height: 12), - _buildStepRow('1', 'افتح تطبيق البنك أو المحفظة الإلكترونية الخاصة بك.', isDark), + _buildStepRow('1', + 'افتح تطبيق البنك أو المحفظة الإلكترونية الخاصة بك.', isDark), const SizedBox(height: 8), - _buildStepRow('2', 'اختر خدمة "كليك" (CliQ) للتحويل الفوري.', isDark), + _buildStepRow( + '2', 'اختر خدمة "كليك" (CliQ) للتحويل الفوري.', isDark), const SizedBox(height: 8), - _buildStepRow('3', 'قم بالتحويل للاسم المستعار الموضح أعلاه.', isDark), + _buildStepRow( + '3', 'قم بالتحويل للاسم المستعار الموضح أعلاه.', isDark), const SizedBox(height: 8), - _buildStepRow('4', 'بعد إتمام العملية، انسخ "رقم المرجع" من رسالة البنك أو الإشعار والصقه هنا.', isDark), + _buildStepRow( + '4', + 'بعد إتمام العملية، انسخ "رقم المرجع" من رسالة البنك أو الإشعار والصقه هنا.', + isDark), const SizedBox(height: 24), // Reference Number Input Area @@ -60,29 +83,40 @@ class PaymentReceiptView extends StatelessWidget { decoration: BoxDecoration( color: isDark ? const Color(0xFF1E1E2E) : Colors.white, borderRadius: BorderRadius.circular(16), - border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200), + 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 Text('رقم المرجع أو نص الرسالة كاملة', + style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 12), TextField( controller: controller.referenceController, + maxLines: 3, + minLines: 1, decoration: InputDecoration( - hintText: 'مثال: 1234567890', - hintStyle: TextStyle(color: isDark ? Colors.white38 : Colors.grey), + hintText: + 'ألصق رقم المرجع أو نص الرسالة الواردة من البنك هنا...', + hintStyle: TextStyle( + color: isDark ? Colors.white38 : Colors.grey, + fontSize: 13), filled: true, - fillColor: isDark ? const Color(0xFF1A1A2E) : const Color(0xFFF1F5F9), + 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), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 16), ), - keyboardType: TextInputType.text, + keyboardType: TextInputType.multiline, style: TextStyle( fontFamily: 'monospace', + fontSize: 14, color: isDark ? Colors.white : Colors.black87, ), ), @@ -96,23 +130,32 @@ class PaymentReceiptView extends StatelessWidget { 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), - ), - )), + 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), + ), + )), ), ], ), @@ -134,7 +177,10 @@ class PaymentReceiptView extends StatelessWidget { child: Center( child: Text( number, - style: const TextStyle(color: Color(0xFF0F4C81), fontWeight: FontWeight.bold, fontSize: 12), + style: const TextStyle( + color: Color(0xFF0F4C81), + fontWeight: FontWeight.bold, + fontSize: 12), ), ), ), @@ -142,31 +188,62 @@ class PaymentReceiptView extends StatelessWidget { Expanded( child: Text( text, - style: TextStyle(fontSize: 13, color: isDark ? Colors.white70 : Colors.grey.shade700, height: 1.4), + style: TextStyle( + fontSize: 13, + color: isDark ? Colors.white70 : Colors.grey.shade700, + height: 1.4), ), ), ], ); } - Widget _buildInfoRow(String label, String value, bool isDark, {bool isHighlight = false}) { + 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, + Text(label, style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - color: isHighlight ? const Color(0xFF0F4C81) : (isDark ? Colors.white : Colors.black87), - fontFamily: 'monospace', + fontSize: 13, + color: isDark ? Colors.white70 : Colors.grey.shade600)), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: value)); + AppSnackbar.showSuccess('تم النسخ', 'تم نسخ $label إلى الحافظة'); + }, + child: 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), + border: Border.all( + color: const Color(0xFF0F4C81).withOpacity(0.3)), + ) + : null, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + value, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + color: isHighlight + ? const Color(0xFF0F4C81) + : (isDark ? Colors.white : Colors.black87), + fontFamily: 'monospace', + ), + ), + if (isHighlight) ...[ + const SizedBox(width: 8), + const Icon(Icons.copy_rounded, + size: 14, color: Color(0xFF0F4C81)), + ], + ], ), ), ), diff --git a/musadaq-app/pubspec.lock b/musadaq-app/pubspec.lock index d61f199..c91d2cc 100644 --- a/musadaq-app/pubspec.lock +++ b/musadaq-app/pubspec.lock @@ -522,50 +522,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: "8b302d17096ba88f911b7eb317c71d5e691da60a259549f42b38c658d1776d87" url: "https://pub.dev" source: hosted - version: "9.2.4" + version: "10.1.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "3af15a3cb2bf5b8b776832bd01776f8018766aece55623176e28b406481fb320" + url: "https://pub.dev" + source: hosted + version: "0.3.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -720,14 +720,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: transitive description: @@ -1008,6 +1000,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + pedantic: + dependency: transitive + description: + name: pedantic + sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" + url: "https://pub.dev" + source: hosted + version: "1.11.1" permission_handler: dependency: "direct main" description: @@ -1136,6 +1136,62 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + record: + dependency: "direct main" + description: + name: record + sha256: "2e3d56d196abcd69f1046339b75e5f3855b2406fc087e5991f6703f188aa03a6" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + record_android: + dependency: transitive + description: + name: record_android + sha256: "94783f08403aed33ffb68797bf0715b0812eb852f3c7985644c945faea462ba1" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + record_darwin: + dependency: transitive + description: + name: record_darwin + sha256: e487eccb19d82a9a39cd0126945cfc47b9986e0df211734e2788c95e3f63c82c + url: "https://pub.dev" + source: hosted + version: "1.2.2" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "8a81dbc4e14e1272a285bbfef6c9136d070a47d9b0d1f40aa6193516253ee2f6" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" + url: "https://pub.dev" + source: hosted + version: "1.0.7" rxdart: dependency: transitive description: @@ -1189,6 +1245,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + speech_to_text: + dependency: "direct main" + description: + name: speech_to_text + sha256: c07557664974afa061f221d0d4186935bea4220728ea9446702825e8b988db04 + url: "https://pub.dev" + source: hosted + version: "7.3.0" + speech_to_text_platform_interface: + dependency: transitive + description: + name: speech_to_text_platform_interface + sha256: a1935847704e41ee468aad83181ddd2423d0833abe55d769c59afca07adb5114 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + speech_to_text_windows: + dependency: transitive + description: + name: speech_to_text_windows + sha256: "2c9846d18253c7bbe059a276297ef9f27e8a2745dead32192525beb208195072" + url: "https://pub.dev" + source: hosted + version: "1.0.0+beta.8" sqflite: dependency: transitive description: diff --git a/musadaq-app/pubspec.yaml b/musadaq-app/pubspec.yaml index 7068e64..23315a2 100644 --- a/musadaq-app/pubspec.yaml +++ b/musadaq-app/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: # ─── Networking & API ─────────────────────────────── dio: ^5.4.0 - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^10.1.0 # ─── Local Database (ObjectBox) ───────────────────── objectbox: ^4.0.1 @@ -41,7 +41,8 @@ dependencies: printing: ^5.12.0 # ─── Voice & Audio ────────────────────────────────── - # record: ^5.1.0 + record: ^5.1.0 + speech_to_text: ^7.3.0 permission_handler: ^11.3.0 # ─── Connectivity & Background ──────────────────────