mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
d33dc058c2
The call package now says why a call ended. Only our own hang-up is an end call action; a call the other side declined or left, that nobody answered, or that failed is reported to CallKit with that reason, so the call log no longer shows it as one we ended, and no end action comes back to hang up a call that is already over. Also adopts the package's other host changes: the voice call icon beside "Calling…", and the call's large avatar rendered at the 140 pt it is drawn at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
3.1 KiB
Swift
78 lines
3.1 KiB
Swift
//
|
|
// Copyright 2026 Element Creations Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import CallKit
|
|
import Combine
|
|
import ElementCall
|
|
|
|
/// Serves the call package's system-call port from `ElementCallService`.
|
|
///
|
|
/// Stays app-side because the same `CXProvider` and VoIP push registry serve the web-view call path
|
|
/// too, so ownership can't be handed to the package.
|
|
final class NativeCallSystem: ElementCallSystemProvidingProtocol {
|
|
/// Weak because the service owns the call stack, which owns the controller holding this port.
|
|
private weak var service: ElementCallServiceProtocol?
|
|
|
|
/// Stored so the events keep flowing off the subject itself, without reaching back through the
|
|
/// weak reference on every subscription.
|
|
private let serviceActions: AnyPublisher<ElementCallServiceAction, Never>
|
|
|
|
init(service: ElementCallServiceProtocol) {
|
|
self.service = service
|
|
serviceActions = service.actions
|
|
}
|
|
|
|
var events: AnyPublisher<ElementCallSystemEvent, Never> {
|
|
serviceActions
|
|
.compactMap { action in
|
|
switch action {
|
|
case .audioSessionActivated:
|
|
.audioSessionActivated
|
|
case .audioSessionDeactivated:
|
|
.audioSessionDeactivated
|
|
case .setAudioEnabled(let enabled, _):
|
|
// The system reports what the microphone should be; the port speaks in mutes.
|
|
.microphoneMuteChanged(isMuted: !enabled)
|
|
case .endCall(let roomID):
|
|
.endCallRequested(roomID: roomID)
|
|
// All navigation, handled by the flow coordinator. The call reaches this port later,
|
|
// as an ordinary start.
|
|
case .startCall, .receivedIncomingCallRequest, .nativeCall:
|
|
nil
|
|
}
|
|
}
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
func startCall(roomID: String, displayName: String, isVideo: Bool) async {
|
|
await service?.setupCallSession(roomID: roomID, roomDisplayName: displayName, isVideo: isVideo)
|
|
}
|
|
|
|
func reportConnected(roomID: String) {
|
|
service?.reportCallSessionConnected(roomID: roomID)
|
|
}
|
|
|
|
/// Only our own hang-up is an end call action; the rest the system hears as the call ending on
|
|
/// its own, so the call log does not show a declined or unanswered call as one we ended.
|
|
func endCall(roomID: String, reason: ElementCallEndReason) {
|
|
switch reason {
|
|
case .local:
|
|
service?.tearDownCallSession(roomID: roomID)
|
|
case .remoteEnded:
|
|
service?.reportCallSessionEnded(roomID: roomID, reason: .remoteEnded)
|
|
case .unanswered:
|
|
service?.reportCallSessionEnded(roomID: roomID, reason: .unanswered)
|
|
case .failed:
|
|
service?.reportCallSessionEnded(roomID: roomID, reason: .failed)
|
|
}
|
|
}
|
|
|
|
func setMicrophoneEnabled(_ enabled: Bool, roomID: String) {
|
|
service?.setAudioEnabled(enabled, roomID: roomID)
|
|
}
|
|
}
|