From 869d607c4a30d339c23f22202024d613105705d4 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 31 Jan 2025 21:07:00 +0400 Subject: [PATCH] [WIP] Conference --- .../Sources/CallController.swift | 34 +- .../Sources/PresentationCall.swift | 87 +-- .../Sources/PresentationCallManager.swift | 2 + .../Sources/PresentationGroupCall.swift | 106 ++- .../Sources/VideoChatScreen.swift | 20 +- .../VideoChatScreenInviteMembers.swift | 638 +++++++++--------- .../Sources/State/CallSessionManager.swift | 17 +- .../Components/ConferenceButtonView.swift | 2 +- .../Sources/Components/EmojiTooltipView.swift | 6 +- .../Sources/Components/KeyEmojiView.swift | 4 +- .../Sources/PrivateCallScreen.swift | 251 +++---- .../Contents.json | 12 + .../addcaller.pdf | Bin 0 -> 4163 bytes .../Sources/SharedAccountContext.swift | 27 +- .../Sources/OngoingCallThreadLocalContext.mm | 2 + third-party/webp/BUILD | 2 +- 16 files changed, 694 insertions(+), 516 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/addcaller.pdf diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index 7b8cf3a91e..020a0d321a 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -483,12 +483,25 @@ public final class CallController: ViewController { } private func conferenceAddParticipant() { + var disablePeerIds: [EnginePeer.Id] = [] + disablePeerIds.append(self.call.context.account.peerId) + disablePeerIds.append(self.call.peerId) + let controller = CallController.openConferenceAddParticipant(context: self.call.context, disablePeerIds: disablePeerIds, completion: { [weak self] peerIds in + guard let self else { + return + } + + let _ = self.call.upgradeToConference(invitePeerIds: peerIds, completion: { _ in + }) + }) + self.push(controller) + } + + static func openConferenceAddParticipant(context: AccountContext, disablePeerIds: [EnginePeer.Id], completion: @escaping ([EnginePeer.Id]) -> Void) -> ViewController { //TODO:localize - let context = self.call.context - let callPeerId = self.call.peerId let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme) - let controller = self.call.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams( - context: self.call.context, + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams( + context: context, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), title: "Invite Members", mode: .peerSelection(searchChatList: true, searchGroups: false, searchChannels: false), @@ -496,7 +509,7 @@ public final class CallController: ViewController { guard case let .user(user) = peer else { return false } - if user.id == context.account.peerId || user.id == callPeerId { + if disablePeerIds.contains(user.id) { return false } if user.botInfo != nil { @@ -506,11 +519,7 @@ public final class CallController: ViewController { } )) controller.navigationPresentation = .modal - let _ = (controller.result |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self, weak controller] result in - guard let self else { - controller?.dismiss() - return - } + let _ = (controller.result |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak controller] result in guard case let .result(peerIds, _) = result else { controller?.dismiss() return @@ -532,11 +541,10 @@ public final class CallController: ViewController { } } - let _ = self.call.upgradeToConference(invitePeerIds: invitePeerIds, completion: { _ in - }) + completion(invitePeerIds) }) - self.push(controller) + return controller } @objc private func backPressed() { diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index c06fdd894f..bbed98f38b 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -177,6 +177,7 @@ public final class PresentationCallImpl: PresentationCall { public let internalId: CallSessionInternalId public let peerId: EnginePeer.Id public let isOutgoing: Bool + private let isIncomingConference: Bool public var isVideo: Bool public var isVideoPossible: Bool private let enableStunMarking: Bool @@ -327,6 +328,7 @@ public final class PresentationCallImpl: PresentationCall { internalId: CallSessionInternalId, peerId: EnginePeer.Id, isOutgoing: Bool, + isIncomingConference: Bool, peer: EnginePeer?, proxyServer: ProxyServerSettings?, auxiliaryServers: [CallAuxiliaryServer], @@ -361,6 +363,7 @@ public final class PresentationCallImpl: PresentationCall { self.internalId = internalId self.peerId = peerId self.isOutgoing = isOutgoing + self.isIncomingConference = isIncomingConference self.isVideo = initialState?.type == .video self.isVideoPossible = isVideoPossible self.enableStunMarking = enableStunMarking @@ -805,6 +808,9 @@ public final class PresentationCallImpl: PresentationCall { conferenceCall.upgradedConferenceCall = self conferenceCall.setInvitedPeers(self.pendingInviteToConferencePeerIds) + for peerId in self.pendingInviteToConferencePeerIds { + let _ = conferenceCall.invitePeer(peerId) + } conferenceCall.setIsMuted(action: self.isMutedValue ? .muted(isPushToTalkActive: false) : .unmuted) if let videoCapturer = self.videoCapturer { @@ -1027,9 +1033,33 @@ public final class PresentationCallImpl: PresentationCall { self.callKitIntegration?.dropCall(uuid: self.internalId) } } - if let presentationState { - self.statePromise.set(presentationState) - self.updateTone(presentationState, callContextState: callContextState, previous: previous) + + var isConference = false + if case let .active(_, _, _, _, _, _, _, _, conferenceCall) = sessionState.state { + isConference = conferenceCall != nil + } else if case .switchedToConference = sessionState.state { + isConference = true + } + if self.conferenceCallImpl != nil { + isConference = true + } + if self.conferenceStateValue != nil { + isConference = true + } + if self.isIncomingConference { + isConference = true + } + + if isConference { + if self.currentTone != nil { + self.currentTone = nil + self.sharedAudioContext?.audioDevice?.setTone(tone: nil) + } + } else { + if let presentationState { + self.statePromise.set(presentationState) + self.updateTone(presentationState, callContextState: callContextState, previous: previous) + } } } @@ -1161,11 +1191,15 @@ public final class PresentationCallImpl: PresentationCall { present(c, a) }, openSettings: { openSettings() - }, { [weak self] value in - guard let strongSelf = self else { + }, { [weak strongSelf] value in + guard let strongSelf else { return } if value { + if strongSelf.isIncomingConference { + strongSelf.conferenceStateValue = .preparing + } + strongSelf.callSessionManager.accept(internalId: strongSelf.internalId) if !fromCallKitAction { strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId) @@ -1175,6 +1209,10 @@ public final class PresentationCallImpl: PresentationCall { } }) } else { + if strongSelf.isIncomingConference { + strongSelf.conferenceStateValue = .preparing + } + strongSelf.callSessionManager.accept(internalId: strongSelf.internalId) if !fromCallKitAction { strongSelf.callKitIntegration?.answerCall(uuid: strongSelf.internalId) @@ -1316,23 +1354,12 @@ public final class PresentationCallImpl: PresentationCall { public func upgradeToConference(invitePeerIds: [EnginePeer.Id], completion: @escaping (PresentationGroupCall) -> Void) -> Disposable { if let conferenceCall = self.conferenceCall { completion(conferenceCall) - - for peerId in invitePeerIds { - let _ = self.requestAddToConference(peerId: peerId) - } - return EmptyDisposable } self.pendingInviteToConferencePeerIds = invitePeerIds - let index = self.upgradedToConferenceCompletions.add({ [weak self] call in + let index = self.upgradedToConferenceCompletions.add({ call in completion(call) - - if let self { - for peerId in invitePeerIds { - let _ = self.requestAddToConference(peerId: peerId) - } - } }) self.conferenceStateValue = .preparing @@ -1348,32 +1375,6 @@ public final class PresentationCallImpl: PresentationCall { } } - private func requestAddToConference(peerId: EnginePeer.Id) -> Disposable { - var conferenceCall: (conference: GroupCallReference, encryptionKey: Data)? - if let sessionState = self.sessionState { - switch sessionState.state { - case let .active(_, key, _, _, _, _, _, _, conferenceCallValue): - if let conferenceCallValue { - conferenceCall = (conferenceCallValue, key) - } - case let .switchedToConference(key, _, conferenceCallValue): - conferenceCall = (conferenceCallValue, key) - default: - break - } - } - guard let conferenceCall else { - return EmptyDisposable - } - return (self.callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: conferenceCall) - |> deliverOnMainQueue).startStandalone(next: { [weak self] requestedInternalId in - guard let self else { - return - } - let _ = self - }) - } - public func setCurrentAudioOutput(_ output: AudioSessionOutput) { if let sharedAudioContext = self.sharedAudioContext { sharedAudioContext.setCurrentAudioOutput(output) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 84852f730a..f6f1714e20 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -325,6 +325,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, + isIncomingConference: firstState.2.isConference, peer: EnginePeer(firstState.1), proxyServer: strongSelf.proxyServer, auxiliaryServers: [], @@ -572,6 +573,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { internalId: internalId, peerId: peerId, isOutgoing: true, + isIncomingConference: false, peer: nil, proxyServer: strongSelf.proxyServer, auxiliaryServers: [], diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 823cadcdac..cdacb13681 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -600,6 +600,52 @@ private final class ScreencastEmbeddedIPCContext: ScreencastIPCContext { } } +private final class PendingConferenceInvitationContext { + private let callSessionManager: CallSessionManager + private var requestDisposable: Disposable? + private var stateDisposable: Disposable? + private var internalId: CallSessionInternalId? + + private var didNotifyEnded: Bool = false + + init(callSessionManager: CallSessionManager, groupCall: GroupCallReference, encryptionKey: Data, peerId: PeerId, onEnded: @escaping () -> Void) { + self.callSessionManager = callSessionManager + + self.requestDisposable = (callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: (groupCall, encryptionKey)) + |> deliverOnMainQueue).startStrict(next: { [weak self] internalId in + guard let self else { + return + } + self.internalId = internalId + + self.stateDisposable = (self.callSessionManager.callState(internalId: internalId) + |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { + return + } + switch state.state { + case .dropping, .terminated: + if !self.didNotifyEnded { + self.didNotifyEnded = true + onEnded() + } + default: + break + } + }) + }) + } + + deinit { + self.requestDisposable?.dispose() + self.stateDisposable?.dispose() + + if let internalId = self.internalId { + self.callSessionManager.drop(internalId: internalId, reason: .hangUp, debugLog: .single(nil)) + } + } +} + public final class PresentationGroupCallImpl: PresentationGroupCall { private enum InternalState { case requesting @@ -1018,7 +1064,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { let debugLog = Promise() - weak var upgradedConferenceCall: PresentationCallImpl? + public weak var upgradedConferenceCall: PresentationCallImpl? + private var conferenceInvitationContexts: [PeerId: PendingConferenceInvitationContext] = [:] init( accountContext: AccountContext, @@ -1859,6 +1906,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var encryptionKey: Data? encryptionKey = self.encryptionKey?.key + encryptionKey = nil let contextAudioSessionActive: Signal if self.sharedAudioContext != nil { @@ -3575,17 +3623,55 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func invitePeer(_ peerId: PeerId) -> Bool { - guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(peerId) else { + if self.isConference { + guard let initialCall = self.initialCall, let encryptionKey = self.encryptionKey else { + return false + } + if conferenceInvitationContexts[peerId] != nil { + return false + } + var onEnded: (() -> Void)? + var didEndAlready = false + let invitationContext = PendingConferenceInvitationContext( + callSessionManager: self.accountContext.account.callSessionManager, + groupCall: GroupCallReference(id: initialCall.id, accessHash: initialCall.accessHash), + encryptionKey: encryptionKey.key, + peerId: peerId, + onEnded: { + didEndAlready = true + onEnded?() + } + ) + if !didEndAlready { + conferenceInvitationContexts[peerId] = invitationContext + if !self.invitedPeersValue.contains(peerId) { + self.invitedPeersValue.append(peerId) + } + onEnded = { [weak self, weak invitationContext] in + guard let self, let invitationContext else { + return + } + if self.conferenceInvitationContexts[peerId] === invitationContext { + self.conferenceInvitationContexts.removeValue(forKey: peerId) + self.invitedPeersValue.removeAll(where: { $0 == peerId }) + } + } + } + return false + } else { + guard let callInfo = self.internalState.callInfo, !self.invitedPeersValue.contains(peerId) else { + return false + } + + var updatedInvitedPeers = self.invitedPeersValue + updatedInvitedPeers.insert(peerId, at: 0) + self.invitedPeersValue = updatedInvitedPeers + + let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start() + + return true } - - var updatedInvitedPeers = self.invitedPeersValue - updatedInvitedPeers.insert(peerId, at: 0) - self.invitedPeersValue = updatedInvitedPeers - - let _ = self.accountContext.engine.calls.inviteToGroupCall(callId: callInfo.id, accessHash: callInfo.accessHash, peerId: peerId).start() - - return true } func setInvitedPeers(_ peerIds: [PeerId]) { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 2156452ce6..faa3b83808 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -1609,15 +1609,19 @@ final class VideoChatScreenComponent: Component { if let members = self.members, let callState = self.callState { var canInvite = true var inviteIsLink = false - if case let .channel(peer) = self.peer { - if peer.flags.contains(.isGigagroup) { - if peer.flags.contains(.isCreator) || peer.adminRights != nil { - } else { - canInvite = false + if case let .group(groupCall) = self.currentCall, groupCall.isConference { + canInvite = true + } else { + if case let .channel(peer) = self.peer { + if peer.flags.contains(.isGigagroup) { + if peer.flags.contains(.isCreator) || peer.adminRights != nil { + } else { + canInvite = false + } + } + if case .broadcast = peer.info, !(peer.addressName?.isEmpty ?? true) { + inviteIsLink = true } - } - if case .broadcast = peer.info, !(peer.addressName?.isEmpty ?? true) { - inviteIsLink = true } } var inviteType: VideoChatParticipantsComponent.Participants.InviteType? diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index 9cb562c58c..22557857bc 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -13,336 +13,358 @@ extension VideoChatScreenComponent.View { return } - var canInvite = true - var inviteIsLink = false - if case let .channel(peer) = self.peer { - if peer.flags.contains(.isGigagroup) { - if peer.flags.contains(.isCreator) || peer.adminRights != nil { - } else { - canInvite = false + if groupCall.isConference { + var disablePeerIds: [EnginePeer.Id] = [] + disablePeerIds.append(groupCall.accountContext.account.peerId) + if let members = self.members { + for participant in members.participants { + if !disablePeerIds.contains(participant.peer.id) { + disablePeerIds.append(participant.peer.id) + } } } - if case .broadcast = peer.info, !(peer.addressName?.isEmpty ?? true) { - inviteIsLink = true - } - } - var inviteType: VideoChatParticipantsComponent.Participants.InviteType? - if canInvite { - if inviteIsLink { - inviteType = .shareLink - } else { - inviteType = .invite - } - } - - guard let inviteType else { - return - } - guard let peerId = groupCall.peerId else { - return - } - - switch inviteType { - case .invite: - 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 environment = self.environment, case let .group(groupCall) = self.currentCall, let groupPeer else { + let controller = CallController.openConferenceAddParticipant(context: groupCall.accountContext, disablePeerIds: disablePeerIds, completion: { [weak self] peerIds in + guard let self, case let .group(groupCall) = self.currentCall else { return } - let inviteLinks = self.inviteLinks - if case let .channel(groupPeer) = groupPeer { - var canInviteMembers = true - if case .broadcast = groupPeer.info, !(groupPeer.addressName?.isEmpty ?? true) { - canInviteMembers = false - } - if !canInviteMembers { - if let inviteLinks { - self.presentShare(inviteLinks) - } - return - } + for peerId in peerIds { + let _ = groupCall.invitePeer(peerId) } - - var filters: [ChannelMembersSearchFilter] = [] - if let members = self.members { - filters.append(.disable(Array(members.participants.map { $0.peer.id }))) - } - if case let .channel(groupPeer) = groupPeer { - if !groupPeer.hasPermission(.inviteMembers) && inviteLinks?.listenerLink == nil { - filters.append(.excludeNonMembers) - } - } else if case let .legacyGroup(groupPeer) = groupPeer { - if groupPeer.hasBannedPermission(.banAddMembers) { - filters.append(.excludeNonMembers) - } - } - filters.append(.excludeBots) - - var dismissController: (() -> Void)? - 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 - } - guard let callState = self.callState else { - return - } - - let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) - if peer.id == callState.myPeerId { - return - } - if let participant { - dismissController?() - - 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: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - } else { - text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string - } - self.presentUndoOverlay(content: .invitedToVoiceChat(context: groupCall.accountContext, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) - } + }) + self.environment?.controller()?.push(controller) + } else { + var canInvite = true + var inviteIsLink = false + if case let .channel(peer) = self.peer { + if peer.flags.contains(.isGigagroup) { + if peer.flags.contains(.isCreator) || peer.adminRights != nil { } 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: 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: 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, case let .group(groupCall) = self.currentCall else { - return - } - - 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: 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: 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: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), groupPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + canInvite = false + } + } + if case .broadcast = peer.info, !(peer.addressName?.isEmpty ?? true) { + inviteIsLink = true + } + } + var inviteType: VideoChatParticipantsComponent.Participants.InviteType? + if canInvite { + if inviteIsLink { + inviteType = .shareLink + } else { + inviteType = .invite + } + } + + guard let inviteType else { + return + } + guard let peerId = groupCall.peerId else { + return + } + + switch inviteType { + case .invite: + 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 environment = self.environment, case let .group(groupCall) = self.currentCall, let groupPeer else { + return + } + let inviteLinks = self.inviteLinks + + if case let .channel(groupPeer) = groupPeer { + var canInviteMembers = true + if case .broadcast = groupPeer.info, !(groupPeer.addressName?.isEmpty ?? true) { + canInviteMembers = false + } + if !canInviteMembers { + if let inviteLinks { + self.presentShare(inviteLinks) } - - 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 - } - - if case let .channel(groupPeer) = groupPeer { - guard let selfController = environment.controller() else { - return - } - let inviteDisposable = self.inviteDisposable - 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: { - cancelImpl?() - })) - selfController?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - inviteSignal = inviteSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { - inviteDisposable.set(nil) - } - - inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in - dismissController?() - guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { - return - } - - let text: String - switch error { - case .limitExceeded: - text = environment.strings.Channel_ErrorAddTooMuch - case .tooMuchJoined: - text = environment.strings.Invite_ChannelsTooMuch - case .generic: - text = environment.strings.Login_UnknownError - case .restricted: - text = environment.strings.Channel_ErrorAddBlocked - case .notMutualContact: - if case .broadcast = groupPeer.info { - text = environment.strings.Channel_AddUserLeftError - } else { - text = environment.strings.GroupInfo_AddUserLeftError - } - case .botDoesntSupportGroups: - text = environment.strings.Channel_BotDoesntSupportGroups - case .tooMuchBots: - text = environment.strings.Channel_TooMuchBots - case .bot: - text = environment.strings.Login_UnknownError - case .kicked: - text = environment.strings.Channel_AddUserKickedError - } - 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 environment = self.environment, case let .group(groupCall) = self.currentCall else { - dismissController?() - return - } - dismissController?() - - 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: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) - } - })) - } else if case let .legacyGroup(groupPeer) = groupPeer { - guard let selfController = environment.controller() else { - return - } - let inviteDisposable = self.inviteDisposable - 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: { - cancelImpl?() - })) - selfController?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - inviteSignal = inviteSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - cancelImpl = { - inviteDisposable.set(nil) - } - - inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in - dismissController?() - guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { - return - } - let context = groupCall.accountContext - - switch error { - case .privacy: - 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: 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)) - case .tooManyChannels: - environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) - case .groupFull, .generic: - 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 environment = self.environment, case let .group(groupCall) = self.currentCall else { - dismissController?() - return - } - dismissController?() - - 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: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) - } - })) - } - })]), in: .window(.root)) + return } } - }) - controller.copyInviteLink = { [weak self] in - dismissController?() - guard let self, case let .group(groupCall) = self.currentCall else { - return + var filters: [ChannelMembersSearchFilter] = [] + if let members = self.members { + filters.append(.disable(Array(members.participants.map { $0.peer.id }))) } - guard let callPeerId = groupCall.peerId else { - return - } - - let _ = (groupCall.accountContext.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: callPeerId), - TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: callPeerId) - ) - |> map { peer, exportedInvitation -> String? in - if let link = inviteLinks?.listenerLink { - return link - } else if let peer = peer, let addressName = peer.addressName, !addressName.isEmpty { - return "https://t.me/\(addressName)" - } else if let link = exportedInvitation?.link { - return link - } else { - return nil + if case let .channel(groupPeer) = groupPeer { + if !groupPeer.hasPermission(.inviteMembers) && inviteLinks?.listenerLink == nil { + filters.append(.excludeNonMembers) + } + } else if case let .legacyGroup(groupPeer) = groupPeer { + if groupPeer.hasBannedPermission(.banAddMembers) { + filters.append(.excludeNonMembers) } } - |> deliverOnMainQueue).start(next: { [weak self] link in - guard let self, let environment = self.environment else { + filters.append(.excludeBots) + + var dismissController: (() -> Void)? + 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 + } + guard let callState = self.callState else { return } - if let link { - UIPasteboard.general.string = link + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme) + if peer.id == callState.myPeerId { + return + } + if let participant { + dismissController?() - self.presentUndoOverlay(content: .linkCopied(title: nil, text: environment.strings.VoiceChat_InviteLinkCopiedText), action: { _ in return false }) + 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: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + } else { + text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string + } + 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: 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: 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, case let .group(groupCall) = self.currentCall else { + return + } + + 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: 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: 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: 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: 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 + } + + if case let .channel(groupPeer) = groupPeer { + guard let selfController = environment.controller() else { + return + } + let inviteDisposable = self.inviteDisposable + 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: { + cancelImpl?() + })) + selfController?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + inviteSignal = inviteSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { + inviteDisposable.set(nil) + } + + inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in + dismissController?() + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + + let text: String + switch error { + case .limitExceeded: + text = environment.strings.Channel_ErrorAddTooMuch + case .tooMuchJoined: + text = environment.strings.Invite_ChannelsTooMuch + case .generic: + text = environment.strings.Login_UnknownError + case .restricted: + text = environment.strings.Channel_ErrorAddBlocked + case .notMutualContact: + if case .broadcast = groupPeer.info { + text = environment.strings.Channel_AddUserLeftError + } else { + text = environment.strings.GroupInfo_AddUserLeftError + } + case .botDoesntSupportGroups: + text = environment.strings.Channel_BotDoesntSupportGroups + case .tooMuchBots: + text = environment.strings.Channel_TooMuchBots + case .bot: + text = environment.strings.Login_UnknownError + case .kicked: + text = environment.strings.Channel_AddUserKickedError + } + 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 environment = self.environment, case let .group(groupCall) = self.currentCall else { + dismissController?() + return + } + dismissController?() + + 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: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + } + })) + } else if case let .legacyGroup(groupPeer) = groupPeer { + guard let selfController = environment.controller() else { + return + } + let inviteDisposable = self.inviteDisposable + 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: { + cancelImpl?() + })) + selfController?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + inviteSignal = inviteSignal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { + inviteDisposable.set(nil) + } + + inviteDisposable.set((inviteSignal |> deliverOnMainQueue).start(error: { [weak self] error in + dismissController?() + guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { + return + } + let context = groupCall.accountContext + + switch error { + case .privacy: + 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: 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)) + case .tooManyChannels: + environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) + case .groupFull, .generic: + 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 environment = self.environment, case let .group(groupCall) = self.currentCall else { + dismissController?() + return + } + dismissController?() + + 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: groupCall.accountContext, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false }) + } + })) + } + })]), in: .window(.root)) + } } }) + controller.copyInviteLink = { [weak self] in + dismissController?() + + guard let self, case let .group(groupCall) = self.currentCall else { + return + } + guard let callPeerId = groupCall.peerId else { + return + } + + let _ = (groupCall.accountContext.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: callPeerId), + TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: callPeerId) + ) + |> map { peer, exportedInvitation -> String? in + if let link = inviteLinks?.listenerLink { + return link + } else if let peer = peer, let addressName = peer.addressName, !addressName.isEmpty { + return "https://t.me/\(addressName)" + } else if let link = exportedInvitation?.link { + return link + } else { + return nil + } + } + |> deliverOnMainQueue).start(next: { [weak self] link in + guard let self, let environment = self.environment else { + return + } + + if let link { + UIPasteboard.general.string = link + + self.presentUndoOverlay(content: .linkCopied(title: nil, text: environment.strings.VoiceChat_InviteLinkCopiedText), action: { _ in return false }) + } + }) + } + dismissController = { [weak controller] in + controller?.dismiss() + } + environment.controller()?.push(controller) + }) + case .shareLink: + guard let inviteLinks = self.inviteLinks else { + return } - dismissController = { [weak controller] in - controller?.dismiss() - } - environment.controller()?.push(controller) - }) - case .shareLink: - guard let inviteLinks = self.inviteLinks else { - return + self.presentShare(inviteLinks) } - self.presentShare(inviteLinks) } } } diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index c21584858e..51d4f5a6dc 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -137,6 +137,7 @@ public struct CallSessionRingingState: Equatable { public let peerId: PeerId public let isVideo: Bool public let isVideoPossible: Bool + public let isConference: Bool } public enum DropCallReason { @@ -334,6 +335,7 @@ private func parseConnectionSet(primary: Api.PhoneConnection, alternative: [Api. private final class CallSessionContext { let peerId: PeerId let isOutgoing: Bool + let isConference: Bool var type: CallSession.CallType var isVideoPossible: Bool let pendingConference: (conference: GroupCallReference, encryptionKey: Data)? @@ -353,9 +355,10 @@ private final class CallSessionContext { } } - init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { + init(peerId: PeerId, isOutgoing: Bool, isConference: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { self.peerId = peerId self.isOutgoing = isOutgoing + self.isConference = isConference self.type = type self.isVideoPossible = isVideoPossible self.pendingConference = pendingConference @@ -547,7 +550,13 @@ private final class CallSessionManagerContext { var ringingContexts: [CallSessionRingingState] = [] for (id, context) in self.contexts { if case .ringing = context.state { - ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId, isVideo: context.type == .video, isVideoPossible: context.isVideoPossible)) + ringingContexts.append(CallSessionRingingState( + id: id, + peerId: context.peerId, + isVideo: context.type == .video, + isVideoPossible: context.isVideoPossible, + isConference: context.isConference + )) } } return ringingContexts @@ -590,7 +599,7 @@ private final class CallSessionManagerContext { //#endif let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId) - let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) + let context = CallSessionContext(peerId: peerId, isOutgoing: false, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) self.contexts[internalId] = context let queue = self.queue @@ -1164,7 +1173,7 @@ private final class CallSessionManagerContext { let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self)) let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free) if randomStatus == 0 { - self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in + self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, isConference: conferenceCall != nil, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in if let strongSelf = self, let context = strongSelf.contexts[internalId] { if case .requesting = context.state { switch result { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ConferenceButtonView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ConferenceButtonView.swift index c04926106a..3430c7ea91 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ConferenceButtonView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ConferenceButtonView.swift @@ -91,7 +91,7 @@ final class ConferenceButtonView: HighlightTrackingButton, OverlayMaskContainerV transition.setFrame(view: self.backdropBackgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) if self.iconView.image == nil { - self.iconView.image = UIImage(bundleImageName: "Contact List/AddMemberIcon")?.withRenderingMode(.alwaysTemplate) + self.iconView.image = UIImage(bundleImageName: "Call/CallNavigationAddPerson")?.withRenderingMode(.alwaysTemplate) self.iconView.tintColor = .white } diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/EmojiTooltipView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/EmojiTooltipView.swift index efb0bdd5d6..17aae06536 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/EmojiTooltipView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/EmojiTooltipView.swift @@ -67,7 +67,7 @@ final class EmojiTooltipView: OverlayMaskContainerView { } func animateIn() { - let anchorPoint = CGPoint(x: self.bounds.width - 46.0, y: 0.0) + let anchorPoint = CGPoint(x: self.bounds.width * 0.5, y: 0.0) self.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -75,7 +75,7 @@ final class EmojiTooltipView: OverlayMaskContainerView { } func animateOut(completion: @escaping () -> Void) { - let anchorPoint = CGPoint(x: self.bounds.width - 46.0, y: 0.0) + let anchorPoint = CGPoint(x: self.bounds.width * 0.5, y: 0.0) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in completion() }) @@ -126,7 +126,7 @@ final class EmojiTooltipView: OverlayMaskContainerView { addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(x: 0.0, y: arrowHeight), size: CGSize(width: size.width, height: size.height - arrowHeight)), radius: 14.0) context.fillPath() - context.translateBy(x: size.width - floor(params.subjectWidth * 0.5) - 20.0, y: 0.0) + context.translateBy(x: size.width * 0.5 - 10.0, y: 0.0) let _ = try? drawSvgPath(context, path: "M9.0981,1.1979 C9.547,0.6431 10.453,0.6431 10.9019,1.1979 C12.4041,3.0542 15.6848,6.5616 20,8 H-0.0002 C4.3151,6.5616 7.5959,3.0542 9.0981,1.1978 Z ") }) self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/KeyEmojiView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/KeyEmojiView.swift index 557c56a4d1..21814cde23 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/KeyEmojiView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/KeyEmojiView.swift @@ -93,7 +93,7 @@ final class KeyEmojiView: HighlightTrackingButton { for i in 0 ..< self.emojiViews.count { let emojiView = self.emojiViews[i] emojiView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - emojiView.layer.animatePosition(from: CGPoint(x: -CGFloat(self.emojiViews.count - 1 - i) * 30.0, y: 0.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + emojiView.layer.animatePosition(from: CGPoint(x: CGFloat(i) * 30.0, y: 0.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } } @@ -109,7 +109,7 @@ final class KeyEmojiView: HighlightTrackingButton { } private func update(params: Params, transition: ComponentTransition) -> CGSize { - let itemSpacing: CGFloat = 3.0 + let itemSpacing: CGFloat = 1.0 var height: CGFloat = 0.0 var nextX = 0.0 diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index caa0d4c1e9..b9ff915b87 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -610,7 +610,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden { self.displayEmojiTooltip = true - self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { [weak self] _ in + self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in guard let self else { return } @@ -962,126 +962,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu } } - if !isConferencePossible, case let .active(activeState) = params.state.lifecycleState { - let emojiView: KeyEmojiView - var emojiTransition = transition - var emojiAlphaTransition = genericAlphaTransition - if let current = self.emojiView { - emojiView = current - } else { - emojiTransition = transition.withAnimation(.none) - emojiAlphaTransition = genericAlphaTransition.withAnimation(.none) - emojiView = KeyEmojiView(emoji: activeState.emojiKey) - self.emojiView = emojiView - emojiView.pressAction = { [weak self] in - guard let self else { - return - } - if !self.isEmojiKeyExpanded { - self.isEmojiKeyExpanded = true - self.displayEmojiTooltip = false - self.update(transition: .spring(duration: 0.4)) - } - } - } - if emojiView.superview == nil { - self.addSubview(emojiView) - if !transition.animation.isImmediate { - emojiView.animateIn() - } - } - emojiView.isUserInteractionEnabled = !self.isEmojiKeyExpanded - - let emojiViewWasExpanded = emojiView.isExpanded - let emojiViewSize = emojiView.update(isExpanded: self.isEmojiKeyExpanded, transition: emojiTransition) - - if self.isEmojiKeyExpanded { - let emojiViewFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiViewSize.width) * 0.5), y: params.insets.top + 93.0), size: emojiViewSize) - - if case let .curve(duration, curve) = transition.animation, let emojiViewWasExpanded, !emojiViewWasExpanded { - let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y) - let positionKeyframes = generateParabollicMotionKeyframes(from: emojiView.center, to: emojiViewFrame.center, elevation: -distance.y * 0.8, duration: duration, curve: curve, reverse: false) - emojiView.center = emojiViewFrame.center - emojiView.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position", additive: false) - } else { - emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center) - } - emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size)) - if self.isAnimatedOutToGroupCall { - emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) - } - - if let emojiTooltipView = self.emojiTooltipView { - self.emojiTooltipView = nil - emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in - emojiTooltipView?.removeFromSuperview() - }) - } - } else { - let emojiY: CGFloat - if currentAreControlsHidden { - emojiY = -8.0 - emojiViewSize.height - } else { - emojiY = params.insets.top + 12.0 - } - let emojiViewFrame = CGRect(origin: CGPoint(x: params.size.width - params.insets.right - 12.0 - emojiViewSize.width, y: emojiY), size: emojiViewSize) - - if case let .curve(duration, curve) = transition.animation, let emojiViewWasExpanded, emojiViewWasExpanded { - let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y) - let positionKeyframes = generateParabollicMotionKeyframes(from: emojiViewFrame.center, to: emojiView.center, elevation: distance.y * 0.8, duration: duration, curve: curve, reverse: true) - emojiView.center = emojiViewFrame.center - emojiView.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position", additive: false) - } else { - emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center) - } - emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size)) - emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) - - if self.displayEmojiTooltip { - let emojiTooltipView: EmojiTooltipView - var emojiTooltipTransition = transition - var animateIn = false - if let current = self.emojiTooltipView { - emojiTooltipView = current - } else { - emojiTooltipTransition = emojiTooltipTransition.withAnimation(.none) - emojiTooltipView = EmojiTooltipView(text: params.state.strings.Call_EncryptionKeyTooltip) - animateIn = true - self.emojiTooltipView = emojiTooltipView - self.addSubview(emojiTooltipView) - } - - let emojiTooltipSize = emojiTooltipView.update(constrainedWidth: params.size.width - 32.0 * 2.0, subjectWidth: emojiViewSize.width - 20.0) - let emojiTooltipFrame = CGRect(origin: CGPoint(x: emojiViewFrame.maxX - emojiTooltipSize.width, y: emojiViewFrame.maxY + 8.0), size: emojiTooltipSize) - emojiTooltipTransition.setFrame(view: emojiTooltipView, frame: emojiTooltipFrame) - - if animateIn && !transition.animation.isImmediate { - emojiTooltipView.animateIn() - } - } else if let emojiTooltipView = self.emojiTooltipView { - self.emojiTooltipView = nil - emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in - emojiTooltipView?.removeFromSuperview() - }) - } - } - - emojiAlphaTransition.setAlpha(view: emojiView, alpha: 1.0) - } else { - if let emojiView = self.emojiView { - self.emojiView = nil - genericAlphaTransition.setAlpha(view: emojiView, alpha: 0.0, completion: { [weak emojiView] _ in - emojiView?.removeFromSuperview() - }) - } - if let emojiTooltipView = self.emojiTooltipView { - self.emojiTooltipView = nil - emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in - emojiTooltipView?.removeFromSuperview() - }) - } - } - let collapsedAvatarSize: CGFloat = 136.0 let blobSize: CGFloat = collapsedAvatarSize + 40.0 @@ -1435,9 +1315,91 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu transition.setFrame(view: self.titleView, frame: titleFrame) genericAlphaTransition.setAlpha(view: self.titleView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) + var emojiViewSizeValue: CGSize? + var emojiTransition = transition + var emojiAlphaTransition = genericAlphaTransition + let emojiViewWasExpanded = self.emojiView?.isExpanded ?? false + if case let .active(activeState) = params.state.lifecycleState { + let emojiView: KeyEmojiView + if let current = self.emojiView { + emojiView = current + } else { + emojiTransition = transition.withAnimation(.none) + emojiAlphaTransition = genericAlphaTransition.withAnimation(.none) + emojiView = KeyEmojiView(emoji: activeState.emojiKey) + self.emojiView = emojiView + emojiView.pressAction = { [weak self] in + guard let self else { + return + } + if !self.isEmojiKeyExpanded { + self.isEmojiKeyExpanded = true + self.displayEmojiTooltip = false + self.update(transition: .spring(duration: 0.4)) + } + } + } + if emojiView.superview == nil { + self.addSubview(emojiView) + if !transition.animation.isImmediate { + emojiView.animateIn() + } + } + emojiView.isUserInteractionEnabled = !self.isEmojiKeyExpanded + + let emojiViewSize = emojiView.update(isExpanded: self.isEmojiKeyExpanded, transition: emojiTransition) + emojiViewSizeValue = emojiViewSize + + if self.isEmojiKeyExpanded { + let emojiViewFrame = CGRect(origin: CGPoint(x: floor((params.size.width - emojiViewSize.width) * 0.5), y: params.insets.top + 93.0), size: emojiViewSize) + + if case let .curve(duration, curve) = transition.animation, !emojiViewWasExpanded { + let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y) + let positionKeyframes = generateParabollicMotionKeyframes(from: emojiView.center, to: emojiViewFrame.center, elevation: -distance.y * 0.8, duration: duration, curve: curve, reverse: false) + emojiView.center = emojiViewFrame.center + emojiView.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position", additive: false) + } else { + emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center) + } + emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size)) + if self.isAnimatedOutToGroupCall { + emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) + } + + if let emojiTooltipView = self.emojiTooltipView { + self.emojiTooltipView = nil + emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in + emojiTooltipView?.removeFromSuperview() + }) + } + } else { + // Inline mode handled after calculating status frame + } + + emojiAlphaTransition.setAlpha(view: emojiView, alpha: 1.0) + } else { + if let emojiView = self.emojiView { + self.emojiView = nil + genericAlphaTransition.setAlpha(view: emojiView, alpha: 0.0, completion: { [weak emojiView] _ in + emojiView?.removeFromSuperview() + }) + } + if let emojiTooltipView = self.emojiTooltipView { + self.emojiTooltipView = nil + emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in + emojiTooltipView?.removeFromSuperview() + }) + } + } + + var statusEmojiWidth: CGFloat = 0.0 + let statusEmojiSpacing: CGFloat = 8.0 + if !self.isEmojiKeyExpanded, let emojiViewSize = emojiViewSizeValue { + statusEmojiWidth = statusEmojiSpacing + emojiViewSize.width + } let statusFrame = CGRect( origin: CGPoint( - x: (params.size.width - statusSize.width) * 0.5, + x: (params.size.width - statusSize.width - statusEmojiWidth) * 0.5, y: titleFrame.maxY + (havePrimaryVideo ? 0.0 : 4.0) ), size: statusSize @@ -1455,6 +1417,51 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu genericAlphaTransition.setAlpha(view: self.statusView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) } + if case .active = params.state.lifecycleState { + if let emojiView = self.emojiView, !self.isEmojiKeyExpanded, let emojiViewSize = emojiViewSizeValue { + let emojiViewFrame = CGRect(origin: CGPoint(x: statusFrame.maxX + statusEmojiSpacing, y: statusFrame.minY + floorToScreenPixels((statusFrame.height - emojiViewSize.height) * 0.5)), size: emojiViewSize) + + if case let .curve(duration, curve) = transition.animation, emojiViewWasExpanded { + let distance = CGPoint(x: emojiViewFrame.midX - emojiView.center.x, y: emojiViewFrame.midY - emojiView.center.y) + let positionKeyframes = generateParabollicMotionKeyframes(from: emojiViewFrame.center, to: emojiView.center, elevation: distance.y * 0.8, duration: duration, curve: curve, reverse: true) + emojiView.center = emojiViewFrame.center + emojiView.layer.animateKeyframes(values: positionKeyframes.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position", additive: false) + } else { + emojiTransition.setPosition(view: emojiView, position: emojiViewFrame.center) + } + emojiTransition.setBounds(view: emojiView, bounds: CGRect(origin: CGPoint(), size: emojiViewFrame.size)) + emojiAlphaTransition.setAlpha(view: emojiView, alpha: (currentAreControlsHidden || self.isAnimatedOutToGroupCall) ? 0.0 : 1.0) + + if self.displayEmojiTooltip { + let emojiTooltipView: EmojiTooltipView + var emojiTooltipTransition = transition + var animateIn = false + if let current = self.emojiTooltipView { + emojiTooltipView = current + } else { + emojiTooltipTransition = emojiTooltipTransition.withAnimation(.none) + emojiTooltipView = EmojiTooltipView(text: params.state.strings.Call_EncryptionKeyTooltip) + animateIn = true + self.emojiTooltipView = emojiTooltipView + self.addSubview(emojiTooltipView) + } + + let emojiTooltipSize = emojiTooltipView.update(constrainedWidth: params.size.width - 32.0 * 2.0, subjectWidth: emojiViewSize.width * 0.5) + let emojiTooltipFrame = CGRect(origin: CGPoint(x: emojiViewFrame.minX + floor((emojiViewFrame.width - emojiTooltipSize.width) * 0.5), y: emojiViewFrame.maxY + 8.0), size: emojiTooltipSize) + emojiTooltipTransition.setFrame(view: emojiTooltipView, frame: emojiTooltipFrame) + + if animateIn && !transition.animation.isImmediate { + emojiTooltipView.animateIn() + } + } else if let emojiTooltipView = self.emojiTooltipView { + self.emojiTooltipView = nil + emojiTooltipView.animateOut(completion: { [weak emojiTooltipView] in + emojiTooltipView?.removeFromSuperview() + }) + } + } + } + if case let .active(activeState) = params.state.lifecycleState, activeState.signalInfo.quality <= 0.2, !self.isEmojiKeyExpanded, (!self.displayEmojiTooltip || !havePrimaryVideo) { let weakSignalView: WeakSignalView if let current = self.weakSignalView { diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/Contents.json new file mode 100644 index 0000000000..f3d3c4027c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "addcaller.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/addcaller.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallNavigationAddPerson.imageset/addcaller.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cd0252b6f831f30ddbcafde599e7b9c6f9b480af GIT binary patch literal 4163 zcmai1c{r3^8z;sxkwS#%5fWhxW|)-hk);_s5rdJj4P(S>36UjR$i9;`5=nM4vP4-T zyNE){zGwMndaL*AdcW)X&L7WnU%zwi-*fK!JomYNw*XRERTL^F4F*F2aR6s&2L^yk zmjH-@0|A3~LK83mL>Y4%YlYFdsz{n54Tvh1JV$yvkfxO}Ryb?S&lydO(=CE6AOV9* z|3tI_Ns=We2X-akF=$7yN9yx;%gHgo-k?sXUF!X=j3w_3;y^F&Yp^&-5+>__d78eJj#}f6qFkc;G~(ERN5mdzEcre%({qnw5Ktn!Y0d;Zntco%{guA-V^w;oMJ^ z*M^NHwK0A_M$l{F+GJxku0KS6uV?kxV-PD(Wp5rcnwh4{kWXae;_Ona;-bx_~oc{hi^>yJhNF%fJg5mtOA!EJxVR7n%Db146q|ngnWs-v!ZMFZ`AKGiDgweL!Bvz_0hHnlk1QDKsC6f~6a4`? zS`;Ashz6@f^MJ#e3d(EP&Si20q!f*0U}d7`rLUkIp*bo43W?AI4Jceun#~OBffX>- zFh$6-^qBVuo&v?9Ig9DJn&ZV;HvOn3ksc>cGvyN(CQUu0O=%2?`I9!=XT7O-{BoBy zJUBr}{)gaew6El$C=oqC7<@9AISZb(ipnjp)1xY490;fhh{-;OI@4<;byDl9WY1gM zE9+n>_O!@}Kr!Wnoa>n>z2kGTb0^kWcP|9p?QKd#r;0mhQlI0Az1Kl}XlV!4O6*Le z>^k+Cn=MTLB`2Eu>75}phqJdYS04AG8x8r^vWdQ0_D<%sHsh@z*O#D=GBQvxCimm+ zV0X&FC@!K;Q|$7H2Ui`F92n9vJE^fExWc`CXU}zyLBZcb-uWywvq#|bz_W^sSrN`* z;;M&@rFf@~sW)7|qm+eY5L$eA{(fS4iq6$)<-as{fU9xO9$0m*%)mSK+hLs>o%Jz- z?Og3%?WOVNg66`$1_$wbkB%t3MEd78p@tqIvz)cjYG{+_BHNNv1&P^41s*yp$senj zwL6dli{zzBPVc~9It&$_iIM}3N1Zcf&8 zK3{KP-~2#QFRafjDL0f;=y9>PweJOo|@6C%E6&i0I`cuC2!#=QZZpv(% ztj~%@S4|xwvJqYQ^!X}#FXnjW1h=wUNlz1|?WSwb8L{N4$8_+FoVXrP|_;jA{tdGAOZ(j1X zIHts|IH)A9c)!H8c(nMi9Qu4}L~m%!OxbwmneAHkdzDhhHx4_M!H!GAsBiHj-<~d% zf45w+fRy8&W;pk_hqx3FPFeOPDs(A+*1sB_lRs_KcDpS~dsEvoUsBumqGW1p>h+6J z5V%Q~x#=^HewD#{(>kBdy5-ikR`IyS)y!A9j1`Ti*DO@8joFW9cqMx9d#0{>e8PSb zT6I|U?#5d>TDy5>)p^&i)_c~+)fsvucwO1--3Z+(+)7$~zGk*GIv>1pba`|^y5+sK z>#*BbAz#|Vh=Xy;8NUa9tA6n?wLB-dV&8!P4f&foA{-9BG~I5d$0zd z2p?Q^EJQDgRk>1iD!Vz`E+@~>vvjNxFES!_5#s3_*IV>ef6(A@srF5uI}v-FZ{u#B1<(5rK$%rEMKh7p*&~{U&&R^`jG`bx25)!A?pSNdI@R#G7o`zyr1+4t}QtDy# z6ZmXpp1J6q%M(xD-E_IRUR6y(3;TRyS1d^?y7E#xn@I!599W(GaPe!QdF!`MAH`}VU8tg$1{MuXZI`{)Y_xnTGZ2Jxuc8Q!u6t!0Ci|S zpLX9dzwjy*xT}VObqk+Wde_?qC^d!Qk*0*~;o+pB;bBO-2$D!axOM=FpzGxWW1Osi zDngP)R-Isp-}3W^eEbDV{)TS=5OpObMYJo%8X#*`9l(rC^TS!{_bRe>Rl+&o@Vd@u zE6fims{{qeEIK3^N>-x3d|-e0{9jQECsX{XWho;bDny=!(y468sg&@&g2G#$rn4v` z6xC5|m-$p&eTB|Mh2d|(byM=pU(!-NIRhco?OnUF?Anmu7>ZBoMu|;(8LdQ~k1{a& zn(0}#wh_7SP?v$DZ*T*OxLc|N?()s$JuNQiQ4m-Y7aAz}R5-;prx_E5d|an+tnooD zYUc7gMu8~H{;yR7>#McTXfCqEn0*3mICM&`!z%?2VgX?|rxJGot0I>PTi#2&vIg+C`ueuz3oM*c`a1`17IONSjG=o0# z4KJG(+iM^$A3ABhkTEL)zhZjPok$g7>b<1hFqim|!W?=eclOr^8%${%JAKhelz;ns zdnNw8pyx%@1^xOPPen=+P|@^XCerj9W?Ov)CyecqG;T>TDj}^<6>RD?$6~-np@9P$ zE6c`akaGvBm5=@S4TNcso@nkxO#X|CrdmAkB$Erq7`^Gn(V~7x~wvgX|GO z(xAmBfe}Pef5VxhH;!DGKDu^-V;}tSgKkwAIt%hSG(8SpzM8hZ zEc&@M+{=P0{(vPQWPx@~SrVUjdp{!Zpw*dS$?p6PDqrUpSK<~+%I-v3?ov*2bZ;rp z;I<%lNVVF8&-@>SMgDRB?(5)i@!x;H^l$V>x98-9Be()L%zn0QdVe{S8b2iZ*00V^ z3uBE%E8;wW8-Tc&_>b|AA@v*j$NI&jgK@>V;jJ*P0J%oV1w?Kkm2gf3j1%dL!C5PvX{19biULGs4_ z4qORMKs(@W{psp~$Jl_y0jN0m@A%1201ksm!T=Kfb3ny`9~R*B6Ow=+kz)LFmCV1mBw;X868~5w36mzB@1Kyg6e-F-AsGqM4fqox;L%tI3?58AcXee_ zBG0?x95F(`d91b2-|2+t;BW+hoT?v31JT8LV#sm-Xyeg%!jDWy%E(B71q4*CtNsTg CZA9z< literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 936e89ac60..4bc8355609 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -807,6 +807,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } if call !== self.call { + let previousCall = self.call self.call = call self.callController?.dismiss() @@ -814,6 +815,25 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.hasOngoingCall.set(false) self.callState.set(.single(nil)) + if let previousCall, let groupCallController = self.groupCallController { + var matches = false + switch groupCallController.call { + case let .conferenceSource(conferenceSource): + if conferenceSource === previousCall { + matches = true + } + case let .group(groupCall): + if (groupCall as? PresentationGroupCallImpl)?.upgradedConferenceCall === previousCall { + matches = true + } + } + + if matches { + self.groupCallController = nil + groupCallController.dismiss(closing: true, manual: false) + } + } + self.notificationController?.setBlocking(nil) self.callPeerDisposable?.dispose() @@ -822,7 +842,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.callIsConferenceDisposable = nil if let call { - if call.conferenceCall == nil { + if call.conferenceStateValue == nil && call.conferenceCall == nil { self.callState.set(call.state |> map(Optional.init)) } @@ -841,6 +861,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { return } guard let callController = self.callController, callController.call === call else { + if self.callController == nil, call.conferenceStateValue != nil { + self.callState.set(.single(nil)) + self.presentControllerWithCurrentCall() + self.notificationController?.setBlocking(nil) + } return } if call.conferenceStateValue != nil { diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index ea146373e4..34cd9ae39e 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -90,6 +90,7 @@ public: _audioDeviceModule->StopPlayout(); _audioDeviceModule->StopRecording(); } + _audioDeviceModule->ActualTerminate(); _audioDeviceModule = nullptr; } else { tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() { @@ -97,6 +98,7 @@ public: _audioDeviceModule->StopPlayout(); _audioDeviceModule->StopRecording(); } + _audioDeviceModule->ActualTerminate(); _audioDeviceModule = nullptr; }); } diff --git a/third-party/webp/BUILD b/third-party/webp/BUILD index b6ef3be99f..310c69bfd3 100644 --- a/third-party/webp/BUILD +++ b/third-party/webp/BUILD @@ -56,7 +56,7 @@ genrule( mkdir -p "$$BUILD_DIR/Public/libwebp" - PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" sh $$BUILD_DIR/build-webp-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libwebp" "$$BUILD_DIR" + PATH="$$PATH:$$CMAKE_DIR/cmake-3.23.1-macos-universal/CMake.app/Contents/bin" sh $$BUILD_DIR/build-webp-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libwebp" "$$BUILD_DIR" >/dev/null 2>&1 """ + "\n".join([ "cp -f \"$$BUILD_DIR/libwebp/src/webp/{}\" \"$(location Public/webp/{})\"".format(header, header) for header in headers