Skip to content

Commit 9081d1c

Browse files
committed
refactor: unify registration event handling and improve continueStartCallIntent logic during uninitialized state
1 parent 02e2298 commit 9081d1c

5 files changed

Lines changed: 136 additions & 102 deletions

File tree

lib/features/call/bloc/call_bloc.dart

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -218,32 +218,42 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
218218
_logger.info(() => 'status transitions: $currentProcessingStatuses -> $nextProcessingStatuses');
219219
}
220220

221+
/// RegistrationStatus can be not allowed if the signaling state
222+
/// was not yet fully initialized. For this situation we decide allow RegistrationStatus be nullable that indicate signaling was not initialized yet
223+
///
224+
/// This scenario is particularly relevant when a call is triggered before the app
225+
/// is fully active, such as via [CallkeepDelegate.continueStartCallIntent]
226+
/// (e.g., from phone recents).
227+
///
228+
221229
final newRegistration = change.nextState.callServiceState.registration;
222230
final previousRegistration = change.currentState.callServiceState.registration;
231+
223232
if (newRegistration != previousRegistration) {
224233
_logger.fine('_onRegistrationChange: $newRegistration to $previousRegistration');
225-
final newRegistrationStatus = newRegistration.status;
226-
final previousRegistrationStatus = previousRegistration.status;
227234

228-
if (newRegistrationStatus.isRegistered && !previousRegistrationStatus.isRegistered) {
235+
final newRegistrationStatus = newRegistration?.status;
236+
final previousRegistrationStatus = previousRegistration?.status;
237+
238+
if (newRegistrationStatus?.isRegistered == true && previousRegistrationStatus?.isRegistered == false) {
229239
presenceRepository.resetLastSettingsSync();
230240
submitNotification(AppOnlineNotification());
231241
}
232242

233-
if (!newRegistrationStatus.isRegistered && previousRegistrationStatus.isRegistered) {
243+
if (newRegistrationStatus?.isRegistered == false && previousRegistrationStatus?.isRegistered == true) {
234244
submitNotification(AppOfflineNotification());
235245
}
236246

237-
if (newRegistrationStatus.isFailed || newRegistrationStatus.isUnregistered) {
247+
if (newRegistrationStatus?.isFailed == true || newRegistrationStatus?.isUnregistered == true) {
238248
add(const _ResetStateEvent.completeCalls());
239249
}
240250

241-
if (newRegistrationStatus.isFailed) {
251+
if (newRegistrationStatus?.isFailed == true) {
242252
submitNotification(
243253
SipRegistrationFailedNotification(
244-
knownCode: SignalingRegistrationFailedCode.values.byCode(newRegistration.code),
245-
systemCode: newRegistration.code,
246-
systemReason: newRegistration.reason,
254+
knownCode: SignalingRegistrationFailedCode.values.byCode(newRegistration?.code),
255+
systemCode: newRegistration?.code,
256+
systemReason: newRegistration?.reason,
247257
),
248258
);
249259
}
@@ -676,7 +686,6 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
676686
state.copyWith(
677687
callServiceState: state.callServiceState.copyWith(
678688
signalingClientStatus: SignalingClientStatus.disconnect,
679-
registration: const Registration(status: RegistrationStatus.registering),
680689
lastSignalingClientDisconnectError: null,
681690
lastSignalingDisconnectCode: null,
682691
),
@@ -705,7 +714,6 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
705714

706715
CallState newState = state.copyWith(
707716
callServiceState: state.callServiceState.copyWith(
708-
registration: const Registration(status: RegistrationStatus.registering),
709717
signalingClientStatus: SignalingClientStatus.disconnect,
710718
lastSignalingDisconnectCode: event.code,
711719
),
@@ -714,9 +722,10 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
714722
bool shouldReconnect = true;
715723

716724
if (code == SignalingDisconnectCode.appUnregisteredError) {
725+
add(const _CallSignalingEvent.registration(RegistrationStatus.unregistered));
726+
717727
newState = state.copyWith(
718728
callServiceState: state.callServiceState.copyWith(
719-
registration: const Registration(status: RegistrationStatus.unregistered),
720729
signalingClientStatus: SignalingClientStatus.disconnect,
721730
lastSignalingDisconnectCode: event.code,
722731
),
@@ -824,11 +833,7 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
824833
_CallSignalingEventNotifyRefer() => __onCallSignalingEventNotifyRefer(event, emit),
825834
_CallSignalingEventNotifyPresence() => __onCallSignalingEventNotifyPresence(event, emit),
826835
_CallSignalingEventNotifyUnknown() => __onCallSignalingEventNotifyUnknown(event, emit),
827-
_CallSignalingEventRegistering() => __onCallSignalingEventRegistering(event, emit),
828-
_CallSignalingEventRegistered() => __onCallSignalingEventRegistered(event, emit),
829-
_CallSignalingEventRegisterationFailed() => __onCallSignalingEventRegistrationFailed(event, emit),
830-
_CallSignalingEventUnregistering() => __onCallSignalingEventUnregistering(event, emit),
831-
_CallSignalingEventUnregistered() => __onCallSignalingEventUnregistered(event, emit),
836+
_CallSignalingEventRegistration() => __onCallSignalingEventRegistration(event, emit),
832837
};
833838
}
834839

@@ -1164,41 +1169,12 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
11641169
_logger.fine('_CallSignalingEventNotifyUnknown: $event');
11651170
}
11661171

1167-
Future<void> __onCallSignalingEventRegistering(_CallSignalingEventRegistering event, Emitter<CallState> emit) async {
1168-
add(const _RegistrationChange(registration: Registration(status: RegistrationStatus.registering)));
1169-
}
1170-
1171-
Future<void> __onCallSignalingEventRegistered(_CallSignalingEventRegistered event, Emitter<CallState> emit) async {
1172-
add(const _RegistrationChange(registration: Registration(status: RegistrationStatus.registered)));
1173-
}
1174-
1175-
Future<void> __onCallSignalingEventRegistrationFailed(
1176-
_CallSignalingEventRegisterationFailed event,
1177-
Emitter<CallState> emit,
1178-
) async {
1179-
add(
1180-
_RegistrationChange(
1181-
registration: Registration(
1182-
status: RegistrationStatus.registration_failed,
1183-
code: event.code,
1184-
reason: event.reason,
1185-
),
1186-
),
1187-
);
1188-
}
1189-
1190-
Future<void> __onCallSignalingEventUnregistering(
1191-
_CallSignalingEventUnregistering event,
1172+
Future<void> __onCallSignalingEventRegistration(
1173+
_CallSignalingEventRegistration event,
11921174
Emitter<CallState> emit,
11931175
) async {
1194-
add(const _RegistrationChange(registration: Registration(status: RegistrationStatus.unregistering)));
1195-
}
1196-
1197-
Future<void> __onCallSignalingEventUnregistered(
1198-
_CallSignalingEventUnregistered event,
1199-
Emitter<CallState> emit,
1200-
) async {
1201-
add(const _RegistrationChange(registration: Registration(status: RegistrationStatus.unregistered)));
1176+
final registration = Registration(status: event.status, code: event.code, reason: event.reason);
1177+
add(_RegistrationChange(registration: registration));
12021178
}
12031179

12041180
// processing call control events
@@ -1225,7 +1201,7 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
12251201
}
12261202

12271203
Future<void> __onCallControlEventStarted(_CallControlEventStarted event, Emitter<CallState> emit) async {
1228-
if (!state.callServiceState.registration.status.isRegistered) {
1204+
if (state.callServiceState.registration?.status.isRegistered == false) {
12291205
_logger.info('__onCallControlEventStarted account is not registered');
12301206
submitNotification(CallWhileUnregisteredNotification());
12311207
return;
@@ -1668,7 +1644,7 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
16681644
}
16691645

16701646
Future<void> __onCallPerformEventStarted(_CallPerformEventStarted event, Emitter<CallState> emit) async {
1671-
if (!state.callServiceState.registration.status.isRegistered) {
1647+
if (state.callServiceState.registration?.status.isRegistered == false) {
16721648
_logger.info('__onCallPerformEventStarted account is not registered');
16731649
submitNotification(CallWhileUnregisteredNotification());
16741650

@@ -2501,15 +2477,20 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
25012477
),
25022478
});
25032479
} else if (event is RegisteringEvent) {
2504-
add(const _CallSignalingEvent.registering());
2480+
add(const _CallSignalingEvent.registration(RegistrationStatus.registering));
25052481
} else if (event is RegisteredEvent) {
2506-
add(const _CallSignalingEvent.registered());
2482+
add(const _CallSignalingEvent.registration(RegistrationStatus.registered));
25072483
} else if (event is RegistrationFailedEvent) {
2508-
add(_CallSignalingEvent.registrationFailed(event.code, event.reason));
2484+
final registrationFailedEvent = _CallSignalingEvent.registration(
2485+
RegistrationStatus.registration_failed,
2486+
code: event.code,
2487+
reason: event.reason,
2488+
);
2489+
add(registrationFailedEvent);
25092490
} else if (event is UnregisteringEvent) {
2510-
add(const _CallSignalingEvent.unregistering());
2491+
add(const _CallSignalingEvent.registration(RegistrationStatus.unregistering));
25112492
} else if (event is UnregisteredEvent) {
2512-
add(const _CallSignalingEvent.unregistered());
2493+
add(const _CallSignalingEvent.registration(RegistrationStatus.unregistered));
25132494
} else if (event is TransferringEvent) {
25142495
add(_CallSignalingEvent.transferring(line: event.line, callId: event.callId));
25152496
} else {
@@ -2541,15 +2522,75 @@ class CallBloc extends Bloc<CallEvent, CallState> with WidgetsBindingObserver im
25412522
void continueStartCallIntent(CallkeepHandle handle, String? displayName, bool video) {
25422523
_logger.fine(() => 'continueStartCallIntent handle: $handle displayName: $displayName video: $video');
25432524

2544-
add(
2545-
CallControlEvent.started(
2525+
_continueStartCallIntent(handle, displayName, video);
2526+
}
2527+
2528+
Future<void> _continueStartCallIntent(CallkeepHandle handle, String? displayName, bool video) async {
2529+
_logger.fine(
2530+
() => StringBuffer()
2531+
..write('_continueStartCallIntent - Attempting to start call')
2532+
..write(' handle: $handle')
2533+
..write(' displayName: $displayName')
2534+
..write(' video: $video')
2535+
..write(' isHandshakeActive: ${state.isHandshakeEstablished}')
2536+
..write(' isSignalingActive: ${state.isSignalingEstablished}')
2537+
..toString(),
2538+
);
2539+
2540+
try {
2541+
// Wait until both signaling and handshake are active.
2542+
// If the desired state is not reached within kSignalingClientConnectionTimeout, a TimeoutException will be thrown.
2543+
final resolvedState = await stream
2544+
.firstWhere((state) => state.isHandshakeEstablished && state.isSignalingEstablished)
2545+
.timeout(kSignalingClientConnectionTimeout);
2546+
2547+
if (isClosed) return;
2548+
2549+
_logger.fine(
2550+
() => StringBuffer()
2551+
..write('_continueStartCallIntent - Signaling and handshake are now active for')
2552+
..write(' handle: $handle')
2553+
..write(' displayName: $displayName')
2554+
..write(' video: $video')
2555+
..write(' isHandshakeActive: ${resolvedState.isHandshakeEstablished}')
2556+
..write(' isSignalingActive: ${resolvedState.isSignalingEstablished}')
2557+
..toString(),
2558+
);
2559+
2560+
final event = CallControlEvent.started(
25462561
generic: handle.isGeneric ? handle.value : null,
25472562
number: handle.isNumber ? handle.value : null,
25482563
email: handle.isEmail ? handle.value : null,
25492564
displayName: displayName,
25502565
video: video,
2551-
),
2552-
);
2566+
);
2567+
2568+
add(event);
2569+
} on TimeoutException {
2570+
if (isClosed) return;
2571+
2572+
_logger.warning(
2573+
() => StringBuffer()
2574+
..write('_continueStartCallIntent - Failed to start call')
2575+
..write(' handle: $handle')
2576+
..write(' (Signaling/handshake connection timed out after ${kSignalingClientConnectionTimeout.inSeconds}s)')
2577+
..write(' isHandshakeActive: ${state.isHandshakeEstablished}')
2578+
..write(' isSignalingActive: ${state.isSignalingEstablished}')
2579+
..toString(),
2580+
);
2581+
2582+
submitNotification(const SignalingConnectFailedNotification());
2583+
} catch (e, s) {
2584+
if (isClosed) return;
2585+
2586+
final serveMessage = StringBuffer()
2587+
..write('_continueStartCallIntent - An unexpected error occurred while waiting for signaling')
2588+
..write(' handle: $handle')
2589+
..toString();
2590+
_logger.severe(() => serveMessage, e, s);
2591+
2592+
submitNotification(ErrorMessageNotification(e.toString()));
2593+
}
25532594
}
25542595

25552596
@override

lib/features/call/bloc/call_event.dart

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -208,16 +208,8 @@ sealed class _CallSignalingEvent extends CallEvent {
208208
required String? content,
209209
}) = _CallSignalingEventNotifyUnknown;
210210

211-
const factory _CallSignalingEvent.registering() = _CallSignalingEventRegistering;
212-
213-
const factory _CallSignalingEvent.registered() = _CallSignalingEventRegistered;
214-
215-
const factory _CallSignalingEvent.registrationFailed(int code, String reason) =
216-
_CallSignalingEventRegisterationFailed;
217-
218-
const factory _CallSignalingEvent.unregistering() = _CallSignalingEventUnregistering;
219-
220-
const factory _CallSignalingEvent.unregistered() = _CallSignalingEventUnregistered;
211+
const factory _CallSignalingEvent.registration(RegistrationStatus status, {int? code, String? reason}) =
212+
_CallSignalingEventRegistration;
221213
}
222214

223215
class _CallSignalingEventIncoming extends _CallSignalingEvent {
@@ -459,30 +451,15 @@ class _CallSignalingEventNotifyUnknown extends _CallSignalingEvent {
459451
List<Object?> get props => [line, callId, notify, subscriptionState, contentType, content];
460452
}
461453

462-
class _CallSignalingEventRegistering extends _CallSignalingEvent {
463-
const _CallSignalingEventRegistering();
464-
}
454+
class _CallSignalingEventRegistration extends _CallSignalingEvent {
455+
const _CallSignalingEventRegistration(this.status, {this.code, this.reason});
465456

466-
class _CallSignalingEventRegistered extends _CallSignalingEvent {
467-
const _CallSignalingEventRegistered();
468-
}
469-
470-
class _CallSignalingEventRegisterationFailed extends _CallSignalingEvent {
471-
const _CallSignalingEventRegisterationFailed(this.code, this.reason);
472-
473-
final int code;
474-
final String reason;
457+
final RegistrationStatus status;
458+
final int? code;
459+
final String? reason;
475460

476461
@override
477-
List<Object?> get props => [code, reason];
478-
}
479-
480-
class _CallSignalingEventUnregistering extends _CallSignalingEvent {
481-
const _CallSignalingEventUnregistering();
482-
}
483-
484-
class _CallSignalingEventUnregistered extends _CallSignalingEvent {
485-
const _CallSignalingEventUnregistered();
462+
List<Object?> get props => [status, code, reason];
486463
}
487464

488465
// call push events

lib/features/call/bloc/call_state.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ class CallState with _$CallState {
3939

4040
CallStatus get status => callServiceState.status;
4141

42+
/// Indicates that the handshake phase has completed and registration status is available.
43+
bool get isHandshakeEstablished => callServiceState.registration?.status != null;
44+
45+
/// Indicates that the signaling connection to the server is successfully established.
46+
bool get isSignalingEstablished => callServiceState.signalingClientStatus.isConnect;
47+
4248
int? retrieveIdleLine() {
4349
for (var line = 0; line < linesCount; line++) {
4450
if (!activeCalls.any((activeCall) => activeCall.line == line)) {

lib/models/call/call_service_state.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ enum NetworkStatus { changing, available, none }
1515
class CallServiceState with _$CallServiceState {
1616
const CallServiceState({
1717
this.signalingClientStatus = SignalingClientStatus.connecting,
18-
this.registration = const Registration(status: RegistrationStatus.registering),
18+
this.registration,
1919
this.networkStatus,
2020
this.lastSignalingClientConnectError,
2121
this.lastSignalingClientDisconnectError,
@@ -25,8 +25,18 @@ class CallServiceState with _$CallServiceState {
2525
@override
2626
final SignalingClientStatus signalingClientStatus;
2727

28+
/// Represents the current registration status of the signaling client.
29+
///
30+
/// This status is updated based on signaling-related events, such as:
31+
/// - `onDisconnect`: triggered when the signaling connection is lost.
32+
/// - `_onHandshakeSignalingEventState`: triggered during handshake updates.
33+
/// - `RegisteringEvent` / `RegisteredEvent`: indicate ongoing or successful registration.
34+
/// - `RegistrationFailedEvent`: indicates registration failure.
35+
/// - `UnregisteringEvent` / `UnregisteredEvent`: indicate ongoing or completed unregistration.
36+
///
37+
/// The `registration` field reflects the most recent registration state of the signaling client.
2838
@override
29-
final Registration registration;
39+
final Registration? registration;
3040

3141
@override
3242
final NetworkStatus? networkStatus;
@@ -47,13 +57,13 @@ class CallServiceState with _$CallServiceState {
4757
return CallStatus.connectivityNone;
4858
} else if (lastSignalingClientConnectError != null) {
4959
return CallStatus.connectError;
50-
} else if (registration.status.isUnregistered) {
60+
} else if (registration?.status.isUnregistered == true) {
5161
return CallStatus.appUnregistered;
52-
} else if (registration.status.isFailed) {
62+
} else if (registration?.status.isFailed == true) {
5363
return CallStatus.connectIssue;
5464
} else if (lastSignalingDisconnectCode != null) {
5565
return CallStatus.connectIssue;
56-
} else if (signalingClientStatus.isConnect && registration.status.isRegistered) {
66+
} else if (signalingClientStatus.isConnect && registration?.status.isRegistered == true) {
5767
return CallStatus.ready;
5868
} else {
5969
return CallStatus.inProgress;

0 commit comments

Comments
 (0)