feat: DTMF dialpad #55
@ -199,6 +199,24 @@ class MainActivity : FlutterActivity() {
|
||||
checkAndRequestDefaultDialer()
|
||||
result.success(true)
|
||||
}
|
||||
"sendDtmfTone" -> {
|
||||
val digit = call.argument<String>("digit")
|
||||
if (digit != null) {
|
||||
val success = MyInCallService.sendDtmfTone(digit)
|
||||
result.success(success)
|
||||
} else {
|
||||
result.error("INVALID_ARGUMENT", "Digit is null", null)
|
||||
}
|
||||
}
|
||||
"isDefaultDialer" -> {
|
||||
val isDefault = isDefaultDialer()
|
||||
Log.d(TAG, "isDefaultDialer called, returning: $isDefault")
|
||||
result.success(isDefault)
|
||||
}
|
||||
"requestDefaultDialer" -> {
|
||||
checkAndRequestDefaultDialer()
|
||||
result.success(true)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,22 @@ class MyInCallService : InCallService() {
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun sendDtmfTone(digit: String): Boolean {
|
||||
return instance?.let { service ->
|
||||
try {
|
||||
currentCall?.let { call ->
|
||||
call.playDtmfTone(digit[0])
|
||||
call.stopDtmfTone()
|
||||
Log.d(TAG, "Sent DTMF tone: $digit")
|
||||
true
|
||||
} ?: false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send DTMF tone: $e")
|
||||
false
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
|
||||
private val callCallback = object : Call.Callback() {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||
import 'package:dialer/services/call_service.dart';
|
||||
import 'package:dialer/services/obfuscate_service.dart';
|
||||
import 'package:dialer/widgets/username_color_generator.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CallPage extends StatefulWidget {
|
||||
final String displayName;
|
||||
@ -124,10 +124,32 @@ class _CallPageState extends State<CallPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _addDigit(String digit) {
|
||||
void _addDigit(String digit) async {
|
||||
print('CallPage: Tapped digit: $digit');
|
||||
setState(() {
|
||||
_typedDigits += digit;
|
||||
});
|
||||
// Send DTMF tone
|
||||
const channel = MethodChannel('call_service');
|
||||
try {
|
||||
final success =
|
||||
await channel.invokeMethod<bool>('sendDtmfTone', {'digit': digit});
|
||||
if (success != true) {
|
||||
print('CallPage: Failed to send DTMF tone for $digit');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to send DTMF tone')),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('CallPage: Error sending DTMF tone: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Error sending DTMF tone: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleMute() async {
|
||||
@ -195,7 +217,7 @@ class _CallPageState extends State<CallPage> {
|
||||
print('CallPage: Error hanging up: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Error hanging up: $e")),
|
||||
SnackBar(content: Text('Error hanging up: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -227,8 +249,14 @@ class _CallPageState extends State<CallPage> {
|
||||
print(
|
||||
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
||||
return PopScope(
|
||||
canPop:
|
||||
_callStatus == "Call Ended", // Allow navigation only if call ended
|
||||
canPop: _callStatus == "Call Ended",
|
||||
onPopInvoked: (didPop) {
|
||||
if (!didPop) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Cannot leave during an active call')),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Container(
|
||||
color: Colors.black,
|
||||
@ -254,8 +282,7 @@ class _CallPageState extends State<CallPage> {
|
||||
children: [
|
||||
Icon(
|
||||
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
||||
color:
|
||||
icingProtocolOk ? Colors.green : Colors.red,
|
||||
color: icingProtocolOk ? Colors.green : Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
@ -282,12 +309,16 @@ class _CallPageState extends State<CallPage> {
|
||||
Text(
|
||||
widget.phoneNumber,
|
||||
style: TextStyle(
|
||||
fontSize: statusFontSize, color: Colors.white70),
|
||||
fontSize: statusFontSize,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_callStatus,
|
||||
style: TextStyle(
|
||||
fontSize: statusFontSize, color: Colors.white70),
|
||||
fontSize: statusFontSize,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -298,8 +329,7 @@ class _CallPageState extends State<CallPage> {
|
||||
if (isKeypadVisible) ...[
|
||||
const Spacer(flex: 2),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -319,20 +349,23 @@ 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.35,
|
||||
height: MediaQuery.of(context).size.height * 0.4,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: 1.3,
|
||||
childAspectRatio: 1.5,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
children: List.generate(12, (index) {
|
||||
@ -357,7 +390,9 @@ class _CallPageState extends State<CallPage> {
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 32, color: Colors.white),
|
||||
fontSize: 32,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -369,8 +404,7 @@ class _CallPageState extends State<CallPage> {
|
||||
] else ...[
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -395,7 +429,8 @@ class _CallPageState extends State<CallPage> {
|
||||
isMuted ? 'Unmute' : 'Mute',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -414,7 +449,8 @@ class _CallPageState extends State<CallPage> {
|
||||
'Keypad',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -437,7 +473,8 @@ class _CallPageState extends State<CallPage> {
|
||||
'Speaker',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -464,7 +501,8 @@ class _CallPageState extends State<CallPage> {
|
||||
'Add Contact',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -483,7 +521,8 @@ class _CallPageState extends State<CallPage> {
|
||||
'Change SIM',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -519,6 +558,7 @@ class _CallPageState extends State<CallPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user