feat: implement pending SIM switch handling and improve call state management
This commit is contained in:
parent
babe733dff
commit
4eff7b503c
@ -73,11 +73,16 @@ class CallService {
|
|||||||
'CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked');
|
'CallService: State changed to $state, wasPhoneLocked: $wasPhoneLocked');
|
||||||
_callStateController.add(state);
|
_callStateController.add(state);
|
||||||
if (state == "disconnected" || state == "disconnecting") {
|
if (state == "disconnected" || state == "disconnecting") {
|
||||||
_closeCallPage();
|
// Only close call page if there's no pending SIM switch
|
||||||
|
if (_pendingSimSwitch == null) {
|
||||||
|
_closeCallPage();
|
||||||
|
}
|
||||||
if (wasPhoneLocked) {
|
if (wasPhoneLocked) {
|
||||||
await _channel.invokeMethod("callEndedFromFlutter");
|
await _channel.invokeMethod("callEndedFromFlutter");
|
||||||
}
|
}
|
||||||
_activeCallNumber = null;
|
_activeCallNumber = null;
|
||||||
|
// Handle pending SIM switch after call is disconnected
|
||||||
|
_handlePendingSimSwitch();
|
||||||
} else if (state == "active" || state == "dialing") {
|
} else if (state == "active" || state == "dialing") {
|
||||||
final phoneNumber = call.arguments["callId"] as String?;
|
final phoneNumber = call.arguments["callId"] as String?;
|
||||||
if (phoneNumber != null &&
|
if (phoneNumber != null &&
|
||||||
@ -123,7 +128,10 @@ class CallService {
|
|||||||
wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false;
|
wasPhoneLocked = call.arguments["wasPhoneLocked"] as bool? ?? false;
|
||||||
print(
|
print(
|
||||||
'CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked');
|
'CallService: Call ended/removed, wasPhoneLocked: $wasPhoneLocked');
|
||||||
_closeCallPage();
|
// Only close call page if there's no pending SIM switch
|
||||||
|
if (_pendingSimSwitch == null) {
|
||||||
|
_closeCallPage();
|
||||||
|
}
|
||||||
if (wasPhoneLocked) {
|
if (wasPhoneLocked) {
|
||||||
await _channel.invokeMethod("callEndedFromFlutter");
|
await _channel.invokeMethod("callEndedFromFlutter");
|
||||||
}
|
}
|
||||||
@ -421,6 +429,13 @@ class CallService {
|
|||||||
print('CallService: Cannot close page, context is null');
|
print('CallService: Cannot close page, context is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only attempt to close if a call page is actually visible
|
||||||
|
if (!_isCallPageVisible) {
|
||||||
|
print('CallService: Call page already closed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
print(
|
print(
|
||||||
'CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible');
|
'CallService: Closing call page, _isCallPageVisible: $_isCallPageVisible');
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
@ -428,7 +443,8 @@ class CallService {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_isCallPageVisible = false;
|
_isCallPageVisible = false;
|
||||||
} else {
|
} else {
|
||||||
print('CallService: No page to pop');
|
print('CallService: No page to pop, setting _isCallPageVisible to false');
|
||||||
|
_isCallPageVisible = false;
|
||||||
}
|
}
|
||||||
_activeCallNumber = null;
|
_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 {
|
Future<Map<String, dynamic>> hangUpCall(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
print('CallService: Hanging up call');
|
print('CallService: Hanging up call');
|
||||||
@ -521,4 +543,86 @@ class CallService {
|
|||||||
return {"status": "error", "message": e.toString()};
|
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...";
|
String _callStatus = "Calling...";
|
||||||
StreamSubscription<String>? _callStateSubscription;
|
StreamSubscription<String>? _callStateSubscription;
|
||||||
StreamSubscription<Map<String, dynamic>>? _audioStateSubscription;
|
StreamSubscription<Map<String, dynamic>>? _audioStateSubscription;
|
||||||
|
bool _isCallActive = true; // Track if call is still active
|
||||||
|
|
||||||
bool get isNumberUnknown => widget.displayName == widget.phoneNumber;
|
bool get isNumberUnknown => widget.displayName == widget.phoneNumber;
|
||||||
|
|
||||||
@ -70,10 +71,19 @@ class _CallPageState extends State<CallPage> {
|
|||||||
try {
|
try {
|
||||||
final state = await _callService.getCallState();
|
final state = await _callService.getCallState();
|
||||||
print('CallPage: Initial call state: $state');
|
print('CallPage: Initial call state: $state');
|
||||||
if (mounted && state == "active") {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_callStatus = "00:00";
|
if (state == "active") {
|
||||||
_startCallTimer();
|
_callStatus = "00:00";
|
||||||
|
_isCallActive = true;
|
||||||
|
_startCallTimer();
|
||||||
|
} else if (state == "disconnected" || state == "disconnecting") {
|
||||||
|
_callStatus = "Call Ended";
|
||||||
|
_isCallActive = false;
|
||||||
|
} else {
|
||||||
|
_callStatus = "Calling...";
|
||||||
|
_isCallActive = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -88,12 +98,23 @@ class _CallPageState extends State<CallPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
if (state == "active") {
|
if (state == "active") {
|
||||||
_callStatus = "00:00";
|
_callStatus = "00:00";
|
||||||
|
_isCallActive = true;
|
||||||
_startCallTimer();
|
_startCallTimer();
|
||||||
} else if (state == "disconnected" || state == "disconnecting") {
|
} else if (state == "disconnected" || state == "disconnecting") {
|
||||||
_callTimer?.cancel();
|
_callTimer?.cancel();
|
||||||
_callStatus = "Call Ended";
|
_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 {
|
} else {
|
||||||
_callStatus = "Calling...";
|
_callStatus = "Calling...";
|
||||||
|
_isCallActive = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -219,76 +240,39 @@ class _CallPageState extends State<CallPage> {
|
|||||||
void _switchToNewSim(int simSlot) async {
|
void _switchToNewSim(int simSlot) async {
|
||||||
try {
|
try {
|
||||||
print(
|
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
|
// Use the CallService to handle the SIM switch logic
|
||||||
if (!mounted) {
|
await _callService.switchSimAndRedial(
|
||||||
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,
|
|
||||||
phoneNumber: widget.phoneNumber,
|
phoneNumber: widget.phoneNumber,
|
||||||
displayName: widget.displayName,
|
displayName: widget.displayName,
|
||||||
thumbnail: widget.thumbnail,
|
|
||||||
simSlot: simSlot,
|
simSlot: simSlot,
|
||||||
|
thumbnail: widget.thumbnail,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if widget is still mounted before showing snackbar
|
print('CallPage: SIM switch initiated successfully');
|
||||||
if (!mounted) {
|
} catch (e) {
|
||||||
print('CallPage: Widget unmounted, skipping result notification');
|
print('CallPage: Error initiating SIM switch: $e');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result['status'] == 'calling') {
|
// Show error feedback if widget is still mounted
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Switched to SIM ${simSlot + 1} and redialing...'),
|
content: Text('Error switching SIM: $e'),
|
||||||
backgroundColor: Colors.green,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Failed to switch SIM and redial: ${result['message'] ?? 'Unknown error'}'),
|
|
||||||
backgroundColor: Colors.red,
|
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 {
|
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 {
|
try {
|
||||||
print('CallPage: Initiating hangUp');
|
print('CallPage: Initiating hangUp');
|
||||||
final result = await _callService.hangUpCall(context);
|
final result = await _callService.hangUpCall(context);
|
||||||
@ -331,9 +315,9 @@ class _CallPageState extends State<CallPage> {
|
|||||||
print(
|
print(
|
||||||
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
'CallPage: Building UI, _callStatus: $_callStatus, route: ${ModalRoute.of(context)?.settings.name ?? "unknown"}');
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: _callStatus == "Call Ended",
|
canPop: !_isCallActive,
|
||||||
onPopInvoked: (didPop) {
|
onPopInvoked: (didPop) {
|
||||||
if (!didPop) {
|
if (!didPop && _isCallActive) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Cannot leave during an active call')),
|
SnackBar(content: Text('Cannot leave during an active call')),
|
||||||
);
|
);
|
||||||
@ -592,10 +576,14 @@ class _CallPageState extends State<CallPage> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _showSimSelectionDialog,
|
onPressed: _isCallActive
|
||||||
icon: const Icon(
|
? _showSimSelectionDialog
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
Icons.sim_card,
|
Icons.sim_card,
|
||||||
color: Colors.white,
|
color: _isCallActive
|
||||||
|
? Colors.white
|
||||||
|
: Colors.grey,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -621,15 +609,15 @@ class _CallPageState extends State<CallPage> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _hangUp,
|
onTap: _isCallActive ? _hangUp : null,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.red,
|
color: _isCallActive ? Colors.red : Colors.grey,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: Icon(
|
||||||
Icons.call_end,
|
_isCallActive ? Icons.call_end : Icons.call_end,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user