feat: CallPage UI, ReceivingCall UI, Default dialer app, can call/receive calls and hangup #46

Closed
florian wants to merge 16 commits from addCallpageUI into dev
3 changed files with 63 additions and 17 deletions
Showing only changes of commit c886e29d75 - Show all commits

View File

@ -5,23 +5,31 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle
import android.telecom.TelecomManager import android.telecom.TelecomManager
import android.telecom.PhoneAccountHandle
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import android.content.pm.PackageManager import android.content.pm.PackageManager
object CallService { object CallService {
private var phoneAccountHandle: PhoneAccountHandle? = null
fun setPhoneAccountHandle(handle: PhoneAccountHandle) {
phoneAccountHandle = handle
}
fun makeGsmCall(context: Context, phoneNumber: String): Boolean { fun makeGsmCall(context: Context, phoneNumber: String): Boolean {
return try { return try {
val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
val uri = Uri.parse("tel:$phoneNumber") val uri = Uri.parse("tel:$phoneNumber")
// Check CALL_PHONE permission
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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 true
} else { } else {
Log.e("CallService", "GSM call not supported below Android M") Log.e("CallService", "GSM call not supported below Android M")

View File

@ -1,8 +1,10 @@
import 'package:dialer/features/home/home_page.dart'; import 'package:dialer/features/home/home_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dialer/features/contacts/contact_state.dart'; import 'package:dialer/features/contacts/contact_state.dart';
import 'package:dialer/services/call_service.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart'; import 'package:dialer/services/cryptography/asymmetric_crypto_service.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
void main() async { void main() async {
@ -13,19 +15,35 @@ void main() async {
final AsymmetricCryptoService cryptoService = AsymmetricCryptoService(); final AsymmetricCryptoService cryptoService = AsymmetricCryptoService();
await cryptoService.initializeDefaultKeyPair(); await cryptoService.initializeDefaultKeyPair();
// Request permissions before running the app
await _requestPermissions();
CallService(); // Initialize CallService
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
Provider<AsymmetricCryptoService>( Provider<AsymmetricCryptoService>(
create: (_) => cryptoService, create: (_) => cryptoService,
), ),
// Add other providers here
], ],
child: Dialer(), child: Dialer(),
), ),
); );
} }
Future<void> _requestPermissions() async {
Map<Permission, PermissionStatus> 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 { class Dialer extends StatelessWidget {
const Dialer({super.key}); const Dialer({super.key});
@ -33,11 +51,12 @@ class Dialer extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ContactState( return ContactState(
child: MaterialApp( child: MaterialApp(
navigatorKey: CallService.navigatorKey,
theme: ThemeData( theme: ThemeData(
brightness: Brightness.dark brightness: Brightness.dark,
), ),
home: SafeArea(child: MyHomePage()), home: SafeArea(child: MyHomePage()),
) ),
); );
} }
} }

View File

@ -4,26 +4,45 @@ import '../features/call/call_page.dart';
class CallService { class CallService {
static const MethodChannel _channel = MethodChannel('call_service'); 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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<void> makeGsmCall( Future<void> makeGsmCall(
BuildContext context, { BuildContext context, {
required String phoneNumber, required String phoneNumber,
String? displayName, String? displayName,
Uint8List? thumbnail, // Added optional thumbnail Uint8List? thumbnail,
}) async { }) async {
try { try {
currentPhoneNumber = phoneNumber;
final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber});
if (result["status"] == "calling") { if (result["status"] == "calling") {
Navigator.push( // CallPage will be shown via CallConnectionService callback
context,
MaterialPageRoute(
builder: (context) => CallPage(
displayName: displayName ?? phoneNumber, // Fallback to phoneNumber if no name
phoneNumber: phoneNumber,
thumbnail: thumbnail, // Pass the thumbnail
),
),
);
} else if (result["status"] == "pending_default_dialer") { } else if (result["status"] == "pending_default_dialer") {
print("Waiting for user to set app as default dialer"); print("Waiting for user to set app as default dialer");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(