feat: DTMF dialpad #55
@ -199,6 +199,15 @@ class MainActivity : FlutterActivity() {
|
|||||||
checkAndRequestDefaultDialer()
|
checkAndRequestDefaultDialer()
|
||||||
result.success(true)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,22 @@ class MyInCallService : InCallService() {
|
|||||||
}
|
}
|
||||||
} ?: false
|
} ?: 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() {
|
private val callCallback = object : Call.Callback() {
|
||||||
@ -111,7 +127,7 @@ class MyInCallService : InCallService() {
|
|||||||
}
|
}
|
||||||
call.registerCallback(callCallback)
|
call.registerCallback(callCallback)
|
||||||
if (callAudioState != null) {
|
if (callAudioState != null) {
|
||||||
val audioState = callAudioState
|
val audioState = callAudioState
|
||||||
channel?.invokeMethod("audioStateChanged", mapOf(
|
channel?.invokeMethod("audioStateChanged", mapOf(
|
||||||
"route" to audioState.route,
|
"route" to audioState.route,
|
||||||
"muted" to audioState.isMuted,
|
"muted" to audioState.isMuted,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_contacts/flutter_contacts.dart';
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
||||||
import 'package:dialer/services/call_service.dart';
|
import 'package:dialer/services/call_service.dart';
|
||||||
import 'package:dialer/services/obfuscate_service.dart';
|
import 'package:dialer/services/obfuscate_service.dart';
|
||||||
import 'package:dialer/widgets/username_color_generator.dart';
|
import 'package:dialer/widgets/username_color_generator.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class CallPage extends StatefulWidget {
|
class CallPage extends StatefulWidget {
|
||||||
final String displayName;
|
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(() {
|
setState(() {
|
||||||
_typedDigits += digit;
|
_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 {
|
void _toggleMute() async {
|
||||||
@ -195,7 +217,7 @@ class _CallPageState extends State<CallPage> {
|
|||||||
print('CallPage: Error hanging up: $e');
|
print('CallPage: Error hanging up: $e');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("Error hanging up: $e")),
|
SnackBar(content: Text('Error hanging up: $e')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,298 +249,316 @@ class _CallPageState extends State<CallPage> {
|
|||||||
print(
|
print(
|
||||||
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop:
|
canPop: _callStatus == "Call Ended",
|
||||||
_callStatus == "Call Ended", // Allow navigation only if call ended
|
onPopInvoked: (didPop) {
|
||||||
child: Scaffold(
|
if (!didPop) {
|
||||||
body: Container(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
color: Colors.black,
|
SnackBar(content: Text('Cannot leave during an active call')),
|
||||||
child: SafeArea(
|
);
|
||||||
child: Column(
|
}
|
||||||
children: [
|
},
|
||||||
Container(
|
child: Scaffold(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
body: Container(
|
||||||
child: Column(
|
color: Colors.black,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: SafeArea(
|
||||||
children: [
|
child: Column(
|
||||||
const SizedBox(height: 35),
|
children: [
|
||||||
ObfuscatedAvatar(
|
Container(
|
||||||
imageBytes: widget.thumbnail,
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
radius: avatarRadius,
|
child: Column(
|
||||||
backgroundColor:
|
mainAxisSize: MainAxisSize.min,
|
||||||
generateColorFromName(widget.displayName),
|
children: [
|
||||||
fallbackInitial: widget.displayName,
|
const SizedBox(height: 35),
|
||||||
),
|
ObfuscatedAvatar(
|
||||||
const SizedBox(height: 4),
|
imageBytes: widget.thumbnail,
|
||||||
Row(
|
radius: avatarRadius,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
backgroundColor:
|
||||||
children: [
|
generateColorFromName(widget.displayName),
|
||||||
Icon(
|
fallbackInitial: widget.displayName,
|
||||||
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
||||||
|
color: icingProtocolOk ? Colors.green : Colors.red,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'Icing protocol: ${icingProtocolOk ? "ok" : "ko"}',
|
||||||
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
icingProtocolOk ? Colors.green : Colors.red,
|
icingProtocolOk ? Colors.green : Colors.red,
|
||||||
size: 16,
|
fontSize: 12,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'Icing protocol: ${icingProtocolOk ? "ok" : "ko"}',
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
icingProtocolOk ? Colors.green : Colors.red,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_obfuscateService.obfuscateData(widget.displayName),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: nameFontSize,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
widget.phoneNumber,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: statusFontSize, color: Colors.white70),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_callStatus,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: statusFontSize, color: Colors.white70),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (isKeypadVisible) ...[
|
|
||||||
const Spacer(flex: 2),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_typedDigits,
|
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: _toggleKeypad,
|
|
||||||
icon: const Icon(Icons.close,
|
|
||||||
color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
height: MediaQuery.of(context).size.height * 0.35,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
child: GridView.count(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
crossAxisCount: 3,
|
|
||||||
childAspectRatio: 1.3,
|
|
||||||
mainAxisSpacing: 8,
|
|
||||||
crossAxisSpacing: 8,
|
|
||||||
children: List.generate(12, (index) {
|
|
||||||
String label;
|
|
||||||
if (index < 9) {
|
|
||||||
label = '${index + 1}';
|
|
||||||
} else if (index == 9) {
|
|
||||||
label = '*';
|
|
||||||
} else if (index == 10) {
|
|
||||||
label = '0';
|
|
||||||
} else {
|
|
||||||
label = '#';
|
|
||||||
}
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => _addDigit(label),
|
|
||||||
child: Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 32, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(flex: 1),
|
|
||||||
] else ...[
|
|
||||||
const Spacer(),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 32.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleMute,
|
|
||||||
icon: Icon(
|
|
||||||
isMuted ? Icons.mic_off : Icons.mic,
|
|
||||||
color: isMuted
|
|
||||||
? Colors.amber
|
|
||||||
: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
isMuted ? 'Unmute' : 'Mute',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleKeypad,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.dialpad,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Keypad',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _toggleSpeaker,
|
|
||||||
icon: Icon(
|
|
||||||
isSpeaker
|
|
||||||
? Icons.volume_up
|
|
||||||
: Icons.volume_off,
|
|
||||||
color: isSpeaker
|
|
||||||
? Colors.amber
|
|
||||||
: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Speaker',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
if (isNumberUnknown)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _addContact,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.person_add,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Add Contact',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.sim_card,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Change SIM',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(flex: 3),
|
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
),
|
Text(
|
||||||
Padding(
|
_obfuscateService.obfuscateData(widget.displayName),
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
style: TextStyle(
|
||||||
child: GestureDetector(
|
fontSize: nameFontSize,
|
||||||
onTap: _hangUp,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.call_end,
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 32,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
widget.phoneNumber,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: statusFontSize,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_callStatus,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: statusFontSize,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (isKeypadVisible) ...[
|
||||||
|
const Spacer(flex: 2),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_typedDigits,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: _toggleKeypad,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
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.5,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
children: List.generate(12, (index) {
|
||||||
|
String label;
|
||||||
|
if (index < 9) {
|
||||||
|
label = '${index + 1}';
|
||||||
|
} else if (index == 9) {
|
||||||
|
label = '*';
|
||||||
|
} else if (index == 10) {
|
||||||
|
label = '0';
|
||||||
|
} else {
|
||||||
|
label = '#';
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => _addDigit(label),
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(flex: 1),
|
||||||
|
] else ...[
|
||||||
|
const Spacer(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _toggleMute,
|
||||||
|
icon: Icon(
|
||||||
|
isMuted ? Icons.mic_off : Icons.mic,
|
||||||
|
color: isMuted
|
||||||
|
? Colors.amber
|
||||||
|
: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
isMuted ? 'Unmute' : 'Mute',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _toggleKeypad,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.dialpad,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Keypad',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _toggleSpeaker,
|
||||||
|
icon: Icon(
|
||||||
|
isSpeaker
|
||||||
|
? Icons.volume_up
|
||||||
|
: Icons.volume_off,
|
||||||
|
color: isSpeaker
|
||||||
|
? Colors.amber
|
||||||
|
: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Speaker',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
if (isNumberUnknown)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _addContact,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.person_add,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Add Contact',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.sim_card,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Change SIM',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(flex: 3),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: _hangUp,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.call_end,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user