diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index d88b32285c..5a12deb7b8 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -131,6 +131,11 @@ public final class PresentationCallVideoView { } } +public enum PresentationCallConferenceState { + case preparing + case ready +} + public protocol PresentationCall: AnyObject { var context: AccountContext { get } var isIntegratedWithCallKit: Bool { get } @@ -144,7 +149,8 @@ public protocol PresentationCall: AnyObject { var state: Signal { get } var audioLevel: Signal { get } - var hasConference: Signal { get } + var conferenceState: Signal { get } + var conferenceStateValue: PresentationCallConferenceState? { get } var conferenceCall: PresentationGroupCall? { get } var isMuted: Signal { get } @@ -468,6 +474,28 @@ public protocol PresentationGroupCall: AnyObject { func loadMoreMembers(token: String) } +public enum VideoChatCall: Equatable { + case group(PresentationGroupCall) + case conferenceSource(PresentationCall) + + public static func ==(lhs: VideoChatCall, rhs: VideoChatCall) -> Bool { + switch lhs { + case let .group(lhsGroup): + if case let .group(rhsGroup) = rhs, lhsGroup === rhsGroup { + return true + } else { + return false + } + case let .conferenceSource(lhsConferenceSource): + if case let .conferenceSource(rhsConferenceSource) = rhs, lhsConferenceSource === rhsConferenceSource { + return true + } else { + return false + } + } + } +} + public protocol PresentationCallManager: AnyObject { var currentCallSignal: Signal { get } var currentGroupCallSignal: Signal { get } diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index 15565d04ea..3f3b896e7e 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -517,18 +517,20 @@ public final class CallController: ViewController { } controller?.displayProgress = true - let _ = self.call.upgradeToConference(completion: { [weak self] _ in - guard let self else { + let call = self.call + + controller?.dismiss() + + let _ = self.call.upgradeToConference(completion: { [weak call] _ in + guard let call else { return } for peerId in peerIds { if case let .peer(peerId) = peerId { - let _ = (self.call as? PresentationCallImpl)?.requestAddToConference(peerId: peerId) + let _ = (call as? PresentationCallImpl)?.requestAddToConference(peerId: peerId) } } - - controller?.dismiss() }) }) diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index 95e303b4b0..7910f6c09e 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -1007,7 +1007,10 @@ public final class MediaStreamComponent: CombinedComponent { public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController { private let context: AccountContext - public let call: PresentationGroupCall + public let callImpl: PresentationGroupCall + public var call: VideoChatCall { + return .group(self.callImpl) + } public private(set) var currentOverlayController: VoiceChatOverlayController? = nil public var parentNavigationController: NavigationController? @@ -1020,7 +1023,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai public init(call: PresentationGroupCall) { self.context = call.accountContext - self.call = call + self.callImpl = call super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl), navigationBarAppearance: .none) @@ -1131,7 +1134,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai guard let strongSelf = self else { return } - guard let peerId = strongSelf.call.peerId else { + guard let peerId = strongSelf.callImpl.peerId else { return } @@ -1175,11 +1178,11 @@ public final class MediaStreamComponentController: ViewControllerComponentContai } let _ = formatSendTitle - guard let peerId = self.call.peerId else { + guard let peerId = self.callImpl.peerId else { return } - let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(peerId), self.call.state |> take(1)) + let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(peerId), self.callImpl.state |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] peer, callState in if let strongSelf = self { var inviteLinks = inviteLinks diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index ad8ac4eeca..44a3dcce2e 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -120,10 +120,16 @@ public final class PresentationCallImpl: PresentationCall { private var useFrontCamera: Bool = true private var videoCapturer: OngoingCallVideoCapturer? + public var hasVideo: Bool { + return self.videoCapturer != nil + } private var screencastBufferServerContext: IpcGroupCallBufferAppContext? private var screencastCapturer: OngoingCallVideoCapturer? private var isScreencastActive: Bool = false + public var hasScreencast: Bool { + return self.screencastCapturer != nil + } private var proximityManagerIndex: Int? @@ -133,26 +139,22 @@ public final class PresentationCallImpl: PresentationCall { private var conferenceCallImpl: PresentationGroupCallImpl? public var conferenceCall: PresentationGroupCall? { - if !self.hasConferenceValue { - return nil - } - return self.conferenceCallImpl } private var conferenceCallDisposable: Disposable? private var upgradedToConferenceCompletions = Bag<(PresentationGroupCall) -> Void>() private var waitForConferenceCallReadyDisposable: Disposable? - private let hasConferencePromise = ValuePromise(false) - private var hasConferenceValue: Bool = false { + private let conferenceStatePromise = ValuePromise(nil) + public private(set) var conferenceStateValue: PresentationCallConferenceState? { didSet { - if self.hasConferenceValue != oldValue { - self.hasConferencePromise.set(self.hasConferenceValue) + if self.conferenceStateValue != oldValue { + self.conferenceStatePromise.set(self.conferenceStateValue) } } } - public var hasConference: Signal { - return self.hasConferencePromise.get() + public var conferenceState: Signal { + return self.conferenceStatePromise.get() } private var localVideoEndpointId: String? @@ -367,6 +369,13 @@ public final class PresentationCallImpl: PresentationCall { } } + func internal_markAsCanBeRemoved() { + if !self.didSetCanBeRemoved { + self.didSetCanBeRemoved = true + self.canBeRemovedPromise.set(.single(true)) + } + } + private func updateSessionState(sessionState: CallSession, callContextState: OngoingCallContextState?, reception: Int32?, audioSessionControl: ManagedAudioSessionControl?) { self.reception = reception @@ -602,14 +611,9 @@ public final class PresentationCallImpl: PresentationCall { break } - if let (key, keyVisualHash, conferenceCall) = conferenceCallData { + if let (key, _, conferenceCall) = conferenceCallData { if self.conferenceCallDisposable == nil { self.conferenceCallDisposable = EmptyDisposable - - self.ongoingContextStateDisposable?.dispose() - self.ongoingContextStateDisposable = nil - self.ongoingContext?.stop(debugLogValue: Promise()) - self.ongoingContext = nil let conferenceCall = PresentationGroupCallImpl( accountContext: self.context, @@ -636,183 +640,13 @@ public final class PresentationCallImpl: PresentationCall { sharedAudioDevice: self.sharedAudioDevice ) self.conferenceCallImpl = conferenceCall + conferenceCall.upgradedConferenceCall = self conferenceCall.setIsMuted(action: self.isMutedValue ? .muted(isPushToTalkActive: false) : .unmuted) if let videoCapturer = self.videoCapturer { conferenceCall.requestVideo(capturer: videoCapturer) } - let accountPeerId = conferenceCall.account.peerId - let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = conferenceCall.members - |> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in - guard let members else { - return (nil, nil) - } - var local: String? - var remote: PresentationGroupCallRequestedVideo? - for participant in members.participants { - if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) { - if participant.peer.id == accountPeerId { - local = video.endpointId - } else { - if remote == nil { - remote = video - } - } - } - } - return (local, remote) - } - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs == rhs - }) - - var startTimestamp: Double? - self.ongoingContextStateDisposable = (combineLatest(queue: .mainQueue(), - conferenceCall.state, - videoEndpoints, - conferenceCall.signalBars, - conferenceCall.isFailed - ) - |> deliverOnMainQueue).startStrict(next: { [weak self] callState, videoEndpoints, signalBars, isFailed in - guard let self else { - return - } - - var mappedLocalVideoState: PresentationCallState.VideoState = .inactive - var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive - - if let local = videoEndpoints.local { - mappedLocalVideoState = .active(isScreencast: false, endpointId: local) - } - if let remote = videoEndpoints.remote { - mappedRemoteVideoState = .active(endpointId: remote.endpointId) - } - - self.localVideoEndpointId = videoEndpoints.local - self.remoteVideoEndpointId = videoEndpoints.remote?.endpointId - - if let conferenceCall = self.conferenceCall { - var requestedVideo: [PresentationGroupCallRequestedVideo] = [] - if let remote = videoEndpoints.remote { - requestedVideo.append(remote) - } - conferenceCall.setRequestedVideoList(items: requestedVideo) - } - - let mappedState: PresentationCallState.State - if isFailed { - mappedState = .terminating(.error(.disconnected)) - } else { - switch callState.networkState { - case .connecting: - mappedState = .connecting(keyVisualHash) - case .connected: - let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent() - startTimestamp = timestamp - mappedState = .active(timestamp, signalBars, keyVisualHash) - } - } - - if !self.didDropCall && !self.droppedCall { - /*let presentationState = PresentationCallState( - state: mappedState, - videoState: mappedLocalVideoState, - remoteVideoState: mappedRemoteVideoState, - remoteAudioState: .active, - remoteBatteryLevel: .normal - )*/ - let _ = mappedState - - let timestamp: Double - if let activeTimestamp = self.activeTimestamp { - timestamp = activeTimestamp - } else { - timestamp = CFAbsoluteTimeGetCurrent() - self.activeTimestamp = timestamp - } - - mappedLocalVideoState = .inactive - mappedRemoteVideoState = .inactive - if self.videoCapturer != nil { - mappedLocalVideoState = .active(isScreencast: false, endpointId: "local") - } - - if let callContextState = self.callContextState { - switch callContextState.remoteVideoState { - case .active, .paused: - mappedRemoteVideoState = .active(endpointId: "temp-\(self.peerId.toInt64())") - case .inactive: - break - } - } - - let presentationState = PresentationCallState( - state: .active(timestamp, signalBars, keyVisualHash), - videoState: mappedLocalVideoState, - remoteVideoState: mappedRemoteVideoState, - remoteAudioState: .active, - remoteBatteryLevel: .normal - ) - self.statePromise.set(presentationState) - self.updateTone(presentationState, callContextState: nil, previous: nil) - } - }) - - self.ongoingContextIsFailedDisposable = (conferenceCall.isFailed - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak self] _ in - guard let self else { - return - } - if !self.didDropCall { - self.didDropCall = true - self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) - } - }) - - self.ongoingContextIsDroppedDisposable = (conferenceCall.canBeRemoved - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak self] _ in - guard let self else { - return - } - if !self.didDropCall { - self.didDropCall = true - self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: .single(nil)) - } - }) - - var audioLevelId: UInt32? - let audioLevel = conferenceCall.audioLevels |> map { audioLevels -> Float in - var result: Float = 0 - for item in audioLevels { - if let audioLevelId { - if item.1 == audioLevelId { - result = item.2 - break - } - } else { - if item.1 != 0 { - audioLevelId = item.1 - result = item.2 - break - } - } - } - - return result - } - - self.audioLevelDisposable = (audioLevel - |> deliverOnMainQueue).start(next: { [weak self] level in - if let strongSelf = self { - strongSelf.audioLevelPromise.set(level) - } - }) - let waitForLocalVideo = self.videoCapturer != nil let waitForRemotePeerId: EnginePeer.Id? = self.peerId @@ -826,6 +660,8 @@ public final class PresentationCallImpl: PresentationCall { } } + self.conferenceStateValue = .preparing + self.waitForConferenceCallReadyDisposable?.dispose() self.waitForConferenceCallReadyDisposable = (combineLatest(queue: .mainQueue(), conferenceCall.state, @@ -881,7 +717,12 @@ public final class PresentationCallImpl: PresentationCall { guard let self else { return } - self.hasConferenceValue = true + + self.ongoingContext?.stop(debugLogValue: Promise()) + self.ongoingContext = nil + self.ongoingContextStateDisposable?.dispose() + + self.conferenceStateValue = .ready let upgradedToConferenceCompletions = self.upgradedToConferenceCompletions.copyItems() self.upgradedToConferenceCompletions.removeAll() @@ -974,7 +815,6 @@ public final class PresentationCallImpl: PresentationCall { if wasActive { let debugLogValue = Promise() self.ongoingContext?.stop(sendDebugLogs: options.contains(.sendDebugLogs), debugLogValue: debugLogValue) - let _ = self.conferenceCallImpl?.leave(terminateIfPossible: false).start() } case .dropping: break @@ -982,12 +822,7 @@ public final class PresentationCallImpl: PresentationCall { self.audioSessionShouldBeActive.set(false) if wasActive { let debugLogValue = Promise() - if let conferenceCall = self.conferenceCallImpl { - debugLogValue.set(conferenceCall.debugLog.get()) - let _ = conferenceCall.leave(terminateIfPossible: false).start() - } else { - self.ongoingContext?.stop(debugLogValue: debugLogValue) - } + self.ongoingContext?.stop(debugLogValue: debugLogValue) } } var terminating = false @@ -1184,12 +1019,7 @@ public final class PresentationCallImpl: PresentationCall { public func hangUp() -> Signal { let debugLogValue = Promise() self.callSessionManager.drop(internalId: self.internalId, reason: .hangUp, debugLog: debugLogValue.get()) - if let conferenceCall = self.conferenceCallImpl { - debugLogValue.set(conferenceCall.debugLog.get()) - let _ = conferenceCall.leave(terminateIfPossible: false).start() - } else { - self.ongoingContext?.stop(debugLogValue: debugLogValue) - } + self.ongoingContext?.stop(debugLogValue: debugLogValue) return self.hungUpPromise.get() } @@ -1197,12 +1027,7 @@ public final class PresentationCallImpl: PresentationCall { public func rejectBusy() { self.callSessionManager.drop(internalId: self.internalId, reason: .busy, debugLog: .single(nil)) let debugLog = Promise() - if let conferenceCall = self.conferenceCallImpl { - debugLog.set(conferenceCall.debugLog.get()) - let _ = conferenceCall.leave(terminateIfPossible: false).start() - } else { - self.ongoingContext?.stop(debugLogValue: debugLog) - } + self.ongoingContext?.stop(debugLogValue: debugLog) } public func toggleIsMuted() { @@ -1223,8 +1048,17 @@ public final class PresentationCallImpl: PresentationCall { if let videoCapturer = self.videoCapturer { if let ongoingContext = self.ongoingContext { ongoingContext.requestVideo(videoCapturer) - } else if let conferenceCall = self.conferenceCallImpl { - conferenceCall.requestVideo(capturer: videoCapturer) + } + } + } + + public func requestVideo(capturer: OngoingCallVideoCapturer) { + if self.videoCapturer == nil { + self.videoCapturer = capturer + } + if let videoCapturer = self.videoCapturer { + if let ongoingContext = self.ongoingContext { + ongoingContext.requestVideo(videoCapturer) } } } @@ -1239,8 +1073,6 @@ public final class PresentationCallImpl: PresentationCall { self.videoCapturer = nil if let ongoingContext = self.ongoingContext { ongoingContext.disableVideo() - } else if let conferenceCall = self.conferenceCallImpl { - conferenceCall.disableVideo() } } } @@ -1289,8 +1121,6 @@ public final class PresentationCallImpl: PresentationCall { self.isScreencastActive = true if let ongoingContext = self.ongoingContext { ongoingContext.requestVideo(screencastCapturer) - } else if let conferenceCall = self.conferenceCallImpl { - conferenceCall.requestVideo(capturer: screencastCapturer) } } } @@ -1388,8 +1218,6 @@ public final class PresentationCallImpl: PresentationCall { if isIncoming { if let ongoingContext = self.ongoingContext { return ongoingContext.video(isIncoming: isIncoming) - } else if let conferenceCall = self.conferenceCallImpl, let remoteVideoEndpointId = self.remoteVideoEndpointId { - return conferenceCall.video(endpointId: remoteVideoEndpointId) } else { return nil } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 5f4584f698..56478235d9 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -856,6 +856,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return .joined } + public func switchToConference(call: PresentationCall) { + + } + private func startGroupCall( accountContext: AccountContext, peerId: PeerId, diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 74d10bbcf9..51a965da2f 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1006,6 +1006,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let debugLog = Promise() + weak var upgradedConferenceCall: PresentationCallImpl? + init( accountContext: AccountContext, audioSession: ManagedAudioSession, @@ -2795,6 +2797,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self._canBeRemoved.set(.single(true)) + if let upgradedConferenceCall = self.upgradedConferenceCall { + upgradedConferenceCall.internal_markAsCanBeRemoved() + } + if self.didConnectOnce { if let callManager = self.accountContext.sharedContext.callManager { let _ = (callManager.currentGroupCallSignal diff --git a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift index e54ce0cc77..88379221c0 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift @@ -14,7 +14,7 @@ import AvatarNode import ContextUI final class VideoChatParticipantThumbnailComponent: Component { - let call: PresentationGroupCall + let call: VideoChatCall let theme: PresentationTheme let participant: GroupCallParticipantsContext.Participant let isPresentation: Bool @@ -26,7 +26,7 @@ final class VideoChatParticipantThumbnailComponent: Component { let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? init( - call: PresentationGroupCall, + call: VideoChatCall, theme: PresentationTheme, participant: GroupCallParticipantsContext.Participant, isPresentation: Bool, @@ -50,7 +50,7 @@ final class VideoChatParticipantThumbnailComponent: Component { } static func ==(lhs: VideoChatParticipantThumbnailComponent, rhs: VideoChatParticipantThumbnailComponent) -> Bool { - if lhs.call !== rhs.call { + if lhs.call != rhs.call { return false } if lhs.theme !== rhs.theme { @@ -280,7 +280,7 @@ final class VideoChatParticipantThumbnailComponent: Component { videoLayer.blurredLayer.opacity = 0.25 - if let input = (component.call as! PresentationGroupCallImpl).video(endpointId: videoDescription.endpointId) { + if let input = component.call.video(endpointId: videoDescription.endpointId) { let videoSource = AdaptedCallVideoSource(videoStreamSignal: input) self.videoSource = videoSource @@ -474,7 +474,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { } } - let call: PresentationGroupCall + let call: VideoChatCall let theme: PresentationTheme let displayVideo: Bool let participants: [Participant] @@ -485,7 +485,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? init( - call: PresentationGroupCall, + call: VideoChatCall, theme: PresentationTheme, displayVideo: Bool, participants: [Participant], @@ -507,7 +507,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { } static func ==(lhs: VideoChatExpandedParticipantThumbnailsComponent, rhs: VideoChatExpandedParticipantThumbnailsComponent) -> Bool { - if lhs.call !== rhs.call { + if lhs.call != rhs.call { return false } if lhs.theme !== rhs.theme { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift index 1541bf5ff9..2460da8ea5 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatMicButtonComponent.swift @@ -188,7 +188,7 @@ final class VideoChatMicButtonComponent: Component { case scheduled(state: ScheduledState) } - let call: PresentationGroupCall + let call: VideoChatCall let strings: PresentationStrings let content: Content let isCollapsed: Bool @@ -197,7 +197,7 @@ final class VideoChatMicButtonComponent: Component { let scheduleAction: () -> Void init( - call: PresentationGroupCall, + call: VideoChatCall, strings: PresentationStrings, content: Content, isCollapsed: Bool, @@ -215,6 +215,9 @@ final class VideoChatMicButtonComponent: Component { } static func ==(lhs: VideoChatMicButtonComponent, rhs: VideoChatMicButtonComponent) -> Bool { + if lhs.call != rhs.call { + return false + } if lhs.content != rhs.content { return false } @@ -612,8 +615,8 @@ final class VideoChatMicButtonComponent: Component { switch component.content { case .unmuted: if self.audioLevelDisposable == nil { - self.audioLevelDisposable = (component.call.myAudioLevel - |> deliverOnMainQueue).startStrict(next: { [weak self] value in + self.audioLevelDisposable = (component.call.myAudioLevelAndSpeaking + |> deliverOnMainQueue).startStrict(next: { [weak self] value, _ in guard let self, let blobView = self.blobView else { return } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift index 834fc03318..e538a7179b 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantAvatarComponent.swift @@ -132,14 +132,14 @@ private final class BlobView: UIView { } final class VideoChatParticipantAvatarComponent: Component { - let call: PresentationGroupCall + let call: VideoChatCall let peer: EnginePeer let myPeerId: EnginePeer.Id let isSpeaking: Bool let theme: PresentationTheme init( - call: PresentationGroupCall, + call: VideoChatCall, peer: EnginePeer, myPeerId: EnginePeer.Id, isSpeaking: Bool, @@ -153,7 +153,7 @@ final class VideoChatParticipantAvatarComponent: Component { } static func ==(lhs: VideoChatParticipantAvatarComponent, rhs: VideoChatParticipantAvatarComponent) -> Bool { - if lhs.call !== rhs.call { + if lhs.call != rhs.call { return false } if lhs.peer != rhs.peer { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index fd36252698..dcb9cf08bd 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -40,7 +40,7 @@ private let activityBorderImage: UIImage = { final class VideoChatParticipantVideoComponent: Component { let theme: PresentationTheme let strings: PresentationStrings - let call: PresentationGroupCall + let call: VideoChatCall let participant: GroupCallParticipantsContext.Participant let isMyPeer: Bool let isPresentation: Bool @@ -59,7 +59,7 @@ final class VideoChatParticipantVideoComponent: Component { init( theme: PresentationTheme, strings: PresentationStrings, - call: PresentationGroupCall, + call: VideoChatCall, participant: GroupCallParticipantsContext.Participant, isMyPeer: Bool, isPresentation: Bool, @@ -95,6 +95,9 @@ final class VideoChatParticipantVideoComponent: Component { } static func ==(lhs: VideoChatParticipantVideoComponent, rhs: VideoChatParticipantVideoComponent) -> Bool { + if lhs.call != rhs.call { + return false + } if lhs.participant != rhs.participant { return false } @@ -514,7 +517,7 @@ final class VideoChatParticipantVideoComponent: Component { videoLayer.blurredLayer.opacity = 0.0 - if let input = (component.call as! PresentationGroupCallImpl).video(endpointId: videoDescription.endpointId) { + if let input = component.call.video(endpointId: videoDescription.endpointId) { let videoSource = AdaptedCallVideoSource(videoStreamSignal: input) self.videoSource = videoSource diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index 85cf39dffc..a268377ee0 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -126,7 +126,7 @@ final class VideoChatParticipantsComponent: Component { } } - let call: PresentationGroupCall + let call: VideoChatCall let participants: Participants? let speakingParticipants: Set let expandedVideoState: ExpandedVideoState? @@ -145,7 +145,7 @@ final class VideoChatParticipantsComponent: Component { let visibleParticipantsUpdated: (Set) -> Void init( - call: PresentationGroupCall, + call: VideoChatCall, participants: Participants?, speakingParticipants: Set, expandedVideoState: ExpandedVideoState?, @@ -183,6 +183,9 @@ final class VideoChatParticipantsComponent: Component { } static func ==(lhs: VideoChatParticipantsComponent, rhs: VideoChatParticipantsComponent) -> Bool { + if lhs.call != rhs.call { + return false + } if lhs.participants != rhs.participants { return false } @@ -1853,7 +1856,7 @@ final class VideoChatParticipantsComponent: Component { } } } - (component.call as! PresentationGroupCallImpl).setRequestedVideoList(items: requestedVideo) + component.call.setRequestedVideoList(items: requestedVideo) transition.setPosition(view: self.scrollViewClippingContainer, position: itemLayout.scrollClippingFrame.center) transition.setBounds(view: self.scrollViewClippingContainer, bounds: CGRect(origin: CGPoint(x: itemLayout.scrollClippingFrame.minX - itemLayout.listFrame.minX, y: itemLayout.scrollClippingFrame.minY - itemLayout.listFrame.minY), size: itemLayout.scrollClippingFrame.size)) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 70ebfc1ef5..fc43cda227 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -24,18 +24,151 @@ import TelegramAudio import LegacyComponents import TooltipUI +extension VideoChatCall { + var accountContext: AccountContext { + switch self { + case let .group(group): + return group.accountContext + case let .conferenceSource(conferenceSource): + return conferenceSource.context + } + } + + var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { + switch self { + case let .group(group): + return group.myAudioLevelAndSpeaking + case let .conferenceSource(conferenceSource): + return conferenceSource.audioLevel |> map { value in + return (value, false) + } + } + } + + var audioLevels: Signal<[(EnginePeer.Id, UInt32, Float, Bool)], NoError> { + switch self { + case let .group(group): + return group.audioLevels + case let .conferenceSource(conferenceSource): + let peerId = conferenceSource.peerId + return conferenceSource.audioLevel |> map { value in + return [(peerId, 0, value, false)] + } + } + } + + func video(endpointId: String) -> Signal? { + switch self { + case let .group(group): + return (group as! PresentationGroupCallImpl).video(endpointId: endpointId) + case let .conferenceSource(conferenceSource): + if endpointId == "temp-local" { + return (conferenceSource as! PresentationCallImpl).video(isIncoming: false) + } else { + return (conferenceSource as! PresentationCallImpl).video(isIncoming: true) + } + } + } + + func loadMoreMembers(token: String) { + switch self { + case let .group(group): + group.loadMoreMembers(token: token) + case .conferenceSource: + break + } + } + + func setRequestedVideoList(items: [PresentationGroupCallRequestedVideo]) { + switch self { + case let .group(group): + group.setRequestedVideoList(items: items) + case .conferenceSource: + break + } + } + + var hasVideo: Bool { + switch self { + case let .group(group): + return group.hasVideo + case let .conferenceSource(conferenceSource): + return (conferenceSource as! PresentationCallImpl).hasVideo + } + } + + var hasScreencast: Bool { + switch self { + case let .group(group): + return group.hasScreencast + case let .conferenceSource(conferenceSource): + return (conferenceSource as! PresentationCallImpl).hasScreencast + } + } + + func disableVideo() { + switch self { + case let .group(group): + group.disableVideo() + case let .conferenceSource(conferenceSource): + conferenceSource.disableVideo() + } + } + + func disableScreencast() { + switch self { + case let .group(group): + group.disableScreencast() + case let .conferenceSource(conferenceSource): + (conferenceSource as! PresentationCallImpl).disableScreencast() + } + } + + func setIsMuted(action: PresentationGroupCallMuteAction) { + switch self { + case let .group(group): + group.setIsMuted(action: action) + case let .conferenceSource(conferenceSource): + switch action { + case .unmuted: + conferenceSource.setIsMuted(false) + case let .muted(isPushToTalkActive): + conferenceSource.setIsMuted(!isPushToTalkActive) + } + } + } + + func requestVideo(capturer: OngoingCallVideoCapturer, useFrontCamera: Bool) { + switch self { + case let .group(groupCall): + (groupCall as! PresentationGroupCallImpl).requestVideo(capturer: capturer, useFrontCamera: useFrontCamera) + case let .conferenceSource(conferenceSource): + (conferenceSource as! PresentationCallImpl).requestVideo(capturer: capturer) + } + } + + func setCurrentAudioOutput(_ output: AudioSessionOutput) { + switch self { + case let .group(group): + group.setCurrentAudioOutput(output) + case let .conferenceSource(conferenceSource): + conferenceSource.setCurrentAudioOutput(output) + } + } +} + final class VideoChatScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let initialData: VideoChatScreenV2Impl.InitialData - let call: PresentationGroupCall + let initialCall: VideoChatCall init( initialData: VideoChatScreenV2Impl.InitialData, - call: PresentationGroupCall + initialCall: VideoChatCall ) { self.initialData = initialData - self.call = call + self.initialCall = initialCall } static func ==(lhs: VideoChatScreenComponent, rhs: VideoChatScreenComponent) -> Bool { @@ -86,9 +219,13 @@ final class VideoChatScreenComponent: Component { var reconnectedAsEventsDisposable: Disposable? var memberEventsDisposable: Disposable? + var currentCall: VideoChatCall? + var appliedCurrentCall: VideoChatCall? + var peer: EnginePeer? var callState: PresentationGroupCallState? var stateDisposable: Disposable? + var conferenceCallStateDisposable: Disposable? var audioOutputState: ([AudioSessionOutput], AudioSessionOutput?)? var audioOutputStateDisposable: Disposable? @@ -156,6 +293,7 @@ final class VideoChatScreenComponent: Component { self.inviteLinksDisposable?.dispose() self.updateAvatarDisposable.dispose() self.inviteDisposable.dispose() + self.conferenceCallStateDisposable?.dispose() } func animateIn() { @@ -396,16 +534,16 @@ final class VideoChatScreenComponent: Component { } func openTitleEditing() { - guard let component = self.component else { + guard case let .group(groupCall) = self.currentCall else { return } - guard let peerId = component.call.peerId else { + guard let peerId = groupCall.peerId else { return } - let _ = (component.call.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] chatPeer in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } guard let callState = self.callState, let peer = self.peer else { @@ -424,15 +562,15 @@ final class VideoChatScreenComponent: Component { text = environment.strings.VoiceChat_EditTitleText } - let controller = voiceChatTitleEditController(sharedContext: component.call.accountContext.sharedContext, account: component.call.accountContext.account, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in - guard let self, let component = self.component, let environment = self.environment else { + let controller = voiceChatTitleEditController(sharedContext: groupCall.accountContext.sharedContext, account: groupCall.accountContext.account, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } guard let title = title, title != initialTitle else { return } - component.call.updateTitle(title) + groupCall.updateTitle(title) let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { @@ -448,7 +586,7 @@ final class VideoChatScreenComponent: Component { } func presentUndoOverlay(content: UndoOverlayContent, action: @escaping (UndoOverlayAction) -> Bool) { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment, let currentCall = self.currentCall else { return } var animateInAsReplacement = false @@ -459,15 +597,15 @@ final class VideoChatScreenComponent: Component { } return true } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: action), in: .current) } func presentShare(_ inviteLinks: GroupCallInviteLinks) { - guard let component = self.component else { + guard case let .group(groupCall) = self.currentCall else { return } - guard let peerId = component.call.peerId else { + guard let peerId = groupCall.peerId else { return } @@ -483,9 +621,9 @@ final class VideoChatScreenComponent: Component { return string } - let _ = (component.call.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } guard let peer = self.peer else { @@ -512,33 +650,33 @@ final class VideoChatScreenComponent: Component { return formatSendTitle(environment.strings.VoiceChat_InviteLink_InviteListeners(Int32(count))) })] } - let shareController = ShareController(context: component.call.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: environment.theme, forcedActionTitle: environment.strings.VoiceChat_CopyInviteLink) + let shareController = ShareController(context: groupCall.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: environment.theme, forcedActionTitle: environment.strings.VoiceChat_CopyInviteLink) shareController.completed = { [weak self] peerIds in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = (component.call.accountContext.engine.data.get( + let _ = (groupCall.accountContext.engine.data.get( EngineDataList( peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) ) ) |> deliverOnMainQueue).start(next: { [weak self] peerList in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } let peers = peerList.compactMap { $0 } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let text: String var isSavedMessages = false if peers.count == 1, let peer = peers.first { - isSavedMessages = peer.id == component.call.accountContext.account.peerId - let peerName = peer.id == component.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + isSavedMessages = peer.id == groupCall.accountContext.account.peerId + let peerName = peer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) text = presentationData.strings.VoiceChat_ForwardTooltip_Chat(peerName).string } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { - let firstPeerName = firstPeer.id == component.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - let secondPeerName = secondPeer.id == component.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let firstPeerName = firstPeer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let secondPeerName = secondPeer.id == groupCall.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) text = presentationData.strings.VoiceChat_ForwardTooltip_TwoChats(firstPeerName, secondPeerName).string } else if let peer = peers.first { let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) @@ -551,10 +689,10 @@ final class VideoChatScreenComponent: Component { }) } shareController.actionCompleted = { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) } environment.controller()?.present(shareController, in: .window(.root)) @@ -562,27 +700,30 @@ final class VideoChatScreenComponent: Component { } private func onCameraPressed() { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment else { + return + } + guard let currentCall = self.currentCall else { return } HapticFeedback().impact(.light) - if component.call.hasVideo { - component.call.disableVideo() + if currentCall.hasVideo { + currentCall.disableVideo() } else { - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) DeviceAccess.authorizeAccess(to: .camera(.videoCall), onlyCheck: true, presentationData: presentationData, present: { [weak self] c, a in guard let self, let environment = self.environment, let controller = environment.controller() else { return } controller.present(c, in: .window(.root), with: a) }, openSettings: { [weak self] in - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - component.call.accountContext.sharedContext.applicationBindings.openSettings() + currentCall.accountContext.sharedContext.applicationBindings.openSettings() }, _: { [weak self] ready in - guard let self, let component = self.component, let environment = self.environment, ready else { + guard let self, let environment = self.environment, let currentCall = self.currentCall, ready else { return } var isFrontCamera = true @@ -592,13 +733,13 @@ final class VideoChatScreenComponent: Component { videoView.updateIsEnabled(true) let cameraNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil) - let controller = VoiceChatCameraPreviewController(sharedContext: component.call.accountContext.sharedContext, cameraNode: cameraNode, shareCamera: { [weak self] _, unmuted in - guard let self, let component = self.component else { + let controller = VoiceChatCameraPreviewController(sharedContext: currentCall.accountContext.sharedContext, cameraNode: cameraNode, shareCamera: { [weak self] _, unmuted in + guard let self, let currentCall = self.currentCall else { return } - component.call.setIsMuted(action: unmuted ? .unmuted : .muted(isPushToTalkActive: false)) - (component.call as! PresentationGroupCallImpl).requestVideo(capturer: videoCapturer, useFrontCamera: isFrontCamera) + currentCall.setIsMuted(action: unmuted ? .unmuted : .muted(isPushToTalkActive: false)) + currentCall.requestVideo(capturer: videoCapturer, useFrontCamera: isFrontCamera) }, switchCamera: { Queue.mainQueue().after(0.1) { isFrontCamera = !isFrontCamera @@ -612,7 +753,7 @@ final class VideoChatScreenComponent: Component { } private func onAudioRoutePressed() { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment, let currentCall = self.currentCall else { return } @@ -628,12 +769,12 @@ final class VideoChatScreenComponent: Component { if availableOutputs.count == 2 { for output in availableOutputs { if output != currentOutput { - component.call.setCurrentAudioOutput(output) + currentCall.setCurrentAudioOutput(output) break } } } else { - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] for output in availableOutputs { @@ -665,10 +806,10 @@ final class VideoChatScreenComponent: Component { items.append(CallRouteActionSheetItem(title: title, icon: icon, selected: output == currentOutput, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - component.call.setCurrentAudioOutput(output) + currentCall.setCurrentAudioOutput(output) })) } @@ -685,92 +826,96 @@ final class VideoChatScreenComponent: Component { } private func onLeavePressed() { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment, let currentCall = self.currentCall else { return } - //TODO:release - let isScheduled = !"".isEmpty - - let action: (Bool) -> Void = { [weak self] terminateIfPossible in - guard let self, let component = self.component else { - return - } - - let _ = component.call.leave(terminateIfPossible: terminateIfPossible).startStandalone() + switch currentCall { + case let .group(groupCall): + let isScheduled = self.callState?.scheduleTimestamp != nil - if let controller = self.environment?.controller() as? VideoChatScreenV2Impl { - controller.dismiss(closing: true, manual: false) - } - } - - if let callState = self.callState, callState.canManageCall { - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) - let actionSheet = ActionSheetController(presentationData: presentationData) - var items: [ActionSheetItem] = [] - - let leaveTitle: String - let leaveAndCancelTitle: String - - if case let .channel(channel) = self.peer, case .broadcast = channel.info { - leaveTitle = environment.strings.LiveStream_LeaveConfirmation - leaveAndCancelTitle = isScheduled ? environment.strings.LiveStream_LeaveAndCancelVoiceChat : environment.strings.LiveStream_LeaveAndEndVoiceChat - } else { - leaveTitle = environment.strings.VoiceChat_LeaveConfirmation - leaveAndCancelTitle = isScheduled ? environment.strings.VoiceChat_LeaveAndCancelVoiceChat : environment.strings.VoiceChat_LeaveAndEndVoiceChat - } - - items.append(ActionSheetTextItem(title: leaveTitle)) - items.append(ActionSheetButtonItem(title: leaveAndCancelTitle, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - - guard let self, let component = self.component, let environment = self.environment else { + let action: (Bool) -> Void = { [weak self] terminateIfPossible in + guard let self, case let .group(groupCall) = self.currentCall else { return } - let title: String - let text: String - if case let .channel(channel) = self.peer, case .broadcast = channel.info { - title = isScheduled ? environment.strings.LiveStream_CancelConfirmationTitle : environment.strings.LiveStream_EndConfirmationTitle - text = isScheduled ? environment.strings.LiveStream_CancelConfirmationText : environment.strings.LiveStream_EndConfirmationText - } else { - title = isScheduled ? environment.strings.VoiceChat_CancelConfirmationTitle : environment.strings.VoiceChat_EndConfirmationTitle - text = isScheduled ? environment.strings.VoiceChat_CancelConfirmationText : environment.strings.VoiceChat_EndConfirmationText - } - if let _ = self.members { - let alertController = textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: isScheduled ? environment.strings.VoiceChat_CancelConfirmationEnd : environment.strings.VoiceChat_EndConfirmationEnd, action: { - action(true) - })]) - environment.controller()?.present(alertController, in: .window(.root)) - } else { - action(true) - } - })) - - let leaveText: String - if case let .channel(channel) = self.peer, case .broadcast = channel.info { - leaveText = environment.strings.LiveStream_LeaveVoiceChat - } else { - leaveText = environment.strings.VoiceChat_LeaveVoiceChat - } - - items.append(ActionSheetButtonItem(title: leaveText, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() + let _ = groupCall.leave(terminateIfPossible: terminateIfPossible).startStandalone() - action(false) - })) + if let controller = self.environment?.controller() as? VideoChatScreenV2Impl { + controller.dismiss(closing: true, manual: false) + } + } - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: environment.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) + if let callState = self.callState, callState.canManageCall { + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let actionSheet = ActionSheetController(presentationData: presentationData) + var items: [ActionSheetItem] = [] + + let leaveTitle: String + let leaveAndCancelTitle: String + + if case let .channel(channel) = self.peer, case .broadcast = channel.info { + leaveTitle = environment.strings.LiveStream_LeaveConfirmation + leaveAndCancelTitle = isScheduled ? environment.strings.LiveStream_LeaveAndCancelVoiceChat : environment.strings.LiveStream_LeaveAndEndVoiceChat + } else { + leaveTitle = environment.strings.VoiceChat_LeaveConfirmation + leaveAndCancelTitle = isScheduled ? environment.strings.VoiceChat_LeaveAndCancelVoiceChat : environment.strings.VoiceChat_LeaveAndEndVoiceChat + } + + items.append(ActionSheetTextItem(title: leaveTitle)) + items.append(ActionSheetButtonItem(title: leaveAndCancelTitle, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + let title: String + let text: String + if case let .channel(channel) = self.peer, case .broadcast = channel.info { + title = isScheduled ? environment.strings.LiveStream_CancelConfirmationTitle : environment.strings.LiveStream_EndConfirmationTitle + text = isScheduled ? environment.strings.LiveStream_CancelConfirmationText : environment.strings.LiveStream_EndConfirmationText + } else { + title = isScheduled ? environment.strings.VoiceChat_CancelConfirmationTitle : environment.strings.VoiceChat_EndConfirmationTitle + text = isScheduled ? environment.strings.VoiceChat_CancelConfirmationText : environment.strings.VoiceChat_EndConfirmationText + } + + if let _ = self.members { + let alertController = textAlertController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: isScheduled ? environment.strings.VoiceChat_CancelConfirmationEnd : environment.strings.VoiceChat_EndConfirmationEnd, action: { + action(true) + })]) + environment.controller()?.present(alertController, in: .window(.root)) + } else { + action(true) + } + })) + + let leaveText: String + if case let .channel(channel) = self.peer, case .broadcast = channel.info { + leaveText = environment.strings.LiveStream_LeaveVoiceChat + } else { + leaveText = environment.strings.VoiceChat_LeaveVoiceChat + } + + items.append(ActionSheetButtonItem(title: leaveText, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + action(false) + })) + + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: items), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: environment.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) ]) - ]) - environment.controller()?.present(actionSheet, in: .window(.root)) - } else { - action(false) + environment.controller()?.present(actionSheet, in: .window(.root)) + } else { + action(false) + } + case let .conferenceSource(conferenceSource): + let _ = conferenceSource.hangUp().startStandalone() } } @@ -827,303 +972,544 @@ final class VideoChatScreenComponent: Component { self.peer = component.initialData.peer self.members = component.initialData.members self.callState = component.initialData.callState + } + + var call = self.currentCall ?? component.initialCall + if case let .conferenceSource(conferenceSource) = call, let conferenceCall = conferenceSource.conferenceCall, conferenceSource.conferenceStateValue == .ready { + call = .group(conferenceCall) + } + + self.currentCall = call + if self.appliedCurrentCall != call { + self.appliedCurrentCall = call - self.membersDisposable = (component.call.members - |> deliverOnMainQueue).startStrict(next: { [weak self] members in - guard let self else { - return - } - if self.members != members { - var members = members - - #if DEBUG && false - if let membersValue = members { - var participants = membersValue.participants - for i in 1 ... 20 { - for participant in membersValue.participants { - guard let user = participant.peer as? TelegramUser else { - continue + switch call { + case let .group(groupCall): + self.membersDisposable?.dispose() + self.membersDisposable = (groupCall.members + |> deliverOnMainQueue).startStrict(next: { [weak self] members in + guard let self else { + return + } + if self.members != members { + var members = members + if let membersValue = members { + let participants = membersValue.participants + members = PresentationGroupCallMembers( + participants: participants, + speakingParticipants: membersValue.speakingParticipants, + totalCount: membersValue.totalCount, + loadMoreToken: membersValue.loadMoreToken + ) + } + + self.members = members + + if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden { + var videoCount = 0 + for participant in members.participants { + if participant.presentationDescription != nil { + videoCount += 1 + } + if participant.videoDescription != nil { + videoCount += 1 + } + } + if videoCount == 1, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let participantsComponent = participantsView.component { + if participantsComponent.layout.videoColumn != nil { + self.expandedParticipantsVideoState = nil + self.focusedSpeakerAutoSwitchDeadline = 0.0 } - let mappedUser = TelegramUser( - id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(user.id.id._internalGetInt64Value() + Int64(i))), - accessHash: user.accessHash, - firstName: user.firstName, - lastName: user.lastName, - username: user.username, - phone: user.phone, - photo: user.photo, - botInfo: user.botInfo, - restrictionInfo: user.restrictionInfo, - flags: user.flags, - emojiStatus: user.emojiStatus, - usernames: user.usernames, - storiesHidden: user.storiesHidden, - nameColor: user.nameColor, - backgroundEmojiId: user.backgroundEmojiId, - profileColor: user.profileColor, - profileBackgroundEmojiId: user.profileBackgroundEmojiId, - subscriberCount: user.subscriberCount, - verification: user.verification - ) - participants.append(GroupCallParticipantsContext.Participant( - peer: mappedUser, - ssrc: participant.ssrc, - videoDescription: participant.videoDescription, - presentationDescription: participant.presentationDescription, - joinTimestamp: participant.joinTimestamp, - raiseHandRating: participant.raiseHandRating, - hasRaiseHand: participant.hasRaiseHand, - activityTimestamp: participant.activityTimestamp, - activityRank: participant.activityRank, - muteState: participant.muteState, - volume: participant.volume, - about: participant.about, - joinedVideo: participant.joinedVideo - )) } } - members = PresentationGroupCallMembers( - participants: participants, - speakingParticipants: membersValue.speakingParticipants, - totalCount: membersValue.totalCount, - loadMoreToken: membersValue.loadMoreToken - ) - } - #endif - - if let membersValue = members { - let participants = membersValue.participants - members = PresentationGroupCallMembers( - participants: participants, - speakingParticipants: membersValue.speakingParticipants, - totalCount: membersValue.totalCount, - loadMoreToken: membersValue.loadMoreToken - ) - } - - self.members = members - - if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden { - var videoCount = 0 - for participant in members.participants { - if participant.presentationDescription != nil { - videoCount += 1 - } - if participant.videoDescription != nil { - videoCount += 1 - } - } - if videoCount == 1, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let participantsComponent = participantsView.component { - if participantsComponent.layout.videoColumn != nil { - self.expandedParticipantsVideoState = nil - self.focusedSpeakerAutoSwitchDeadline = 0.0 - } - } - } - - if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members { - if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in - if let callState = self.callState, participant.peer.id == callState.myPeerId { + + if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members { + if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in + if let callState = self.callState, participant.peer.id == callState.myPeerId { + return false + } + if participant.videoDescription != nil || participant.presentationDescription != nil { + if members.speakingParticipants.contains(participant.peer.id) { + return true + } + } return false + }) { + if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id { + if participant.presentationDescription != nil { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } else { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } + self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 + } } - if participant.videoDescription != nil || participant.presentationDescription != nil { - if members.speakingParticipants.contains(participant.peer.id) { + + if let _ = members.participants.first(where: { participant in + if participant.peer.id == expandedParticipantsVideoState.mainParticipant.id { + if expandedParticipantsVideoState.mainParticipant.isPresentation { + if participant.presentationDescription == nil { + return false + } + } else { + if participant.videoDescription == nil { + return false + } + } return true } - } - return false - }) { - if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id { + return false + }) { + } else if let participant = members.participants.first(where: { participant in + if participant.presentationDescription != nil { + return true + } + if participant.videoDescription != nil { + return true + } + return false + }) { if participant.presentationDescription != nil { self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) } else { self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) } self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 - } - } - - if let _ = members.participants.first(where: { participant in - if participant.peer.id == expandedParticipantsVideoState.mainParticipant.id { - if expandedParticipantsVideoState.mainParticipant.isPresentation { - if participant.presentationDescription == nil { - return false - } - } else { - if participant.videoDescription == nil { - return false - } - } - return true - } - return false - }) { - } else if let participant = members.participants.first(where: { participant in - if participant.presentationDescription != nil { - return true - } - if participant.videoDescription != nil { - return true - } - return false - }) { - if participant.presentationDescription != nil { - self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) } else { - self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + self.expandedParticipantsVideoState = nil + self.focusedSpeakerAutoSwitchDeadline = 0.0 } - self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 } else { self.expandedParticipantsVideoState = nil self.focusedSpeakerAutoSwitchDeadline = 0.0 } - } else { - self.expandedParticipantsVideoState = nil - self.focusedSpeakerAutoSwitchDeadline = 0.0 - } - - if !self.isUpdating { - self.state?.updated(transition: .spring(duration: 0.4)) - } - - var speakingParticipantPeers: [EnginePeer] = [] - if let members, !members.speakingParticipants.isEmpty { - for participant in members.participants { - if members.speakingParticipants.contains(participant.peer.id) { - speakingParticipantPeers.append(EnginePeer(participant.peer)) - } - } - } - if self.speakingParticipantPeers != speakingParticipantPeers { - self.speakingParticipantPeers = speakingParticipantPeers - self.updateTitleSpeakingStatus() - } - } - }) - - self.stateDisposable = (component.call.state - |> deliverOnMainQueue).startStrict(next: { [weak self] callState in - guard let self else { - return - } - if self.callState != callState { - self.callState = callState - - if !self.isUpdating { - self.state?.updated(transition: .spring(duration: 0.4)) - } - } - }) - - self.applicationStateDisposable = (combineLatest(queue: .mainQueue(), - component.call.accountContext.sharedContext.applicationBindings.applicationIsActive, - self.isPresentedValue.get() - ) - |> deliverOnMainQueue).startStrict(next: { [weak self] applicationIsActive, isPresented in - guard let self, let component = self.component else { - return - } - let suspendVideoChannelRequests = !applicationIsActive || !isPresented - component.call.setSuspendVideoChannelRequests(suspendVideoChannelRequests) - }) - - self.audioOutputStateDisposable = (component.call.audioOutputState - |> deliverOnMainQueue).start(next: { [weak self] state in - guard let self else { - return - } - - var existingOutputs = Set() - var filteredOutputs: [AudioSessionOutput] = [] - for output in state.0 { - if case let .port(port) = output { - if !existingOutputs.contains(port.name) { - existingOutputs.insert(port.name) - filteredOutputs.append(output) - } - } else { - filteredOutputs.append(output) - } - } - - self.audioOutputState = (filteredOutputs, state.1) - self.state?.updated(transition: .spring(duration: 0.4)) - }) - - let currentAccountPeer = component.call.accountContext.account.postbox.loadedPeerWithId(component.call.accountContext.account.peerId) - |> map { peer in - return [FoundPeer(peer: peer, subscribers: nil)] - } - let cachedDisplayAsAvailablePeers: Signal<[FoundPeer], NoError> - if let peerId = component.call.peerId { - cachedDisplayAsAvailablePeers = component.call.accountContext.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId) - } else { - cachedDisplayAsAvailablePeers = .single([]) - } - let displayAsPeers: Signal<[FoundPeer], NoError> = currentAccountPeer - |> then( - combineLatest(currentAccountPeer, cachedDisplayAsAvailablePeers) - |> map { currentAccountPeer, availablePeers -> [FoundPeer] in - var result = currentAccountPeer - result.append(contentsOf: availablePeers) - return result - } - ) - self.displayAsPeersDisposable = (displayAsPeers - |> deliverOnMainQueue).start(next: { [weak self] value in - guard let self else { - return - } - self.displayAsPeers = value - }) - - self.inviteLinksDisposable = (component.call.inviteLinks - |> deliverOnMainQueue).startStrict(next: { [weak self] value in - guard let self else { - return - } - self.inviteLinks = value - }) - - self.reconnectedAsEventsDisposable = (component.call.reconnectedAsEvents - |> deliverOnMainQueue).startStrict(next: { [weak self] peer in - guard let self, let component = self.component, let environment = self.environment else { - return - } - let text: String - if case let .channel(channel) = self.peer, case .broadcast = channel.info { - text = environment.strings.LiveStream_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - } else { - text = environment.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) - }) - - if component.call.peerId != nil { - self.memberEventsDisposable = (component.call.memberEvents - |> deliverOnMainQueue).start(next: { [weak self] event in - guard let self, let members = self.members, let component = self.component, let environment = self.environment else { - return - } - if event.joined { - var displayEvent = false - if case let .channel(channel) = self.peer, case .broadcast = channel.info { - displayEvent = false - } - if members.totalCount < 40 { - displayEvent = true - } else if event.peer.isVerified { - displayEvent = true - } else if event.isContact || event.isInChatList { - displayEvent = true + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) } - if displayEvent { - let text = environment.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + var speakingParticipantPeers: [EnginePeer] = [] + if let members, !members.speakingParticipants.isEmpty { + for participant in members.participants { + if members.speakingParticipants.contains(participant.peer.id) { + speakingParticipantPeers.append(EnginePeer(participant.peer)) + } + } + } + if self.speakingParticipantPeers != speakingParticipantPeers { + self.speakingParticipantPeers = speakingParticipantPeers + self.updateTitleSpeakingStatus() } } }) + + self.stateDisposable?.dispose() + self.stateDisposable = (groupCall.state + |> deliverOnMainQueue).startStrict(next: { [weak self] callState in + guard let self else { + return + } + if self.callState != callState { + self.callState = callState + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + }) + + self.conferenceCallStateDisposable?.dispose() + self.conferenceCallStateDisposable = nil + + self.applicationStateDisposable?.dispose() + self.applicationStateDisposable = (combineLatest(queue: .mainQueue(), + groupCall.accountContext.sharedContext.applicationBindings.applicationIsActive, + self.isPresentedValue.get() + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] applicationIsActive, isPresented in + guard let self, let currentCall = self.currentCall else { + return + } + let suspendVideoChannelRequests = !applicationIsActive || !isPresented + if case let .group(groupCall) = currentCall { + groupCall.setSuspendVideoChannelRequests(suspendVideoChannelRequests) + } + }) + + self.audioOutputStateDisposable?.dispose() + self.audioOutputStateDisposable = (groupCall.audioOutputState + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + + var existingOutputs = Set() + var filteredOutputs: [AudioSessionOutput] = [] + for output in state.0 { + if case let .port(port) = output { + if !existingOutputs.contains(port.name) { + existingOutputs.insert(port.name) + filteredOutputs.append(output) + } + } else { + filteredOutputs.append(output) + } + } + + self.audioOutputState = (filteredOutputs, state.1) + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + + let currentAccountPeer = groupCall.accountContext.account.postbox.loadedPeerWithId(groupCall.accountContext.account.peerId) + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } + let cachedDisplayAsAvailablePeers: Signal<[FoundPeer], NoError> + if let peerId = groupCall.peerId { + cachedDisplayAsAvailablePeers = groupCall.accountContext.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId) + } else { + cachedDisplayAsAvailablePeers = .single([]) + } + let displayAsPeers: Signal<[FoundPeer], NoError> = currentAccountPeer + |> then( + combineLatest(currentAccountPeer, cachedDisplayAsAvailablePeers) + |> map { currentAccountPeer, availablePeers -> [FoundPeer] in + var result = currentAccountPeer + result.append(contentsOf: availablePeers) + return result + } + ) + self.displayAsPeersDisposable?.dispose() + self.displayAsPeersDisposable = (displayAsPeers + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let self else { + return + } + self.displayAsPeers = value + }) + + self.inviteLinksDisposable?.dispose() + self.inviteLinksDisposable = (groupCall.inviteLinks + |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + self.inviteLinks = value + }) + + self.reconnectedAsEventsDisposable?.dispose() + self.reconnectedAsEventsDisposable = (groupCall.reconnectedAsEvents + |> deliverOnMainQueue).startStrict(next: { [weak self] peer in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + let text: String + if case let .channel(channel) = self.peer, case .broadcast = channel.info { + text = environment.strings.LiveStream_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + } else { + text = environment.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + } + self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + }) + + self.memberEventsDisposable?.dispose() + if groupCall.peerId != nil { + self.memberEventsDisposable = (groupCall.memberEvents + |> deliverOnMainQueue).start(next: { [weak self] event in + guard let self, let members = self.members, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + if event.joined { + var displayEvent = false + if case let .channel(channel) = self.peer, case .broadcast = channel.info { + displayEvent = false + } + if members.totalCount < 40 { + displayEvent = true + } else if event.peer.isVerified { + displayEvent = true + } else if event.isContact || event.isInChatList { + displayEvent = true + } + + if displayEvent { + let text = environment.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + } + } + }) + } + case let .conferenceSource(conferenceSource): + self.membersDisposable?.dispose() + self.membersDisposable = (combineLatest(queue: .mainQueue(), + conferenceSource.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: conferenceSource.context.account.peerId), + TelegramEngine.EngineData.Item.Peer.Peer(id: conferenceSource.peerId) + ), + conferenceSource.state + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] peers, state in + guard let self else { + return + } + + var participants: [GroupCallParticipantsContext.Participant] = [] + let (myPeer, remotePeer) = peers + if let myPeer { + participants.append(GroupCallParticipantsContext.Participant( + peer: myPeer._asPeer(), + ssrc: nil, + videoDescription: nil, + presentationDescription: nil, + joinTimestamp: 0, + raiseHandRating: nil, + hasRaiseHand: false, + activityTimestamp: nil, + activityRank: nil, + muteState: nil, + volume: nil, + about: nil, + joinedVideo: false + )) + } + if let remotePeer { + participants.append(GroupCallParticipantsContext.Participant( + peer: remotePeer._asPeer(), + ssrc: nil, + videoDescription: nil, + presentationDescription: nil, + joinTimestamp: 0, + raiseHandRating: nil, + hasRaiseHand: false, + activityTimestamp: nil, + activityRank: nil, + muteState: nil, + volume: nil, + about: nil, + joinedVideo: false + )) + } + let members: PresentationGroupCallMembers? = PresentationGroupCallMembers( + participants: participants, + speakingParticipants: Set(), + totalCount: 2, + loadMoreToken: nil + ) + + if self.members != members { + var members = members + if let membersValue = members { + let participants = membersValue.participants + members = PresentationGroupCallMembers( + participants: participants, + speakingParticipants: membersValue.speakingParticipants, + totalCount: membersValue.totalCount, + loadMoreToken: membersValue.loadMoreToken + ) + } + + self.members = members + + if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden { + var videoCount = 0 + for participant in members.participants { + if participant.presentationDescription != nil { + videoCount += 1 + } + if participant.videoDescription != nil { + videoCount += 1 + } + } + if videoCount == 1, let participantsView = self.participants.view as? VideoChatParticipantsComponent.View, let participantsComponent = participantsView.component { + if participantsComponent.layout.videoColumn != nil { + self.expandedParticipantsVideoState = nil + self.focusedSpeakerAutoSwitchDeadline = 0.0 + } + } + } + + if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, let members { + if CFAbsoluteTimeGetCurrent() > self.focusedSpeakerAutoSwitchDeadline, !expandedParticipantsVideoState.isMainParticipantPinned, let participant = members.participants.first(where: { participant in + if let callState = self.callState, participant.peer.id == callState.myPeerId { + return false + } + if participant.videoDescription != nil || participant.presentationDescription != nil { + if members.speakingParticipants.contains(participant.peer.id) { + return true + } + } + return false + }) { + if participant.peer.id != expandedParticipantsVideoState.mainParticipant.id { + if participant.presentationDescription != nil { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } else { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } + self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 + } + } + + if let _ = members.participants.first(where: { participant in + if participant.peer.id == expandedParticipantsVideoState.mainParticipant.id { + if expandedParticipantsVideoState.mainParticipant.isPresentation { + if participant.presentationDescription == nil { + return false + } + } else { + if participant.videoDescription == nil { + return false + } + } + return true + } + return false + }) { + } else if let participant = members.participants.first(where: { participant in + if participant.presentationDescription != nil { + return true + } + if participant.videoDescription != nil { + return true + } + return false + }) { + if participant.presentationDescription != nil { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: true), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } else { + self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: VideoChatParticipantsComponent.VideoParticipantKey(id: participant.peer.id, isPresentation: false), isMainParticipantPinned: false, isUIHidden: expandedParticipantsVideoState.isUIHidden) + } + self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 1.0 + } else { + self.expandedParticipantsVideoState = nil + self.focusedSpeakerAutoSwitchDeadline = 0.0 + } + } else { + self.expandedParticipantsVideoState = nil + self.focusedSpeakerAutoSwitchDeadline = 0.0 + } + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + var speakingParticipantPeers: [EnginePeer] = [] + if let members, !members.speakingParticipants.isEmpty { + for participant in members.participants { + if members.speakingParticipants.contains(participant.peer.id) { + speakingParticipantPeers.append(EnginePeer(participant.peer)) + } + } + } + if self.speakingParticipantPeers != speakingParticipantPeers { + self.speakingParticipantPeers = speakingParticipantPeers + self.updateTitleSpeakingStatus() + } + } + }) + + self.stateDisposable?.dispose() + self.stateDisposable = (combineLatest(queue: .mainQueue(), + conferenceSource.state, + conferenceSource.isMuted + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] state, isMuted in + guard let self, case let .conferenceSource(conferenceSource) = self.currentCall else { + return + } + + let mappedNetworkState: PresentationGroupCallState.NetworkState + switch state.state { + case .active: + mappedNetworkState = .connected + default: + mappedNetworkState = .connecting + } + + let callState = PresentationGroupCallState( + myPeerId: conferenceSource.context.account.peerId, + networkState: mappedNetworkState, + canManageCall: false, + adminIds: Set([conferenceSource.context.account.peerId, conferenceSource.peerId]), + muteState: isMuted ? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: true) : nil, + defaultParticipantMuteState: nil, + recordingStartTimestamp: nil, + title: nil, + raisedHand: false, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isVideoEnabled: true, + isVideoWatchersLimitReached: false + ) + + if self.callState != callState { + self.callState = callState + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + }) + + self.conferenceCallStateDisposable?.dispose() + self.conferenceCallStateDisposable = (conferenceSource.conferenceState + |> filter { $0 == .ready } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self, case let .conferenceSource(conferenceSource) = self.currentCall else { + return + } + guard let conferenceCall = conferenceSource.conferenceCall else { + return + } + self.currentCall = .group(conferenceCall) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + }) + + self.applicationStateDisposable?.dispose() + self.applicationStateDisposable = nil + + self.audioOutputStateDisposable?.dispose() + self.audioOutputStateDisposable = (conferenceSource.audioOutputState + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + + var existingOutputs = Set() + var filteredOutputs: [AudioSessionOutput] = [] + for output in state.0 { + if case let .port(port) = output { + if !existingOutputs.contains(port.name) { + existingOutputs.insert(port.name) + filteredOutputs.append(output) + } + } else { + filteredOutputs.append(output) + } + } + + self.audioOutputState = (filteredOutputs, state.1) + self.state?.updated(transition: .spring(duration: 0.4)) + }) + + self.displayAsPeersDisposable?.dispose() + self.displayAsPeersDisposable = nil + self.displayAsPeers = nil + + self.inviteLinksDisposable?.dispose() + self.inviteLinksDisposable = nil + self.inviteLinks = nil + + self.reconnectedAsEventsDisposable?.dispose() + self.reconnectedAsEventsDisposable = nil + + self.memberEventsDisposable?.dispose() + self.memberEventsDisposable = nil } } @@ -1385,7 +1771,7 @@ final class VideoChatScreenComponent: Component { isRecording: self.callState?.recordingStartTimestamp != nil, strings: environment.strings, tapAction: self.callState?.recordingStartTimestamp != nil ? { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } guard let titleView = self.title.view as? VideoChatTitleComponent.View, let recordingIndicatorView = titleView.recordingIndicatorView else { @@ -1406,7 +1792,7 @@ final class VideoChatScreenComponent: Component { } else { text = environment.strings.VoiceChat_RecordingInProgress } - environment.controller()?.present(TooltipScreen(account: component.call.accountContext.account, sharedContext: component.call.accountContext.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _, _ in + environment.controller()?.present(TooltipScreen(account: currentCall.accountContext.account, sharedContext: currentCall.accountContext.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _, _ in return .dismiss(consume: true) }), in: .current) } @@ -1599,7 +1985,7 @@ final class VideoChatScreenComponent: Component { let _ = self.participants.update( transition: transition, component: AnyComponent(VideoChatParticipantsComponent( - call: component.call, + call: call, participants: mappedParticipants, speakingParticipants: self.members?.speakingParticipants ?? Set(), expandedVideoState: self.expandedParticipantsVideoState, @@ -1786,12 +2172,12 @@ final class VideoChatScreenComponent: Component { let _ = self.microphoneButton.update( transition: transition, component: AnyComponent(VideoChatMicButtonComponent( - call: component.call, + call: call, strings: environment.strings, content: micButtonContent, isCollapsed: areButtonsCollapsed, updateUnmutedStateIsPushToTalk: { [weak self] unmutedStateIsPushToTalk in - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } guard let callState = self.callState else { @@ -1803,42 +2189,44 @@ final class VideoChatScreenComponent: Component { if let muteState = callState.muteState { if muteState.canUnmute { self.isPushToTalkActive = true - component.call.setIsMuted(action: .muted(isPushToTalkActive: true)) + currentCall.setIsMuted(action: .muted(isPushToTalkActive: true)) } else { self.isPushToTalkActive = false } } else { self.isPushToTalkActive = true - component.call.setIsMuted(action: .muted(isPushToTalkActive: true)) + currentCall.setIsMuted(action: .muted(isPushToTalkActive: true)) } } else { if let muteState = callState.muteState { if muteState.canUnmute { - component.call.setIsMuted(action: .unmuted) + currentCall.setIsMuted(action: .unmuted) } } self.isPushToTalkActive = false } self.state?.updated(transition: .spring(duration: 0.5)) } else { - component.call.setIsMuted(action: .muted(isPushToTalkActive: false)) + currentCall.setIsMuted(action: .muted(isPushToTalkActive: false)) self.isPushToTalkActive = false self.state?.updated(transition: .spring(duration: 0.5)) } }, raiseHand: { [weak self] in - guard let self, let component = self.component else { + guard let self else { return } guard let callState = self.callState else { return } if !callState.raisedHand { - component.call.raiseHand() + if case let .group(groupCall) = self.currentCall { + groupCall.raiseHand() + } } }, scheduleAction: { [weak self] in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } guard let callState = self.callState else { @@ -1849,9 +2237,9 @@ final class VideoChatScreenComponent: Component { } if callState.canManageCall { - component.call.startScheduled() + groupCall.startScheduled() } else { - component.call.toggleScheduledSubscription(!callState.subscribedToScheduled) + groupCall.toggleScheduledSubscription(!callState.subscribedToScheduled) } } )), @@ -1992,7 +2380,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo } } - public let call: PresentationGroupCall + public fileprivate(set) var call: VideoChatCall public var currentOverlayController: VoiceChatOverlayController? public var parentNavigationController: NavigationController? @@ -2009,7 +2397,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo public init( initialData: InitialData, - call: PresentationGroupCall, + call: VideoChatCall, sourceCallController: CallController? ) { self.call = call @@ -2029,7 +2417,7 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo context: call.accountContext, component: VideoChatScreenComponent( initialData: initialData, - call: call + initialCall: call ), navigationBarAppearance: .none, statusBarStyle: .default, @@ -2122,26 +2510,50 @@ final class VideoChatScreenV2Impl: ViewControllerComponentContainer, VoiceChatCo super.dismiss() } - static func initialData(call: PresentationGroupCall) -> Signal { - let callPeer: Signal - if let peerId = call.peerId { - callPeer = call.accountContext.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - } else { - callPeer = .single(nil) - } - return combineLatest( - callPeer, - call.members |> take(1), - call.state |> take(1) - ) - |> map { peer, members, callState -> InitialData in - return InitialData( - peer: peer, - members: members, - callState: callState + static func initialData(call: VideoChatCall) -> Signal { + switch call { + case let .group(groupCall): + let callPeer: Signal + if let peerId = groupCall.peerId { + callPeer = groupCall.accountContext.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + } else { + callPeer = .single(nil) + } + return combineLatest( + callPeer, + groupCall.members |> take(1), + groupCall.state |> take(1) ) + |> map { peer, members, callState -> InitialData in + return InitialData( + peer: peer, + members: members, + callState: callState + ) + } + case let .conferenceSource(conferenceSource): + //TODO:release move initialization from component + return .single(InitialData( + peer: nil, + members: nil, + callState: PresentationGroupCallState( + myPeerId: conferenceSource.context.account.peerId, + networkState: .connected, + canManageCall: false, + adminIds: Set(), + muteState: nil, + defaultParticipantMuteState: nil, + recordingStartTimestamp: nil, + title: nil, + raisedHand: false, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isVideoEnabled: true, + isVideoWatchersLimitReached: false + ) + )) } } } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index b14e7d5ac5..9cb562c58c 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -9,7 +9,7 @@ import PresentationDataUtils extension VideoChatScreenComponent.View { func openInviteMembers() { - guard let component = self.component else { + guard case let .group(groupCall) = self.currentCall else { return } @@ -38,16 +38,16 @@ extension VideoChatScreenComponent.View { guard let inviteType else { return } - guard let peerId = component.call.peerId else { + guard let peerId = groupCall.peerId else { return } switch inviteType { case .invite: - let groupPeer = component.call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + let groupPeer = groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let _ = (groupPeer |> deliverOnMainQueue).start(next: { [weak self] groupPeer in - guard let self, let component = self.component, let environment = self.environment, let groupPeer else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall, let groupPeer else { return } let inviteLinks = self.inviteLinks @@ -81,8 +81,8 @@ extension VideoChatScreenComponent.View { filters.append(.excludeBots) var dismissController: (() -> Void)? - let controller = ChannelMembersSearchController(context: component.call.accountContext, peerId: groupPeer.id, forceTheme: environment.theme, mode: .inviteToCall, filters: filters, openPeer: { [weak self] peer, participant in - guard let self, let component = self.component, let environment = self.environment else { + let controller = ChannelMembersSearchController(context: groupCall.accountContext, peerId: groupPeer.id, forceTheme: environment.theme, mode: .inviteToCall, filters: filters, openPeer: { [weak self] peer, participant in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { dismissController?() return } @@ -90,51 +90,51 @@ extension VideoChatScreenComponent.View { return } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) if peer.id == callState.myPeerId { return } if let participant { dismissController?() - if component.call.invitePeer(participant.peer.id) { + if groupCall.invitePeer(participant.peer.id) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { - text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } else { - text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) } } else { if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) { - let text = environment.strings.VoiceChat_SendPublicLinkText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + let text = environment.strings.VoiceChat_SendPublicLinkText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_SendPublicLinkSend, action: { [weak self] in + environment.controller()?.present(textAlertController(context: groupCall.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_SendPublicLinkSend, action: { [weak self] in dismissController?() - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = (enqueueMessages(account: component.call.accountContext.account, peerId: peer.id, messages: [.message(text: listenerLink, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) - |> deliverOnMainQueue).start(next: { [weak self] _ in - guard let self, let environment = self.environment else { + let _ = (enqueueMessages(account: groupCall.accountContext.account, peerId: peer.id, messages: [.message(text: listenerLink, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - self.presentUndoOverlay(content: .forward(savedMessages: false, text: environment.strings.UserInfo_LinkForwardTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true }) + self.presentUndoOverlay(content: .forward(savedMessages: false, text: environment.strings.UserInfo_LinkForwardTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true }) }) })]), in: .window(.root)) } else { let text: String if case let .channel(groupPeer) = groupPeer, case .broadcast = groupPeer.info { - text = environment.strings.VoiceChat_InviteMemberToChannelFirstText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + text = environment.strings.VoiceChat_InviteMemberToChannelFirstText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), EnginePeer(groupPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } else { - text = environment.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), groupPeer.displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + text = environment.strings.VoiceChat_InviteMemberToGroupFirstText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), groupPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } - environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + environment.controller()?.present(textAlertController(context: groupCall.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_InviteMemberToGroupFirstAdd, action: { [weak self] in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } @@ -143,7 +143,7 @@ extension VideoChatScreenComponent.View { return } let inviteDisposable = self.inviteDisposable - var inviteSignal = component.call.accountContext.peerChannelMemberCategoriesContextsManager.addMembers(engine: component.call.accountContext.engine, peerId: groupPeer.id, memberIds: [peer.id]) + var inviteSignal = groupCall.accountContext.peerChannelMemberCategoriesContextsManager.addMembers(engine: groupCall.accountContext.engine, peerId: groupPeer.id, memberIds: [peer.id]) var cancelImpl: (() -> Void)? let progressSignal = Signal { [weak selfController] subscriber in let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { @@ -172,7 +172,7 @@ extension VideoChatScreenComponent.View { inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in dismissController?() - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } @@ -201,22 +201,22 @@ extension VideoChatScreenComponent.View { case .kicked: text = environment.strings.Channel_AddUserKickedError } - environment.controller()?.present(textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) + environment.controller()?.present(textAlertController(context: groupCall.accountContext, forceTheme: environment.theme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) }, completed: { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { dismissController?() return } dismissController?() - if component.call.invitePeer(peer.id) { + if groupCall.invitePeer(peer.id) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string } else { text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) } })) } else if case let .legacyGroup(groupPeer) = groupPeer { @@ -224,7 +224,7 @@ extension VideoChatScreenComponent.View { return } let inviteDisposable = self.inviteDisposable - var inviteSignal = component.call.accountContext.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: peer.id) + var inviteSignal = groupCall.accountContext.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: peer.id) var cancelImpl: (() -> Void)? let progressSignal = Signal { [weak selfController] subscriber in let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { @@ -253,19 +253,19 @@ extension VideoChatScreenComponent.View { inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in dismissController?() - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - let context = component.call.accountContext + let context = groupCall.accountContext switch error { case .privacy: - let _ = (component.call.accountContext.account.postbox.loadedPeerWithId(peer.id) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let component = self.component, let environment = self.environment else { + let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peer.id) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - environment.controller()?.present(textAlertController(context: component.call.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) + environment.controller()?.present(textAlertController(context: groupCall.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) }) case .notMutualContact: environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) @@ -275,20 +275,20 @@ extension VideoChatScreenComponent.View { environment.controller()?.present(textAlertController(context: context, forceTheme: environment.theme, title: nil, text: environment.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) } }, completed: { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { dismissController?() return } dismissController?() - if component.call.invitePeer(peer.id) { + if groupCall.invitePeer(peer.id) { let text: String if case let .channel(channel) = self.peer, case .broadcast = channel.info { text = environment.strings.LiveStream_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string } else { text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder)).string } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: component.call.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) } })) } @@ -299,14 +299,14 @@ extension VideoChatScreenComponent.View { controller.copyInviteLink = { [weak self] in dismissController?() - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - guard let callPeerId = component.call.peerId else { + guard let callPeerId = groupCall.peerId else { return } - let _ = (component.call.accountContext.engine.data.get( + let _ = (groupCall.accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: callPeerId), TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: callPeerId) ) @@ -321,7 +321,7 @@ extension VideoChatScreenComponent.View { return nil } } - |> deliverOnMainQueue).start(next: { [weak self] link in + |> deliverOnMainQueue).start(next: { [weak self] link in guard let self, let environment = self.environment else { return } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index fb4d9b614d..b0e242ab73 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -20,7 +20,10 @@ extension VideoChatScreenComponent.View { guard let sourceView = self.navigationLeftButton.view else { return } - guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + guard let currentCall = self.currentCall else { return } guard let callState = self.callState else { @@ -35,7 +38,7 @@ extension VideoChatScreenComponent.View { for peer in displayAsPeers { if peer.peer.id == callState.myPeerId { let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: component.call.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { [weak self] c, _ in guard let self else { return } @@ -201,19 +204,19 @@ extension VideoChatScreenComponent.View { } if callState.isVideoEnabled && (callState.muteState?.canUnmute ?? true) { - if component.call.hasScreencast { + if currentCall.hasScreencast { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_StopScreenSharing, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/ShareScreen"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - component.call.disableScreencast() + currentCall.disableScreencast() }))) } else { - items.append(.custom(VoiceChatShareScreenContextItem(context: component.call.accountContext, text: environment.strings.VoiceChat_ShareScreen, icon: { theme in + items.append(.custom(VoiceChatShareScreenContextItem(context: currentCall.accountContext, text: environment.strings.VoiceChat_ShareScreen, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/ShareScreen"), color: theme.actionSheet.primaryTextColor) }, action: { _, _ in }), false)) } @@ -224,15 +227,15 @@ extension VideoChatScreenComponent.View { items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } - let alertController = textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: nil, text: environment.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_StopRecordingStop, action: { [weak self] in - guard let self, let component = self.component, let environment = self.environment else { + let alertController = textAlertController(context: currentCall.accountContext, forceTheme: environment.theme, title: nil, text: environment.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: environment.strings.VoiceChat_StopRecordingStop, action: { [weak self] in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - component.call.setShouldBeRecording(false, title: nil, videoOrientation: nil) + groupCall.setShouldBeRecording(false, title: nil, videoOrientation: nil) Queue.mainQueue().after(0.88) { HapticFeedback().success() @@ -245,8 +248,8 @@ extension VideoChatScreenComponent.View { text = environment.strings.VideoChat_RecordingSaved } self.presentUndoOverlay(content: .forward(savedMessages: true, text: text), action: { [weak self] value in - if case .info = value, let self, let component = self.component, let environment = self.environment, let navigationController = environment.controller()?.navigationController as? NavigationController { - let context = component.call.accountContext + if case .info = value, let self, let environment = self.environment, let currentCall = self.currentCall, let navigationController = environment.controller()?.navigationController as? NavigationController { + let context = currentCall.accountContext environment.controller()?.dismiss(completion: { [weak navigationController] in Queue.mainQueue().justDispatch { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) @@ -279,12 +282,12 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component, let environment = self.environment, let peer = self.peer else { + guard let self, let environment = self.environment, let currentCall = self.currentCall, let peer = self.peer else { return } - let controller = VoiceChatRecordingSetupController(context: component.call.accountContext, peer: peer, completion: { [weak self] videoOrientation in - guard let self, let component = self.component, let environment = self.environment, let peer = self.peer else { + let controller = VoiceChatRecordingSetupController(context: currentCall.accountContext, peer: peer, completion: { [weak self] videoOrientation in + guard let self, let environment = self.environment, let currentCall = self.currentCall, let peer = self.peer else { return } let title: String @@ -311,12 +314,12 @@ extension VideoChatScreenComponent.View { } } - let controller = voiceChatTitleEditController(sharedContext: component.call.accountContext.sharedContext, account: component.call.account, forceTheme: environment.theme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak self] title in - guard let self, let component = self.component, let environment = self.environment, let peer = self.peer, let title else { + let controller = voiceChatTitleEditController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: title, text: text, placeholder: placeholder, value: nil, maxLength: 40, apply: { [weak self] title in + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall, let peer = self.peer, let title else { return } - component.call.setShouldBeRecording(true, title: title, videoOrientation: videoOrientation) + groupCall.setShouldBeRecording(true, title: title, videoOrientation: videoOrientation) let text: String if case let .channel(channel) = peer, case .broadcast = channel.info { @@ -326,7 +329,7 @@ extension VideoChatScreenComponent.View { } self.presentUndoOverlay(content: .voiceChatRecording(text: text), action: { _ in return false }) - component.call.playTone(.recordingStarted) + groupCall.playTone(.recordingStarted) }) environment.controller()?.present(controller, in: .window(.root)) }) @@ -348,24 +351,37 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } let action: () -> Void = { [weak self] in - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - let _ = (component.call.leave(terminateIfPossible: true) - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let self, let environment = self.environment else { - return - } - environment.controller()?.dismiss() - }) + switch currentCall { + case let .group(groupCall): + let _ = (groupCall.leave(terminateIfPossible: true) + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self, let environment = self.environment else { + return + } + environment.controller()?.dismiss() + }) + case let .conferenceSource(conferenceSource): + let _ = (conferenceSource.hangUp() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self, let environment = self.environment else { + return + } + environment.controller()?.dismiss() + }) + } } let title: String @@ -378,7 +394,7 @@ extension VideoChatScreenComponent.View { text = isScheduled ? environment.strings.VoiceChat_CancelConfirmationText : environment.strings.VoiceChat_EndConfirmationText } - let alertController = textAlertController(context: component.call.accountContext, forceTheme: environment.theme, title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: isScheduled ? environment.strings.VoiceChat_CancelConfirmationEnd : environment.strings.VoiceChat_EndConfirmationEnd, action: { + let alertController = textAlertController(context: currentCall.accountContext, forceTheme: environment.theme, title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: environment.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: isScheduled ? environment.strings.VoiceChat_CancelConfirmationEnd : environment.strings.VoiceChat_EndConfirmationEnd, action: { action() })]) environment.controller()?.present(alertController, in: .window(.root)) @@ -395,29 +411,45 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - let _ = (component.call.leave(terminateIfPossible: false) - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).start(completed: { [weak self] in - guard let self, let environment = self.environment else { - return - } - environment.controller()?.dismiss() - }) + switch currentCall { + case let .group(groupCall): + let _ = (groupCall.leave(terminateIfPossible: false) + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self, let environment = self.environment else { + return + } + environment.controller()?.dismiss() + }) + case let .conferenceSource(conferenceSource): + let _ = (conferenceSource.hangUp() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self, let environment = self.environment else { + return + } + environment.controller()?.dismiss() + }) + } }))) } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let contextController = ContextController(presentationData: presentationData, source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) controller.presentInGlobalOverlay(contextController) } private func contextMenuDisplayAsItems() -> [ContextMenuItem] { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment else { + return [] + } + guard case let .group(groupCall) = self.currentCall else { return [] } guard let callState = self.callState else { @@ -469,7 +501,7 @@ extension VideoChatScreenComponent.View { let isSelected = peer.peer.id == myPeerId let extendedAvatarSize = CGSize(width: 35.0, height: 35.0) let theme = environment.theme - let avatarSignal = peerAvatarCompleteImage(account: component.call.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize) + let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize) |> map { image -> UIImage? in if isSelected, let image = image { return generateImage(extendedAvatarSize, rotatedContext: { size, context in @@ -490,15 +522,15 @@ extension VideoChatScreenComponent.View { } } - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in + items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in f(.default) - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } if peer.peer.id != myPeerId { - component.call.reconnect(as: peer.peer.id) + groupCall.reconnect(as: peer.peer.id) } }))) @@ -548,11 +580,11 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.default) - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } - component.call.setCurrentAudioOutput(output) + currentCall.setCurrentAudioOutput(output) }))) } @@ -583,10 +615,10 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - component.call.updateDefaultParticipantsAreMuted(isMuted: false) + groupCall.updateDefaultParticipantsAreMuted(isMuted: false) }))) items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_SpeakPermissionAdmin, icon: { theme in if !isMuted { @@ -597,10 +629,10 @@ extension VideoChatScreenComponent.View { }, action: { [weak self] _, f in f(.dismissWithoutContent) - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - component.call.updateDefaultParticipantsAreMuted(isMuted: true) + groupCall.updateDefaultParticipantsAreMuted(isMuted: true) }))) } return items diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift index 55d47be155..d34f216387 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift @@ -15,17 +15,20 @@ import LegacyMediaPickerUI extension VideoChatScreenComponent.View { func openParticipantContextMenu(id: EnginePeer.Id, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?) { - guard let component = self.component, let environment = self.environment else { + guard let environment = self.environment else { return } guard let members = self.members, let participant = members.participants.first(where: { $0.peer.id == id }) else { return } + guard let currentCall = self.currentCall else { + return + } let muteStatePromise = Promise(participant.muteState) let itemsForEntry: (GroupCallParticipantsContext.Participant.MuteState?) -> [ContextMenuItem] = { [weak self] muteState in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return [] } guard let callState = self.callState else { @@ -48,15 +51,15 @@ extension VideoChatScreenComponent.View { minValue = 0.0 } items.append(.custom(VoiceChatVolumeContextItem(minValue: minValue, value: participant.volume.flatMap { CGFloat($0) / 10000.0 } ?? 1.0, valueChanged: { [weak self] newValue, finished in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } if finished && newValue.isZero { - let updatedMuteState = component.call.updateMuteState(peerId: peer.id, isMuted: true) + let updatedMuteState = groupCall.updateMuteState(peerId: peer.id, isMuted: true) muteStatePromise.set(.single(updatedMuteState)) } else { - component.call.setVolume(peerId: peer.id, volume: Int32(newValue * 10000), sync: finished) + groupCall.setVolume(peerId: peer.id, volume: Int32(newValue * 10000), sync: finished) } }), true)) } @@ -73,10 +76,10 @@ extension VideoChatScreenComponent.View { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_CancelSpeakRequest, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/RevokeSpeak"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - component.call.lowerHand() + groupCall.lowerHand() f(.default) }))) @@ -100,7 +103,7 @@ extension VideoChatScreenComponent.View { f(.default) Queue.mainQueue().after(0.1) { - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } let maxBioLength: Int @@ -109,17 +112,17 @@ extension VideoChatScreenComponent.View { } else { maxBioLength = 100 } - let controller = voiceChatTitleEditController(sharedContext: component.call.accountContext.sharedContext, account: component.call.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_EditBioTitle, text: environment.strings.VoiceChat_EditBioText, placeholder: environment.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, value: participant.about, maxLength: maxBioLength, apply: { [weak self] bio in - guard let self, let component = self.component, let environment = self.environment, let bio else { + let controller = voiceChatTitleEditController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_EditBioTitle, text: environment.strings.VoiceChat_EditBioText, placeholder: environment.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, value: participant.about, maxLength: maxBioLength, apply: { [weak self] bio in + guard let self, let environment = self.environment, let currentCall = self.currentCall, let bio else { return } if peer.id.namespace == Namespaces.Peer.CloudUser { - let _ = (component.call.accountContext.engine.accountData.updateAbout(about: bio) + let _ = (currentCall.accountContext.engine.accountData.updateAbout(about: bio) |> `catch` { _ -> Signal in return .complete() }).start() } else { - let _ = (component.call.accountContext.engine.peers.updatePeerDescription(peerId: peer.id, description: bio) + let _ = (currentCall.accountContext.engine.peers.updatePeerDescription(peerId: peer.id, description: bio) |> `catch` { _ -> Signal in return .complete() }).start() @@ -138,14 +141,14 @@ extension VideoChatScreenComponent.View { f(.default) Queue.mainQueue().after(0.1) { - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } - let controller = voiceChatUserNameController(sharedContext: component.call.accountContext.sharedContext, account: component.call.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: environment.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: environment.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { [weak self] firstAndLastName in - guard let self, let component = self.component, let environment = self.environment, let (firstName, lastName) = firstAndLastName else { + let controller = voiceChatUserNameController(sharedContext: currentCall.accountContext.sharedContext, account: currentCall.accountContext.account, forceTheme: environment.theme, title: environment.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: environment.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: environment.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: environment.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { [weak self] firstAndLastName in + guard let self, let environment = self.environment, let currentCall = self.currentCall, let (firstName, lastName) = firstAndLastName else { return } - let _ = component.call.accountContext.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).startStandalone() + let _ = currentCall.accountContext.engine.accountData.updateAccountPeerName(firstName: firstName, lastName: lastName).startStandalone() self.presentUndoOverlay(content: .info(title: nil, text: environment.strings.VoiceChat_EditNameSuccess, timeout: nil, customUndoText: nil), action: { _ in return false }) }) @@ -154,18 +157,18 @@ extension VideoChatScreenComponent.View { }))) } } else { - if (callState.canManageCall || callState.adminIds.contains(component.call.accountContext.account.peerId)) { + if (callState.canManageCall || callState.adminIds.contains(currentCall.accountContext.account.peerId)) { if callState.adminIds.contains(peer.id) { if let _ = muteState { } else { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_MutePeer, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = component.call.updateMuteState(peerId: peer.id, isMuted: true) + let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: true) f(.default) }))) } @@ -174,24 +177,24 @@ extension VideoChatScreenComponent.View { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_UnmutePeer, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: participant.hasRaiseHand ? "Call/Context Menu/AllowToSpeak" : "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - let _ = component.call.updateMuteState(peerId: peer.id, isMuted: false) + let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false) f(.default) - self.presentUndoOverlay(content: .voiceChatCanSpeak(text: environment.strings.VoiceChat_UserCanNowSpeak(EnginePeer(participant.peer).displayTitle(strings: environment.strings, displayOrder: component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true }) + self.presentUndoOverlay(content: .voiceChatCanSpeak(text: environment.strings.VoiceChat_UserCanNowSpeak(EnginePeer(participant.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string), action: { _ in return true }) }))) } else { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_MutePeer, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = component.call.updateMuteState(peerId: peer.id, isMuted: true) + let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: true) f(.default) }))) } @@ -201,22 +204,22 @@ extension VideoChatScreenComponent.View { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_UnmuteForMe, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Unmute"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = component.call.updateMuteState(peerId: peer.id, isMuted: false) + let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: false) f(.default) }))) } else { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_MuteForMe, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/Mute"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - let _ = component.call.updateMuteState(peerId: peer.id, isMuted: true) + let _ = groupCall.updateMuteState(peerId: peer.id, isMuted: true) f(.default) }))) } @@ -239,7 +242,7 @@ extension VideoChatScreenComponent.View { items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, let currentCall = self.currentCall else { return } @@ -247,7 +250,7 @@ extension VideoChatScreenComponent.View { return } - let context = component.call.accountContext + let context = currentCall.accountContext controller.dismiss(completion: { [weak navigationController] in Queue.mainQueue().after(0.1) { guard let navigationController else { @@ -260,43 +263,43 @@ extension VideoChatScreenComponent.View { f(.dismissWithoutContent) }))) - if (callState.canManageCall && !callState.adminIds.contains(peer.id)), peer.id != component.call.peerId { + if case let .group(groupCall) = self.currentCall, (callState.canManageCall && !callState.adminIds.contains(peer.id)), peer.id != groupCall.peerId { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [weak self] c, _ in c?.dismiss(completion: { - guard let self, let component = self.component else { + guard let self, case let .group(groupCall) = self.currentCall else { return } - guard let peerId = component.call.peerId else { + guard let peerId = groupCall.peerId else { return } - let _ = (component.call.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] chatPeer in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] let nameDisplayOrder = presentationData.nameDisplayOrder - items.append(DeleteChatPeerActionSheetItem(context: component.call.accountContext, peer: EnginePeer(peer), chatPeer: EnginePeer(chatPeer), action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder)) + items.append(DeleteChatPeerActionSheetItem(context: groupCall.accountContext, peer: EnginePeer(peer), chatPeer: EnginePeer(chatPeer), action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder)) items.append(ActionSheetButtonItem(title: environment.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - guard let callPeerId = component.call.peerId else { + guard let callPeerId = groupCall.peerId else { return } - let _ = component.call.accountContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: component.call.accountContext.engine, peerId: callPeerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start() - component.call.removedPeer(peer.id) + let _ = groupCall.accountContext.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: groupCall.accountContext.engine, peerId: callPeerId, memberId: peer.id, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max)).start() + groupCall.removedPeer(peer.id) self.presentUndoOverlay(content: .banned(text: environment.strings.VoiceChat_RemovedPeerText(EnginePeer(peer).displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false }) })) @@ -323,7 +326,7 @@ extension VideoChatScreenComponent.View { return itemsForEntry(muteState) } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let contextController = ContextController( presentationData: presentationData, source: .extracted(ParticipantExtractedContentSource(contentView: sourceView)), @@ -345,7 +348,7 @@ extension VideoChatScreenComponent.View { } private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) { - guard let component = self.component else { + guard let currentCall = self.currentCall else { return } guard let callState = self.callState else { @@ -353,19 +356,19 @@ extension VideoChatScreenComponent.View { } let peerId = callState.myPeerId - let _ = (component.call.accountContext.engine.data.get( + let _ = (currentCall.accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Configuration.SearchBots() ) |> deliverOnMainQueue).start(next: { [weak self] peer, searchBotsConfiguration in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let currentCall = self.currentCall, let environment = self.environment else { return } guard let peer else { return } - let presentationData = component.call.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + let presentationData = currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) let legacyController = LegacyController(presentation: .custom, theme: environment.theme) legacyController.statusBar.statusBarStyle = .Ignore @@ -387,13 +390,13 @@ extension VideoChatScreenComponent.View { let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: peerId.namespace == Namespaces.Peer.CloudUser, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)! mixin.forceDark = true - mixin.stickersContext = LegacyPaintStickersContext(context: component.call.accountContext) + mixin.stickersContext = LegacyPaintStickersContext(context: currentCall.accountContext) let _ = self.currentAvatarMixin.swap(mixin) mixin.requestSearchController = { [weak self] assetsController in - guard let self, let component = self.component, let environment = self.environment else { + guard let self, let currentCall = self.currentCall, let environment = self.environment else { return } - let controller = WebSearchController(context: component.call.accountContext, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.id.namespace == Namespaces.Peer.CloudUser ? nil : peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in + let controller = WebSearchController(context: currentCall.accountContext, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.id.namespace == Namespaces.Peer.CloudUser ? nil : peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in assetsController?.dismiss() guard let self else { @@ -426,13 +429,13 @@ extension VideoChatScreenComponent.View { } let proceed = { [weak self] in - guard let self, let component = self.component else { + guard let self, let currentCall = self.currentCall else { return } let _ = self.currentAvatarMixin.swap(nil) - let postbox = component.call.accountContext.account.postbox - self.updateAvatarDisposable.set((component.call.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in + let postbox = currentCall.accountContext.account.postbox + self.updateAvatarDisposable.set((currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) |> deliverOnMainQueue).start()) @@ -473,7 +476,7 @@ extension VideoChatScreenComponent.View { } private func updateProfilePhoto(_ image: UIImage) { - guard let component = self.component else { + guard let currentCall = self.currentCall else { return } guard let callState = self.callState else { @@ -486,15 +489,15 @@ extension VideoChatScreenComponent.View { let peerId = callState.myPeerId let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - component.call.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + currentCall.accountContext.account.postbox.mediaBox.storeResourceData(resource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) self.currentUpdatingAvatar = (representation, 0.0) - let postbox = component.call.account.postbox - let signal = peerId.namespace == Namespaces.Peer.CloudUser ? component.call.accountContext.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in + let postbox = currentCall.accountContext.account.postbox + let signal = peerId.namespace == Namespaces.Peer.CloudUser ? currentCall.accountContext.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) - }) : component.call.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: component.call.accountContext.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in + }) : currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: currentCall.accountContext.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) }) @@ -516,7 +519,7 @@ extension VideoChatScreenComponent.View { } private func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?) { - guard let component = self.component else { + guard let currentCall = self.currentCall else { return } guard let callState = self.callState else { @@ -528,7 +531,7 @@ extension VideoChatScreenComponent.View { let peerId = callState.myPeerId let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - component.call.accountContext.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) + currentCall.accountContext.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) self.currentUpdatingAvatar = (representation, 0.0) @@ -538,7 +541,7 @@ extension VideoChatScreenComponent.View { videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue } - let context = component.call.accountContext + let context = currentCall.accountContext let account = context.account let signal = Signal { [weak self] subscriber in let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 8033762d00..134ff0cb0f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -242,7 +242,7 @@ struct VoiceChatPeerEntry: Identifiable { } public protocol VoiceChatController: ViewController { - var call: PresentationGroupCall { get } + var call: VideoChatCall { get } var currentOverlayController: VoiceChatOverlayController? { get } var parentNavigationController: NavigationController? { get set } var onViewDidAppear: (() -> Void)? { get set } @@ -6859,7 +6859,10 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { } private let sharedContext: SharedAccountContext - public let call: PresentationGroupCall + public let callImpl: PresentationGroupCall + public var call: VideoChatCall { + return .group(self.callImpl) + } private let presentationData: PresentationData public var parentNavigationController: NavigationController? @@ -6891,7 +6894,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { public init(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) { self.sharedContext = sharedContext - self.call = call + self.callImpl = call self.presentationData = sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: nil) @@ -6936,7 +6939,7 @@ final class VoiceChatControllerImpl: ViewController, VoiceChatController { } override public func loadDisplayNode() { - self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.call) + self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.callImpl) self.displayNodeDidLoad() } @@ -7138,7 +7141,7 @@ public func shouldUseV2VideoChatImpl(context: AccountContext) -> Bool { return useV2 } -public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) -> Signal { +public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountContext, accountContext: AccountContext, call: VideoChatCall) -> Signal { let useV2 = shouldUseV2VideoChatImpl(context: accountContext) if useV2 { @@ -7148,6 +7151,6 @@ public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountConte } } -public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall, initialData: Any, sourceCallController: CallController?) -> VoiceChatController { +public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: VideoChatCall, initialData: Any, sourceCallController: CallController?) -> VoiceChatController { return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call, sourceCallController: sourceCallController) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d0ced72f79..3de3ad94c3 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -830,7 +830,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.hasOngoingCall.set(true) setNotificationCall(call) - self.callIsConferenceDisposable = (call.hasConference + self.callIsConferenceDisposable = (call.conferenceState + |> filter { $0 != nil } + |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] _ in guard let self else { return @@ -911,7 +913,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.groupCallDisposable = (callManager.currentGroupCallSignal |> deliverOnMainQueue).start(next: { [weak self] call in if let strongSelf = self { - if call !== strongSelf.groupCallController?.call { + if call.flatMap(VideoChatCall.group) != strongSelf.groupCallController?.call { strongSelf.groupCallController?.dismiss(closing: true, manual: false) strongSelf.groupCallController = nil strongSelf.hasOngoingCall.set(false) @@ -939,13 +941,13 @@ public final class SharedAccountContextImpl: SharedAccountContext { } else { strongSelf.hasGroupCallOnScreenPromise.set(true) - let _ = (makeVoiceChatControllerInitialData(sharedContext: strongSelf, accountContext: call.accountContext, call: call) + let _ = (makeVoiceChatControllerInitialData(sharedContext: strongSelf, accountContext: call.accountContext, call: .group(call)) |> deliverOnMainQueue).start(next: { [weak strongSelf, weak navigationController] initialData in guard let strongSelf, let navigationController else { return } - let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call, initialData: initialData, sourceCallController: nil) + let groupCallController = makeVoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil) groupCallController.onViewDidAppear = { [weak strongSelf] in if let strongSelf { strongSelf.hasGroupCallOnScreenPromise.set(true) @@ -979,14 +981,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let call else { return .single((nil, nil)) } - return combineLatest(call.state, call.hasConference) - |> map { [weak call] state, _ -> (PresentationCall?, PresentationGroupCall?) in + return call.state + |> map { [weak call] state -> (PresentationCall?, PresentationGroupCall?) in guard let call else { return (nil, nil) } - if let conferenceCall = call.conferenceCall { - return (nil, conferenceCall) - } switch state.state { case .ringing: return (nil, nil) @@ -1217,9 +1216,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { return } - if let conferenceCall = call.conferenceCall { + if call.conferenceStateValue != nil { if let groupCallController = self.groupCallController { - if groupCallController.call === conferenceCall { + if groupCallController.call == call.conferenceCall.flatMap(VideoChatCall.group) || groupCallController.call == .conferenceSource(call) { return } groupCallController.dismiss(closing: true, manual: false) @@ -1228,11 +1227,19 @@ public final class SharedAccountContextImpl: SharedAccountContext { var transitioniongCallController: CallController? if let callController = self.callController { transitioniongCallController = callController - callController.dismissWithoutAnimation() + if callController.navigationPresentation != .flatModal { + callController.dismissWithoutAnimation() + } self.callController = nil } - let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: conferenceCall.accountContext, call: conferenceCall) + let groupCall: VideoChatCall + if let conferenceCall = call.conferenceCall, case .ready = call.conferenceStateValue { + groupCall = .group(conferenceCall) + } else { + groupCall = .conferenceSource(call) + } + let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: call.context, call: groupCall) |> deliverOnMainQueue).start(next: { [weak self, weak transitioniongCallController] initialData in guard let self else { return @@ -1240,11 +1247,18 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let navigationController = self.mainWindow?.viewController as? NavigationController else { return } - guard let call = self.call, let conferenceCall = call.conferenceCall else { + guard let call = self.call else { return } - let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: conferenceCall.accountContext, call: conferenceCall, initialData: initialData, sourceCallController: transitioniongCallController) + let groupCall: VideoChatCall + if let conferenceCall = call.conferenceCall, case .ready = call.conferenceStateValue { + groupCall = .group(conferenceCall) + } else { + groupCall = .conferenceSource(call) + } + + let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: call.context, call: groupCall, initialData: initialData, sourceCallController: transitioniongCallController) groupCallController.onViewDidAppear = { [weak self] in if let self { self.hasGroupCallOnScreenPromise.set(true) @@ -1258,7 +1272,24 @@ public final class SharedAccountContextImpl: SharedAccountContext { groupCallController.navigationPresentation = .flatModal groupCallController.parentNavigationController = navigationController self.groupCallController = groupCallController - navigationController.pushViewController(groupCallController) + + transitioniongCallController?.onViewDidAppear = nil + transitioniongCallController?.onViewDidDisappear = nil + + self.hasGroupCallOnScreenPromise.set(true) + if let transitioniongCallController, let navigationController = transitioniongCallController.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(where: { $0 === transitioniongCallController }) { + viewControllers.insert(groupCallController, at: index) + navigationController.setViewControllers(viewControllers, animated: false) + viewControllers.remove(at: index + 1) + navigationController.setViewControllers(viewControllers, animated: false) + } else { + navigationController.pushViewController(groupCallController) + } + } else { + navigationController.pushViewController(groupCallController) + } }) } else { if let currentCallController = self.callController {