feat: implement pending SIM switch handling and improve call state management
This commit is contained in:
parent
cdd3a470c0
commit
c8ea9204ff
@ -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<String, dynamic>? _pendingSimSwitch;
|
||||
|
||||
// Getter to check if there's a pending SIM switch
|
||||
static bool get hasPendingSimSwitch => _pendingSimSwitch != null;
|
||||
|
||||
Future<Map<String, dynamic>> hangUpCall(BuildContext context) async {
|
||||
try {
|
||||
print('CallService: Hanging up call');
|
||||
@ -521,4 +543,86 @@ class CallService {
|
||||
return {"status": "error", "message": e.toString()};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ class _CallPageState extends State<CallPage> {
|
||||
String _callStatus = "Calling...";
|
||||
StreamSubscription<String>? _callStateSubscription;
|
||||
StreamSubscription<Map<String, dynamic>>? _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<CallPage> {
|
||||
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<CallPage> {
|
||||
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<CallPage> {
|
||||
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<CallPage> {
|
||||
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<CallPage> {
|
||||
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<CallPage> {
|
||||
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,
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user