diff --git a/musadaq-app/lib/app/routes/app_pages.dart b/musadaq-app/lib/app/routes/app_pages.dart index af5d26d..b938045 100644 --- a/musadaq-app/lib/app/routes/app_pages.dart +++ b/musadaq-app/lib/app/routes/app_pages.dart @@ -24,6 +24,7 @@ import '../../features/onboarding/views/onboarding_view.dart'; import '../../features/reports/views/tax_report_view.dart'; import '../../features/audit/views/audit_log_view.dart'; import '../../features/referral/views/referral_view.dart'; + import '../../features/ai_usage/views/ai_usage_view.dart'; part 'app_routes.dart'; @@ -166,5 +167,9 @@ class AppPages { name: AppRoutes.REFERRAL, page: () => const ReferralView(), ), + GetPage( + name: AppRoutes.AI_USAGE, + page: () => const AiUsageView(), + ), ]; } diff --git a/musadaq-app/lib/app/routes/app_routes.dart b/musadaq-app/lib/app/routes/app_routes.dart index 9dadece..12eb4be 100644 --- a/musadaq-app/lib/app/routes/app_routes.dart +++ b/musadaq-app/lib/app/routes/app_routes.dart @@ -24,4 +24,5 @@ abstract class AppRoutes { static const TAX_REPORT = '/tax-report'; static const AUDIT_LOG = '/audit-log'; static const REFERRAL = '/referral'; + static const AI_USAGE = '/ai-usage'; } diff --git a/musadaq-app/lib/features/ai_usage/controllers/ai_usage_controller.dart b/musadaq-app/lib/features/ai_usage/controllers/ai_usage_controller.dart new file mode 100644 index 0000000..f190811 --- /dev/null +++ b/musadaq-app/lib/features/ai_usage/controllers/ai_usage_controller.dart @@ -0,0 +1,33 @@ +import 'package:get/get.dart'; +import '../../../core/network/dio_client.dart'; +import '../../../core/utils/logger.dart'; + +class AiUsageController extends GetxController { + var isLoading = true.obs; + var data = Rxn>(); + + @override + void onInit() { + super.onInit(); + fetchUsage(); + } + + Map get overall => Map.from(data.value?['overall'] ?? {}); + Map get today => Map.from(data.value?['today'] ?? {}); + Map get thisMonth => Map.from(data.value?['this_month'] ?? {}); + List get dailyBreakdown => data.value?['daily_breakdown'] ?? []; + + Future fetchUsage() async { + try { + isLoading.value = true; + final res = await DioClient().client.get('dashboard/ai-usage'); + if (res.data['success'] == true) { + data.value = Map.from(res.data['data']); + } + } catch (e) { + AppLogger.error('Failed to fetch AI usage', e); + } finally { + isLoading.value = false; + } + } +} diff --git a/musadaq-app/lib/features/ai_usage/views/ai_usage_view.dart b/musadaq-app/lib/features/ai_usage/views/ai_usage_view.dart new file mode 100644 index 0000000..ef39d03 --- /dev/null +++ b/musadaq-app/lib/features/ai_usage/views/ai_usage_view.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/ai_usage_controller.dart'; + +class AiUsageView extends StatelessWidget { + const AiUsageView({super.key}); + + @override + Widget build(BuildContext context) { + final controller = Get.put(AiUsageController()); + 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)), + centerTitle: true, + backgroundColor: isDark ? const Color(0xFF1E1E2E) : const Color(0xFF0F4C81), + foregroundColor: Colors.white, + ), + body: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator(color: Color(0xFF0F4C81))); + } + + final overall = controller.overall; + final today = controller.today; + final month = controller.thisMonth; + final daily = controller.dailyBreakdown; + + return RefreshIndicator( + onRefresh: () async => controller.fetchUsage(), + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Hero Stats + _buildHeroCard(overall, isDark), + const SizedBox(height: 16), + + // Today vs This Month + Row( + children: [ + Expanded(child: _buildPeriodCard('اليوم', today, Icons.today, const Color(0xFF3B82F6), isDark)), + const SizedBox(width: 12), + Expanded(child: _buildPeriodCard('هذا الشهر', month, Icons.calendar_month, const Color(0xFF6366F1), isDark)), + ], + ), + const SizedBox(height: 20), + + // Cost per invoice + _buildCostPerInvoiceCard(overall, isDark), + const SizedBox(height: 20), + + // Daily breakdown + if (daily.isNotEmpty) ...[ + Text('التفصيل اليومي', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black87)), + const SizedBox(height: 12), + ...daily.take(15).map((d) => _buildDailyRow(d, isDark)), + ], + const SizedBox(height: 40), + ], + ), + ), + ); + }), + ); + } + + Widget _buildHeroCard(Map overall, bool isDark) { + final totalRequests = overall['total_requests'] ?? 0; + final totalTokens = overall['total_tokens'] ?? 0; + final totalCostJod = (overall['total_cost_jod'] ?? 0).toDouble(); + + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [Color(0xFF0F4C81), Color(0xFF1A6BB5), Color(0xFF6366F1)], + begin: Alignment.topLeft, end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow(color: const Color(0xFF0F4C81).withValues(alpha: 0.3), blurRadius: 15, offset: const Offset(0, 6)), + ], + ), + child: Column( + children: [ + const Icon(Icons.psychology, size: 40, color: Color(0xFFD4AF37)), + const SizedBox(height: 8), + const Text('إجمالي استخدام AI', style: TextStyle(fontSize: 14, color: Colors.white70)), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _heroStat('الطلبات', '$totalRequests', Icons.receipt_long), + Container(width: 1, height: 40, color: Colors.white24), + _heroStat('التوكنات', _formatNumber(totalTokens), Icons.token), + Container(width: 1, height: 40, color: Colors.white24), + _heroStat('التكلفة', '${totalCostJod.toStringAsFixed(3)} د.أ', Icons.attach_money), + ], + ), + ], + ), + ); + } + + Widget _heroStat(String label, String value, IconData icon) { + return Column( + children: [ + Icon(icon, size: 20, color: const Color(0xFFD4AF37)), + const SizedBox(height: 6), + Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: Colors.white, fontFamily: 'monospace')), + const SizedBox(height: 2), + Text(label, style: const TextStyle(fontSize: 11, color: Colors.white60)), + ], + ); + } + + Widget _buildPeriodCard(String title, Map data, IconData icon, Color color, bool isDark) { + final requests = data['requests'] ?? 0; + final tokens = data['tokens'] ?? 0; + final costJod = (data['cost_jod'] ?? 0).toDouble(); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1E1E2E) : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: color.withValues(alpha: 0.2)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 18, color: color), + const SizedBox(width: 6), + Text(title, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color)), + ], + ), + const SizedBox(height: 12), + Text('$requests', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: isDark ? Colors.white : Colors.black87, fontFamily: 'monospace')), + Text('فاتورة', style: TextStyle(fontSize: 11, color: isDark ? Colors.white38 : Colors.grey)), + const SizedBox(height: 8), + Text('${_formatNumber(tokens)} توكن', style: TextStyle(fontSize: 12, color: isDark ? Colors.white54 : Colors.black54)), + Text('${costJod.toStringAsFixed(4)} د.أ', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: color)), + ], + ), + ); + } + + Widget _buildCostPerInvoiceCard(Map overall, bool isDark) { + final avgCost = (overall['avg_cost_per_invoice_jod'] ?? 0).toDouble(); + final avgTokens = (overall['avg_tokens_per_invoice'] ?? 0).toDouble(); + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1E1E2E) : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.3)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(Icons.analytics, size: 20, color: Color(0xFF10B981)), + SizedBox(width: 8), + Text('تحليل التكلفة لكل فاتورة', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFF10B981).withValues(alpha: 0.06), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text('${avgCost.toStringAsFixed(5)}', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: Color(0xFF10B981), fontFamily: 'monospace')), + const Text('د.أ / فاتورة', style: TextStyle(fontSize: 11, color: Colors.grey)), + ], + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFF3B82F6).withValues(alpha: 0.06), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Text('${avgTokens.toStringAsFixed(0)}', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: Color(0xFF3B82F6), fontFamily: 'monospace')), + const Text('توكن / فاتورة', style: TextStyle(fontSize: 11, color: Colors.grey)), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: const Color(0xFFD4AF37).withValues(alpha: 0.06), + borderRadius: BorderRadius.circular(8), + ), + child: const Row( + children: [ + Icon(Icons.lightbulb, size: 16, color: Color(0xFFD4AF37)), + SizedBox(width: 8), + Expanded( + child: Text( + 'تكلفة AI أقل من ربع قرش لكل فاتورة — هامش ربح 99%+', + style: TextStyle(fontSize: 11, color: Color(0xFFD4AF37), fontWeight: FontWeight.w600), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildDailyRow(dynamic d, bool isDark) { + final date = d['date'] ?? ''; + final requests = d['requests'] ?? 0; + final tokens = d['tokens'] ?? 0; + final costJod = (d['cost_jod'] ?? 0).toDouble(); + + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1E1E2E) : Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade200), + ), + child: Row( + children: [ + Text(date, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: isDark ? Colors.white70 : Colors.black87, fontFamily: 'monospace')), + const Spacer(), + _dailyChip('$requests', Icons.receipt, const Color(0xFF3B82F6)), + const SizedBox(width: 8), + _dailyChip(_formatNumber(tokens), Icons.token, const Color(0xFF6366F1)), + const SizedBox(width: 8), + _dailyChip('${costJod.toStringAsFixed(3)}', Icons.attach_money, const Color(0xFF10B981)), + ], + ), + ); + } + + Widget _dailyChip(String value, IconData icon, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 12, color: color), + const SizedBox(width: 3), + Text(value, style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: color, fontFamily: 'monospace')), + ], + ), + ); + } + + String _formatNumber(dynamic num) { + final n = (num is int) ? num : int.tryParse(num.toString()) ?? 0; + if (n >= 1000000) return '${(n / 1000000).toStringAsFixed(1)}M'; + if (n >= 1000) return '${(n / 1000).toStringAsFixed(1)}K'; + return '$n'; + } +} diff --git a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart index ffbd82f..41b1a31 100644 --- a/musadaq-app/lib/features/dashboard/views/dashboard_view.dart +++ b/musadaq-app/lib/features/dashboard/views/dashboard_view.dart @@ -258,6 +258,14 @@ class DashboardView extends GetView { isDark, () => Get.toNamed(AppRoutes.REFERRAL), ), + const SizedBox(width: 12), + _buildAdminActionCard( + 'استهلاك AI', + Icons.psychology, + const Color(0xFF6366F1), + isDark, + () => Get.toNamed(AppRoutes.AI_USAGE), + ), ], ), ), diff --git a/musadaq-app/lib/features/referral/views/referral_view.dart b/musadaq-app/lib/features/referral/views/referral_view.dart index 1e0c25c..e32c0f6 100644 --- a/musadaq-app/lib/features/referral/views/referral_view.dart +++ b/musadaq-app/lib/features/referral/views/referral_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../controllers/referral_controller.dart'; import '../../../core/utils/app_snackbar.dart'; @@ -29,23 +30,14 @@ class ReferralView extends StatelessWidget { padding: const EdgeInsets.all(16), child: Column( children: [ - // Hero Banner _buildHeroBanner(controller, isDark), const SizedBox(height: 20), - - // Stats Cards _buildStatsRow(controller.stats, isDark), const SizedBox(height: 20), - - // Rewards Info _buildRewardsCard(controller.rewardRules, isDark), const SizedBox(height: 20), - - // How it works _buildHowItWorks(isDark), const SizedBox(height: 20), - - // Recent referrals if (controller.recent.isNotEmpty) ...[ _buildRecentReferrals(controller.recent, isDark), ], @@ -92,53 +84,109 @@ class ReferralView extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - controller.code, - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 3, fontFamily: 'monospace'), - ), - const SizedBox(width: 12), - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: controller.code)); - AppSnackbar.showSuccess('تم النسخ', 'تم نسخ رمز الإحالة'); - }, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: const Color(0xFFD4AF37), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon(Icons.copy, size: 18, color: Colors.white), + Expanded( + child: Text( + controller.code, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: Colors.white, letterSpacing: 4, fontFamily: 'monospace'), ), ), ], ), ), - const SizedBox(height: 12), + const SizedBox(height: 16), - // Share link button + // WhatsApp Share Button SizedBox( width: double.infinity, child: ElevatedButton.icon( - onPressed: () { - Clipboard.setData(ClipboardData(text: controller.link)); - AppSnackbar.showSuccess('تم النسخ', 'تم نسخ رابط الإحالة'); - }, - icon: const Icon(Icons.share, size: 18), - label: const Text('نسخ رابط الدعوة', style: TextStyle(fontWeight: FontWeight.bold)), + onPressed: () => _shareViaWhatsApp(controller.code, controller.link), + icon: const Icon(Icons.chat, size: 20), + label: const Text('أرسل عبر واتساب', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFD4AF37), + backgroundColor: const Color(0xFF25D366), foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + elevation: 4, ), ), ), + const SizedBox(height: 10), + + // Copy Code + Copy Link Row + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: controller.code)); + AppSnackbar.showSuccess('تم النسخ', 'تم نسخ رمز الإحالة: ${controller.code}'); + }, + icon: const Icon(Icons.copy, size: 16), + label: const Text('نسخ الرمز', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white.withValues(alpha: 0.2), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 0, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: controller.link)); + AppSnackbar.showSuccess('تم النسخ', 'تم نسخ رابط الدعوة'); + }, + icon: const Icon(Icons.link, size: 16), + label: const Text('نسخ الرابط', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13)), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFD4AF37), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + elevation: 0, + ), + ), + ), + ], + ), ], ), ); } + void _shareViaWhatsApp(String code, String link) async { + final message = Uri.encodeComponent( + 'مرحباً 👋\n\n' + 'أدعوك لتجربة تطبيق *مُصادَق* — أذكى نظام فوترة إلكتروني في الأردن 🇯🇴\n\n' + '✅ استخراج الفواتير بالذكاء الاصطناعي\n' + '✅ ربط مباشر مع جوفوترة\n' + '✅ تقارير ضريبية جاهزة\n\n' + '🎁 استخدم رمز الدعوة: *$code*\n' + 'واحصل على شهر مجاني!\n\n' + '🔗 $link' + ); + + final whatsappUrl = Uri.parse('https://wa.me/?text=$message'); + + try { + if (await canLaunchUrl(whatsappUrl)) { + await launchUrl(whatsappUrl, mode: LaunchMode.externalApplication); + } else { + // Fallback — copy to clipboard + Clipboard.setData(ClipboardData(text: Uri.decodeComponent(message))); + AppSnackbar.showInfo('واتساب غير متوفر', 'تم نسخ الرسالة — ألصقها يدوياً'); + } + } catch (e) { + Clipboard.setData(ClipboardData(text: Uri.decodeComponent(message))); + AppSnackbar.showInfo('واتساب غير متوفر', 'تم نسخ الرسالة — ألصقها يدوياً'); + } + } + Widget _buildStatsRow(Map stats, bool isDark) { return Row( children: [ @@ -236,7 +284,7 @@ class ReferralView extends StatelessWidget { children: [ Text('كيف يعمل؟', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: isDark ? Colors.white : Colors.black87)), const SizedBox(height: 14), - _stepItem('1', 'شارك رمز الإحالة مع زميلك', Icons.share, const Color(0xFF3B82F6), isDark), + _stepItem('1', 'أرسل الرمز لصديقك عبر واتساب', Icons.chat, const Color(0xFF25D366), isDark), const SizedBox(height: 10), _stepItem('2', 'يسجّل في مُصادَق بالرمز', Icons.person_add, const Color(0xFF6366F1), isDark), const SizedBox(height: 10), diff --git a/musadaq-app/lib/features/users/views/users_management_view.dart b/musadaq-app/lib/features/users/views/users_management_view.dart index a422d2f..6daa796 100644 --- a/musadaq-app/lib/features/users/views/users_management_view.dart +++ b/musadaq-app/lib/features/users/views/users_management_view.dart @@ -194,24 +194,43 @@ class UsersManagementView extends StatelessWidget { ), ), const Spacer(), - // Toggle active - IconButton( - icon: Icon(isActive ? Icons.toggle_on : Icons.toggle_off, color: isActive ? const Color(0xFF10B981) : Colors.grey, size: 28), - onPressed: () => controller.toggleUserActive(user['id'], !isActive), - tooltip: isActive ? 'تعطيل' : 'تفعيل', - ), - // Edit - IconButton( - icon: const Icon(Icons.edit, size: 20, color: Color(0xFF0F4C81)), - onPressed: () => _showEditDialog(context, user, controller), - tooltip: 'تعديل', - ), - // Delete - IconButton( - icon: const Icon(Icons.delete_outline, size: 20, color: Colors.red), - onPressed: () => _confirmDelete(context, controller, user['id'], user['name'] ?? ''), - tooltip: 'حذف', - ), + // Only show actions for non-super_admin users + if (role != 'super_admin') ...[ + // Toggle active + IconButton( + icon: Icon(isActive ? Icons.toggle_on : Icons.toggle_off, color: isActive ? const Color(0xFF10B981) : Colors.grey, size: 28), + onPressed: () => controller.toggleUserActive(user['id'], !isActive), + tooltip: isActive ? 'تعطيل' : 'تفعيل', + ), + // Edit + IconButton( + icon: const Icon(Icons.edit, size: 20, color: Color(0xFF0F4C81)), + onPressed: () => _showEditDialog(context, user, controller), + tooltip: 'تعديل', + ), + // Delete + IconButton( + icon: const Icon(Icons.delete_outline, size: 20, color: Colors.red), + onPressed: () => _confirmDelete(context, controller, user['id'], user['name'] ?? ''), + tooltip: 'حذف', + ), + ] else ...[ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.shield, size: 14, color: Color(0xFF6366F1)), + SizedBox(width: 4), + Text('محمي', style: TextStyle(fontSize: 11, color: Color(0xFF6366F1), fontWeight: FontWeight.w600)), + ], + ), + ), + ], ], ), ], diff --git a/musadaq-app/pubspec.lock b/musadaq-app/pubspec.lock index dfc46fa..9c2ffbe 100644 --- a/musadaq-app/pubspec.lock +++ b/musadaq-app/pubspec.lock @@ -181,10 +181,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -748,26 +748,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -836,18 +836,18 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" matrix2d: dependency: transitive description: @@ -860,10 +860,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1401,10 +1401,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.4" timing: dependency: transitive description: @@ -1421,6 +1421,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.dev" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -1429,6 +1453,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -1465,10 +1497,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: @@ -1550,5 +1582,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.0-0 <4.0.0" + dart: ">=3.8.0 <4.0.0" flutter: ">=3.32.0" diff --git a/musadaq-app/pubspec.yaml b/musadaq-app/pubspec.yaml index e852282..fbf2b3d 100644 --- a/musadaq-app/pubspec.yaml +++ b/musadaq-app/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: shimmer: ^3.0.0 lottie: ^3.1.0 share_plus: ^12.0.2 + url_launcher: ^6.2.5 # ─── Utilities ────────────────────────────────────── uuid: ^4.3.3