From a6d39e5d5df215d5f249e7c5acd58f7d8ffb4d5f Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 21:39:39 +0300 Subject: [PATCH 01/16] feat: fetch contact in makeGsmCall to show it in call --- dialer/lib/services/call_service.dart | 152 ++++++++++++-------------- 1 file changed, 70 insertions(+), 82 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index e03c652..c7e41a6 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -2,124 +2,115 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; +import '../services/contact_service.dart'; // Import your ContactService class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; + static String? currentDisplayName; // Store display name + static Uint8List? currentThumbnail; // Store thumbnail static bool _isCallPageVisible = false; - static Map? _pendingCall; - static bool wasPhoneLocked = false; + final ContactService _contactService = ContactService(); // Instantiate ContactService static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { - print('CallService: Handling method call: ${call.method}'); + final context = navigatorKey.currentContext; + print('CallService: Received method ${call.method} with args ${call.arguments}'); + if (context == null) { + print('CallService: Navigator context is null, cannot navigate'); + return; + } + switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); + // Fetch contact info using ContactService if not already set + await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); if (state == "ringing") { - _handleIncomingCall(phoneNumber); + _navigateToIncomingCallPage(context); } else { - _navigateToCallPage(); + _navigateToCallPage(context); } break; case "callStateChanged": final state = call.arguments["state"] as String; - wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); + print('CallService: State changed to $state'); if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(); - if (wasPhoneLocked) { - _channel.invokeMethod("callEndedFromFlutter"); - } + _closeCallPage(context); } else if (state == "active" || state == "dialing") { - _navigateToCallPage(); + _navigateToCallPage(context); } else if (state == "ringing") { - final phoneNumber = call.arguments["callId"] as String; - _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); + _navigateToIncomingCallPage(context); } break; case "callEnded": case "callRemoved": - wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); - _closeCallPage(); - if (wasPhoneLocked) { - _channel.invokeMethod("callEndedFromFlutter"); - } + print('CallService: Call ended/removed'); + _closeCallPage(context); currentPhoneNumber = null; - break; - case "incomingCallFromNotification": - final phoneNumber = call.arguments["phoneNumber"] as String; - wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - currentPhoneNumber = phoneNumber; - print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); - _handleIncomingCall(phoneNumber); + currentDisplayName = null; + currentThumbnail = null; break; } }); } - void _handleIncomingCall(String phoneNumber) { - final context = navigatorKey.currentContext; - if (context == null) { - print('CallService: Context is null, queuing incoming call: $phoneNumber'); - _pendingCall = {"phoneNumber": phoneNumber}; - Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); - } else { - _navigateToIncomingCallPage(context); - } - } - - void _checkPendingCall() { - if (_pendingCall != null) { - final context = navigatorKey.currentContext; - if (context != null) { - print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); - currentPhoneNumber = _pendingCall!["phoneNumber"]; - _navigateToIncomingCallPage(context); - _pendingCall = null; - } else { - print('CallService: Context still null, retrying...'); - Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); + Future _fetchContactInfo(String phoneNumber) async { + if (currentDisplayName != null && currentThumbnail != null) return; // Already set + try { + final contacts = await _contactService.fetchContacts(); // Use ContactService + for (var contact in contacts) { + for (var phone in contact.phones) { + if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { + currentDisplayName = contact.displayName; + currentThumbnail = contact.thumbnail; + return; + } + } } + // If no match found, use phone number as fallback + currentDisplayName ??= phoneNumber; + currentThumbnail ??= null; + } catch (e) { + print('CallService: Error fetching contact info: $e'); + currentDisplayName = phoneNumber; + currentThumbnail = null; } } - void _navigateToCallPage() { - final context = navigatorKey.currentContext; - if (context == null) { - print('CallService: Cannot navigate to CallPage, context is null'); - return; - } - if (_isCallPageVisible) { + String _normalizePhoneNumber(String number) { + return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); + } + + void _navigateToCallPage(BuildContext context) { + if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') { print('CallService: CallPage already visible, skipping navigation'); return; } print('CallService: Navigating to CallPage'); - Navigator.push( + Navigator.pushReplacement( context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), builder: (context) => CallPage( - displayName: currentPhoneNumber!, + displayName: currentDisplayName ?? currentPhoneNumber!, phoneNumber: currentPhoneNumber!, - thumbnail: null, + thumbnail: currentThumbnail, ), ), ).then((_) { _isCallPageVisible = false; - print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { - if (_isCallPageVisible) { + if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') { print('CallService: IncomingCallPage already visible, skipping navigation'); return; } @@ -129,35 +120,30 @@ class CallService { MaterialPageRoute( settings: const RouteSettings(name: '/incoming_call'), builder: (context) => IncomingCallPage( - displayName: currentPhoneNumber!, + displayName: currentDisplayName ?? currentPhoneNumber!, phoneNumber: currentPhoneNumber!, - thumbnail: null, + thumbnail: currentThumbnail, ), ), ).then((_) { _isCallPageVisible = false; - print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } - void _closeCallPage() { - final context = navigatorKey.currentContext; - if (context == null) { - print('CallService: Cannot close page, context is null'); + void _closeCallPage(BuildContext context) { + if (!_isCallPageVisible) { + print('CallService: CallPage not visible, skipping pop'); return; } - print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { - print('CallService: Popping call page'); + print('CallService: Popping CallPage'); Navigator.pop(context); _isCallPageVisible = false; - } else { - print('CallService: No page to pop'); } } - Future> makeGsmCall( + Future makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -165,43 +151,45 @@ class CallService { }) async { try { currentPhoneNumber = phoneNumber; + currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later + currentThumbnail = thumbnail; // Use provided or fetch later print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "calling") { + if (result["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); + return; } - return resultMap; + // Fetch contact info if not provided + await _fetchContactInfo(phoneNumber); + _navigateToCallPage(context); // Navigate immediately after call initiation } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } - Future> hangUpCall(BuildContext context) async { + Future hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "ended") { + if (result["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } - return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } } \ No newline at end of file -- 2.45.2 From 6fb7fefef46d5841a15fd032fe121c644974e56f Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 21:55:26 +0300 Subject: [PATCH 02/16] feat: give parameters to callPage to avoid fetching when possible --- .../contacts/widgets/contact_modal.dart | 8 +++-- dialer/lib/features/history/history_page.dart | 7 +++- dialer/lib/services/call_service.dart | 32 ++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/dialer/lib/features/contacts/widgets/contact_modal.dart b/dialer/lib/features/contacts/widgets/contact_modal.dart index 198f614..a73f7e6 100644 --- a/dialer/lib/features/contacts/widgets/contact_modal.dart +++ b/dialer/lib/features/contacts/widgets/contact_modal.dart @@ -267,8 +267,12 @@ class _ContactModalState extends State { ), onTap: () async { if (widget.contact.phones.isNotEmpty) { - await _callService.makeGsmCall(context, - phoneNumber: phoneNumber); + await _callService.makeGsmCall( + context, + phoneNumber: phoneNumber, + displayName: widget.contact.displayName, + thumbnail: widget.contact.thumbnail, + ); } }, ), diff --git a/dialer/lib/features/history/history_page.dart b/dialer/lib/features/history/history_page.dart index 3c5b1f1..9906908 100644 --- a/dialer/lib/features/history/history_page.dart +++ b/dialer/lib/features/history/history_page.dart @@ -425,7 +425,12 @@ class _HistoryPageState extends State icon: const Icon(Icons.phone, color: Colors.green), onPressed: () async { if (contact.phones.isNotEmpty) { - _callService.makeGsmCall(context, phoneNumber: contact.phones.first.number); + await _callService.makeGsmCall( + context, + phoneNumber: contact.phones.first.number, + displayName: contact.displayName, + thumbnail: contact.thumbnail, + ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index c7e41a6..c7e8ad2 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,16 +1,17 @@ +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; -import '../services/contact_service.dart'; // Import your ContactService +import '../services/contact_service.dart'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; - static String? currentDisplayName; // Store display name - static Uint8List? currentThumbnail; // Store thumbnail + static String? currentDisplayName; + static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; - final ContactService _contactService = ContactService(); // Instantiate ContactService + final ContactService _contactService = ContactService(); static final GlobalKey navigatorKey = GlobalKey(); @@ -28,7 +29,6 @@ class CallService { final phoneNumber = call.arguments["callId"] as String; final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); - // Fetch contact info using ContactService if not already set await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); if (state == "ringing") { @@ -61,12 +61,12 @@ class CallService { } Future _fetchContactInfo(String phoneNumber) async { - if (currentDisplayName != null && currentThumbnail != null) return; // Already set try { - final contacts = await _contactService.fetchContacts(); // Use ContactService + final contacts = await _contactService.fetchContacts(); + final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); for (var contact in contacts) { for (var phone in contact.phones) { - if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { + if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; @@ -74,8 +74,8 @@ class CallService { } } // If no match found, use phone number as fallback - currentDisplayName ??= phoneNumber; - currentThumbnail ??= null; + currentDisplayName = phoneNumber; + currentThumbnail = null; } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; @@ -151,8 +151,12 @@ class CallService { }) async { try { currentPhoneNumber = phoneNumber; - currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later - currentThumbnail = thumbnail; // Use provided or fetch later + // Use provided displayName and thumbnail if available, otherwise fetch + currentDisplayName = displayName ?? phoneNumber; + currentThumbnail = thumbnail; + if (displayName == null || thumbnail == null) { + await _fetchContactInfo(phoneNumber); + } print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); @@ -162,9 +166,7 @@ class CallService { ); return; } - // Fetch contact info if not provided - await _fetchContactInfo(phoneNumber); - _navigateToCallPage(context); // Navigate immediately after call initiation + _navigateToCallPage(context); } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( -- 2.45.2 From a0331adf224116840d0fd1f0fde80ae7ae1468b2 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 22:06:43 +0300 Subject: [PATCH 03/16] fix: no screen stacking when making calls --- dialer/lib/services/call_service.dart | 64 ++++++++++++++++++--------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index c7e8ad2..88e7811 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,4 +1,3 @@ -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; @@ -11,6 +10,7 @@ class CallService { static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; + static String? _currentCallState; final ContactService _contactService = ContactService(); static final GlobalKey navigatorKey = GlobalKey(); @@ -31,22 +31,12 @@ class CallService { currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); - if (state == "ringing") { - _navigateToIncomingCallPage(context); - } else { - _navigateToCallPage(context); - } + _handleCallState(context, state); break; case "callStateChanged": final state = call.arguments["state"] as String; print('CallService: State changed to $state'); - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); - } else if (state == "ringing") { - _navigateToIncomingCallPage(context); - } + _handleCallState(context, state); break; case "callEnded": case "callRemoved": @@ -55,6 +45,7 @@ class CallService { currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; + _currentCallState = null; break; } }); @@ -73,7 +64,6 @@ class CallService { } } } - // If no match found, use phone number as fallback currentDisplayName = phoneNumber; currentThumbnail = null; } catch (e) { @@ -87,12 +77,33 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } + void _handleCallState(BuildContext context, String state) { + if (_currentCallState == state) { + print('CallService: State $state already handled, skipping'); + return; + } + _currentCallState = state; + + if (state == "disconnected" || state == "disconnecting") { + _closeCallPage(context); + } else if (state == "active" || state == "dialing") { + _navigateToCallPage(context); + } else if (state == "ringing") { + _navigateToIncomingCallPage(context); + } + } + void _navigateToCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') { + final currentRoute = ModalRoute.of(context)?.settings.name; + print('CallService: Navigating to CallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); + if (_isCallPageVisible && currentRoute == '/call') { print('CallService: CallPage already visible, skipping navigation'); return; } - print('CallService: Navigating to CallPage'); + if (_isCallPageVisible && currentRoute == '/incoming_call') { + print('CallService: Replacing IncomingCallPage with CallPage'); + Navigator.pop(context); + } Navigator.pushReplacement( context, MaterialPageRoute( @@ -104,17 +115,23 @@ class CallService { ), ), ).then((_) { + print('CallService: CallPage popped'); _isCallPageVisible = false; }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { - if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') { + final currentRoute = ModalRoute.of(context)?.settings.name; + print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); + if (_isCallPageVisible && currentRoute == '/incoming_call') { print('CallService: IncomingCallPage already visible, skipping navigation'); return; } - print('CallService: Navigating to IncomingCallPage'); + if (_isCallPageVisible && currentRoute == '/call') { + print('CallService: CallPage visible, not showing IncomingCallPage'); + return; + } Navigator.push( context, MaterialPageRoute( @@ -126,20 +143,24 @@ class CallService { ), ), ).then((_) { + print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; }); _isCallPageVisible = true; } void _closeCallPage(BuildContext context) { + print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); return; } if (Navigator.canPop(context)) { - print('CallService: Popping CallPage'); + print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); _isCallPageVisible = false; + } else { + print('CallService: Cannot pop, no routes to pop'); } } @@ -151,7 +172,6 @@ class CallService { }) async { try { currentPhoneNumber = phoneNumber; - // Use provided displayName and thumbnail if available, otherwise fetch currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { @@ -166,7 +186,7 @@ class CallService { ); return; } - _navigateToCallPage(context); + _handleCallState(context, "dialing"); } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( @@ -185,6 +205,8 @@ class CallService { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); + } else { + _closeCallPage(context); } } catch (e) { print("CallService: Error hanging up call: $e"); -- 2.45.2 From 59e813974c445bb5338732a2e8e5ed31408eacd7 Mon Sep 17 00:00:00 2001 From: florian Date: Thu, 17 Apr 2025 12:26:32 +0000 Subject: [PATCH 04/16] callNotifications and various fix related to calls (#49) Reviewed-on: https://git.gmoker.com/icing/monorepo/pulls/49 Co-authored-by: florian Co-committed-by: florian --- dialer/lib/services/call_service.dart | 34 +++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 88e7811..7057ad2 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -17,13 +17,7 @@ class CallService { CallService() { _channel.setMethodCallHandler((call) async { - final context = navigatorKey.currentContext; - print('CallService: Received method ${call.method} with args ${call.arguments}'); - if (context == null) { - print('CallService: Navigator context is null, cannot navigate'); - return; - } - + print('CallService: Handling method call: ${call.method}'); switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; @@ -40,8 +34,12 @@ class CallService { break; case "callEnded": case "callRemoved": - print('CallService: Call ended/removed'); - _closeCallPage(context); + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); + _closeCallPage(); + if (wasPhoneLocked) { + _channel.invokeMethod("callEndedFromFlutter"); + } currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; @@ -117,6 +115,7 @@ class CallService { ).then((_) { print('CallService: CallPage popped'); _isCallPageVisible = false; + print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -145,6 +144,7 @@ class CallService { ).then((_) { print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; + print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -155,6 +155,7 @@ class CallService { print('CallService: CallPage not visible, skipping pop'); return; } + print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); @@ -164,7 +165,7 @@ class CallService { } } - Future makeGsmCall( + Future> makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -180,7 +181,8 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); @@ -192,28 +194,30 @@ class CallService { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } - Future hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } else { _closeCallPage(context); } + return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } } \ No newline at end of file -- 2.45.2 From bf9bb3ed9df82c5c6239cb35e0b8313f98cb64b7 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 21:55:26 +0300 Subject: [PATCH 05/16] feat: give parameters to callPage to avoid fetching when possible --- dialer/lib/services/call_service.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 7057ad2..d828416 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; -- 2.45.2 From 24fef24c6da03ce02264ce9773bf1b8bb08927d0 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 22:06:43 +0300 Subject: [PATCH 06/16] fix: no screen stacking when making calls --- dialer/lib/services/call_service.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index d828416..7057ad2 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,4 +1,3 @@ -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; -- 2.45.2 From 540b330f0d32cc1f26935ceb5dec70cab3f1b587 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Thu, 17 Apr 2025 16:07:08 +0300 Subject: [PATCH 07/16] feat: merge call_service with callNotifications changes --- dialer/lib/services/call_service.dart | 83 ++++++++++++++++++++------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 7057ad2..26307be 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -11,13 +11,15 @@ class CallService { static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; static String? _currentCallState; + static Map? _pendingCall; + static bool wasPhoneLocked = false; final ContactService _contactService = ContactService(); static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { - print('CallService: Handling method call: ${call.method}'); + print('CallService: Received method ${call.method} with args ${call.arguments}'); switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; @@ -25,12 +27,13 @@ class CallService { currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); - _handleCallState(context, state); + _handleCallState(state); break; case "callStateChanged": final state = call.arguments["state"] as String; - print('CallService: State changed to $state'); - _handleCallState(context, state); + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); + _handleCallState(state); break; case "callEnded": case "callRemoved": @@ -45,6 +48,14 @@ class CallService { currentThumbnail = null; _currentCallState = null; break; + case "incomingCallFromNotification": + final phoneNumber = call.arguments["phoneNumber"] as String; + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + currentPhoneNumber = phoneNumber; + await _fetchContactInfo(currentPhoneNumber!); + print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); + _handleIncomingCall(phoneNumber); + break; } }); } @@ -75,7 +86,38 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } - void _handleCallState(BuildContext context, String state) { + void _handleIncomingCall(String phoneNumber) { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Context is null, queuing incoming call: $phoneNumber'); + _pendingCall = {"phoneNumber": phoneNumber}; + Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); + } else { + _navigateToIncomingCallPage(context); + } + } + + void _checkPendingCall() { + if (_pendingCall != null) { + final context = navigatorKey.currentContext; + if (context != null) { + print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); + currentPhoneNumber = _pendingCall!["phoneNumber"]; + _navigateToIncomingCallPage(context); + _pendingCall = null; + } else { + print('CallService: Context still null, retrying...'); + Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); + } + } + } + + void _handleCallState(String state) { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Navigator context is null, cannot navigate'); + return; + } if (_currentCallState == state) { print('CallService: State $state already handled, skipping'); return; @@ -83,7 +125,7 @@ class CallService { _currentCallState = state; if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); + _closeCallPage(); } else if (state == "active" || state == "dialing") { _navigateToCallPage(context); } else if (state == "ringing") { @@ -115,7 +157,6 @@ class CallService { ).then((_) { print('CallService: CallPage popped'); _isCallPageVisible = false; - print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -144,18 +185,21 @@ class CallService { ).then((_) { print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; - print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } - void _closeCallPage(BuildContext context) { + void _closeCallPage() { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Cannot close page, context is null'); + return; + } print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); return; } - print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); @@ -165,7 +209,7 @@ class CallService { } } - Future> makeGsmCall( + Future makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -181,43 +225,40 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "calling") { + if (result["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); return; } - _handleCallState(context, "dialing"); + _handleCallState("dialing"); } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } - Future> hangUpCall(BuildContext context) async { + Future hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "ended") { + if (result["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } else { - _closeCallPage(context); + _closeCallPage(); } - return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } } \ No newline at end of file -- 2.45.2 From c76826ba62b72c87788d90fcbcdd91974ba78962 Mon Sep 17 00:00:00 2001 From: florian Date: Thu, 17 Apr 2025 12:26:32 +0000 Subject: [PATCH 08/16] callNotifications and various fix related to calls (#49) Reviewed-on: https://git.gmoker.com/icing/monorepo/pulls/49 Co-authored-by: florian Co-committed-by: florian --- dialer/lib/services/call_service.dart | 96 +++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 26307be..43d34e4 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -2,7 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; +<<<<<<< HEAD import '../services/contact_service.dart'; +======= +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) class CallService { static const MethodChannel _channel = MethodChannel('call_service'); @@ -10,16 +13,25 @@ class CallService { static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; +<<<<<<< HEAD static String? _currentCallState; static Map? _pendingCall; static bool wasPhoneLocked = false; final ContactService _contactService = ContactService(); +======= + static Map? _pendingCall; + static bool wasPhoneLocked = false; +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { +<<<<<<< HEAD print('CallService: Received method ${call.method} with args ${call.arguments}'); +======= + print('CallService: Handling method call: ${call.method}'); +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; @@ -27,13 +39,35 @@ class CallService { currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); +<<<<<<< HEAD _handleCallState(state); +======= + if (state == "ringing") { + _handleIncomingCall(phoneNumber); + } else { + _navigateToCallPage(); + } +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) break; case "callStateChanged": final state = call.arguments["state"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); +<<<<<<< HEAD _handleCallState(state); +======= + if (state == "disconnected" || state == "disconnecting") { + _closeCallPage(); + if (wasPhoneLocked) { + _channel.invokeMethod("callEndedFromFlutter"); + } + } else if (state == "active" || state == "dialing") { + _navigateToCallPage(); + } else if (state == "ringing") { + final phoneNumber = call.arguments["callId"] as String; + _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); + } +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) break; case "callEnded": case "callRemoved": @@ -56,10 +90,18 @@ class CallService { print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; + case "incomingCallFromNotification": + final phoneNumber = call.arguments["phoneNumber"] as String; + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + currentPhoneNumber = phoneNumber; + print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); + _handleIncomingCall(phoneNumber); + break; } }); } +<<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { try { final contacts = await _contactService.fetchContacts(); @@ -86,6 +128,8 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } +======= +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) void _handleIncomingCall(String phoneNumber) { final context = navigatorKey.currentContext; if (context == null) { @@ -112,6 +156,7 @@ class CallService { } } +<<<<<<< HEAD void _handleCallState(String state) { final context = navigatorKey.currentContext; if (context == null) { @@ -145,6 +190,20 @@ class CallService { Navigator.pop(context); } Navigator.pushReplacement( +======= + void _navigateToCallPage() { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Cannot navigate to CallPage, context is null'); + return; + } + if (_isCallPageVisible) { + print('CallService: CallPage already visible, skipping navigation'); + return; + } + print('CallService: Navigating to CallPage'); + Navigator.push( +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), @@ -157,14 +216,19 @@ class CallService { ).then((_) { print('CallService: CallPage popped'); _isCallPageVisible = false; + print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { +<<<<<<< HEAD final currentRoute = ModalRoute.of(context)?.settings.name; print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); if (_isCallPageVisible && currentRoute == '/incoming_call') { +======= + if (_isCallPageVisible) { +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) print('CallService: IncomingCallPage already visible, skipping navigation'); return; } @@ -185,6 +249,7 @@ class CallService { ).then((_) { print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; + print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -193,23 +258,35 @@ class CallService { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot close page, context is null'); +<<<<<<< HEAD return; } print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); +======= +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) return; } + print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { +<<<<<<< HEAD print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); _isCallPageVisible = false; } else { print('CallService: Cannot pop, no routes to pop'); +======= + print('CallService: Popping call page'); + Navigator.pop(context); + _isCallPageVisible = false; + } else { + print('CallService: No page to pop'); +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) } } - Future makeGsmCall( + Future> makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -225,40 +302,47 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); return; } +<<<<<<< HEAD _handleCallState("dialing"); +======= + return resultMap; +>>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } - Future hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } else { _closeCallPage(); } + return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } } \ No newline at end of file -- 2.45.2 From c6a88e237b920547239fe595fb9e95ad5418c5b1 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 21:39:39 +0300 Subject: [PATCH 09/16] feat: fetch contact in makeGsmCall to show it in call --- dialer/lib/services/call_service.dart | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 43d34e4..9233a85 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -3,18 +3,29 @@ import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; <<<<<<< HEAD +<<<<<<< HEAD import '../services/contact_service.dart'; ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +import '../services/contact_service.dart'; // Import your ContactService +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; +<<<<<<< HEAD static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; <<<<<<< HEAD static String? _currentCallState; +======= + static String? currentDisplayName; // Store display name + static Uint8List? currentThumbnail; // Store thumbnail + static bool _isCallPageVisible = false; + final ContactService _contactService = ContactService(); // Instantiate ContactService +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) static Map? _pendingCall; static bool wasPhoneLocked = false; final ContactService _contactService = ContactService(); @@ -37,6 +48,10 @@ class CallService { final phoneNumber = call.arguments["callId"] as String; final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); +<<<<<<< HEAD +======= + // Fetch contact info using ContactService if not already set +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); <<<<<<< HEAD @@ -80,13 +95,19 @@ class CallService { currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; +<<<<<<< HEAD _currentCallState = null; +======= +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) break; case "incomingCallFromNotification": final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; +<<<<<<< HEAD await _fetchContactInfo(currentPhoneNumber!); +======= +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; @@ -101,6 +122,7 @@ class CallService { }); } +<<<<<<< HEAD <<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { try { @@ -109,14 +131,28 @@ class CallService { for (var contact in contacts) { for (var phone in contact.phones) { if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { +======= + Future _fetchContactInfo(String phoneNumber) async { + if (currentDisplayName != null && currentThumbnail != null) return; // Already set + try { + final contacts = await _contactService.fetchContacts(); // Use ContactService + for (var contact in contacts) { + for (var phone in contact.phones) { + if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; } } } +<<<<<<< HEAD currentDisplayName = phoneNumber; currentThumbnail = null; +======= + // If no match found, use phone number as fallback + currentDisplayName ??= phoneNumber; + currentThumbnail ??= null; } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; @@ -128,8 +164,39 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } + Future _fetchContactInfo(String phoneNumber) async { + if (currentDisplayName != null && currentThumbnail != null) return; // Already set + try { + final contacts = await _contactService.fetchContacts(); // Use ContactService + for (var contact in contacts) { + for (var phone in contact.phones) { + if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { + currentDisplayName = contact.displayName; + currentThumbnail = contact.thumbnail; + return; + } + } + } + // If no match found, use phone number as fallback + currentDisplayName ??= phoneNumber; + currentThumbnail ??= null; +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) + } catch (e) { + print('CallService: Error fetching contact info: $e'); + currentDisplayName = phoneNumber; + currentThumbnail = null; + } + } + + String _normalizePhoneNumber(String number) { + return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); + } + +<<<<<<< HEAD ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) void _handleIncomingCall(String phoneNumber) { final context = navigatorKey.currentContext; if (context == null) { @@ -294,11 +361,16 @@ class CallService { }) async { try { currentPhoneNumber = phoneNumber; +<<<<<<< HEAD currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { await _fetchContactInfo(phoneNumber); } +======= + currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later + currentThumbnail = thumbnail; // Use provided or fetch later +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); @@ -309,11 +381,17 @@ class CallService { ); return; } +<<<<<<< HEAD <<<<<<< HEAD _handleCallState("dialing"); ======= return resultMap; >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= + // Fetch contact info if not provided + await _fetchContactInfo(phoneNumber); + _navigateToCallPage(context); // Navigate immediately after call initiation +>>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( -- 2.45.2 From 7cb5993382d232afa3d481a3aec8490f6c353a0b Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 21:55:26 +0300 Subject: [PATCH 10/16] feat: give parameters to callPage to avoid fetching when possible --- dialer/lib/services/call_service.dart | 43 ++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 9233a85..26c5096 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,19 +1,25 @@ +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD import '../services/contact_service.dart'; ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= import '../services/contact_service.dart'; // Import your ContactService >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= +import '../services/contact_service.dart'; +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; +<<<<<<< HEAD <<<<<<< HEAD static String? currentDisplayName; static Uint8List? currentThumbnail; @@ -23,16 +29,27 @@ class CallService { ======= static String? currentDisplayName; // Store display name static Uint8List? currentThumbnail; // Store thumbnail +======= + static String? currentDisplayName; + static Uint8List? currentThumbnail; +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static bool _isCallPageVisible = false; +<<<<<<< HEAD final ContactService _contactService = ContactService(); // Instantiate ContactService >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) static Map? _pendingCall; static bool wasPhoneLocked = false; +<<<<<<< HEAD final ContactService _contactService = ContactService(); ======= static Map? _pendingCall; static bool wasPhoneLocked = false; >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +======= + final ContactService _contactService = ContactService(); +>>>>>>> acbccaa (feat: give parameters to callPage to avoid fetching when possible) +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static final GlobalKey navigatorKey = GlobalKey(); @@ -49,9 +66,12 @@ class CallService { final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); <<<<<<< HEAD +<<<<<<< HEAD ======= // Fetch contact info using ContactService if not already set >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); <<<<<<< HEAD @@ -133,13 +153,17 @@ class CallService { if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { ======= Future _fetchContactInfo(String phoneNumber) async { - if (currentDisplayName != null && currentThumbnail != null) return; // Already set try { - final contacts = await _contactService.fetchContacts(); // Use ContactService + final contacts = await _contactService.fetchContacts(); + final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); for (var contact in contacts) { for (var phone in contact.phones) { +<<<<<<< HEAD if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= + if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; @@ -151,8 +175,8 @@ class CallService { currentThumbnail = null; ======= // If no match found, use phone number as fallback - currentDisplayName ??= phoneNumber; - currentThumbnail ??= null; + currentDisplayName = phoneNumber; + currentThumbnail = null; } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; @@ -362,15 +386,22 @@ class CallService { try { currentPhoneNumber = phoneNumber; <<<<<<< HEAD +<<<<<<< HEAD +======= + // Use provided displayName and thumbnail if available, otherwise fetch +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { await _fetchContactInfo(phoneNumber); } +<<<<<<< HEAD ======= currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later currentThumbnail = thumbnail; // Use provided or fetch later >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); @@ -382,6 +413,7 @@ class CallService { return; } <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD _handleCallState("dialing"); ======= @@ -392,6 +424,9 @@ class CallService { await _fetchContactInfo(phoneNumber); _navigateToCallPage(context); // Navigate immediately after call initiation >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= + _navigateToCallPage(context); +>>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( -- 2.45.2 From fa6e538c24cade30bf1665490b0d79808c02ae37 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Tue, 8 Apr 2025 22:06:43 +0300 Subject: [PATCH 11/16] fix: no screen stacking when making calls --- dialer/lib/services/call_service.dart | 104 +++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 26c5096..2b113b9 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,4 +1,3 @@ -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; @@ -24,6 +23,7 @@ class CallService { static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; +<<<<<<< HEAD <<<<<<< HEAD static String? _currentCallState; ======= @@ -34,6 +34,8 @@ class CallService { static Uint8List? currentThumbnail; >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static bool _isCallPageVisible = false; +======= +>>>>>>> 9e76148 (fix: no screen stacking when making calls) <<<<<<< HEAD final ContactService _contactService = ContactService(); // Instantiate ContactService >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) @@ -47,6 +49,9 @@ class CallService { >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= ======= +======= + static String? _currentCallState; +>>>>>>> f491fb6 (fix: no screen stacking when making calls) final ContactService _contactService = ContactService(); >>>>>>> acbccaa (feat: give parameters to callPage to avoid fetching when possible) >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) @@ -74,9 +79,12 @@ class CallService { >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); +<<<<<<< HEAD <<<<<<< HEAD _handleCallState(state); ======= +======= +>>>>>>> 9e76148 (fix: no screen stacking when making calls) if (state == "ringing") { _handleIncomingCall(phoneNumber); } else { @@ -102,7 +110,18 @@ class CallService { final phoneNumber = call.arguments["callId"] as String; _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); } +<<<<<<< HEAD >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +======= + _handleCallState(context, state); + break; + case "callStateChanged": + final state = call.arguments["state"] as String; + print('CallService: State changed to $state'); + _handleCallState(context, state); +>>>>>>> f491fb6 (fix: no screen stacking when making calls) +>>>>>>> 9e76148 (fix: no screen stacking when making calls) break; case "callEnded": case "callRemoved": @@ -115,10 +134,14 @@ class CallService { currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; +<<<<<<< HEAD <<<<<<< HEAD _currentCallState = null; ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= + _currentCallState = null; +>>>>>>> 9e76148 (fix: no screen stacking when making calls) break; case "incomingCallFromNotification": final phoneNumber = call.arguments["phoneNumber"] as String; @@ -170,11 +193,14 @@ class CallService { } } } +<<<<<<< HEAD <<<<<<< HEAD currentDisplayName = phoneNumber; currentThumbnail = null; ======= // If no match found, use phone number as fallback +======= +>>>>>>> 9e76148 (fix: no screen stacking when making calls) currentDisplayName = phoneNumber; currentThumbnail = null; } catch (e) { @@ -188,6 +214,7 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } +<<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { if (currentDisplayName != null && currentThumbnail != null) return; // Already set try { @@ -228,10 +255,25 @@ class CallService { _pendingCall = {"phoneNumber": phoneNumber}; Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } else { +======= + void _handleCallState(BuildContext context, String state) { + if (_currentCallState == state) { + print('CallService: State $state already handled, skipping'); + return; + } + _currentCallState = state; + + if (state == "disconnected" || state == "disconnecting") { + _closeCallPage(context); + } else if (state == "active" || state == "dialing") { + _navigateToCallPage(context); + } else if (state == "ringing") { +>>>>>>> f491fb6 (fix: no screen stacking when making calls) _navigateToIncomingCallPage(context); } } +<<<<<<< HEAD void _checkPendingCall() { if (_pendingCall != null) { final context = navigatorKey.currentContext; @@ -294,7 +336,24 @@ class CallService { } print('CallService: Navigating to CallPage'); Navigator.push( +<<<<<<< HEAD >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +======= + void _navigateToCallPage(BuildContext context) { + final currentRoute = ModalRoute.of(context)?.settings.name; + print('CallService: Navigating to CallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); + if (_isCallPageVisible && currentRoute == '/call') { + print('CallService: CallPage already visible, skipping navigation'); + return; + } + if (_isCallPageVisible && currentRoute == '/incoming_call') { + print('CallService: Replacing IncomingCallPage with CallPage'); + Navigator.pop(context); + } + Navigator.pushReplacement( +>>>>>>> f491fb6 (fix: no screen stacking when making calls) +>>>>>>> 9e76148 (fix: no screen stacking when making calls) context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), @@ -313,6 +372,7 @@ class CallService { } void _navigateToIncomingCallPage(BuildContext context) { +<<<<<<< HEAD <<<<<<< HEAD final currentRoute = ModalRoute.of(context)?.settings.name; print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); @@ -320,6 +380,14 @@ class CallService { ======= if (_isCallPageVisible) { >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= + if (_isCallPageVisible) { +======= + final currentRoute = ModalRoute.of(context)?.settings.name; + print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); + if (_isCallPageVisible && currentRoute == '/incoming_call') { +>>>>>>> f491fb6 (fix: no screen stacking when making calls) +>>>>>>> 9e76148 (fix: no screen stacking when making calls) print('CallService: IncomingCallPage already visible, skipping navigation'); return; } @@ -345,10 +413,12 @@ class CallService { _isCallPageVisible = true; } +<<<<<<< HEAD void _closeCallPage() { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot close page, context is null'); +<<<<<<< HEAD <<<<<<< HEAD return; } @@ -357,10 +427,19 @@ class CallService { print('CallService: CallPage not visible, skipping pop'); ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +======= + void _closeCallPage(BuildContext context) { + print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); + if (!_isCallPageVisible) { + print('CallService: CallPage not visible, skipping pop'); +>>>>>>> f491fb6 (fix: no screen stacking when making calls) +>>>>>>> 9e76148 (fix: no screen stacking when making calls) return; } print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { +<<<<<<< HEAD <<<<<<< HEAD print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); @@ -368,12 +447,24 @@ class CallService { } else { print('CallService: Cannot pop, no routes to pop'); ======= +======= +>>>>>>> 9e76148 (fix: no screen stacking when making calls) print('CallService: Popping call page'); Navigator.pop(context); _isCallPageVisible = false; } else { print('CallService: No page to pop'); +<<<<<<< HEAD >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= +======= + print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); + Navigator.pop(context); + _isCallPageVisible = false; + } else { + print('CallService: Cannot pop, no routes to pop'); +>>>>>>> f491fb6 (fix: no screen stacking when making calls) +>>>>>>> 9e76148 (fix: no screen stacking when making calls) } } @@ -387,9 +478,12 @@ class CallService { currentPhoneNumber = phoneNumber; <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD ======= // Use provided displayName and thumbnail if available, otherwise fetch >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) +======= +>>>>>>> 9e76148 (fix: no screen stacking when making calls) currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { @@ -414,6 +508,7 @@ class CallService { } <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD _handleCallState("dialing"); ======= @@ -427,6 +522,9 @@ class CallService { ======= _navigateToCallPage(context); >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) +======= + _handleCallState(context, "dialing"); +>>>>>>> 9e76148 (fix: no screen stacking when making calls) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( @@ -447,7 +545,11 @@ class CallService { SnackBar(content: Text("Failed to end call")), ); } else { +<<<<<<< HEAD _closeCallPage(); +======= + _closeCallPage(context); +>>>>>>> 9e76148 (fix: no screen stacking when making calls) } return resultMap; } catch (e) { -- 2.45.2 From 4a078c139a67dafabb2312064933d50ac2da340a Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Thu, 17 Apr 2025 16:07:08 +0300 Subject: [PATCH 12/16] feat: merge call_service with callNotifications changes --- dialer/lib/services/call_service.dart | 102 ++++++++++++++++++++++---- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 2b113b9..21e93cb 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -51,7 +51,12 @@ class CallService { ======= ======= static String? _currentCallState; +<<<<<<< HEAD >>>>>>> f491fb6 (fix: no screen stacking when making calls) +======= + static Map? _pendingCall; + static bool wasPhoneLocked = false; +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) final ContactService _contactService = ContactService(); >>>>>>> acbccaa (feat: give parameters to callPage to avoid fetching when possible) >>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) @@ -60,11 +65,15 @@ class CallService { CallService() { _channel.setMethodCallHandler((call) async { +<<<<<<< HEAD <<<<<<< HEAD print('CallService: Received method ${call.method} with args ${call.arguments}'); ======= print('CallService: Handling method call: ${call.method}'); >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) +======= + print('CallService: Received method ${call.method} with args ${call.arguments}'); +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; @@ -80,11 +89,14 @@ class CallService { await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD _handleCallState(state); ======= ======= >>>>>>> 9e76148 (fix: no screen stacking when making calls) +======= +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) if (state == "ringing") { _handleIncomingCall(phoneNumber); } else { @@ -121,7 +133,19 @@ class CallService { print('CallService: State changed to $state'); _handleCallState(context, state); >>>>>>> f491fb6 (fix: no screen stacking when making calls) +<<<<<<< HEAD >>>>>>> 9e76148 (fix: no screen stacking when making calls) +======= +======= + _handleCallState(state); + break; + case "callStateChanged": + final state = call.arguments["state"] as String; + wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; + print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); + _handleCallState(state); +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) break; case "callEnded": case "callRemoved": @@ -147,10 +171,13 @@ class CallService { final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; +<<<<<<< HEAD <<<<<<< HEAD await _fetchContactInfo(currentPhoneNumber!); ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; @@ -158,6 +185,9 @@ class CallService { final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; +======= + await _fetchContactInfo(currentPhoneNumber!); +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; @@ -214,6 +244,7 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } +<<<<<<< HEAD <<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { if (currentDisplayName != null && currentThumbnail != null) return; // Already set @@ -243,11 +274,16 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } +<<<<<<< HEAD <<<<<<< HEAD ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= >>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) +======= +======= +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) void _handleIncomingCall(String phoneNumber) { final context = navigatorKey.currentContext; if (context == null) { @@ -255,8 +291,36 @@ class CallService { _pendingCall = {"phoneNumber": phoneNumber}; Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } else { +<<<<<<< HEAD ======= void _handleCallState(BuildContext context, String state) { +======= + _navigateToIncomingCallPage(context); + } + } + + void _checkPendingCall() { + if (_pendingCall != null) { + final context = navigatorKey.currentContext; + if (context != null) { + print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); + currentPhoneNumber = _pendingCall!["phoneNumber"]; + _navigateToIncomingCallPage(context); + _pendingCall = null; + } else { + print('CallService: Context still null, retrying...'); + Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); + } + } + } + + void _handleCallState(String state) { + final context = navigatorKey.currentContext; + if (context == null) { + print('CallService: Navigator context is null, cannot navigate'); + return; + } +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) if (_currentCallState == state) { print('CallService: State $state already handled, skipping'); return; @@ -264,7 +328,7 @@ class CallService { _currentCallState = state; if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(context); + _closeCallPage(); } else if (state == "active" || state == "dialing") { _navigateToCallPage(context); } else if (state == "ringing") { @@ -366,7 +430,6 @@ class CallService { ).then((_) { print('CallService: CallPage popped'); _isCallPageVisible = false; - print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -408,17 +471,20 @@ class CallService { ).then((_) { print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; - print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) void _closeCallPage() { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot close page, context is null'); <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD return; } @@ -428,8 +494,14 @@ class CallService { ======= >>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) ======= +======= +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) ======= void _closeCallPage(BuildContext context) { +======= + return; + } +>>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); @@ -437,7 +509,6 @@ class CallService { >>>>>>> 9e76148 (fix: no screen stacking when making calls) return; } - print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { <<<<<<< HEAD <<<<<<< HEAD @@ -468,7 +539,7 @@ class CallService { } } - Future> makeGsmCall( + Future makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, @@ -499,8 +570,7 @@ class CallService { print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "calling") { + if (result["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); @@ -509,6 +579,7 @@ class CallService { <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD _handleCallState("dialing"); ======= @@ -525,39 +596,44 @@ class CallService { ======= _handleCallState(context, "dialing"); >>>>>>> 9e76148 (fix: no screen stacking when making calls) +======= + _handleCallState("dialing"); +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } - Future> hangUpCall(BuildContext context) async { + Future hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - final resultMap = Map.from(result as Map); - if (resultMap["status"] != "ended") { + if (result["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } else { +<<<<<<< HEAD <<<<<<< HEAD _closeCallPage(); ======= _closeCallPage(context); >>>>>>> 9e76148 (fix: no screen stacking when making calls) +======= + _closeCallPage(); +>>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) } - return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - return {"status": "error", "message": e.toString()}; + rethrow; } } } \ No newline at end of file -- 2.45.2 From dac6b53b2a9f0e9650fba84551586bfc1eb03242 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Thu, 17 Apr 2025 16:47:11 +0300 Subject: [PATCH 13/16] feat: merged --- dialer/lib/services/call_service.dart | 375 -------------------------- 1 file changed, 375 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 21e93cb..26307be 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -2,141 +2,31 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; import '../features/call/incoming_call_page.dart'; -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD import '../services/contact_service.dart'; -======= ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -import '../services/contact_service.dart'; // Import your ContactService ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= -import '../services/contact_service.dart'; ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; -<<<<<<< HEAD -<<<<<<< HEAD static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; -<<<<<<< HEAD -<<<<<<< HEAD static String? _currentCallState; -======= - static String? currentDisplayName; // Store display name - static Uint8List? currentThumbnail; // Store thumbnail -======= - static String? currentDisplayName; - static Uint8List? currentThumbnail; ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) - static bool _isCallPageVisible = false; -======= ->>>>>>> 9e76148 (fix: no screen stacking when making calls) -<<<<<<< HEAD - final ContactService _contactService = ContactService(); // Instantiate ContactService ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) static Map? _pendingCall; static bool wasPhoneLocked = false; -<<<<<<< HEAD final ContactService _contactService = ContactService(); -======= - static Map? _pendingCall; - static bool wasPhoneLocked = false; ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -======= -======= - static String? _currentCallState; -<<<<<<< HEAD ->>>>>>> f491fb6 (fix: no screen stacking when making calls) -======= - static Map? _pendingCall; - static bool wasPhoneLocked = false; ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) - final ContactService _contactService = ContactService(); ->>>>>>> acbccaa (feat: give parameters to callPage to avoid fetching when possible) ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) static final GlobalKey navigatorKey = GlobalKey(); CallService() { _channel.setMethodCallHandler((call) async { -<<<<<<< HEAD -<<<<<<< HEAD print('CallService: Received method ${call.method} with args ${call.arguments}'); -======= - print('CallService: Handling method call: ${call.method}'); ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= - print('CallService: Received method ${call.method} with args ${call.arguments}'); ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String; final state = call.arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); -<<<<<<< HEAD -<<<<<<< HEAD -======= - // Fetch contact info using ContactService if not already set ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) await _fetchContactInfo(currentPhoneNumber!); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - _handleCallState(state); -======= -======= ->>>>>>> 9e76148 (fix: no screen stacking when making calls) -======= ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) - if (state == "ringing") { - _handleIncomingCall(phoneNumber); - } else { - _navigateToCallPage(); - } ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) - break; - case "callStateChanged": - final state = call.arguments["state"] as String; - wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); -<<<<<<< HEAD - _handleCallState(state); -======= - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(); - if (wasPhoneLocked) { - _channel.invokeMethod("callEndedFromFlutter"); - } - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(); - } else if (state == "ringing") { - final phoneNumber = call.arguments["callId"] as String; - _handleIncomingCall(phoneNumber.replaceFirst('tel:', '')); - } -<<<<<<< HEAD ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -======= - _handleCallState(context, state); - break; - case "callStateChanged": - final state = call.arguments["state"] as String; - print('CallService: State changed to $state'); - _handleCallState(context, state); ->>>>>>> f491fb6 (fix: no screen stacking when making calls) -<<<<<<< HEAD ->>>>>>> 9e76148 (fix: no screen stacking when making calls) -======= -======= _handleCallState(state); break; case "callStateChanged": @@ -144,8 +34,6 @@ class CallService { wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); _handleCallState(state); ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) break; case "callEnded": case "callRemoved": @@ -158,36 +46,13 @@ class CallService { currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; -<<<<<<< HEAD -<<<<<<< HEAD _currentCallState = null; -======= ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= - _currentCallState = null; ->>>>>>> 9e76148 (fix: no screen stacking when making calls) break; case "incomingCallFromNotification": final phoneNumber = call.arguments["phoneNumber"] as String; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; currentPhoneNumber = phoneNumber; -<<<<<<< HEAD -<<<<<<< HEAD await _fetchContactInfo(currentPhoneNumber!); -======= ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) - print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); - _handleIncomingCall(phoneNumber); - break; - case "incomingCallFromNotification": - final phoneNumber = call.arguments["phoneNumber"] as String; - wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - currentPhoneNumber = phoneNumber; -======= - await _fetchContactInfo(currentPhoneNumber!); ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(phoneNumber); break; @@ -195,8 +60,6 @@ class CallService { }); } -<<<<<<< HEAD -<<<<<<< HEAD Future _fetchContactInfo(String phoneNumber) async { try { final contacts = await _contactService.fetchContacts(); @@ -204,33 +67,12 @@ class CallService { for (var contact in contacts) { for (var phone in contact.phones) { if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { -======= - Future _fetchContactInfo(String phoneNumber) async { - try { - final contacts = await _contactService.fetchContacts(); - final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); - for (var contact in contacts) { - for (var phone in contact.phones) { -<<<<<<< HEAD - if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= - if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; return; } } } -<<<<<<< HEAD -<<<<<<< HEAD - currentDisplayName = phoneNumber; - currentThumbnail = null; -======= - // If no match found, use phone number as fallback -======= ->>>>>>> 9e76148 (fix: no screen stacking when making calls) currentDisplayName = phoneNumber; currentThumbnail = null; } catch (e) { @@ -244,46 +86,6 @@ class CallService { return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); } -<<<<<<< HEAD -<<<<<<< HEAD - Future _fetchContactInfo(String phoneNumber) async { - if (currentDisplayName != null && currentThumbnail != null) return; // Already set - try { - final contacts = await _contactService.fetchContacts(); // Use ContactService - for (var contact in contacts) { - for (var phone in contact.phones) { - if (_normalizePhoneNumber(phone.number) == _normalizePhoneNumber(phoneNumber)) { - currentDisplayName = contact.displayName; - currentThumbnail = contact.thumbnail; - return; - } - } - } - // If no match found, use phone number as fallback - currentDisplayName ??= phoneNumber; - currentThumbnail ??= null; ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) - } catch (e) { - print('CallService: Error fetching contact info: $e'); - currentDisplayName = phoneNumber; - currentThumbnail = null; - } - } - - String _normalizePhoneNumber(String number) { - return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); - } - -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= -======= ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) void _handleIncomingCall(String phoneNumber) { final context = navigatorKey.currentContext; if (context == null) { @@ -291,10 +93,6 @@ class CallService { _pendingCall = {"phoneNumber": phoneNumber}; Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } else { -<<<<<<< HEAD -======= - void _handleCallState(BuildContext context, String state) { -======= _navigateToIncomingCallPage(context); } } @@ -314,46 +112,6 @@ class CallService { } } - void _handleCallState(String state) { - final context = navigatorKey.currentContext; - if (context == null) { - print('CallService: Navigator context is null, cannot navigate'); - return; - } ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) - if (_currentCallState == state) { - print('CallService: State $state already handled, skipping'); - return; - } - _currentCallState = state; - - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(); - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); - } else if (state == "ringing") { ->>>>>>> f491fb6 (fix: no screen stacking when making calls) - _navigateToIncomingCallPage(context); - } - } - -<<<<<<< HEAD - void _checkPendingCall() { - if (_pendingCall != null) { - final context = navigatorKey.currentContext; - if (context != null) { - print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); - currentPhoneNumber = _pendingCall!["phoneNumber"]; - _navigateToIncomingCallPage(context); - _pendingCall = null; - } else { - print('CallService: Context still null, retrying...'); - Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); - } - } - } - -<<<<<<< HEAD void _handleCallState(String state) { final context = navigatorKey.currentContext; if (context == null) { @@ -387,37 +145,6 @@ class CallService { Navigator.pop(context); } Navigator.pushReplacement( -======= - void _navigateToCallPage() { - final context = navigatorKey.currentContext; - if (context == null) { - print('CallService: Cannot navigate to CallPage, context is null'); - return; - } - if (_isCallPageVisible) { - print('CallService: CallPage already visible, skipping navigation'); - return; - } - print('CallService: Navigating to CallPage'); - Navigator.push( -<<<<<<< HEAD ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -======= - void _navigateToCallPage(BuildContext context) { - final currentRoute = ModalRoute.of(context)?.settings.name; - print('CallService: Navigating to CallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); - if (_isCallPageVisible && currentRoute == '/call') { - print('CallService: CallPage already visible, skipping navigation'); - return; - } - if (_isCallPageVisible && currentRoute == '/incoming_call') { - print('CallService: Replacing IncomingCallPage with CallPage'); - Navigator.pop(context); - } - Navigator.pushReplacement( ->>>>>>> f491fb6 (fix: no screen stacking when making calls) ->>>>>>> 9e76148 (fix: no screen stacking when making calls) context, MaterialPageRoute( settings: const RouteSettings(name: '/call'), @@ -435,22 +162,9 @@ class CallService { } void _navigateToIncomingCallPage(BuildContext context) { -<<<<<<< HEAD -<<<<<<< HEAD final currentRoute = ModalRoute.of(context)?.settings.name; print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); if (_isCallPageVisible && currentRoute == '/incoming_call') { -======= - if (_isCallPageVisible) { ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= - if (_isCallPageVisible) { -======= - final currentRoute = ModalRoute.of(context)?.settings.name; - print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); - if (_isCallPageVisible && currentRoute == '/incoming_call') { ->>>>>>> f491fb6 (fix: no screen stacking when making calls) ->>>>>>> 9e76148 (fix: no screen stacking when making calls) print('CallService: IncomingCallPage already visible, skipping navigation'); return; } @@ -475,67 +189,23 @@ class CallService { _isCallPageVisible = true; } -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) void _closeCallPage() { final context = navigatorKey.currentContext; if (context == null) { print('CallService: Cannot close page, context is null'); -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD return; } print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); -======= ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -======= ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) -======= - void _closeCallPage(BuildContext context) { -======= - return; - } ->>>>>>> 6628dd6 (feat: merge call_service with callNotifications changes) - print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); - if (!_isCallPageVisible) { - print('CallService: CallPage not visible, skipping pop'); ->>>>>>> f491fb6 (fix: no screen stacking when making calls) ->>>>>>> 9e76148 (fix: no screen stacking when making calls) return; } if (Navigator.canPop(context)) { -<<<<<<< HEAD -<<<<<<< HEAD print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); Navigator.pop(context); _isCallPageVisible = false; } else { print('CallService: Cannot pop, no routes to pop'); -======= -======= ->>>>>>> 9e76148 (fix: no screen stacking when making calls) - print('CallService: Popping call page'); - Navigator.pop(context); - _isCallPageVisible = false; - } else { - print('CallService: No page to pop'); -<<<<<<< HEAD ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= -======= - print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); - Navigator.pop(context); - _isCallPageVisible = false; - } else { - print('CallService: Cannot pop, no routes to pop'); ->>>>>>> f491fb6 (fix: no screen stacking when making calls) ->>>>>>> 9e76148 (fix: no screen stacking when making calls) } } @@ -547,26 +217,11 @@ class CallService { }) async { try { currentPhoneNumber = phoneNumber; -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= - // Use provided displayName and thumbnail if available, otherwise fetch ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) -======= ->>>>>>> 9e76148 (fix: no screen stacking when making calls) currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { await _fetchContactInfo(phoneNumber); } -<<<<<<< HEAD -======= - currentDisplayName = displayName ?? phoneNumber; // Use provided or fetch later - currentThumbnail = thumbnail; // Use provided or fetch later ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); @@ -576,29 +231,7 @@ class CallService { ); return; } -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD _handleCallState("dialing"); -======= - return resultMap; ->>>>>>> ec1779b (callNotifications and various fix related to calls (#49)) -======= - // Fetch contact info if not provided - await _fetchContactInfo(phoneNumber); - _navigateToCallPage(context); // Navigate immediately after call initiation ->>>>>>> 3e2be8a (feat: fetch contact in makeGsmCall to show it in call) -======= - _navigateToCallPage(context); ->>>>>>> c2c646a (feat: give parameters to callPage to avoid fetching when possible) -======= - _handleCallState(context, "dialing"); ->>>>>>> 9e76148 (fix: no screen stacking when making calls) -======= - _handleCallState("dialing"); ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( @@ -618,15 +251,7 @@ class CallService { SnackBar(content: Text("Failed to end call")), ); } else { -<<<<<<< HEAD -<<<<<<< HEAD _closeCallPage(); -======= - _closeCallPage(context); ->>>>>>> 9e76148 (fix: no screen stacking when making calls) -======= - _closeCallPage(); ->>>>>>> b3c8bf7 (feat: merge call_service with callNotifications changes) } } catch (e) { print("CallService: Error hanging up call: $e"); -- 2.45.2 From f6559f8a938ee3b98048490612c2fbd4a43557c8 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Fri, 18 Apr 2025 22:14:38 +0300 Subject: [PATCH 14/16] feat: timer in call page --- dialer/lib/features/call/call_page.dart | 117 +++++++-- dialer/lib/services/call_service.dart | 300 +++++++++++++++++------- 2 files changed, 309 insertions(+), 108 deletions(-) diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart index 368ffde..b6dbce5 100644 --- a/dialer/lib/features/call/call_page.dart +++ b/dialer/lib/features/call/call_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:dialer/services/call_service.dart'; @@ -28,6 +29,58 @@ class _CallPageState extends State { bool isKeypadVisible = false; bool icingProtocolOk = true; String _typedDigits = ""; + Timer? _callTimer; + int _callSeconds = 0; + String _callStatus = "Calling..."; + StreamSubscription? _callStateSubscription; + + @override + void initState() { + super.initState(); + _listenToCallState(); + } + + @override + void dispose() { + _callTimer?.cancel(); + _callStateSubscription?.cancel(); + super.dispose(); + } + + void _listenToCallState() { + _callStateSubscription = _callService.callStateStream.listen((state) { + final eventTime = DateTime.now().millisecondsSinceEpoch; + print('CallPage: [${eventTime}ms] Call state changed to $state'); + if (mounted) { + setState(() { + if (state == "active") { + _callStatus = "00:00"; + _startCallTimer(); + } else if (state == "disconnected" || state == "disconnecting") { + _callTimer?.cancel(); + _callStatus = "Call Ended"; + } else { + _callStatus = "Calling..."; + } + }); + } + }); + } + + void _startCallTimer() { + _callTimer?.cancel(); + _callTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (mounted) { + setState(() { + _callSeconds++; + final minutes = (_callSeconds ~/ 60).toString().padLeft(2, '0'); + final seconds = (_callSeconds % 60).toString().padLeft(2, '0'); + _callStatus = '$minutes:$seconds'; + print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Timer updated, duration: $_callStatus'); + }); + } + }); + } void _addDigit(String digit) { setState(() { @@ -61,16 +114,17 @@ class _CallPageState extends State { void _hangUp() async { try { + final hangUpStart = DateTime.now().millisecondsSinceEpoch; + print('CallPage: [${hangUpStart}ms] Initiating hangUp'); final result = await _callService.hangUpCall(context); - print('CallPage: Hang up result: $result'); - if (result["status"] == "ended" && mounted && Navigator.canPop(context)) { - Navigator.pop(context); - } + print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Hang up result: $result'); } catch (e) { - print("CallPage: Error hanging up: $e"); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Error hanging up: $e")), - ); + print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Error hanging up: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Error hanging up: $e")), + ); + } } } @@ -80,6 +134,7 @@ class _CallPageState extends State { final double nameFontSize = isKeypadVisible ? 24.0 : 24.0; final double statusFontSize = isKeypadVisible ? 16.0 : 16.0; + print('CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}'); return Scaffold( body: Container( color: Colors.black, @@ -91,7 +146,7 @@ class _CallPageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 35), + const SizedBox(height: 35), ObfuscatedAvatar( imageBytes: widget.thumbnail, radius: avatarRadius, @@ -134,7 +189,7 @@ class _CallPageState extends State { fontSize: statusFontSize, color: Colors.white70), ), Text( - 'Calling...', + _callStatus, style: TextStyle( fontSize: statusFontSize, color: Colors.white70), ), @@ -167,8 +222,7 @@ class _CallPageState extends State { IconButton( padding: EdgeInsets.zero, onPressed: _toggleKeypad, - icon: - const Icon(Icons.close, color: Colors.white), + icon: const Icon(Icons.close, color: Colors.white), ), ], ), @@ -247,8 +301,11 @@ class _CallPageState extends State { children: [ IconButton( onPressed: _toggleKeypad, - icon: const Icon(Icons.dialpad, - color: Colors.white, size: 32), + icon: const Icon( + Icons.dialpad, + color: Colors.white, + size: 32, + ), ), const Text( 'Keypad', @@ -290,12 +347,17 @@ class _CallPageState extends State { children: [ IconButton( onPressed: () {}, - icon: const Icon(Icons.person_add, - color: Colors.white, size: 32), + icon: const Icon( + Icons.person_add, + color: Colors.white, + size: 32, + ), + ), + const Text( + 'Add Contact', + style: TextStyle( + color: Colors.white, fontSize: 14), ), - const Text('Add Contact', - style: TextStyle( - color: Colors.white, fontSize: 14)), ], ), Column( @@ -303,12 +365,17 @@ class _CallPageState extends State { children: [ IconButton( onPressed: () {}, - icon: const Icon(Icons.sim_card, - color: Colors.white, size: 32), + icon: const Icon( + Icons.sim_card, + color: Colors.white, + size: 32, + ), + ), + const Text( + 'Change SIM', + style: TextStyle( + color: Colors.white, fontSize: 14), ), - const Text('Change SIM', - style: TextStyle( - color: Colors.white, fontSize: 14)), ], ), ], @@ -345,4 +412,4 @@ class _CallPageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 26307be..b20b048 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../features/call/call_page.dart'; @@ -10,71 +11,163 @@ class CallService { static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; - static String? _currentCallState; static Map? _pendingCall; static bool wasPhoneLocked = false; + static String? _activeCallNumber; + static bool _isNavigating = false; final ContactService _contactService = ContactService(); + final _callStateController = StreamController.broadcast(); static final GlobalKey navigatorKey = GlobalKey(); + Stream get callStateStream => _callStateController.stream; + CallService() { _channel.setMethodCallHandler((call) async { - print('CallService: Received method ${call.method} with args ${call.arguments}'); + final eventTime = DateTime.now().millisecondsSinceEpoch; + final disconnectCause = call.arguments["disconnectCause"] ?? "none"; + print('CallService: [${eventTime}ms] Handling method call: ${call.method} with args: ${call.arguments}, disconnectCause: $disconnectCause'); switch (call.method) { case "callAdded": - final phoneNumber = call.arguments["callId"] as String; - final state = call.arguments["state"] as String; - currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); - await _fetchContactInfo(currentPhoneNumber!); - print('CallService: Call added, number: $currentPhoneNumber, state: $state'); - _handleCallState(state); + final phoneNumber = call.arguments["callId"] as String?; + final state = call.arguments["state"] as String?; + if (phoneNumber == null || state == null) { + print('CallService: [${eventTime}ms] Invalid callAdded args: $call.arguments'); + return; + } + final decodedPhoneNumber = Uri.decodeComponent(phoneNumber.replaceFirst('tel:', '')); + print('CallService: [${eventTime}ms] Decoded phone number: $decodedPhoneNumber'); + if (_activeCallNumber != decodedPhoneNumber) { + currentPhoneNumber = decodedPhoneNumber; + if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { + await _fetchContactInfo(decodedPhoneNumber); + } + } + print('CallService: [${eventTime}ms] Call added, number: $currentPhoneNumber, displayName: $currentDisplayName, state: $state'); + _callStateController.add(state); + if (state == "ringing") { + _handleIncomingCall(decodedPhoneNumber); + } else { + _navigateToCallPage(); + } break; case "callStateChanged": - final state = call.arguments["state"] as String; + final state = call.arguments["state"] as String?; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); - _handleCallState(state); + if (state == null) { + print('CallService: [${eventTime}ms] Invalid callStateChanged args: $call.arguments'); + return; + } + print('CallService: [${eventTime}ms] State changed to $state, wasPhoneLocked: $wasPhoneLocked, disconnectCause: $disconnectCause'); + _callStateController.add(state); + if (state == "disconnected" || state == "disconnecting") { + final closeStart = DateTime.now().millisecondsSinceEpoch; + print('CallService: [${closeStart}ms] Initiating closeCallPage for state: $state'); + _closeCallPage(); + print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); + if (wasPhoneLocked) { + await _channel.invokeMethod("callEndedFromFlutter"); + } + _activeCallNumber = null; + } else if (state == "active" || state == "dialing") { + final phoneNumber = call.arguments["callId"] as String?; + if (phoneNumber != null && _activeCallNumber != Uri.decodeComponent(phoneNumber.replaceFirst('tel:', ''))) { + currentPhoneNumber = Uri.decodeComponent(phoneNumber.replaceFirst('tel:', '')); + if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { + await _fetchContactInfo(currentPhoneNumber!); + } + } else if (currentPhoneNumber != null && _activeCallNumber != currentPhoneNumber) { + if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { + await _fetchContactInfo(currentPhoneNumber!); + } + } else { + print('CallService: [${eventTime}ms] Skipping fetch, active call: $_activeCallNumber, current: $currentPhoneNumber, displayName: $currentDisplayName'); + } + _navigateToCallPage(); + } else if (state == "ringing") { + final phoneNumber = call.arguments["callId"] as String?; + if (phoneNumber == null) { + print('CallService: [${eventTime}ms] Invalid ringing callId: $call.arguments'); + return; + } + final decodedPhoneNumber = Uri.decodeComponent(phoneNumber.replaceFirst('tel:', '')); + if (_activeCallNumber != decodedPhoneNumber) { + currentPhoneNumber = decodedPhoneNumber; + if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { + await _fetchContactInfo(decodedPhoneNumber); + } + } + _handleIncomingCall(decodedPhoneNumber); + } break; case "callEnded": case "callRemoved": wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); + print('CallService: [${eventTime}ms] Call ended/removed, wasPhoneLocked: $wasPhoneLocked, disconnectCause: $disconnectCause'); + _callStateController.add("disconnected"); + final closeStart = DateTime.now().millisecondsSinceEpoch; + print('CallService: [${closeStart}ms] Initiating closeCallPage for callEnded/callRemoved'); _closeCallPage(); + print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); if (wasPhoneLocked) { - _channel.invokeMethod("callEndedFromFlutter"); + await _channel.invokeMethod("callEndedFromFlutter"); } currentPhoneNumber = null; currentDisplayName = null; currentThumbnail = null; - _currentCallState = null; + _activeCallNumber = null; break; case "incomingCallFromNotification": - final phoneNumber = call.arguments["phoneNumber"] as String; + final phoneNumber = call.arguments["phoneNumber"] as String?; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - currentPhoneNumber = phoneNumber; - await _fetchContactInfo(currentPhoneNumber!); - print('CallService: Incoming call from notification: $phoneNumber, wasPhoneLocked: $wasPhoneLocked'); - _handleIncomingCall(phoneNumber); + if (phoneNumber == null) { + print('CallService: [${eventTime}ms] Invalid incomingCallFromNotification args: $call.arguments'); + return; + } + final decodedPhoneNumber = Uri.decodeComponent(phoneNumber); + if (_activeCallNumber != decodedPhoneNumber) { + currentPhoneNumber = decodedPhoneNumber; + if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { + await _fetchContactInfo(decodedPhoneNumber); + } + } + print('CallService: [${eventTime}ms] Incoming call from notification: $decodedPhoneNumber, displayName: $currentDisplayName, wasPhoneLocked: $wasPhoneLocked'); + _handleIncomingCall(decodedPhoneNumber); + break; + case "audioStateChanged": + final route = call.arguments["route"] as int?; + print('CallService: [${eventTime}ms] Audio state changed, route: $route'); break; } }); } + void dispose() { + _callStateController.close(); + } + Future _fetchContactInfo(String phoneNumber) async { try { + print('CallService: Fetching contact info for $phoneNumber'); final contacts = await _contactService.fetchContacts(); + print('CallService: Retrieved ${contacts.length} contacts'); final normalizedPhoneNumber = _normalizePhoneNumber(phoneNumber); + print('CallService: Normalized phone number: $normalizedPhoneNumber'); for (var contact in contacts) { for (var phone in contact.phones) { - if (_normalizePhoneNumber(phone.number) == normalizedPhoneNumber) { + final normalizedContactNumber = _normalizePhoneNumber(phone.number); + print('CallService: Comparing $normalizedPhoneNumber with contact number $normalizedContactNumber'); + if (normalizedContactNumber == normalizedPhoneNumber) { currentDisplayName = contact.displayName; currentThumbnail = contact.thumbnail; + print('CallService: Found contact - displayName: $currentDisplayName, thumbnail: ${currentThumbnail != null ? "present" : "null"}'); return; } } } currentDisplayName = phoneNumber; currentThumbnail = null; + print('CallService: No contact match, using phoneNumber as displayName: $currentDisplayName'); } catch (e) { print('CallService: Error fetching contact info: $e'); currentDisplayName = phoneNumber; @@ -83,10 +176,16 @@ class CallService { } String _normalizePhoneNumber(String number) { - return number.replaceAll(RegExp(r'[\s\-\(\)]'), ''); + return number.replaceAll(RegExp(r'[\s\-\(\)]'), '').replaceFirst(RegExp(r'^\+'), ''); } void _handleIncomingCall(String phoneNumber) { + if (_activeCallNumber == phoneNumber && _isCallPageVisible) { + print('CallService: Incoming call for $phoneNumber already active, skipping'); + return; + } + _activeCallNumber = phoneNumber; + final context = navigatorKey.currentContext; if (context == null) { print('CallService: Context is null, queuing incoming call: $phoneNumber'); @@ -97,53 +196,64 @@ class CallService { } } - void _checkPendingCall() { - if (_pendingCall != null) { - final context = navigatorKey.currentContext; - if (context != null) { - print('CallService: Processing queued call: ${_pendingCall!["phoneNumber"]}'); - currentPhoneNumber = _pendingCall!["phoneNumber"]; - _navigateToIncomingCallPage(context); - _pendingCall = null; - } else { - print('CallService: Context still null, retrying...'); - Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); - } + Future _checkPendingCall() async { + if (_pendingCall == null) { + print('CallService: No pending call to process'); + return; + } + + final phoneNumber = _pendingCall!["phoneNumber"]; + if (_activeCallNumber == phoneNumber && _isCallPageVisible) { + print('CallService: Pending call for $phoneNumber already active, clearing'); + _pendingCall = null; + return; + } + + final context = navigatorKey.currentContext; + if (context != null) { + print('CallService: Processing queued call: $phoneNumber'); + currentPhoneNumber = phoneNumber; + _activeCallNumber = phoneNumber; + await _fetchContactInfo(phoneNumber); + _navigateToIncomingCallPage(context); + _pendingCall = null; + } else { + print('CallService: Context still null, retrying...'); + Future.delayed(Duration(milliseconds: 500), () => _checkPendingCall()); } } - void _handleCallState(String state) { + void _navigateToCallPage() { + if (_isNavigating) { + print('CallService: Navigation already in progress, skipping'); + return; + } + _isNavigating = true; + final context = navigatorKey.currentContext; if (context == null) { - print('CallService: Navigator context is null, cannot navigate'); + print('CallService: Cannot navigate to CallPage, context is null'); + _isNavigating = false; return; } - if (_currentCallState == state) { - print('CallService: State $state already handled, skipping'); + final currentRoute = ModalRoute.of(context)?.settings.name ?? 'unknown'; + print('CallService: Navigating to CallPage, Visible: $_isCallPageVisible, Current Route: $currentRoute, Active Call: $_activeCallNumber, DisplayName: $currentDisplayName'); + if (_isCallPageVisible && currentRoute == '/call' && _activeCallNumber == currentPhoneNumber) { + print('CallService: CallPage already visible for $_activeCallNumber, skipping navigation'); + _isNavigating = false; return; } - _currentCallState = state; - - if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(); - } else if (state == "active" || state == "dialing") { - _navigateToCallPage(context); - } else if (state == "ringing") { - _navigateToIncomingCallPage(context); - } - } - - void _navigateToCallPage(BuildContext context) { - final currentRoute = ModalRoute.of(context)?.settings.name; - print('CallService: Navigating to CallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); - if (_isCallPageVisible && currentRoute == '/call') { - print('CallService: CallPage already visible, skipping navigation'); - return; - } - if (_isCallPageVisible && currentRoute == '/incoming_call') { - print('CallService: Replacing IncomingCallPage with CallPage'); + if (_isCallPageVisible && currentRoute == '/incoming_call' && _activeCallNumber == currentPhoneNumber) { + print('CallService: Popping IncomingCallPage before navigating to CallPage'); Navigator.pop(context); + _isCallPageVisible = false; } + if (currentPhoneNumber == null) { + print('CallService: Cannot navigate to CallPage, currentPhoneNumber is null'); + _isNavigating = false; + return; + } + _activeCallNumber = currentPhoneNumber; Navigator.pushReplacement( context, MaterialPageRoute( @@ -155,21 +265,35 @@ class CallService { ), ), ).then((_) { - print('CallService: CallPage popped'); _isCallPageVisible = false; + _isNavigating = false; + print('CallService: CallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { - final currentRoute = ModalRoute.of(context)?.settings.name; - print('CallService: Navigating to IncomingCallPage. Visible: $_isCallPageVisible, Current Route: $currentRoute'); - if (_isCallPageVisible && currentRoute == '/incoming_call') { - print('CallService: IncomingCallPage already visible, skipping navigation'); + if (_isNavigating) { + print('CallService: Navigation already in progress, skipping'); + return; + } + _isNavigating = true; + + final currentRoute = ModalRoute.of(context)?.settings.name ?? 'unknown'; + print('CallService: Navigating to IncomingCallPage, Visible: $_isCallPageVisible, Current Route: $currentRoute, Active Call: $_activeCallNumber, DisplayName: $currentDisplayName'); + if (_isCallPageVisible && currentRoute == '/incoming_call' && _activeCallNumber == currentPhoneNumber) { + print('CallService: IncomingCallPage already visible for $_activeCallNumber, skipping navigation'); + _isNavigating = false; return; } if (_isCallPageVisible && currentRoute == '/call') { print('CallService: CallPage visible, not showing IncomingCallPage'); + _isNavigating = false; + return; + } + if (currentPhoneNumber == null) { + print('CallService: Cannot navigate to IncomingCallPage, currentPhoneNumber is null'); + _isNavigating = false; return; } Navigator.push( @@ -183,8 +307,9 @@ class CallService { ), ), ).then((_) { - print('CallService: IncomingCallPage popped'); _isCallPageVisible = false; + _isNavigating = false; + print('CallService: IncomingCallPage popped, _isCallPageVisible set to false'); }); _isCallPageVisible = true; } @@ -195,70 +320,79 @@ class CallService { print('CallService: Cannot close page, context is null'); return; } - print('CallService: Attempting to close call page. Visible: $_isCallPageVisible'); - if (!_isCallPageVisible) { - print('CallService: CallPage not visible, skipping pop'); - return; - } - if (Navigator.canPop(context)) { - print('CallService: Popping CallPage. Current Route: ${ModalRoute.of(context)?.settings.name}'); + final currentRoute = ModalRoute.of(context)?.settings.name ?? 'unknown'; + print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible, Current Route: $currentRoute'); + if (_isCallPageVisible && (currentRoute == '/call' || currentRoute == '/incoming_call')) { + print('CallService: Popping call page'); Navigator.pop(context); - _isCallPageVisible = false; } else { - print('CallService: Cannot pop, no routes to pop'); + print('CallService: No call page to pop, _isCallPageVisible: $_isCallPageVisible, Current Route: $currentRoute'); } + _isCallPageVisible = false; + _activeCallNumber = null; } - Future makeGsmCall( + Future> makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, Uint8List? thumbnail, }) async { try { + if (_activeCallNumber == phoneNumber && _isCallPageVisible) { + print('CallService: Call already active for $phoneNumber, skipping'); + return {"status": "already_active", "message": "Call already in progress"}; + } currentPhoneNumber = phoneNumber; currentDisplayName = displayName ?? phoneNumber; currentThumbnail = thumbnail; if (displayName == null || thumbnail == null) { await _fetchContactInfo(phoneNumber); } - print('CallService: Making GSM call to $phoneNumber'); + print('CallService: Making GSM call to $phoneNumber, displayName: $currentDisplayName'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); - if (result["status"] != "calling") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); - return; } - _handleCallState("dialing"); + return resultMap; } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); - rethrow; + return {"status": "error", "message": e.toString()}; } } - Future hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); - if (result["status"] != "ended") { + final resultMap = Map.from(result as Map); + if (resultMap["status"] == "ended") { + final closeStart = DateTime.now().millisecondsSinceEpoch; + print('CallService: [${closeStart}ms] Initiating closeCallPage for hangUpCall'); + _closeCallPage(); + print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); + } else { + print('CallService: Hang up failed, status: ${resultMap["status"]}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); - } else { - _closeCallPage(); } + return resultMap; } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - rethrow; + _closeCallPage(); + return {"status": "error", "message": e.toString()}; } } } \ No newline at end of file -- 2.45.2 From 5bb64d98b8edf3ed42439f6fcc0d8a5b4c509cd8 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Fri, 18 Apr 2025 23:05:55 +0300 Subject: [PATCH 15/16] feat: contact info in ongoing and incoming call --- dialer/lib/services/call_service.dart | 58 ++++++++++----------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index b20b048..234d850 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -8,7 +8,7 @@ import '../services/contact_service.dart'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; - static String? currentDisplayName; + static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; static Map? _pendingCall; @@ -19,31 +19,29 @@ class CallService { final _callStateController = StreamController.broadcast(); static final GlobalKey navigatorKey = GlobalKey(); - + Stream get callStateStream => _callStateController.stream; CallService() { _channel.setMethodCallHandler((call) async { - final eventTime = DateTime.now().millisecondsSinceEpoch; - final disconnectCause = call.arguments["disconnectCause"] ?? "none"; - print('CallService: [${eventTime}ms] Handling method call: ${call.method} with args: ${call.arguments}, disconnectCause: $disconnectCause'); + print('CallService: Handling method call: ${call.method}, with args: ${call.arguments}'); switch (call.method) { case "callAdded": final phoneNumber = call.arguments["callId"] as String?; final state = call.arguments["state"] as String?; if (phoneNumber == null || state == null) { - print('CallService: [${eventTime}ms] Invalid callAdded args: $call.arguments'); + print('CallService: Invalid callAdded args: $call.arguments'); return; } final decodedPhoneNumber = Uri.decodeComponent(phoneNumber.replaceFirst('tel:', '')); - print('CallService: [${eventTime}ms] Decoded phone number: $decodedPhoneNumber'); + print('CallService: Decoded phone number: $decodedPhoneNumber'); if (_activeCallNumber != decodedPhoneNumber) { currentPhoneNumber = decodedPhoneNumber; if (currentDisplayName == null || currentDisplayName == currentPhoneNumber) { await _fetchContactInfo(decodedPhoneNumber); } } - print('CallService: [${eventTime}ms] Call added, number: $currentPhoneNumber, displayName: $currentDisplayName, state: $state'); + print('CallService: Call added, number: $currentPhoneNumber, displayName: $currentDisplayName, state: $state'); _callStateController.add(state); if (state == "ringing") { _handleIncomingCall(decodedPhoneNumber); @@ -55,16 +53,13 @@ class CallService { final state = call.arguments["state"] as String?; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; if (state == null) { - print('CallService: [${eventTime}ms] Invalid callStateChanged args: $call.arguments'); + print('CallService: Invalid callStateChanged args: $call.arguments'); return; } - print('CallService: [${eventTime}ms] State changed to $state, wasPhoneLocked: $wasPhoneLocked, disconnectCause: $disconnectCause'); + print('CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); _callStateController.add(state); if (state == "disconnected" || state == "disconnecting") { - final closeStart = DateTime.now().millisecondsSinceEpoch; - print('CallService: [${closeStart}ms] Initiating closeCallPage for state: $state'); _closeCallPage(); - print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); if (wasPhoneLocked) { await _channel.invokeMethod("callEndedFromFlutter"); } @@ -81,13 +76,13 @@ class CallService { await _fetchContactInfo(currentPhoneNumber!); } } else { - print('CallService: [${eventTime}ms] Skipping fetch, active call: $_activeCallNumber, current: $currentPhoneNumber, displayName: $currentDisplayName'); + print('CallService: Skipping fetch, active call: $_activeCallNumber, current: $currentPhoneNumber, displayName: $currentDisplayName'); } _navigateToCallPage(); } else if (state == "ringing") { final phoneNumber = call.arguments["callId"] as String?; if (phoneNumber == null) { - print('CallService: [${eventTime}ms] Invalid ringing callId: $call.arguments'); + print('CallService: Invalid ringing callId: $call.arguments'); return; } final decodedPhoneNumber = Uri.decodeComponent(phoneNumber.replaceFirst('tel:', '')); @@ -103,12 +98,8 @@ class CallService { case "callEnded": case "callRemoved": wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; - print('CallService: [${eventTime}ms] Call ended/removed, wasPhoneLocked: $wasPhoneLocked, disconnectCause: $disconnectCause'); - _callStateController.add("disconnected"); - final closeStart = DateTime.now().millisecondsSinceEpoch; - print('CallService: [${closeStart}ms] Initiating closeCallPage for callEnded/callRemoved'); + print('CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); _closeCallPage(); - print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); if (wasPhoneLocked) { await _channel.invokeMethod("callEndedFromFlutter"); } @@ -121,7 +112,7 @@ class CallService { final phoneNumber = call.arguments["phoneNumber"] as String?; wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; if (phoneNumber == null) { - print('CallService: [${eventTime}ms] Invalid incomingCallFromNotification args: $call.arguments'); + print('CallService: Invalid incomingCallFromNotification args: $call.arguments'); return; } final decodedPhoneNumber = Uri.decodeComponent(phoneNumber); @@ -131,12 +122,12 @@ class CallService { await _fetchContactInfo(decodedPhoneNumber); } } - print('CallService: [${eventTime}ms] Incoming call from notification: $decodedPhoneNumber, displayName: $currentDisplayName, wasPhoneLocked: $wasPhoneLocked'); + print('CallService: Incoming call from notification: $decodedPhoneNumber, displayName: $currentDisplayName, wasPhoneLocked: $wasPhoneLocked'); _handleIncomingCall(decodedPhoneNumber); break; case "audioStateChanged": final route = call.arguments["route"] as int?; - print('CallService: [${eventTime}ms] Audio state changed, route: $route'); + print('CallService: Audio state changed, route: $route'); break; } }); @@ -320,18 +311,18 @@ class CallService { print('CallService: Cannot close page, context is null'); return; } - final currentRoute = ModalRoute.of(context)?.settings.name ?? 'unknown'; - print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible, Current Route: $currentRoute'); - if (_isCallPageVisible && (currentRoute == '/call' || currentRoute == '/incoming_call')) { + print('CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); + if (Navigator.canPop(context)) { print('CallService: Popping call page'); Navigator.pop(context); + _isCallPageVisible = false; } else { - print('CallService: No call page to pop, _isCallPageVisible: $_isCallPageVisible, Current Route: $currentRoute'); + print('CallService: No page to pop'); } - _isCallPageVisible = false; _activeCallNumber = null; } + Future> makeGsmCall( BuildContext context, { required String phoneNumber, @@ -368,19 +359,13 @@ class CallService { } } - Future> hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); final resultMap = Map.from(result as Map); - if (resultMap["status"] == "ended") { - final closeStart = DateTime.now().millisecondsSinceEpoch; - print('CallService: [${closeStart}ms] Initiating closeCallPage for hangUpCall'); - _closeCallPage(); - print('CallService: [${DateTime.now().millisecondsSinceEpoch}ms] closeCallPage completed'); - } else { - print('CallService: Hang up failed, status: ${resultMap["status"]}'); + if (resultMap["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); @@ -391,7 +376,6 @@ class CallService { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); - _closeCallPage(); return {"status": "error", "message": e.toString()}; } } -- 2.45.2 From 7339d5982ef2b39a9b17cdcc3b7529ec140fd5cc Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Sun, 20 Apr 2025 00:53:28 +0300 Subject: [PATCH 16/16] fix: timer in call page after incoming call --- .../icing/dialer/activities/MainActivity.kt | 12 +++++++++ dialer/lib/features/call/call_page.dart | 27 ++++++++++++++----- dialer/lib/services/call_service.dart | 16 ++++++++--- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt b/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt index 5f5b29a..eb362de 100644 --- a/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt +++ b/dialer/android/app/src/main/kotlin/com/icing/dialer/activities/MainActivity.kt @@ -153,6 +153,18 @@ class MainActivity : FlutterActivity() { } result.success(true) } + "getCallState" -> { + val stateStr = when (MyInCallService.currentCall?.state) { + android.telecom.Call.STATE_ACTIVE -> "active" + android.telecom.Call.STATE_RINGING -> "ringing" + android.telecom.Call.STATE_DIALING -> "dialing" + android.telecom.Call.STATE_DISCONNECTED -> "disconnected" + android.telecom.Call.STATE_DISCONNECTING -> "disconnecting" + else -> "unknown" + } + Log.d(TAG, "getCallState called, returning: $stateStr") + result.success(stateStr) + } else -> result.notImplemented() } } diff --git a/dialer/lib/features/call/call_page.dart b/dialer/lib/features/call/call_page.dart index b6dbce5..dbbea12 100644 --- a/dialer/lib/features/call/call_page.dart +++ b/dialer/lib/features/call/call_page.dart @@ -37,6 +37,7 @@ class _CallPageState extends State { @override void initState() { super.initState(); + _checkInitialCallState(); _listenToCallState(); } @@ -47,10 +48,24 @@ class _CallPageState extends State { super.dispose(); } + void _checkInitialCallState() async { + try { + final state = await _callService.getCallState(); + print('CallPage: Initial call state: $state'); + if (mounted && state == "active") { + setState(() { + _callStatus = "00:00"; + _startCallTimer(); + }); + } + } catch (e) { + print('CallPage: Error checking initial state: $e'); + } + } + void _listenToCallState() { _callStateSubscription = _callService.callStateStream.listen((state) { - final eventTime = DateTime.now().millisecondsSinceEpoch; - print('CallPage: [${eventTime}ms] Call state changed to $state'); + print('CallPage: Call state changed to $state'); if (mounted) { setState(() { if (state == "active") { @@ -76,7 +91,6 @@ class _CallPageState extends State { final minutes = (_callSeconds ~/ 60).toString().padLeft(2, '0'); final seconds = (_callSeconds % 60).toString().padLeft(2, '0'); _callStatus = '$minutes:$seconds'; - print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Timer updated, duration: $_callStatus'); }); } }); @@ -114,12 +128,11 @@ class _CallPageState extends State { void _hangUp() async { try { - final hangUpStart = DateTime.now().millisecondsSinceEpoch; - print('CallPage: [${hangUpStart}ms] Initiating hangUp'); + print('CallPage: Initiating hangUp'); final result = await _callService.hangUpCall(context); - print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Hang up result: $result'); + print('CallPage: Hang up result: $result'); } catch (e) { - print('CallPage: [${DateTime.now().millisecondsSinceEpoch}ms] Error hanging up: $e'); + print('CallPage: Error hanging up: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up: $e")), diff --git a/dialer/lib/services/call_service.dart b/dialer/lib/services/call_service.dart index 234d850..821608f 100644 --- a/dialer/lib/services/call_service.dart +++ b/dialer/lib/services/call_service.dart @@ -8,7 +8,7 @@ import '../services/contact_service.dart'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; - static String? currentDisplayName; + static String? currentDisplayName; static Uint8List? currentThumbnail; static bool _isCallPageVisible = false; static Map? _pendingCall; @@ -133,6 +133,17 @@ class CallService { }); } + Future getCallState() async { + try { + final state = await _channel.invokeMethod('getCallState'); + print('CallService: getCallState returned: $state'); + return state as String?; + } catch (e) { + print('CallService: Error getting call state: $e'); + return null; + } + } + void dispose() { _callStateController.close(); } @@ -322,7 +333,6 @@ class CallService { _activeCallNumber = null; } - Future> makeGsmCall( BuildContext context, { required String phoneNumber, @@ -359,7 +369,7 @@ class CallService { } } - Future> hangUpCall(BuildContext context) async { + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); -- 2.45.2