diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt index b9c6bd9..8dbf4c1 100644 --- a/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt +++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/services/CallService.kt @@ -5,23 +5,31 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build +import android.os.Bundle import android.telecom.TelecomManager +import android.telecom.PhoneAccountHandle import android.telephony.TelephonyManager import android.util.Log import androidx.core.content.ContextCompat import android.content.pm.PackageManager object CallService { + private var phoneAccountHandle: PhoneAccountHandle? = null + + fun setPhoneAccountHandle(handle: PhoneAccountHandle) { + phoneAccountHandle = handle + } fun makeGsmCall(context: Context, phoneNumber: String): Boolean { return try { val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager val uri = Uri.parse("tel:$phoneNumber") - // Check CALL_PHONE permission if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - telecomManager.placeCall(uri, null) + val extras = Bundle() + extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle) + telecomManager.placeCall(uri, extras) true } else { Log.e("CallService", "GSM call not supported below Android M") diff --git a/dialer/lib/main.dart b/dialer/lib/main.dart index afde3b9..4aa2170 100644 --- a/dialer/lib/main.dart +++ b/dialer/lib/main.dart @@ -1,8 +1,10 @@ import 'package:dialer/features/home/home_page.dart'; import 'package:flutter/material.dart'; import 'package:dialer/features/contacts/contact_state.dart'; +import 'package:dialer/services/call_service.dart'; import 'globals.dart' as globals; import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; void main() async { @@ -13,19 +15,35 @@ void main() async { final AsymmetricCryptoService cryptoService = AsymmetricCryptoService(); await cryptoService.initializeDefaultKeyPair(); + // Request permissions before running the app + await _requestPermissions(); + + CallService(); // Initialize CallService + runApp( MultiProvider( providers: [ Provider( create: (_) => cryptoService, ), - // Add other providers here ], child: Dialer(), ), ); } +Future _requestPermissions() async { + Map statuses = await [ + Permission.phone, + Permission.contacts, + ].request(); + if (statuses.values.every((status) => status.isGranted)) { + print("All required permissions granted"); + } else { + print("Permissions denied: ${statuses.entries.where((e) => !e.value.isGranted).map((e) => e.key).join(', ')}"); + } +} + class Dialer extends StatelessWidget { const Dialer({super.key}); @@ -33,11 +51,12 @@ class Dialer extends StatelessWidget { Widget build(BuildContext context) { return ContactState( child: MaterialApp( + navigatorKey: CallService.navigatorKey, theme: ThemeData( - brightness: Brightness.dark + brightness: Brightness.dark, ), home: SafeArea(child: MyHomePage()), - ) + ), ); } -} +} \ No newline at end of file diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index f02f745..21fef7c 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -4,26 +4,45 @@ import '../features/call/call_page.dart'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); + static String? currentPhoneNumber; + + CallService() { + _channel.setMethodCallHandler((call) async { + if (call.method == "callStateChanged") { + final state = call.arguments["state"] as String; + final phoneNumber = call.arguments["phoneNumber"] as String; + if (state == "dialing" || state == "active") { + Navigator.push( + navigatorKey.currentContext!, + MaterialPageRoute( + builder: (context) => CallPage( + displayName: phoneNumber, // Replace with contact lookup if available + phoneNumber: phoneNumber, + thumbnail: null, + ), + ), + ); + } else if (state == "disconnected") { + Navigator.pop(navigatorKey.currentContext!); + } + } + }); + } + + // Add a GlobalKey for Navigator + static final GlobalKey navigatorKey = GlobalKey(); Future makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, - Uint8List? thumbnail, // Added optional thumbnail + Uint8List? thumbnail, }) async { try { + currentPhoneNumber = phoneNumber; final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); if (result["status"] == "calling") { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CallPage( - displayName: displayName ?? phoneNumber, // Fallback to phoneNumber if no name - phoneNumber: phoneNumber, - thumbnail: thumbnail, // Pass the thumbnail - ), - ), - ); + // CallPage will be shown via CallConnectionService callback } else if (result["status"] == "pending_default_dialer") { print("Waiting for user to set app as default dialer"); ScaffoldMessenger.of(context).showSnackBar(