Compare commits

..

4 Commits

5 changed files with 318 additions and 69 deletions

View File

@ -12,6 +12,8 @@
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:label="Icing Dialer"
@ -21,12 +23,14 @@
<activity
android:name=".activities.MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:showWhenLocked="true"
android:turnScreenOn="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.CallLog
@ -26,23 +25,45 @@ class MainActivity : FlutterActivity() {
private val CALL_CHANNEL = "call_service"
private val TAG = "MainActivity"
private val REQUEST_CODE_SET_DEFAULT_DIALER = 1001
private val REQUEST_CODE_CALL_LOG_PERMISSION = 1002
private var pendingIncomingCall: Pair<String?, Boolean>? = 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")
handleIncomingCallIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
wasPhoneLocked = intent.getBooleanExtra("wasPhoneLocked", false)
Log.d(TAG, "onNewIntent, wasPhoneLocked: $wasPhoneLocked")
handleIncomingCallIntent(intent)
}
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 +81,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 +159,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 +196,18 @@ class MainActivity : FlutterActivity() {
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, 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<Map<String, Any?>> {
val logsList = mutableListOf<Map<String, Any?>>()
val cursor: Cursor? = contentResolver.query(
@ -171,4 +231,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")
}
}
}
}
}
}

View File

@ -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")
}
}

View File

@ -61,9 +61,16 @@ class _CallPageState extends State<CallPage> {
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<CallPage> {
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<CallPage> {
),
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<CallPage> {
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<CallPage> {
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<CallPage> {
),
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<CallPage> {
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<CallPage> {
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<CallPage> {
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<CallPage> {
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<CallPage> {
),
);
}
}
}

View File

@ -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<String, dynamic>? _pendingCall;
static bool wasPhoneLocked = false;
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
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<void> makeGsmCall(
Future<Map<String, dynamic>> 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<String, dynamic>.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<void> hangUpCall(BuildContext context) async {
Future<Map<String, dynamic>> 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<String, dynamic>.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()};
}
}
}