import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD import '../services/contact_service.dart'; ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= import '../services/contact_service.dart'; // Import your ContactService >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) ======= import '../services/contact_service.dart'; >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; <<<<<<< HEAD <<<<<<< HEAD static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; <<<<<<< HEAD static String? _currentCallState; ======= static String? currentDisplayName; // Store display name static Uint8List? currentThumbnail; // Store thumbnail ======= static String? currentDisplayName; static Uint8List? currentThumbnail; >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static bool _isCallPageVisible = false; <<<<<<< HEAD final ContactService _contactService = ContactService(); // Instantiate ContactService >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) static Map? _pendingCall; static bool wasPhoneLocked = false; <<<<<<< HEAD final ContactService _contactService = ContactService(); ======= static Map? _pendingCall; static bool wasPhoneLocked = false; >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= ======= final ContactService _contactService = ContactService(); >>>>>>> acbccaa (feat: give parameters to callPage to avoid fetching when possible) >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { <<<<<<< HEAD print('CallService: Received method ${call.method} with args ${call.arguments}'); ======= print('CallService: Handling method call: ${call.method}'); >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); <<<<<<< HEAD <<<<<<< HEAD ======= // Fetch contact info using ContactService if not already set >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) ======= >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); <<<<<<< HEAD _handleCallState(state); ======= if (state == "ringing") { _handleIncomingCall(phoneNumber); } else { _navigateToCallPage(); } >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) break; case "callStateChanged": final state = call.arguments["state"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); <<<<<<< HEAD _handleCallState(state); ======= if (state == "disconnected" || state == "disconnecting") { _closeCallPage(); if (wasPhoneLocked) { _channel.invokeMethod("callEndedFromFlutter"); } } else if (state == "active" || state == "dialing") { _navigateToCallPage(); } else if (state == "ringing") { final phoneNumber = call.arguments["callId"] as String; _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); } >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) break; case "callEnded": case "callRemoved": wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); _closeCallPage(); if (wasPhoneLocked) { _channel.invokeMethod("callEndedFromFlutter"); } currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; <<<<<<< HEAD _currentCallState = null; ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) break; case "incomingCallFromNotification": final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; <<<<<<< HEAD await _fetchContactInfo(currentPhoneNumber!); ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; case "incomingCallFromNotification": final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; } }); } <<<<<<< HEAD <<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { try { final contacts = await _contactService.fetchContacts(); final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); for (var contact in contacts) { for (var phone in contact.phones) { if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { ======= Future _fetchContactInfo(String phoneNumber) async { try { final contacts = await _contactService.fetchContacts(); final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); for (var contact in contacts) { for (var phone in contact.phones) { <<<<<<< HEAD if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) ======= if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; } } } <<<<<<< HEAD currentDisplayName = phoneNumber; currentThumbnail = null; ======= // If no match found, use phone number as fallback currentDisplayName = phoneNumber; currentThumbnail = null; } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; currentThumbnail = null; } } String _normalizePhoneNumber(String number) { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } Future _fetchContactInfo(String phoneNumber) async { if (currentDisplayName != null && currentThumbnail != null) return; // Already set try { final contacts = await _contactService.fetchContacts(); // Use ContactService for (var contact in contacts) { for (var phone in contact.phones) { if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; } } } // If no match found, use phone number as fallback currentDisplayName ??= phoneNumber; currentThumbnail ??= null; >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; currentThumbnail = null; } } String _normalizePhoneNumber(String number) { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } <<<<<<< HEAD ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) void _handleIncomingCall(String phoneNumber) { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Context is null, queuing incoming call: $phoneNumber'); _pendingCall = {"phoneNumber": phoneNumber}; Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } else { _navigateToIncomingCallPage(context); } } void _checkPendingCall() { if (_pendingCall != null) { final context = navigatorKey.currentContext; if (context != null) { print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); currentPhoneNumber = _pendingCall!["phoneNumber"]; _navigateToIncomingCallPage(context); _pendingCall = null; } else { print('CallService: Context still null, retrying...'); Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } } } <<<<<<< HEAD void _handleCallState(String state) { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Navigator context is null, cannot navigate'); return; } if (_currentCallState == state) { print('CallService: State $state already handled, skipping'); return; } _currentCallState = state; if (state == "disconnected" || state == "disconnecting") { _closeCallPage(); } else if (state == "active" || state == "dialing") { _navigateToCallPage(context); } else if (state == "ringing") { _navigateToIncomingCallPage(context); } } void _navigateToCallPage(BuildContext context) { final currentRoute = ModalRoute.of(context)?.settings.name; print('CallService: Navigating to CallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); if (_isCallPageVisible && currentRoute == '/call') { print('CallService: CallPage already visible, skipping navigation'); return; } if (_isCallPageVisible && currentRoute == '/incoming_call') { print('CallService: Replacing IncomingCallPage with CallPage'); Navigator.pop(context); } Navigator.pushReplacement( ======= void _navigateToCallPage() { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot navigate to CallPage, context is null'); return; } if (_isCallPageVisible) { print('CallService: CallPage already visible, skipping navigation'); return; } print('CallService: Navigating to CallPage'); Navigator.push( >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), builder: (context) => CallPage( displayName: currentDisplayName ?? currentPhoneNumber!, phoneNumber: currentPhoneNumber!, thumbnail: currentThumbnail, ), ), ).then((_) { print('CallService: CallPage popped'); _isCallPageVisible = false; print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { <<<<<<< HEAD final currentRoute = ModalRoute.of(context)?.settings.name; print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); if (_isCallPageVisible && currentRoute == '/incoming_call') { ======= if (_isCallPageVisible) { >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) print('CallService: IncomingCallPage already visible, skipping navigation'); return; } if (_isCallPageVisible && currentRoute == '/call') { print('CallService: CallPage visible, not showing IncomingCallPage'); return; } Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: '/incoming_call'), builder: (context) => IncomingCallPage( displayName: currentDisplayName ?? currentPhoneNumber!, phoneNumber: currentPhoneNumber!, thumbnail: currentThumbnail, ), ), ).then((_) { print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _closeCallPage() { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot close page, context is null'); <<<<<<< HEAD return; } print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) return; } print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { <<<<<<< HEAD print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); _isCallPageVisible = false; } else { print('CallService: Cannot pop, no routes to pop'); ======= print('CallService: Popping call page'); Navigator.pop(context); _isCallPageVisible = false; } else { print('CallService: No page to pop'); >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) } } Future> makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, Uint8List? thumbnail, }) async { try { currentPhoneNumber = phoneNumber; <<<<<<< HEAD <<<<<<< HEAD ======= // Use provided displayName and thumbnail if available, otherwise fetch >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { await _fetchContactInfo(phoneNumber); } <<<<<<< HEAD ======= currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later currentThumbnail = thumbnail; // Use provided or fetch later >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) ======= >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); final resultMap = Map.from(result as Map); if (resultMap["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); return; } <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD _handleCallState("dialing"); ======= return resultMap; >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= // Fetch contact info if not provided await _fetchContactInfo(phoneNumber); _navigateToCallPage(context); // Navigate immediately after call initiation >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) ======= _navigateToCallPage(context); >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); return {"status": "error", "message": e.toString()}; } } Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); final resultMap = Map.from(result as Map); if (resultMap["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } else { _closeCallPage(); } return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); return {"status": "error", "message": e.toString()}; } } }