From 636508fc978f508cfbdc7cf63ffe29e5aa46d909 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 8 Jul 2025 15:59:17 +0400 Subject: [PATCH] Various improvements (cherry picked from commit 7e240c7064a22c0ae48e13c7352dcf383d3a9349) --- .../Sources/PresentationCallManager.swift | 11 +++-- .../AsyncDisplayKit/Source/ASDisplayNode.mm | 2 +- .../CallListUI/Sources/CallListCallItem.swift | 6 +++ .../Sources/CallListController.swift | 7 ++++ .../Sources/CallListControllerNode.swift | 3 ++ .../Sources/Items/ChatImageGalleryItem.swift | 11 +---- .../Sources/PresentationCallManager.swift | 35 ++++++++-------- .../Sources/PresentationGroupCall.swift | 18 +++++++- ...pandedParticipantThumbnailsComponent.swift | 11 +++-- .../Sources/VideoChatScreen.swift | 2 +- ...ideoChatScreenParticipantContextMenu.swift | 7 +++- .../State/ConferenceCallE2EContext.swift | 32 +++++++++++---- ...edSynchronizeEmojiKeywordsOperations.swift | 4 +- .../TelegramEngine/Calls/GroupCalls.swift | 19 +++++++++ .../TelegramEngine/Messages/Stories.swift | 6 +++ .../ChatMessageActionBubbleContentNode.swift | 36 ++++++---------- .../ChatMessageInteractiveMediaNode/BUILD | 1 + .../ChatMessageInteractiveMediaNode.swift | 7 +++- .../TelegramUI/Sources/AccountContext.swift | 41 +++++++++++++++---- .../td/TdBinding/Public/TdBinding/TdBinding.h | 2 +- third-party/td/TdBinding/Sources/TdBinding.mm | 5 ++- 21 files changed, 177 insertions(+), 89 deletions(-) diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 2a6110ce99..6ca0cb1725 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -6,19 +6,24 @@ import SwiftSignalKit import TelegramAudio import Display +public enum CallAlreadyInProgressType { + case peer(EnginePeer.Id?) + case external +} + public enum RequestCallResult { case requested - case alreadyInProgress(EnginePeer.Id?) + case alreadyInProgress(CallAlreadyInProgressType) } public enum JoinGroupCallManagerResult { case joined - case alreadyInProgress(EnginePeer.Id?) + case alreadyInProgress(CallAlreadyInProgressType) } public enum RequestScheduleGroupCallResult { case success - case alreadyInProgress(EnginePeer.Id?) + case alreadyInProgress(CallAlreadyInProgressType) } public struct CallAuxiliaryServer { diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm index 999b33d21e..20e770645e 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm @@ -2794,7 +2794,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -didEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + //ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); ASAssertUnlocked(__instanceLock__); } diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index 6726c4c1ba..970dd08c86 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -854,6 +854,12 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { avatarFrame.origin.x = revealOffset + leftInset - 52.0 transition.updateFrameAdditive(node: self.avatarNode, frame: avatarFrame) + if let conferenceAvatarListNode = self.conferenceAvatarListNode { + var conferenceAvatarFrame = conferenceAvatarListNode.frame + conferenceAvatarFrame.origin.x = avatarFrame.minX + floor((avatarFrame.width - conferenceAvatarFrame.width) / 2.0) + transition.updateFrameAdditive(node: conferenceAvatarListNode, frame: conferenceAvatarFrame) + } + transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size)) transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size)) diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 6c72ed9965..00573603ec 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -519,10 +519,13 @@ public final class CallListController: TelegramBaseController { return } + var dismissSelectionController: (() -> Void)? + let options = [ContactListAdditionalOption(title: self.presentationData.strings.CallList_NewCallLink, icon: .generic(PresentationResourcesItemList.linkIcon(presentationData.theme)!), action: { [weak self] in guard let self else { return } + dismissSelectionController?() self.createGroupCall(peerIds: [], isVideo: false) }, clearHighlightAutomatically: true)] @@ -546,6 +549,10 @@ public final class CallListController: TelegramBaseController { if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController { navigationController.pushViewController(controller) } + + dismissSelectionController = { [weak controller] in + controller?.dismiss() + } let _ = (controller.result |> take(1) diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index 9ea4fff4be..2501bafc92 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -350,6 +350,9 @@ final class CallListControllerNode: ASDisplayNode { let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) var items: [ActionSheetItem] = [] + //TODO:localize + items.append(ActionSheetTextItem(title: "Do you want to delete the information about this call?", parseMarkdown: true)) + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() guard let strongSelf = self else { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 150e1d7857..d3e9451756 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -936,9 +936,6 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { - let wasCaptureProtected = self.imageNode.captureProtected - self.imageNode.captureProtected = false - let contentNode = self.tilingNode ?? self.imageNode var transformedFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view) @@ -980,14 +977,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { let positionDuration: Double = 0.21 - copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView, weak self] _ in + copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in copyView?.removeFromSuperview() - - if wasCaptureProtected { - Queue.mainQueue().after(0.2) { - self?.imageNode.captureProtected = true - } - } }) let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height) copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 02174a1357..e5e051a84c 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -422,28 +422,25 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } public func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult { - var alreadyInCall: Bool = false - var alreadyInCallWithPeerId: PeerId? + var alreadyInCallType: CallAlreadyInProgressType? if let call = self.currentCall { - alreadyInCall = true - alreadyInCallWithPeerId = call.peerId + alreadyInCallType = .peer(call.peerId) } else if let currentGroupCall = self.currentGroupCallValue { - alreadyInCall = true switch currentGroupCall { case let .conferenceSource(conferenceSource): - alreadyInCallWithPeerId = conferenceSource.peerId + alreadyInCallType = .peer(conferenceSource.peerId) case let .group(groupCall): - alreadyInCallWithPeerId = groupCall.peerId + alreadyInCallType = .peer(groupCall.peerId) } } else { if CXCallObserver().calls.contains(where: { $0.hasEnded == false }) { - alreadyInCall = true + alreadyInCallType = .external } } - if alreadyInCall, !endCurrentIfAny { - return .alreadyInProgress(alreadyInCallWithPeerId) + if let alreadyInCallType, !endCurrentIfAny { + return .alreadyInProgress(alreadyInCallType) } if let _ = callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) { let begin: () -> Void = { [weak self] in @@ -942,9 +939,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } else { switch currentGroupCall { case let .conferenceSource(conferenceSource): - return .alreadyInProgress(conferenceSource.peerId) + return .alreadyInProgress(.peer(conferenceSource.peerId)) case let .group(groupCall): - return .alreadyInProgress(groupCall.peerId) + return .alreadyInProgress(.peer(groupCall.peerId)) } } } else if let currentCall = self.currentCall { @@ -955,7 +952,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { begin() })) } else { - return .alreadyInProgress(currentCall.peerId) + return .alreadyInProgress(.peer(currentCall.peerId)) } } else { begin() @@ -1001,9 +998,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } else { switch currentGroupCall { case let .conferenceSource(conferenceSource): - return .alreadyInProgress(conferenceSource.peerId) + return .alreadyInProgress(.peer(conferenceSource.peerId)) case let .group(groupCall): - return .alreadyInProgress(groupCall.peerId) + return .alreadyInProgress(.peer(groupCall.peerId)) } } } else if let currentCall = self.currentCall { @@ -1014,7 +1011,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { begin() })) } else { - return .alreadyInProgress(currentCall.peerId) + return .alreadyInProgress(.peer(currentCall.peerId)) } } else { begin() @@ -1194,9 +1191,9 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } else { switch currentGroupCall { case let .conferenceSource(conferenceSource): - return .alreadyInProgress(conferenceSource.peerId) + return .alreadyInProgress(.peer(conferenceSource.peerId)) case let .group(groupCall): - return .alreadyInProgress(groupCall.peerId) + return .alreadyInProgress(.peer(groupCall.peerId)) } } } else if let currentCall = self.currentCall { @@ -1207,7 +1204,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { begin() })) } else { - return .alreadyInProgress(currentCall.peerId) + return .alreadyInProgress(.peer(currentCall.peerId)) } } else { begin() diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 2f4d097495..0064ab36ab 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -349,8 +349,8 @@ private final class ConferenceCallE2EContextStateImpl: ConferenceCallE2EContextS return self.call.participants().map { $0.userId } } - func applyBlock(block: Data) { - self.call.applyBlock(block) + func applyBlock(block: Data) -> Bool { + return self.call.applyBlock(block) } func applyBroadcastBlock(block: Data) { @@ -636,6 +636,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } private var participantsContextStateDisposable = MetaDisposable() + private var isFailedEventDisposable: Disposable? private var temporaryParticipantsContext: GroupCallParticipantsContext? private var participantsContext: GroupCallParticipantsContext? @@ -1244,6 +1245,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.peerUpdatesSubscription?.dispose() self.screencastStateDisposable?.dispose() self.pendingDisconnedUpgradedConferenceCallTimer?.invalidate() + self.participantsContextStateDisposable.dispose() + self.isFailedEventDisposable?.dispose() } private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) { @@ -2616,6 +2619,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } })) + self.isFailedEventDisposable = (participantsContext.isFailedEvent + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] isFailed in + guard let self, isFailed else { + return + } + let _ = self.leave(terminateIfPossible: false).startStandalone() + }) + let engine = self.accountContext.engine self.memberEventsPipeDisposable.set((participantsContext.memberEvents |> mapToSignal { event -> Signal in @@ -3748,6 +3761,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.conferenceInvitationContexts.removeValue(forKey: peerId) if let messageId = conferenceInvitationContext.messageId { self.accountContext.engine.account.callSessionManager.dropOutgoingConferenceRequest(messageId: messageId) + let _ = self.accountContext.engine.messages.deleteMessagesInteractively(messageIds: [messageId], type: .forEveryone).startStandalone() } } } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift index 600c22dd9a..5560c7e99e 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift @@ -610,9 +610,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { self.scrollView.canCancelContentTouches = true self.scrollView.clipsToBounds = false self.scrollView.contentInsetAdjustmentBehavior = .never - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false self.scrollView.showsVerticalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.alwaysBounceHorizontal = false @@ -620,6 +618,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { self.scrollView.scrollsToTop = false self.scrollView.delegate = self self.scrollView.clipsToBounds = true + self.scrollView.disablesInteractiveTransitionGestureRecognizer = true self.addSubview(self.scrollView) } @@ -698,7 +697,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { transition.animateScale(view: itemComponentView, from: 0.001, to: 1.0) } } - transition.setFrame(view: itemComponentView, frame: itemFrame) + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) } } } @@ -743,6 +742,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { let size = CGSize(width: availableSize.width, height: itemLayout.contentSize.height) self.ignoreScrolling = true + let previousOffsetX = self.scrollView.bounds.minX if self.scrollView.bounds.size != size { transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) } @@ -750,6 +750,9 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { if self.scrollView.contentSize != contentSize { self.scrollView.contentSize = contentSize } + if self.scrollView.isDragging { + self.scrollView.bounds.origin.x = previousOffsetX + } self.ignoreScrolling = false self.updateScrolling(transition: transition) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index b18aa64aa1..2478b8dcdb 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -481,7 +481,7 @@ final class VideoChatScreenComponent: Component { if target === participantsView { return nil } - if let target = target as? UIScrollView { + if let target = target as? UIScrollView, !target.disablesInteractiveTransitionGestureRecognizer { return target } if let parent = target.superview { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift index 4531a143a7..71084b168a 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift @@ -313,14 +313,17 @@ extension VideoChatScreenComponent.View { if groupCall.isConference { groupCall.kickPeer(id: peer.id) + + //TODO:localize + self.presentUndoOverlay(content: .banned(text: "You removed \(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)) from this call. They will no longer be able to join using the invite link."), action: { _ in return false }) } else { if let callPeerId = groupCall.peerId { 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(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false }) } - - self.presentUndoOverlay(content: .banned(text: environment.strings.VoiceChat_RemovedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string), action: { _ in return false }) })) actionSheet.setItemGroups([ diff --git a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift index d947507fb5..c5ed1209c2 100644 --- a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift +++ b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift @@ -7,7 +7,7 @@ public protocol ConferenceCallE2EContextState: AnyObject { func getParticipants() -> [ConferenceCallE2EContext.BlockchainParticipant] func getParticipantLatencies() -> [Int64: Double] - func applyBlock(block: Data) + func applyBlock(block: Data) -> Bool func applyBroadcastBlock(block: Data) func generateRemoveParticipantsBlock(participantIds: [Int64]) -> Data? @@ -51,6 +51,7 @@ public final class ConferenceCallE2EContext { let e2eEncryptionKeyHashValue = ValuePromise(nil) let blockchainParticipantsValue = ValuePromise<[BlockchainParticipant]>([]) + let isFailed = ValuePromise(false, ignoreRepeated: true) private var e2ePoll0Offset: Int? private var e2ePoll0Timer: Foundation.Timer? @@ -167,35 +168,38 @@ public final class ConferenceCallE2EContext { let keyPair = self.keyPair let userId = self.userId let initializeState = self.initializeState - let (outBlocks, outEmoji, outBlockchainParticipants, participantLatencies) = self.state.with({ callState -> ([Data], Data, [BlockchainParticipant], [Int64: Double]) in + let (outBlocks, outEmoji, outBlockchainParticipants, participantLatencies, hadFailure) = self.state.with({ callState -> ([Data], Data, [BlockchainParticipant], [Int64: Double], Bool) in if let state = callState.state { + var hadFailure = false for block in blocks { if subChainId == 0 { - state.applyBlock(block: block) + if !state.applyBlock(block: block) { + hadFailure = true + } } else if subChainId == 1 { state.applyBroadcastBlock(block: block) } } - return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies()) + return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies(), hadFailure) } else { if subChainId == 0 { guard let block = blocks.last else { - return ([], Data(), [], [:]) + return ([], Data(), [], [:], false) } guard let state = initializeState(keyPair, userId, block) else { - return ([], Data(), [], [:]) + return ([], Data(), [], [:], false) } callState.state = state for block in callState.pendingIncomingBroadcastBlocks { state.applyBroadcastBlock(block: block) } callState.pendingIncomingBroadcastBlocks.removeAll() - return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies()) + return (state.takeOutgoingBroadcastBlocks(), state.getEmojiState() ?? Data(), state.getParticipants(), state.getParticipantLatencies(), false) } else if subChainId == 1 { callState.pendingIncomingBroadcastBlocks.append(contentsOf: blocks) - return ([], Data(), [], [:]) + return ([], Data(), [], [:], false) } else { - return ([], Data(), [], [:]) + return ([], Data(), [], [:], false) } } }) @@ -209,6 +213,10 @@ public final class ConferenceCallE2EContext { #if DEBUG print("Latencies: \(participantLatencies)") #endif + + if hadFailure { + self.isFailed.set(true) + } } private func e2ePoll(subChainId: Int) { @@ -454,6 +462,12 @@ public final class ConferenceCallE2EContext { return impl.blockchainParticipantsValue.get().start(next: subscriber.putNext) } } + + public var isFailed: Signal { + return self.impl.signalWith { impl, subscriber in + return impl.isFailed.get().start(next: subscriber.putNext) + } + } public init(engine: TelegramEngine, callId: Int64, accessHash: Int64, userId: Int64, reference: InternalGroupCallReference, keyPair: TelegramKeyPair, initializeState: @escaping (TelegramKeyPair, Int64, Data) -> ConferenceCallE2EContextState?) { let queue = Queue.mainQueue() diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeEmojiKeywordsOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeEmojiKeywordsOperations.swift index f8bcb958b3..49cb80cd7f 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeEmojiKeywordsOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeEmojiKeywordsOperations.swift @@ -199,8 +199,8 @@ private func synchronizeEmojiKeywords(postbox: Postbox, transaction: Transaction var items: [EmojiKeywordItem] = [] var index: Int32 = 0 for apiEmojiKeyword in keywords { - if case let .emojiKeyword(keyword, emoticons) = apiEmojiKeyword, !emoticons.isEmpty { - let keyword = keyword.replacingOccurrences(of: " ", with: "") + if case let .emojiKeyword(fullKeyword, emoticons) = apiEmojiKeyword, !emoticons.isEmpty { + let keyword = fullKeyword let indexKeys = stringIndexTokens(keyword, transliteration: .none).map { $0.toMemoryBuffer() } let item = EmojiKeywordItem(index: ItemCollectionItemIndex(index: index, id: keywordCollectionItemId(keyword, inputLanguageCode: operation.inputLanguageCode)), collectionId: collectionId.id, keyword: keyword, emoticons: emoticons, indexKeys: indexKeys) items.append(item) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 33e820adeb..60765b101b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -1687,6 +1687,11 @@ public final class GroupCallParticipantsContext { return self.memberEventsPipe.signal() } + private let isFailedEventPromise = ValuePromise(false, ignoreRepeated: true) + public var isFailedEvent: Signal { + return self.isFailedEventPromise.get() + } + private var updateQueue: [Update.StateUpdate] = [] private var isProcessingUpdate: Bool = false private let disposable = MetaDisposable() @@ -1715,6 +1720,7 @@ public final class GroupCallParticipantsContext { public private(set) var serviceState: ServiceState private var e2eStateUpdateDisposable: Disposable? + private var e2eIsFailedDisposable: Disposable? private var pendingBlockchainState: [ResolvedBlockchainParticipant]? private var pendingApplyBlockchainStateTimer: Foundation.Timer? @@ -1889,6 +1895,18 @@ public final class GroupCallParticipantsContext { self.applyPendingBlockchainState() } }) + + self.e2eIsFailedDisposable = (e2eContext.isFailed + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] isFailed in + guard let self else { + return + } + if isFailed { + self.isFailedEventPromise.set(true) + } + }) } } @@ -1902,6 +1920,7 @@ public final class GroupCallParticipantsContext { self.resetInviteLinksDisposable.dispose() self.subscribeDisposable.dispose() self.e2eStateUpdateDisposable?.dispose() + self.e2eIsFailedDisposable?.dispose() self.pendingApplyBlockchainStateTimer?.invalidate() } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index da14b4e1a6..59e1183adb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -2394,10 +2394,16 @@ func _internal_updatePeerStoriesHidden(account: Account, id: PeerId, isHidden: B guard let peer = transaction.getPeer(id) else { return nil } + if let user = peer as? TelegramUser { updatePeersCustom(transaction: transaction, peers: [user.withUpdatedStoriesHidden(isHidden)], update: { _, updated in return updated }) + if isHidden { + if !transaction.isPeerContact(peerId: user.id) { + let _ = _internal_removeRecentPeer(account: account, peerId: id).startStandalone() + } + } } else if let channel = peer as? TelegramChannel { updatePeersCustom(transaction: transaction, peers: [channel.withUpdatedStoriesHidden(isHidden)], update: { _, updated in return updated diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index af4cdc198c..fbb1e283bb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -760,20 +760,14 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { if suggestedPost != nil { let backgroundFrame = contentFrame - if item.context.sharedContext.energyUsageSettings.fullTranslucency { - if strongSelf.backgroundNode == nil { - if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - strongSelf.backgroundNode = backgroundNode - backgroundNode.addSubnode(strongSelf.backgroundColorNode) - strongSelf.insertSubnode(backgroundNode, at: 0) - } - } - strongSelf.backgroundColorNode.isHidden = true - } else { - if strongSelf.backgroundMaskNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundMaskNode, at: 0) + if strongSelf.backgroundNode == nil { + if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + strongSelf.backgroundNode = backgroundNode + backgroundNode.addSubnode(strongSelf.backgroundColorNode) + strongSelf.insertSubnode(backgroundNode, at: 0) } } + strongSelf.backgroundColorNode.isHidden = true if let backgroundNode = strongSelf.backgroundNode { backgroundNode.clipsToBounds = true @@ -810,20 +804,14 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { //strongSelf.spoilerTextNode?.visibilityRect = subRect } } else if let (offset, image) = backgroundMaskImage { - if item.context.sharedContext.energyUsageSettings.fullTranslucency { - if strongSelf.backgroundNode == nil { - if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - strongSelf.backgroundNode = backgroundNode - backgroundNode.addSubnode(strongSelf.backgroundColorNode) - strongSelf.insertSubnode(backgroundNode, at: 0) - } - } - strongSelf.backgroundColorNode.isHidden = true - } else { - if strongSelf.backgroundMaskNode.supernode == nil { - strongSelf.insertSubnode(strongSelf.backgroundMaskNode, at: 0) + if strongSelf.backgroundNode == nil { + if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + strongSelf.backgroundNode = backgroundNode + backgroundNode.addSubnode(strongSelf.backgroundColorNode) + strongSelf.insertSubnode(backgroundNode, at: 0) } } + strongSelf.backgroundColorNode.isHidden = true if backgroundMaskUpdated { if let backgroundNode = strongSelf.backgroundNode { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD index 31db545b01..85b0e37320 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/BUILD @@ -44,6 +44,7 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/Utils/RangeSet", "//submodules/MediaResources", + "//submodules/UIKitRuntimeUtils", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 1bbecf6485..38cd50bf85 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -34,6 +34,7 @@ import TextNodeWithEntities import RangeSet import GiftItemComponent import MediaResources +import UIKitRuntimeUtils private struct FetchControls { let fetch: (Bool) -> Void @@ -3198,7 +3199,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr dateAndStatusNode.isHidden = true } - let view: UIView? + var view: UIView? if let strongSelf = self, strongSelf.imageNode.captureProtected { let imageView = UIImageView() imageView.contentMode = .scaleToFill @@ -3207,9 +3208,13 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr if imageView.layer.contents == nil { imageView.layer.contents = imageView.image?.cgImage } + setLayerDisableScreenshots(imageView.layer, true) strongSelf.imageNode.view.superview?.insertSubview(imageView, aboveSubview: strongSelf.imageNode.view) view = self?.view.snapshotContentTree(unhide: true) + if let view { + setLayerDisableScreenshots(view.layer, true) + } imageView.removeFromSuperview() } else { view = self?.view.snapshotContentTree(unhide: true) diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 8a961f0d5f..cc1d099d7f 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -620,12 +620,12 @@ public final class AccountContextImpl: AccountContext { public func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription) { let callResult = self.sharedContext.callManager?.joinGroupCall(context: self, peerId: peerId, invite: invite, requestJoinAsPeerId: requestJoinAsPeerId, initialCall: activeCall, endCurrentIfAny: false) - if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { - if currentPeerId == peerId { + if let callResult = callResult, case let .alreadyInProgress(currentCallType) = callResult { + if case let .peer(currentPeerId) = currentCallType, currentPeerId == peerId { self.sharedContext.navigateToCurrentCall() } else { let dataInput: Signal<(EnginePeer?, EnginePeer?), NoError> - if let currentPeerId = currentPeerId { + if case let .peer(currentPeerId) = currentCallType, let currentPeerId { dataInput = self.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: currentPeerId) @@ -710,9 +710,9 @@ public final class AccountContextImpl: AccountContext { endCurrentIfAny: false, unmuteByDefault: unmuteByDefault ) - if case let .alreadyInProgress(currentPeerId) = result { + if case let .alreadyInProgress(currentCallType) = result { let dataInput: Signal - if let currentPeerId { + if case let .peer(currentPeerId) = currentCallType, let currentPeerId { dataInput = self.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: currentPeerId) ) @@ -785,6 +785,31 @@ public final class AccountContextImpl: AccountContext { ) })]), on: .root) } + } else if case .peer = currentCallType { + let text: String + //TODO:localize + text = "End current call and start a conference?"; + strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_CallInProgressTitle, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + guard let self else { + return + } + let _ = callManager.joinConferenceCall( + accountContext: self, + initialCall: EngineGroupCallDescription( + id: call.id, + accessHash: call.accessHash, + title: nil, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isStream: false + ), + reference: call.reference, + beginWithVideo: isVideo, + invitePeerIds: [], + endCurrentIfAny: true, + unmuteByDefault: unmuteByDefault + ) + })]), on: .root) } else { strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { })]), on: .root) @@ -798,13 +823,13 @@ public final class AccountContextImpl: AccountContext { return } - if case let .alreadyInProgress(currentPeerId) = callResult { - if currentPeerId == peerId { + if case let .alreadyInProgress(currentCallType) = callResult { + if case let .peer(currentPeerId) = currentCallType, currentPeerId == peerId { completion() self.sharedContext.navigateToCurrentCall() } else { let dataInput: Signal<(EnginePeer?, EnginePeer?), NoError> - if let currentPeerId = currentPeerId { + if case let .peer(currentPeerId) = currentCallType, let currentPeerId { dataInput = self.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: currentPeerId) diff --git a/third-party/td/TdBinding/Public/TdBinding/TdBinding.h b/third-party/td/TdBinding/Public/TdBinding/TdBinding.h index 3ec2d6cf0f..07925d0b57 100644 --- a/third-party/td/TdBinding/Public/TdBinding/TdBinding.h +++ b/third-party/td/TdBinding/Public/TdBinding/TdBinding.h @@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary *)participantLatencies; -- (void)applyBlock:(NSData *)block; +- (bool)applyBlock:(NSData *)block; - (void)applyBroadcastBlock:(NSData *)block; - (nullable NSData *)generateRemoveParticipantsBlock:(NSArray *)participantIds; diff --git a/third-party/td/TdBinding/Sources/TdBinding.mm b/third-party/td/TdBinding/Sources/TdBinding.mm index edc8270c38..9022a9f4d9 100644 --- a/third-party/td/TdBinding/Sources/TdBinding.mm +++ b/third-party/td/TdBinding/Sources/TdBinding.mm @@ -236,7 +236,7 @@ static NSString *hexStringFromData(NSData *data) { return @{}; } -- (void)applyBlock:(NSData *)block { +- (bool)applyBlock:(NSData *)block { std::string mappedBlock((uint8_t *)block.bytes, ((uint8_t *)block.bytes) + block.length); #if DEBUG @@ -260,8 +260,9 @@ static NSString *hexStringFromData(NSData *data) { auto result = tde2e_api::call_apply_block(_callId, mappedBlock); if (!result.is_ok()) { - return; + return false; } + return true; } - (void)applyBroadcastBlock:(NSData *)block {