feat: CallPage UI, ReceivingCall UI, Default dialer app, can call/receive calls and hangup #45
@ -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")
|
||||||
|
@ -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()),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user