diff --git a/dialer/lib/domain/services/call_service.dart b/dialer/lib/domain/services/call_service.dart index f3d17b1..4a5b2dd 100644 --- a/dialer/lib/domain/services/call_service.dart +++ b/dialer/lib/domain/services/call_service.dart @@ -73,11 +73,16 @@ class CallService { 'CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked'); _callStateController.add(state); if (state == "disconnected" || state == "disconnecting") { - _closeCallPage(); + // Only close call page if there's no pending SIM switch + if (_pendingSimSwitch == null) { + _closeCallPage(); + } if (wasPhoneLocked) { await _channel.invokeMethod("callEndedFromFlutter"); } _activeCallNumber = null; + // Handle pending SIM switch after call is disconnected + _handlePendingSimSwitch(); } else if (state == "active" || state == "dialing") { final phoneNumber = call.arguments["callId"] as String?; if (phoneNumber != null && @@ -123,7 +128,10 @@ class CallService { wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false; print( 'CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked'); - _closeCallPage(); + // Only close call page if there's no pending SIM switch + if (_pendingSimSwitch == null) { + _closeCallPage(); + } if (wasPhoneLocked) { await _channel.invokeMethod("callEndedFromFlutter"); } @@ -421,6 +429,13 @@ class CallService { print('CallService: Cannot close page, context is null'); return; } + + // Only attempt to close if a call page is actually visible + if (!_isCallPageVisible) { + print('CallService: Call page already closed'); + return; + } + print( 'CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible'); if (Navigator.canPop(context)) { @@ -428,7 +443,8 @@ class CallService { Navigator.pop(context); _isCallPageVisible = false; } else { - print('CallService: No page to pop'); + print('CallService: No page to pop, setting _isCallPageVisible to false'); + _isCallPageVisible = false; } _activeCallNumber = null; } @@ -501,6 +517,12 @@ class CallService { } } + // Pending SIM switch data + static Map? _pendingSimSwitch; + + // Getter to check if there's a pending SIM switch + static bool get hasPendingSimSwitch => _pendingSimSwitch != null; + Future> hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); @@ -521,4 +543,86 @@ class CallService { return {"status": "error", "message": e.toString()}; } } + + Future switchSimAndRedial({ + required String phoneNumber, + required String displayName, + required int simSlot, + Uint8List? thumbnail, + }) async { + try { + print( + 'CallService: Starting SIM switch to slot $simSlot for $phoneNumber'); + + // Store the redial information for after hangup + _pendingSimSwitch = { + 'phoneNumber': phoneNumber, + 'displayName': displayName, + 'simSlot': simSlot, + 'thumbnail': thumbnail, + }; + + // Hang up the current call - this will trigger the disconnected state + await _channel.invokeMethod('hangUpCall'); + print( + 'CallService: Hangup initiated, waiting for disconnection to complete redial'); + } catch (e) { + print('CallService: Error during SIM switch: $e'); + _pendingSimSwitch = null; + rethrow; + } + } + + void _handlePendingSimSwitch() async { + if (_pendingSimSwitch == null) return; + + final switchData = _pendingSimSwitch!; + _pendingSimSwitch = null; + + try { + print('CallService: Executing pending SIM switch redial'); + + // Wait a moment to ensure the previous call is fully disconnected + await Future.delayed(const Duration(milliseconds: 1000)); + + // Store the new call info for the redial + currentPhoneNumber = switchData['phoneNumber']; + currentDisplayName = switchData['displayName']; + currentThumbnail = + switchData['thumbnail']; // Make the new call with the selected SIM + final result = await _channel.invokeMethod('makeGsmCall', { + 'phoneNumber': switchData['phoneNumber'], + 'simSlot': switchData['simSlot'], + }); + + print('CallService: SIM switch redial result: $result'); + + // Show success feedback + final context = navigatorKey.currentContext; + if (context != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Switched to SIM ${switchData['simSlot'] + 1} and redialing...'), + backgroundColor: Colors.green, + ), + ); + } + } catch (e) { + print('CallService: Error during SIM switch redial: $e'); + + // Show error feedback and close the call page + final context = navigatorKey.currentContext; + if (context != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to redial with new SIM: $e'), + backgroundColor: Colors.red, + ), + ); + // Close the call page since redial failed + _closeCallPage(); + } + } + } } diff --git a/dialer/lib/presentation/features/call/call_page.dart b/dialer/lib/presentation/features/call/call_page.dart index 084eae7..885c7e2 100644 --- a/dialer/lib/presentation/features/call/call_page.dart +++ b/dialer/lib/presentation/features/call/call_page.dart @@ -36,6 +36,7 @@ class _CallPageState extends State { String _callStatus = "Calling..."; StreamSubscription? _callStateSubscription; StreamSubscription>? _audioStateSubscription; + bool _isCallActive = true; // Track if call is still active bool get isNumberUnknown => widget.displayName == widget.phoneNumber; @@ -70,10 +71,19 @@ class _CallPageState extends State { try { final state = await _callService.getCallState(); print('CallPage: Initial call state: $state'); - if (mounted && state == "active") { + if (mounted) { setState(() { - _callStatus = "00:00"; - _startCallTimer(); + if (state == "active") { + _callStatus = "00:00"; + _isCallActive = true; + _startCallTimer(); + } else if (state == "disconnected" || state == "disconnecting") { + _callStatus = "Call Ended"; + _isCallActive = false; + } else { + _callStatus = "Calling..."; + _isCallActive = true; + } }); } } catch (e) { @@ -88,12 +98,23 @@ class _CallPageState extends State { setState(() { if (state == "active") { _callStatus = "00:00"; + _isCallActive = true; _startCallTimer(); } else if (state == "disconnected" || state == "disconnecting") { _callTimer?.cancel(); _callStatus = "Call Ended"; + _isCallActive = + false; // Only navigate back if there's no pending SIM switch + if (!CallService.hasPendingSimSwitch) { + Future.delayed(const Duration(seconds: 2), () { + if (mounted && Navigator.canPop(context)) { + Navigator.of(context).pop(); + } + }); + } } else { _callStatus = "Calling..."; + _isCallActive = true; } }); } @@ -219,76 +240,39 @@ class _CallPageState extends State { void _switchToNewSim(int simSlot) async { try { print( - 'CallPage: Switching to SIM slot $simSlot for ${widget.phoneNumber}'); + 'CallPage: Initiating SIM switch to slot $simSlot for ${widget.phoneNumber}'); - // Check if widget is still mounted before starting - if (!mounted) { - print('CallPage: Widget unmounted, canceling SIM switch operation'); - return; - } - - // First hang up the current call - await _callService.hangUpCall(context); - - // Wait a brief moment for the call to end - await Future.delayed(const Duration(milliseconds: 500)); - - // Check if widget is still mounted before proceeding - if (!mounted) { - print('CallPage: Widget unmounted, canceling SIM switch operation'); - return; - } - - // Make a new call with the selected SIM - final result = await _callService.makeGsmCallWithSim( - context, + // Use the CallService to handle the SIM switch logic + await _callService.switchSimAndRedial( phoneNumber: widget.phoneNumber, displayName: widget.displayName, - thumbnail: widget.thumbnail, simSlot: simSlot, + thumbnail: widget.thumbnail, ); - // Check if widget is still mounted before showing snackbar - if (!mounted) { - print('CallPage: Widget unmounted, skipping result notification'); - return; - } + print('CallPage: SIM switch initiated successfully'); + } catch (e) { + print('CallPage: Error initiating SIM switch: $e'); - if (result['status'] == 'calling') { + // Show error feedback if widget is still mounted + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Switched to SIM ${simSlot + 1} and redialing...'), - backgroundColor: Colors.green, - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Failed to switch SIM and redial: ${result['message'] ?? 'Unknown error'}'), + content: Text('Error switching SIM: $e'), backgroundColor: Colors.red, ), ); } - } catch (e) { - print('CallPage: Error switching SIM: $e'); - - // Check if widget is still mounted before showing error snackbar - if (!mounted) { - print('CallPage: Widget unmounted, skipping error notification'); - return; - } - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error switching SIM: $e'), - backgroundColor: Colors.red, - ), - ); } } void _hangUp() async { + // Don't try to hang up if call is already ended + if (!_isCallActive) { + print('CallPage: Ignoring hangup - call already ended'); + return; + } + try { print('CallPage: Initiating hangUp'); final result = await _callService.hangUpCall(context); @@ -331,9 +315,9 @@ class _CallPageState extends State { print( 'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}'); return PopScope( - canPop: _callStatus == "Call Ended", + canPop: !_isCallActive, onPopInvoked: (didPop) { - if (!didPop) { + if (!didPop && _isCallActive) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Cannot leave during an active call')), ); @@ -592,10 +576,14 @@ class _CallPageState extends State { mainAxisSize: MainAxisSize.min, children: [ IconButton( - onPressed: _showSimSelectionDialog, - icon: const Icon( + onPressed: _isCallActive + ? _showSimSelectionDialog + : null, + icon: Icon( Icons.sim_card, - color: Colors.white, + color: _isCallActive + ? Colors.white + : Colors.grey, size: 32, ), ), @@ -621,15 +609,15 @@ class _CallPageState extends State { Padding( padding: const EdgeInsets.only(bottom: 16.0), child: GestureDetector( - onTap: _hangUp, + onTap: _isCallActive ? _hangUp : null, child: Container( padding: const EdgeInsets.all(12), - decoration: const BoxDecoration( - color: Colors.red, + decoration: BoxDecoration( + color: _isCallActive ? Colors.red : Colors.grey, shape: BoxShape.circle, ), - child: const Icon( - Icons.call_end, + child: Icon( + _isCallActive ? Icons.call_end : Icons.call_end, color: Colors.white, size: 32, ),