diff --git a/dialer/android/app/src/main/AndroidManifest.xml b/dialer/android/app/src/main/AndroidManifest.xml index fcaf71f..4c1cb5f 100644 --- a/dialer/android/app/src/main/AndroidManifest.xml +++ b/dialer/android/app/src/main/AndroidManifest.xml @@ -12,6 +12,8 @@ + + ? = null + private var wasPhoneLocked: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d(TAG, "onCreate started") - Log.d(TAG, "Waiting for Flutter to signal permissions") + wasPhoneLocked = intent.getBooleanExtra("wasPhoneLocked", false) + Log.d(TAG, "Was phone locked at start: $wasPhoneLocked") + updateLockScreenFlags(intent) + handleIncomingCallIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + wasPhoneLocked = intent.getBooleanExtra("wasPhoneLocked", false) + Log.d(TAG, "onNewIntent, wasPhoneLocked: $wasPhoneLocked") + updateLockScreenFlags(intent) + handleIncomingCallIntent(intent) + } + + private fun updateLockScreenFlags(intent: Intent?) { + val isIncomingCall = intent?.getBooleanExtra("isIncomingCall", false) ?: false + if (isIncomingCall && wasPhoneLocked) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true) + setTurnScreenOn(true) + } else { + @Suppress("DEPRECATION") + window.addFlags( + android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or + android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + ) + } + Log.d(TAG, "Enabled showWhenLocked and turnScreenOn for incoming call") + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(false) + setTurnScreenOn(false) + } else { + @Suppress("DEPRECATION") + window.clearFlags( + android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or + android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + ) + } + Log.d(TAG, "Disabled showWhenLocked and turnScreenOn for normal usage") + } } override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) Log.d(TAG, "Configuring Flutter engine") - MyInCallService.channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALL_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "permissionsGranted" -> { Log.d(TAG, "Received permissionsGranted from Flutter") + pendingIncomingCall?.let { (phoneNumber, showScreen) -> + if (showScreen) { + MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf( + "phoneNumber" to phoneNumber, + "wasPhoneLocked" to wasPhoneLocked + )) + pendingIncomingCall = null + } + } checkAndRequestDefaultDialer() result.success(true) } @@ -60,37 +112,66 @@ class MainActivity : FlutterActivity() { } } "hangUpCall" -> { - val success = CallService.hangUpCall(this) + val success = MyInCallService.currentCall?.let { + it.disconnect() + Log.d(TAG, "Call disconnected") + MyInCallService.channel?.invokeMethod("callEnded", mapOf( + "callId" to it.details.handle.toString(), + "wasPhoneLocked" to wasPhoneLocked + )) + true + } ?: false if (success) { result.success(mapOf("status" to "ended")) + if (wasPhoneLocked) { + Log.d(TAG, "Finishing and removing task after hangup, phone was locked") + finishAndRemoveTask() + } } else { - result.error("HANGUP_FAILED", "Failed to end call", null) + Log.w(TAG, "No active call to hang up") + result.error("HANGUP_FAILED", "No active call to hang up", null) } } "answerCall" -> { val success = MyInCallService.currentCall?.let { - it.answer(0) // 0 for default video state (audio-only) + it.answer(0) Log.d(TAG, "Answered call") true } ?: false if (success) { result.success(mapOf("status" to "answered")) } else { + Log.w(TAG, "No active call to answer") result.error("ANSWER_FAILED", "No active call to answer", null) } } + "callEndedFromFlutter" -> { + Log.d(TAG, "Call ended from Flutter, wasPhoneLocked: $wasPhoneLocked") + if (wasPhoneLocked) { + finishAndRemoveTask() + Log.d(TAG, "Finishing and removing task after call ended, phone was locked") + } + result.success(true) + } else -> result.notImplemented() } } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KEYSTORE_CHANNEL) - .setMethodCallHandler { call, result -> KeystoreHelper(call, result).handleMethodCall() } + .setMethodCallHandler { call, result -> + KeystoreHelper(call, result).handleMethodCall() + } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CALLLOG_CHANNEL) .setMethodCallHandler { call, result -> if (call.method == "getCallLogs") { - val callLogs = getCallLogs() - result.success(callLogs) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_GRANTED) { + val callLogs = getCallLogs() + result.success(callLogs) + } else { + requestPermissions(arrayOf(Manifest.permission.READ_CALL_LOG), REQUEST_CODE_CALL_LOG_PERMISSION) + result.error("PERMISSION_DENIED", "Call log permission not granted", null) + } } else { result.notImplemented() } @@ -109,8 +190,6 @@ class MainActivity : FlutterActivity() { val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER) startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER) Log.d(TAG, "Launched RoleManager intent for default dialer on API 29+") - } else { - Log.d(TAG, "RoleManager: Available=${roleManager.isRoleAvailable(RoleManager.ROLE_DIALER)}, Held=${roleManager.isRoleHeld(RoleManager.ROLE_DIALER)}") } } else { val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER) @@ -148,6 +227,18 @@ class MainActivity : FlutterActivity() { } } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_CODE_CALL_LOG_PERMISSION) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Call log permission granted") + MyInCallService.channel?.invokeMethod("callLogPermissionGranted", null) + } else { + Log.w(TAG, "Call log permission denied") + } + } + } + private fun getCallLogs(): List> { val logsList = mutableListOf>() val cursor: Cursor? = contentResolver.query( @@ -171,4 +262,25 @@ class MainActivity : FlutterActivity() { } return logsList } + + private fun handleIncomingCallIntent(intent: Intent?) { + intent?.let { + if (it.getBooleanExtra("isIncomingCall", false)) { + val phoneNumber = it.getStringExtra("phoneNumber") + val showScreen = it.getBooleanExtra("showIncomingCallScreen", false) + Log.d(TAG, "Received incoming call intent for $phoneNumber, showScreen=$showScreen, wasPhoneLocked=$wasPhoneLocked") + if (showScreen) { + if (MyInCallService.channel != null) { + MyInCallService.channel?.invokeMethod("incomingCallFromNotification", mapOf( + "phoneNumber" to phoneNumber, + "wasPhoneLocked" to wasPhoneLocked + )) + } else { + pendingIncomingCall = Pair(phoneNumber, true) + Log.d(TAG, "Flutter channel not ready, storing pending call: $phoneNumber") + } + } + } + } + } } \ No newline at end of file diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt index 48b7edd..b5a4c8a 100644 --- a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt +++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/MyInCallService.kt @@ -1,8 +1,17 @@ package com.icing.dialer.services +import android.app.KeyguardManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build import android.telecom.Call import android.telecom.InCallService import android.util.Log +import androidx.core.app.NotificationCompat +import com.icing.dialer.activities.MainActivity import io.flutter.plugin.common.MethodChannel class MyInCallService : InCallService() { @@ -10,6 +19,9 @@ class MyInCallService : InCallService() { var channel: MethodChannel? = null var currentCall: Call? = null private const val TAG = "MyInCallService" + private const val NOTIFICATION_CHANNEL_ID = "incoming_call_channel" + private const val NOTIFICATION_ID = 1 + var wasPhoneLocked: Boolean = false } private val callCallback = object : Call.Callback() { @@ -26,12 +38,22 @@ class MyInCallService : InCallService() { Log.d(TAG, "State changed: $stateStr for call ${call.details.handle}") channel?.invokeMethod("callStateChanged", mapOf( "callId" to call.details.handle.toString(), - "state" to stateStr + "state" to stateStr, + "wasPhoneLocked" to wasPhoneLocked )) - if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) { - Log.d(TAG, "Call ended: ${call.details.handle}") - channel?.invokeMethod("callEnded", mapOf("callId" to call.details.handle.toString())) + if (state == Call.STATE_RINGING) { + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + wasPhoneLocked = keyguardManager.isKeyguardLocked + Log.d(TAG, "Phone locked at ringing: $wasPhoneLocked") + showIncomingCallScreen(call.details.handle.toString().replace("tel:", "")) + } else if (state == Call.STATE_DISCONNECTED || state == Call.STATE_DISCONNECTING) { + Log.d(TAG, "Call ended: ${call.details.handle}, wasPhoneLocked: $wasPhoneLocked") + channel?.invokeMethod("callEnded", mapOf( + "callId" to call.details.handle.toString(), + "wasPhoneLocked" to wasPhoneLocked + )) currentCall = null + cancelNotification() } } } @@ -43,22 +65,32 @@ class MyInCallService : InCallService() { Call.STATE_DIALING -> "dialing" Call.STATE_ACTIVE -> "active" Call.STATE_RINGING -> "ringing" - else -> "dialing" // Default for outgoing + else -> "dialing" } Log.d(TAG, "Call added: ${call.details.handle}, state: $stateStr") channel?.invokeMethod("callAdded", mapOf( "callId" to call.details.handle.toString(), "state" to stateStr )) + if (stateStr == "ringing") { + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + wasPhoneLocked = keyguardManager.isKeyguardLocked + Log.d(TAG, "Phone locked at call added: $wasPhoneLocked") + showIncomingCallScreen(call.details.handle.toString().replace("tel:", "")) + } call.registerCallback(callCallback) } override fun onCallRemoved(call: Call) { super.onCallRemoved(call) - Log.d(TAG, "Call removed: ${call.details.handle}") + Log.d(TAG, "Call removed: ${call.details.handle}, wasPhoneLocked: $wasPhoneLocked") call.unregisterCallback(callCallback) - channel?.invokeMethod("callRemoved", mapOf("callId" to call.details.handle.toString())) + channel?.invokeMethod("callRemoved", mapOf( + "callId" to call.details.handle.toString(), + "wasPhoneLocked" to wasPhoneLocked + )) currentCall = null + cancelNotification() } override fun onCallAudioStateChanged(state: android.telecom.CallAudioState) { @@ -66,4 +98,59 @@ class MyInCallService : InCallService() { Log.d(TAG, "Audio state changed: route=${state.route}") channel?.invokeMethod("audioStateChanged", mapOf("route" to state.route)) } + + private fun showIncomingCallScreen(phoneNumber: String) { + val intent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra("phoneNumber", phoneNumber) + putExtra("isIncomingCall", true) + putExtra("showIncomingCallScreen", true) + putExtra("wasPhoneLocked", wasPhoneLocked) + } + + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + if (keyguardManager.isKeyguardLocked) { + startActivity(intent) + Log.d(TAG, "Launched MainActivity directly for locked screen, phoneNumber: $phoneNumber") + } else { + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + "Incoming Calls", + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Notifications for incoming calls" + enableVibration(true) + setShowBadge(true) + } + notificationManager.createNotificationChannel(channel) + } + + val pendingIntent = PendingIntent.getActivity( + this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) + ) + + val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(android.R.drawable.ic_dialog_alert) + .setContentTitle("Incoming Call") + .setContentText("Call from $phoneNumber") + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setFullScreenIntent(pendingIntent, true) + .setAutoCancel(true) + .setOngoing(true) + .build() + + startActivity(intent) + notificationManager.notify(NOTIFICATION_ID, notification) + Log.d(TAG, "Launched MainActivity with notification for unlocked screen, phoneNumber: $phoneNumber") + } + } + + private fun cancelNotification() { + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(NOTIFICATION_ID) + Log.d(TAG, "Notification canceled") + } } \ No newline at end of file diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart index 1416a98..368ffde 100644 --- a/dialer/lib/features/call/call_page.dart +++ b/dialer/lib/features/call/call_page.dart @@ -61,9 +61,16 @@ class _CallPageState extends State { void _hangUp() async { try { - await _callService.hangUpCall(context); + final result = await _callService.hangUpCall(context); + print('CallPage: Hang up result: $result'); + if (result["status"] == "ended" && mounted && Navigator.canPop(context)) { + Navigator.pop(context); + } } catch (e) { - print("Error hanging up: $e"); + print("CallPage: Error hanging up: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error hanging up: $e")), + ); } } @@ -86,9 +93,10 @@ class _CallPageState extends State { children: [ SizedBox(height: 35), ObfuscatedAvatar( - imageBytes: widget.thumbnail, // Uses thumbnail if provided + imageBytes: widget.thumbnail, radius: avatarRadius, - backgroundColor: generateColorFromName(widget.displayName), + backgroundColor: + generateColorFromName(widget.displayName), fallbackInitial: widget.displayName, ), const SizedBox(height: 4), @@ -122,11 +130,13 @@ class _CallPageState extends State { ), Text( widget.phoneNumber, - style: TextStyle(fontSize: statusFontSize, color: Colors.white70), + style: TextStyle( + fontSize: statusFontSize, color: Colors.white70), ), Text( 'Calling...', - style: TextStyle(fontSize: statusFontSize, color: Colors.white70), + style: TextStyle( + fontSize: statusFontSize, color: Colors.white70), ), ], ), @@ -157,7 +167,8 @@ class _CallPageState extends State { IconButton( padding: EdgeInsets.zero, onPressed: _toggleKeypad, - icon: const Icon(Icons.close, color: Colors.white), + icon: + const Icon(Icons.close, color: Colors.white), ), ], ), @@ -193,7 +204,8 @@ class _CallPageState extends State { child: Center( child: Text( label, - style: const TextStyle(fontSize: 32, color: Colors.white), + style: const TextStyle( + fontSize: 32, color: Colors.white), ), ), ), @@ -225,7 +237,8 @@ class _CallPageState extends State { ), Text( isMuted ? 'Unmute' : 'Mute', - style: const TextStyle(color: Colors.white, fontSize: 14), + style: const TextStyle( + color: Colors.white, fontSize: 14), ), ], ), @@ -234,11 +247,13 @@ class _CallPageState extends State { children: [ IconButton( onPressed: _toggleKeypad, - icon: const Icon(Icons.dialpad, color: Colors.white, size: 32), + icon: const Icon(Icons.dialpad, + color: Colors.white, size: 32), ), const Text( 'Keypad', - style: TextStyle(color: Colors.white, fontSize: 14), + style: TextStyle( + color: Colors.white, fontSize: 14), ), ], ), @@ -248,14 +263,19 @@ class _CallPageState extends State { IconButton( onPressed: _toggleSpeaker, icon: Icon( - isSpeakerOn ? Icons.volume_up : Icons.volume_off, - color: isSpeakerOn ? Colors.amber : Colors.white, + isSpeakerOn + ? Icons.volume_up + : Icons.volume_off, + color: isSpeakerOn + ? Colors.amber + : Colors.white, size: 32, ), ), const Text( 'Speaker', - style: TextStyle(color: Colors.white, fontSize: 14), + style: TextStyle( + color: Colors.white, fontSize: 14), ), ], ), @@ -270,10 +290,12 @@ class _CallPageState extends State { children: [ IconButton( onPressed: () {}, - icon: const Icon(Icons.person_add, color: Colors.white, size: 32), + icon: const Icon(Icons.person_add, + color: Colors.white, size: 32), ), const Text('Add Contact', - style: TextStyle(color: Colors.white, fontSize: 14)), + style: TextStyle( + color: Colors.white, fontSize: 14)), ], ), Column( @@ -281,10 +303,12 @@ class _CallPageState extends State { children: [ IconButton( onPressed: () {}, - icon: const Icon(Icons.sim_card, color: Colors.white, size: 32), + icon: const Icon(Icons.sim_card, + color: Colors.white, size: 32), ), const Text('Change SIM', - style: TextStyle(color: Colors.white, fontSize: 14)), + style: TextStyle( + color: Colors.white, fontSize: 14)), ], ), ], @@ -321,4 +345,4 @@ class _CallPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index d42326e..e03c652 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,24 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; -import '../features/call/incoming_call_page.dart'; // Import the new page +import '../features/call/incoming_call_page.dart'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; static bool _isCallPageVisible = false; + static Map? _pendingCall; + static bool wasPhoneLocked = false; static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { - final context = navigatorKey.currentContext; - print('CallService: Received method ${call.method} with args ${call.arguments}'); - if (context == null) { - print('CallService: Navigator context is null, cannot navigate'); - return; - } - + print('CallService: Handling method call: ${call.method}'); switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; @@ -26,39 +22,86 @@ class CallService { currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); if (state == "ringing") { - _navigateToIncomingCallPage(context); + _handleIncomingCall(phoneNumber); } else { - _navigateToCallPage(context); + _navigateToCallPage(); } break; case "callStateChanged": final state = call.arguments["state"] as String; - print('CallService: State changed to $state'); + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); + _closeCallPage(); + if (wasPhoneLocked) { + _channel.invokeMethod("callEndedFromFlutter"); + } } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); + _navigateToCallPage(); } else if (state == "ringing") { - _navigateToIncomingCallPage(context); + final phoneNumber = call.arguments["callId"] as String; + _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); } break; case "callEnded": case "callRemoved": - print('CallService: Call ended/removed'); - _closeCallPage(context); + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); + _closeCallPage(); + if (wasPhoneLocked) { + _channel.invokeMethod("callEndedFromFlutter"); + } currentPhoneNumber = null; 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; } }); } - void _navigateToCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/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()); + } + } + } + + 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.pushReplacement( + Navigator.push( context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), @@ -70,12 +113,13 @@ class CallService { ), ).then((_) { _isCallPageVisible = false; + print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') { + if (_isCallPageVisible) { print('CallService: IncomingCallPage already visible, skipping navigation'); return; } @@ -92,23 +136,28 @@ class CallService { ), ).then((_) { _isCallPageVisible = false; + print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } - void _closeCallPage(BuildContext context) { - if (!_isCallPageVisible) { - print('CallService: CallPage not visible, skipping pop'); + void _closeCallPage() { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Cannot close page, context is null'); return; } + print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { - print('CallService: Popping CallPage'); + print('CallService: Popping call page'); Navigator.pop(context); _isCallPageVisible = false; + } else { + print('CallService: No page to pop'); } } - Future makeGsmCall( + Future> makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -119,36 +168,40 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); } + return resultMap; } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } - Future hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } + return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } } \ No newline at end of file