TM-SGNL-iOS/Signal/Calls/UserInterface/CallDrawerSheetDataSource.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

345 lines
12 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalRingRTC
import SignalServiceKit
import LibSignalClient
import SignalUI
import UIKit
// MARK: - Call Drawer Protocols
protocol CallDrawerSheetDataSourceObserver: AnyObject {
func callSheetMembershipDidChange(_ dataSource: CallDrawerSheetDataSource)
func callSheetRaisedHandsDidChange(_ dataSource: CallDrawerSheetDataSource)
}
@MainActor
protocol CallDrawerSheetDataSource {
typealias JoinedMember = CallDrawerSheet.JoinedMember
func unsortedMembers(tx: DBReadTransaction) -> [JoinedMember]
func raisedHandMemberIds() -> [JoinedMember.ID]
func raiseHand(raise: Bool)
func addObserver(_ observer: any CallDrawerSheetDataSourceObserver, syncStateImmediately: Bool)
func removeObserver(_ observer: any CallDrawerSheetDataSourceObserver)
}
// MARK: - Group Call data source
final class GroupCallSheetDataSource<Call: GroupCall>: CallDrawerSheetDataSource {
private let ringRtcCall: SignalRingRTC.GroupCall
private let groupCall: Call
@MainActor
init(groupCall: Call) {
self.ringRtcCall = groupCall.ringRtcCall
self.groupCall = groupCall
groupCall.addObserver(self)
}
private var observers: WeakArray<any CallDrawerSheetDataSourceObserver> = []
func addObserver(_ observer: any CallDrawerSheetDataSourceObserver, syncStateImmediately: Bool = false) {
AssertIsOnMainThread()
observers.append(observer)
if syncStateImmediately {
// Synchronize observer with current call state
observer.callSheetMembershipDidChange(self)
observer.callSheetRaisedHandsDidChange(self)
}
}
func removeObserver(_ observer: any CallDrawerSheetDataSourceObserver) {
observers.removeAll(where: { $0 === observer })
}
func raisedHandMemberIds() -> [JoinedMember.ID] {
return groupCall.raisedHands.map { .demuxID($0) }
}
func unsortedMembers(tx: DBReadTransaction) -> [JoinedMember] {
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
guard let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx) else {
return []
}
var members = [JoinedMember]()
let config: DisplayName.ComparableValue.Config = .current()
if self.ringRtcCall.localDeviceState.joinState == .joined {
members += self.ringRtcCall.remoteDeviceStates.values.map { member in
let resolvedName: String
let comparableName: DisplayName.ComparableValue
if member.aci == localIdentifiers.aci {
resolvedName = OWSLocalizedString(
"GROUP_CALL_YOU_ON_ANOTHER_DEVICE",
comment: "Text describing the local user in the group call members sheet when connected from another device."
)
comparableName = .nameValue(resolvedName)
} else {
let displayName = SSKEnvironment.shared.contactManagerRef.displayName(for: member.address, tx: SDSDB.shimOnlyBridge(tx))
resolvedName = displayName.resolvedValue(config: config.displayNameConfig)
comparableName = displayName.comparableValue(config: config)
}
return JoinedMember(
id: .demuxID(member.demuxId),
aci: member.aci,
displayName: resolvedName,
comparableName: comparableName,
demuxID: member.demuxId,
isLocalUser: false,
isUnknown: false,
isAudioMuted: member.audioMuted,
isVideoMuted: member.videoMuted,
isPresenting: member.presenting
)
}
let displayName = CommonStrings.you
let comparableName: DisplayName.ComparableValue = .nameValue(displayName)
let id: JoinedMember.ID
let demuxId: UInt32?
if let localDemuxId = ringRtcCall.localDeviceState.demuxId {
id = .demuxID(localDemuxId)
demuxId = localDemuxId
} else {
id = .aci(localIdentifiers.aci)
demuxId = nil
}
members.append(JoinedMember(
id: id,
aci: localIdentifiers.aci,
displayName: displayName,
comparableName: comparableName,
demuxID: demuxId,
isLocalUser: true,
isUnknown: false,
isAudioMuted: self.ringRtcCall.isOutgoingAudioMuted,
isVideoMuted: self.ringRtcCall.isOutgoingVideoMuted,
isPresenting: false
))
} else {
// If we're not yet in the call, `remoteDeviceStates` will not exist.
// We can get the list of joined members still, provided we are connected.
members += self.ringRtcCall.peekInfo?.joinedMembers.map { aciUuid in
let aci = Aci(fromUUID: aciUuid)
let address = SignalServiceAddress(aci)
let displayName = SSKEnvironment.shared.contactManagerRef.displayName(for: address, tx: SDSDB.shimOnlyBridge(tx))
let isUnknown = switch displayName {
case .nickname, .systemContactName, .profileName, .phoneNumber, .username:
false
case .unknown, .deletedAccount:
true
}
return JoinedMember(
id: .aci(aci),
aci: aci,
displayName: displayName.resolvedValue(config: config.displayNameConfig),
comparableName: displayName.comparableValue(config: config),
demuxID: nil,
isLocalUser: false,
isUnknown: isUnknown,
isAudioMuted: nil,
isVideoMuted: nil,
isPresenting: nil
)
} ?? []
}
return members
}
}
// MARK: GroupCallObserver
extension GroupCallSheetDataSource: GroupCallObserver {
func groupCallLocalDeviceStateChanged(_ call: GroupCall) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func groupCallRemoteDeviceStatesChanged(_ call: GroupCall) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func groupCallPeekChanged(_ call: GroupCall) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func groupCallEnded(_ call: GroupCall, reason: GroupCallEndReason) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func groupCallReceivedRaisedHands(_ call: GroupCall, raisedHands: [DemuxId]) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetRaisedHandsDidChange(self) }
}
func raiseHand(raise: Bool) {
ringRtcCall.raiseHand(raise: raise)
}
}
// MARK: Call Links
typealias CallLinkSheetDataSource = GroupCallSheetDataSource<CallLinkCall>
extension CallLinkSheetDataSource {
func url() -> URL {
return self.groupCall.callLink.url()
}
var callLink: CallLink {
self.groupCall.callLink
}
var isAdmin: Bool {
self.groupCall.adminPasskey != nil
}
var adminPasskey: Data? {
self.groupCall.adminPasskey
}
var callLinkState: SignalServiceKit.CallLinkState {
self.groupCall.callLinkState
}
func removeMember(demuxId: DemuxId) {
ringRtcCall.removeClient(demuxId: demuxId)
}
func blockMember(demuxId: DemuxId) {
ringRtcCall.blockClient(demuxId: demuxId)
}
}
// MARK: - Individual call data source
class IndividualCallSheetDataSource: CallDrawerSheetDataSource {
private var call: SignalCall
private var individualCall: IndividualCall
private var thread: TSContactThread
init(
thread: TSContactThread,
call: SignalCall,
individualCall: IndividualCall
) {
self.call = call
self.thread = thread
self.individualCall = individualCall
individualCall.addObserverAndSyncState(self)
}
func unsortedMembers(tx: any SignalServiceKit.DBReadTransaction) -> [JoinedMember] {
var members = [JoinedMember]()
if let remoteAci = thread.contactAddress.aci {
let remoteDisplayName = SSKEnvironment.shared.contactManagerRef.displayName(
for: thread.contactAddress,
tx: SDSDB.shimOnlyBridge(tx)
).resolvedValue()
let remoteComparableName: DisplayName.ComparableValue = .nameValue(remoteDisplayName)
members.append(JoinedMember(
id: .aci(remoteAci),
aci: remoteAci,
displayName: remoteDisplayName,
comparableName: remoteComparableName,
demuxID: nil,
isLocalUser: false,
isUnknown: false,
isAudioMuted: self.individualCall.isRemoteAudioMuted,
isVideoMuted: self.individualCall.isRemoteVideoEnabled.negated,
isPresenting: self.individualCall.isRemoteSharingScreen
))
}
// Add yourself
let displayName = CommonStrings.you
let comparableName: DisplayName.ComparableValue = .nameValue(displayName)
if let localAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: tx)?.aci {
members.append(JoinedMember(
id: .aci(localAci),
aci: localAci,
displayName: displayName,
comparableName: comparableName,
demuxID: nil,
isLocalUser: true,
isUnknown: false,
isAudioMuted: self.call.isOutgoingAudioMuted,
isVideoMuted: self.call.isOutgoingVideoMuted,
isPresenting: false
))
}
return members
}
func raisedHandMemberIds() -> [JoinedMember.ID] {[]}
func raiseHand(raise: Bool) {
owsFailDebug("Should not be able to raise hand in individual call")
}
private var observers: WeakArray<any CallDrawerSheetDataSourceObserver> = []
func addObserver(_ observer: any CallDrawerSheetDataSourceObserver, syncStateImmediately: Bool = false) {
AssertIsOnMainThread()
observers.append(observer)
if syncStateImmediately {
// Synchronize observer with current call state
observer.callSheetMembershipDidChange(self)
observer.callSheetRaisedHandsDidChange(self)
}
}
func removeObserver(_ observer: any CallDrawerSheetDataSourceObserver) {
observers.removeAll(where: { $0 === observer })
}
}
// MARK: IndividualCallObserver
extension IndividualCallSheetDataSource: IndividualCallObserver {
func individualCallStateDidChange(_ call: IndividualCall, state: CallState) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallLocalVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallLocalAudioMuteDidChange(_ call: IndividualCall, isAudioMuted: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallHoldDidChange(_ call: IndividualCall, isOnHold: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallRemoteAudioMuteDidChange(_ call: IndividualCall, isAudioMuted: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallRemoteVideoMuteDidChange(_ call: IndividualCall, isVideoMuted: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
func individualCallRemoteSharingScreenDidChange(_ call: IndividualCall, isRemoteSharingScreen: Bool) {
AssertIsOnMainThread()
observers.elements.forEach { $0.callSheetMembershipDidChange(self) }
}
}