handleIncomingPush method
Handle an incoming-call push/data message. Call this from your FCM / VoIP
push handler with the payload the backend sent ({sessionId, from, ...}).
Shows the native incoming UI and arms answer/decline.
Implementation
Future<void> handleIncomingPush(Map<String, dynamic> data, {bool alreadyRinging = false}) async {
final sessionId = (data['sessionId'] ?? data['session_id']) as String?;
if (sessionId == null) return;
// Idempotent: the same call can arrive over BOTH realtime and FCM (foreground),
// or be re-delivered — don't reset an already-live call or re-ring.
final existing = current.value;
if (existing != null && existing.sessionId == sessionId && !existing.state.isTerminal) return;
final from = (data['from'] ?? data['callerNumber'] ?? '') as String;
final name = data['callerName'] as String?;
final avatar = (data['callerAvatar'] ?? data['avatarUrl']) as String?;
// The backend's callee placeholder leg — lets a decline BEFORE we answer
// reach the control plane. Replaced by this device's media leg on answer.
final ringLegId = (data['legId'] ?? data['leg_id']) as String?;
_set(NexusCall(
sessionId: sessionId,
direction: VoiceCallDirection.inbound,
state: VoiceCallState.ringing,
remoteAddress: from,
remoteName: name,
remoteAvatar: avatar,
legId: ringLegId,
startedAt: DateTime.now(),
));
// On iOS the VoIP push already raised the CallKit ring natively before Dart
// woke up — reporting the same call again would be rejected as a duplicate.
if (alreadyRinging) return;
if (_hasNativeCallUi) {
// Ring in the SYSTEM call UI via our own native stack (ConnectionService on
// Android, CallKit on iOS) — answered natively, no app launch required.
// (avatarUrl is honoured on Android's notification + ring screen; iOS
// CallKit shows name only — the avatar appears in the in-app call UI.)
await _guard(() => NexusPlatform.instance.voiceReportIncoming(
callId: sessionId,
from: from,
displayName: name,
avatarUrl: avatar,
));
} else {
await _guard(() =>
_callKit.reportIncoming(callId: sessionId, handle: from, displayName: name, avatarUrl: avatar));
}
}