import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:test_sa/modules/cx_module/chat/call/call_error_handler.dart'; /// Production-ready permission helper for audio/video calls /// Handles all permission requests with proper error handling and user feedback class CallPermissionHelper { CallPermissionHelper._(); /// Check and request microphone permission for audio calls /// Returns true if permission is granted, false otherwise static Future requestMicrophonePermission(BuildContext context) async { try { log('🎤 [PERMISSION] Checking microphone permission...', name: 'CallPermissionHelper'); // Check current status final status = await Permission.microphone.status; log(' Current status: ${status.name}', name: 'CallPermissionHelper'); if (status.isGranted) { log('✅ [PERMISSION] Microphone already granted', name: 'CallPermissionHelper'); return true; } // If denied permanently, show settings dialog if (status.isPermanentlyDenied) { log('⚠️ [PERMISSION] Microphone permanently denied - showing settings dialog', name: 'CallPermissionHelper'); if (context.mounted) { await _showPermissionDeniedDialog( context, 'Microphone Permission Required', 'This app needs microphone access to make calls. Please enable it in settings.', Permission.microphone, ); } return false; } // Request permission log('📤 [PERMISSION] Requesting microphone permission...', name: 'CallPermissionHelper'); final result = await Permission.microphone.request(); log(' Result: ${result.name}', name: 'CallPermissionHelper'); if (result.isGranted) { log('✅ [PERMISSION] Microphone permission granted', name: 'CallPermissionHelper'); return true; } if (result.isPermanentlyDenied) { log('❌ [PERMISSION] Microphone permanently denied', name: 'CallPermissionHelper'); if (context.mounted) { await _showPermissionDeniedDialog( context, 'Microphone Permission Required', 'This app needs microphone access to make calls. Please enable it in settings.', Permission.microphone, ); } return false; } // User denied log('❌ [PERMISSION] Microphone permission denied by user', name: 'CallPermissionHelper'); if (context.mounted) { CallErrorHandler.showMicrophonePermissionDenied(context); } return false; } catch (e, stackTrace) { log('❌ [PERMISSION] Error requesting microphone permission: $e\nStack: $stackTrace', name: 'CallPermissionHelper'); if (context.mounted) { CallErrorHandler.showGenericError(context, 'Failed to request microphone permission'); } return false; } } /// Check and request camera permission for video calls /// Returns true if permission is granted, false otherwise static Future requestCameraPermission(BuildContext context) async { try { log('📹 [PERMISSION] Checking camera permission...', name: 'CallPermissionHelper'); // Check current status final status = await Permission.camera.status; log(' Current status: ${status.name}', name: 'CallPermissionHelper'); if (status.isGranted) { log('✅ [PERMISSION] Camera already granted', name: 'CallPermissionHelper'); return true; } // If denied permanently, show settings dialog if (status.isPermanentlyDenied) { log('⚠️ [PERMISSION] Camera permanently denied - showing settings dialog', name: 'CallPermissionHelper'); if (context.mounted) { await _showPermissionDeniedDialog( context, 'Camera Permission Required', 'This app needs camera access for video calls. Please enable it in settings.', Permission.camera, ); } return false; } // Request permission log('📤 [PERMISSION] Requesting camera permission...', name: 'CallPermissionHelper'); final result = await Permission.camera.request(); log(' Result: ${result.name}', name: 'CallPermissionHelper'); if (result.isGranted) { log('✅ [PERMISSION] Camera permission granted', name: 'CallPermissionHelper'); return true; } if (result.isPermanentlyDenied) { log('❌ [PERMISSION] Camera permanently denied', name: 'CallPermissionHelper'); if (context.mounted) { await _showPermissionDeniedDialog( context, 'Camera Permission Required', 'This app needs camera access for video calls. Please enable it in settings.', Permission.camera, ); } return false; } // User denied log('❌ [PERMISSION] Camera permission denied by user', name: 'CallPermissionHelper'); if (context.mounted) { CallErrorHandler.showCameraPermissionDenied(context); } return false; } catch (e, stackTrace) { log('❌ [PERMISSION] Error requesting camera permission: $e\nStack: $stackTrace', name: 'CallPermissionHelper'); if (context.mounted) { CallErrorHandler.showGenericError(context, 'Failed to request camera permission'); } return false; } } /// Request all permissions required for audio calls (microphone only) /// Returns true if all permissions are granted, false otherwise static Future requestAudioCallPermissions(BuildContext context) async { log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); log('🎵 [PERMISSION] Requesting audio call permissions...', name: 'CallPermissionHelper'); final micGranted = await requestMicrophonePermission(context); if (!micGranted) { log('❌ [PERMISSION] Audio call permissions denied', name: 'CallPermissionHelper'); log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); return false; } log('✅ [PERMISSION] All audio call permissions granted', name: 'CallPermissionHelper'); log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); return true; } /// Request all permissions required for video calls (microphone + camera) /// Returns true if all permissions are granted, false otherwise static Future requestVideoCallPermissions(BuildContext context) async { log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); log('📹 [PERMISSION] Requesting video call permissions...', name: 'CallPermissionHelper'); // Request both permissions final micGranted = await requestMicrophonePermission(context); if (!micGranted) { log('❌ [PERMISSION] Video call permissions denied (microphone)', name: 'CallPermissionHelper'); log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); return false; } final cameraGranted = await requestCameraPermission(context); if (!cameraGranted) { log('❌ [PERMISSION] Video call permissions denied (camera)', name: 'CallPermissionHelper'); log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); return false; } log('✅ [PERMISSION] All video call permissions granted', name: 'CallPermissionHelper'); log('═══════════════════════════════════════════', name: 'CallPermissionHelper'); return true; } /// Check if microphone permission is granted (without requesting) static Future isMicrophoneGranted() async { final status = await Permission.microphone.status; return status.isGranted; } /// Check if camera permission is granted (without requesting) static Future isCameraGranted() async { final status = await Permission.camera.status; return status.isGranted; } /// Show dialog when permission is permanently denied, with option to open settings static Future _showPermissionDeniedDialog( BuildContext context, String title, String message, Permission permission, ) async { if (!context.mounted) return; return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( title: Text(title), content: Text(message), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () async { Navigator.of(context).pop(); await openAppSettings(); }, child: const Text('Open Settings'), ), ], ); }, ); } /// Handle permission denial during an active call /// Shows error and optionally ends the call static Future handlePermissionDeniedDuringCall( BuildContext context, String permissionName, { required Function() onEndCall, int delaySeconds = 2, }) async { if (!context.mounted) return; log('❌ [PERMISSION] $permissionName denied during active call', name: 'CallPermissionHelper'); log(' Showing error and ending call in ${delaySeconds}s...', name: 'CallPermissionHelper'); CallErrorHandler.showGenericError( context, '$permissionName permission is required for this call.', ); // Give user time to see the error await Future.delayed(Duration(seconds: delaySeconds)); if (context.mounted) { onEndCall(); } } }