Compare commits
4 Commits
8f36157808
...
9ba714d25c
Author | SHA1 | Date | |
---|---|---|---|
9ba714d25c | |||
7ac8b3ca8f | |||
fa3e13ae33 | |||
bd3ca98883 |
@ -95,7 +95,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
pendingIncomingCall = null
|
pendingIncomingCall = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkAndRequestDefaultDialer()
|
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
"makeGsmCall" -> {
|
"makeGsmCall" -> {
|
||||||
@ -191,6 +190,24 @@ class MainActivity : FlutterActivity() {
|
|||||||
result.error("SPEAKER_FAILED", "No active call or failed to set speaker", null)
|
result.error("SPEAKER_FAILED", "No active call or failed to set speaker", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"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()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +233,13 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isDefaultDialer(): Boolean {
|
||||||
|
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
|
val currentDefault = telecomManager.defaultDialerPackage
|
||||||
|
Log.d(TAG, "Checking default dialer: current=$currentDefault, myPackage=$packageName")
|
||||||
|
return currentDefault == packageName
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkAndRequestDefaultDialer() {
|
private fun checkAndRequestDefaultDialer() {
|
||||||
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
|
||||||
val currentDefault = telecomManager.defaultDialerPackage
|
val currentDefault = telecomManager.defaultDialerPackage
|
||||||
|
@ -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() {
|
||||||
|
@ -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,8 +249,14 @@ 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) {
|
||||||
|
if (!didPop) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Cannot leave during an active call')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -254,8 +282,7 @@ class _CallPageState extends State<CallPage> {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
icingProtocolOk ? Icons.lock : Icons.lock_open,
|
||||||
color:
|
color: icingProtocolOk ? Colors.green : Colors.red,
|
||||||
icingProtocolOk ? Colors.green : Colors.red,
|
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
@ -282,12 +309,16 @@ class _CallPageState extends State<CallPage> {
|
|||||||
Text(
|
Text(
|
||||||
widget.phoneNumber,
|
widget.phoneNumber,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: statusFontSize, color: Colors.white70),
|
fontSize: statusFontSize,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_callStatus,
|
_callStatus,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: statusFontSize, color: Colors.white70),
|
fontSize: statusFontSize,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -298,8 +329,7 @@ class _CallPageState extends State<CallPage> {
|
|||||||
if (isKeypadVisible) ...[
|
if (isKeypadVisible) ...[
|
||||||
const Spacer(flex: 2),
|
const Spacer(flex: 2),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -319,20 +349,23 @@ class _CallPageState extends State<CallPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onPressed: _toggleKeypad,
|
onPressed: _toggleKeypad,
|
||||||
icon: const Icon(Icons.close,
|
icon: const Icon(
|
||||||
color: Colors.white),
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: MediaQuery.of(context).size.height * 0.35,
|
height: MediaQuery.of(context).size.height * 0.4,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 1.3,
|
childAspectRatio: 1.5,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
crossAxisSpacing: 8,
|
crossAxisSpacing: 8,
|
||||||
children: List.generate(12, (index) {
|
children: List.generate(12, (index) {
|
||||||
@ -357,7 +390,9 @@ class _CallPageState extends State<CallPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 32, color: Colors.white),
|
fontSize: 32,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -369,8 +404,7 @@ class _CallPageState extends State<CallPage> {
|
|||||||
] else ...[
|
] else ...[
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||||
const EdgeInsets.symmetric(horizontal: 32.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -395,7 +429,8 @@ class _CallPageState extends State<CallPage> {
|
|||||||
isMuted ? 'Unmute' : 'Mute',
|
isMuted ? 'Unmute' : 'Mute',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -414,7 +449,8 @@ class _CallPageState extends State<CallPage> {
|
|||||||
'Keypad',
|
'Keypad',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -437,7 +473,8 @@ class _CallPageState extends State<CallPage> {
|
|||||||
'Speaker',
|
'Speaker',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -464,7 +501,8 @@ class _CallPageState extends State<CallPage> {
|
|||||||
'Add Contact',
|
'Add Contact',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -483,7 +521,8 @@ class _CallPageState extends State<CallPage> {
|
|||||||
'Change SIM',
|
'Change SIM',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14),
|
fontSize: 14,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -519,6 +558,7 @@ class _CallPageState extends State<CallPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
dialer/lib/features/home/default_dialer_prompt.dart
Normal file
102
dialer/lib/features/home/default_dialer_prompt.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class DefaultDialerPromptScreen extends StatelessWidget {
|
||||||
|
const DefaultDialerPromptScreen({super.key});
|
||||||
|
|
||||||
|
Future<void> _requestDefaultDialer(BuildContext context) async {
|
||||||
|
const channel = MethodChannel('call_service');
|
||||||
|
try {
|
||||||
|
await channel.invokeMethod('requestDefaultDialer');
|
||||||
|
// Navigate to home page after requesting default dialer
|
||||||
|
Navigator.of(context).pushReplacementNamed('/home');
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error requesting default dialer: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _exploreApp(BuildContext context) {
|
||||||
|
// Navigate to home page without requesting default dialer
|
||||||
|
Navigator.of(context).pushReplacementNamed('/home');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Set as Default Dialer',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'To handle calls effectively, Icing needs to be your default dialer app. This allows Icing to manage incoming and outgoing calls seamlessly.\n\nWithout the permission, Icing will not be able to encrypt calls.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => _exploreApp(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[800],
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text('Explore App first'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => _requestDefaultDialer(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 12),
|
||||||
|
),
|
||||||
|
child: Text('Set as Default Dialer'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:dialer/features/home/home_page.dart';
|
import 'package:dialer/features/home/home_page.dart';
|
||||||
|
import 'package:dialer/features/home/default_dialer_prompt.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 'package:dialer/services/call_service.dart';
|
||||||
@ -51,6 +52,17 @@ Future<void> _requestPermissions() async {
|
|||||||
class Dialer extends StatelessWidget {
|
class Dialer extends StatelessWidget {
|
||||||
const Dialer({super.key});
|
const Dialer({super.key});
|
||||||
|
|
||||||
|
Future<bool> _isDefaultDialer() async {
|
||||||
|
const channel = MethodChannel('call_service');
|
||||||
|
try {
|
||||||
|
final isDefault = await channel.invokeMethod<bool>('isDefaultDialer');
|
||||||
|
return isDefault ?? false;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error checking default dialer: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ContactState(
|
return ContactState(
|
||||||
@ -59,7 +71,24 @@ class Dialer extends StatelessWidget {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
),
|
),
|
||||||
home: SafeArea(child: MyHomePage()),
|
initialRoute: '/',
|
||||||
|
routes: {
|
||||||
|
'/': (context) => FutureBuilder<bool>(
|
||||||
|
future: _isDefaultDialer(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snapshot.hasError || !snapshot.hasData || snapshot.data == false) {
|
||||||
|
return DefaultDialerPromptScreen();
|
||||||
|
}
|
||||||
|
return SafeArea(child: MyHomePage());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'/home': (context) => SafeArea(child: MyHomePage()),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user