diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index eb0bbc5839..44762593d8 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -474,9 +474,10 @@ public final class NavigateToChatControllerParams { public let changeColors: Bool public let setupController: (ChatController) -> Void public let completion: (ChatController) -> Void + public let chatListCompletion: ((ChatListController) -> Void)? public let pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? - public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }) { + public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }, chatListCompletion: @escaping (ChatListController) -> Void = { _ in }) { self.navigationController = navigationController self.chatController = chatController self.chatLocationContextHolder = chatLocationContextHolder @@ -506,6 +507,7 @@ public final class NavigateToChatControllerParams { self.setupController = setupController self.pushController = pushController self.completion = completion + self.chatListCompletion = chatListCompletion } } diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 5aef007cbd..8605c86494 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -913,6 +913,7 @@ public protocol ChatController: ViewController { func activateSearch(domain: ChatSearchDomain, query: String) func beginClearHistory(type: InteractiveHistoryClearingType) + func performScrollToTop() -> Bool func transferScrollingVelocity(_ velocity: CGFloat) func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool) } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 2ae27d6417..1f2b11790c 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -21,6 +21,7 @@ public let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageNam public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepostStoryIcon"), color: .white) private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed() private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white) +private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white) public func avatarPlaceholderFont(size: CGFloat) -> UIFont { return Font.with(size: size, design: .round, weight: .bold) @@ -660,7 +661,7 @@ public final class AvatarNode: ASDisplayNode { icon = .repliesIcon case .anonymousSavedMessagesIcon: representation = nil - icon = .repliesIcon + icon = .anonymousSavedMessagesIcon case let .archivedChatsIcon(hiddenByDefault): representation = nil icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault) @@ -897,8 +898,8 @@ public final class AvatarNode: ASDisplayNode { context.scaleBy(x: factor, y: -factor) context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0) - if let repliesIcon = repliesIcon { - context.draw(repliesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - repliesIcon.size.width) / 2.0), y: floor((bounds.size.height - repliesIcon.size.height) / 2.0)), size: repliesIcon.size)) + if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon { + context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size)) } } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage { context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 06ed143139..0f3b4259f5 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2205,7 +2205,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, peerSelected: { [weak self] peer, chatPeer, threadId, _ in interaction.dismissInput() interaction.openPeer(peer, chatPeer, threadId, false) - let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone() + switch location { + case .chatList, .forum: + let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone() + case .savedMessagesChats: + break + } self?.listNode.clearHighlightAnimated(true) }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in @@ -2670,7 +2675,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, filter: peersFilter, peerSelected: { peer, threadId in interaction.openPeer(peer, nil, threadId, true) if threadId == nil { - let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone() + switch location { + case .chatList, .forum: + let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone() + case .savedMessagesChats: + break + } } self?.recentListNode.clearHighlightAnimated(true) }, disabledPeerSelected: { peer, threadId in diff --git a/submodules/Display/Source/DeviceMetrics.swift b/submodules/Display/Source/DeviceMetrics.swift index 32b74325ee..b01c2a487d 100644 --- a/submodules/Display/Source/DeviceMetrics.swift +++ b/submodules/Display/Source/DeviceMetrics.swift @@ -44,7 +44,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { case iPadPro case iPadPro3rdGen case iPadMini6thGen - case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?) + case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?, screenCornerRadius: CGFloat) public static let performance = Performance() @@ -111,14 +111,24 @@ public enum DeviceMetrics: CaseIterable, Equatable { return } } - self = .unknown(screenSize: screenSize, statusBarHeight: statusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight) + + let screenCornerRadius: CGFloat + if screenSize.width >= 1024.0 || screenSize.height >= 1024.0 { + screenCornerRadius = 0.0 + } else if onScreenNavigationHeight != nil { + screenCornerRadius = 39.0 + } else { + screenCornerRadius = 0.0 + } + + self = .unknown(screenSize: screenSize, statusBarHeight: statusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight, screenCornerRadius: screenCornerRadius) } public var type: DeviceType { switch self { case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen: return .tablet - case let .unknown(screenSize, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0: + case let .unknown(screenSize, _, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0: return .tablet default: return .phone @@ -175,7 +185,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { return CGSize(width: 1024.0, height: 1366.0) case .iPadMini6thGen: return CGSize(width: 744.0, height: 1133.0) - case let .unknown(screenSize, _, _): + case let .unknown(screenSize, _, _, _): return screenSize } } @@ -194,12 +204,8 @@ public enum DeviceMetrics: CaseIterable, Equatable { return 53.0 + UIScreenPixel case .iPhone14Pro, .iPhone14ProMax: return 55.0 - case let .unknown(_, _, onScreenNavigationHeight): - if let _ = onScreenNavigationHeight { - return 39.0 - } else { - return 0.0 - } + case let .unknown(_, _, _, screenCornerRadius): + return screenCornerRadius default: return 0.0 } @@ -230,7 +236,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { } else { return nil } - case let .unknown(_, _, onScreenNavigationHeight): + case let .unknown(_, _, onScreenNavigationHeight, _): return onScreenNavigationHeight default: return nil @@ -260,7 +266,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { return 44.0 case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen: return 24.0 - case let .unknown(_, statusBarHeight, _): + case let .unknown(_, statusBarHeight, _, _): return statusBarHeight default: return 20.0 diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift index 335f060cc2..b584d5b1b6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift @@ -415,7 +415,7 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode { super.layout() self.pagerNode.frame = self.bounds - self.pagerNode.containerLayoutUpdated(ContainerViewLayout(size: self.bounds.size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: CGSize(), statusBarHeight: 0.0, onScreenNavigationHeight: nil), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) + self.pagerNode.containerLayoutUpdated(ContainerViewLayout(size: self.bounds.size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: CGSize(), statusBarHeight: 0.0, onScreenNavigationHeight: nil, screenCornerRadius: 0.0), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) self.pageControlNode.layer.transform = CATransform3DIdentity self.pageControlNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height - 20.0), size: CGSize(width: self.bounds.size.width, height: 20.0)) diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index b13783c8a2..0128b35a16 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -148,7 +148,7 @@ public final class CallController: ViewController { } override public func loadDisplayNode() { - var useV2 = self.call.context.sharedContext.immediateExperimentalUISettings.callV2 + var useV2 = true if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_callui_v2"] { useV2 = false } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 89dd7adb28..812b95aee7 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -42,6 +42,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP private var didInitializeIsReady: Bool = false private var callStartTimestamp: Double? + private var smoothSignalQuality: Double? + private var smoothSignalQualityTarget: Double? private var callState: PresentationCallState? var isMuted: Bool = false @@ -77,6 +79,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP private var panGestureState: PanGestureState? private var notifyDismissedInteractivelyOnPanGestureApply: Bool = false + private var signalQualityTimer: Foundation.Timer? + init( sharedContext: SharedAccountContext, account: Account, @@ -190,6 +194,22 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP }) self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) + + self.signalQualityTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + if let smoothSignalQuality = self.smoothSignalQuality, let smoothSignalQualityTarget = self.smoothSignalQualityTarget { + let updatedSmoothSignalQuality = (smoothSignalQuality + smoothSignalQualityTarget) * 0.5 + if abs(updatedSmoothSignalQuality - smoothSignalQuality) > 0.001 { + self.smoothSignalQuality = updatedSmoothSignalQuality + + if let callState = self.callState { + self.updateCallState(callState) + } + } + } + }) } deinit { @@ -197,6 +217,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.isMicrophoneMutedDisposable?.dispose() self.audioLevelDisposable?.dispose() self.audioOutputCheckTimer?.invalidate() + self.signalQualityTimer?.invalidate() } func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) { @@ -211,8 +232,24 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP mappedOutput = .internalSpeaker case .speaker: mappedOutput = .speaker - case .headphones, .port: - mappedOutput = .speaker + case .headphones: + mappedOutput = .headphones + case let .port(port): + switch port.type { + case .wired: + mappedOutput = .headphones + default: + let portName = port.name.lowercased() + if portName.contains("airpods pro") { + mappedOutput = .airpodsPro + } else if portName.contains("airpods max") { + mappedOutput = .airpodsMax + } else if portName.contains("airpods") { + mappedOutput = .airpods + } else { + mappedOutput = .bluetooth + } + } } } else { mappedOutput = .internalSpeaker @@ -342,10 +379,16 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP case .connecting: mappedLifecycleState = .connecting case let .active(startTime, signalQuality, keyData): - self.callStartTimestamp = startTime + var signalQuality = signalQuality.flatMap(Int.init) + self.smoothSignalQualityTarget = Double(signalQuality ?? 4) - var signalQuality = signalQuality - signalQuality = 4 + if let smoothSignalQuality = self.smoothSignalQuality { + signalQuality = Int(round(smoothSignalQuality)) + } else { + signalQuality = 4 + } + + self.callStartTimestamp = startTime let _ = keyData mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState( @@ -354,6 +397,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP emojiKey: self.resolvedEmojiKey(data: keyData) )) case let .reconnecting(startTime, _, keyData): + self.smoothSignalQuality = nil + self.smoothSignalQualityTarget = nil + if self.callStartTimestamp != nil { mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState( startTime: startTime + kCFAbsoluteTimeIntervalSince1970, @@ -517,51 +563,31 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP callScreenState.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) callScreenState.shortName = peer.compactDisplayTitle - if self.currentPeer?.smallProfileImage != peer.smallProfileImage { + if (self.currentPeer?.smallProfileImage != peer.smallProfileImage) || self.callScreenState?.avatarImage == nil { self.peerAvatarDisposable?.dispose() - if let smallProfileImage = peer.largeProfileImage, let peerReference = PeerReference(peer._asPeer()) { - if let thumbnailImage = smallProfileImage.immediateThumbnailData.flatMap(decodeTinyThumbnail).flatMap(UIImage.init(data:)), let cgImage = thumbnailImage.cgImage { - callScreenState.avatarImage = generateImage(CGSize(width: 128.0, height: 128.0), contextGenerator: { size, context in - context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) - }, scale: 1.0).flatMap { image in - return blurredImage(image, radius: 10.0) - } - } - - let postbox = self.call.context.account.postbox - self.peerAvatarDisposable = (Signal { subscriber in - let fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource)).start() - let dataDisposable = postbox.mediaBox.resourceData(smallProfileImage.resource).start(next: { data in - if data.complete, let image = UIImage(contentsOfFile: data.path)?.precomposed() { - subscriber.putNext(image) - subscriber.putCompletion() - } - }) - - return ActionDisposable { - fetchDisposable.dispose() - dataDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { [weak self] image in + let size = CGSize(width: 128.0, height: 128.0) + if let representation = peer.largeProfileImage, let signal = peerAvatarImage(account: self.call.context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: self.callScreenState?.avatarImage == nil) { + self.peerAvatarDisposable = (signal + |> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in guard let self else { return } - if var callScreenState = self.callScreenState { + let image = imageVersions?.0 + if let image { callScreenState.avatarImage = image self.callScreenState = callScreenState self.update(transition: .immediate) } }) } else { - self.peerAvatarDisposable?.dispose() - self.peerAvatarDisposable = nil - - callScreenState.avatarImage = generateImage(CGSize(width: 512, height: 512), scale: 1.0, rotatedContext: { size, context in + let image = generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - drawPeerAvatarLetters(context: context, size: size, font: Font.semibold(20.0), letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor) - }) + drawPeerAvatarLetters(context: context, size: size, font: avatarPlaceholderFont(size: 50.0), letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor) + })! + callScreenState.avatarImage = image + self.callScreenState = callScreenState + self.update(transition: .immediate) } } self.currentPeer = peer @@ -893,6 +919,19 @@ private final class AdaptedCallVideoSource: VideoSource { let width = i420Buffer.width let height = i420Buffer.height + /*output = Output( + resolution: CGSize(width: CGFloat(width), height: CGFloat(height)), + textureLayout: .triPlanar(Output.TriPlanarTextureLayout( + y: yTexture, + uv: uvTexture + )), + dataBuffer: Output.NativeDataBuffer(pixelBuffer: nativeBuffer.pixelBuffer), + rotationAngle: rotationAngle, + followsDeviceOrientation: followsDeviceOrientation, + mirrorDirection: mirrorDirection, + sourceId: sourceId + )*/ + let _ = width let _ = height return diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index a2f37daa0f..19b7211e2d 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -157,7 +157,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode { } public class CallStatusBarNodeImpl: CallStatusBarNode { - public enum Content { + public enum Content: Equatable { case call(SharedAccountContext, Account, PresentationCall) case groupCall(SharedAccountContext, Account, PresentationGroupCall) @@ -167,6 +167,23 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { return sharedContext } } + + public static func ==(lhs: Content, rhs: Content) -> Bool { + switch lhs { + case let .call(sharedContext, account, call): + if case let .call(rhsSharedContext, rhsAccount, rhsCall) = rhs, sharedContext === rhsSharedContext, account === rhsAccount, call === rhsCall { + return true + } else { + return false + } + case let .groupCall(sharedContext, account, groupCall): + if case let .groupCall(rhsSharedContext, rhsAccount, rhsGroupCall) = rhs, sharedContext === rhsSharedContext, account === rhsAccount, groupCall === rhsGroupCall { + return true + } else { + return false + } + } + } } private let backgroundNode: CallStatusBarBackgroundNode @@ -236,10 +253,12 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { } public func update(content: Content) { - self.currentContent = content - self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency - if self.isCurrentlyInHierarchy { - self.update() + if self.currentContent != content { + self.currentContent = content + self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency + if self.isCurrentlyInHierarchy { + self.update() + } } } diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 310b8b36de..0d1df5f992 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -93,6 +93,7 @@ enum AccountStateMutationOperation { case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation) + case UpdatePinnedSavedItemIds(AccountStateUpdatePinnedItemIdsOperation) case UpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) case UpdatePinnedTopicOrder(peerId: PeerId, threadIds: [Int64]) case ReadMessageContents(peerIdsAndMessageIds: (PeerId?, [Int32]), date: Int32?) @@ -571,6 +572,10 @@ struct AccountMutableState { self.addOperation(.UpdatePinnedItemIds(groupId, operation)) } + mutating func addUpdatePinnedSavedItemIds(operation: AccountStateUpdatePinnedItemIdsOperation) { + self.addOperation(.UpdatePinnedSavedItemIds(operation)) + } + mutating func addUpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) { self.addOperation(.UpdatePinnedTopic(peerId: peerId, threadId: threadId, isPinned: isPinned)) } @@ -665,7 +670,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization: break case let .AddMessages(messages, location): for message in messages { diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index 1fb83a9ee5..a54d8a540b 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -839,6 +839,8 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n } if let updatedResource = findUpdatedMediaResource(media: media, previousMedia: nil, resource: resource) { return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + } else if let alternativeMedia = item.alternativeMedia, let updatedResource = findUpdatedMediaResource(media: alternativeMedia, previousMedia: nil, resource: resource) { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } else { return .fail(.generic) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 0610c48e20..b290d3f72d 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1490,6 +1490,27 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } else { updatedState.addUpdatePinnedItemIds(groupId: groupId, operation: .sync) } + case let .updateSavedDialogPinned(flags, peer): + if case let .dialogPeer(peer) = peer { + if (flags & (1 << 0)) != 0 { + updatedState.addUpdatePinnedSavedItemIds(operation: .pin(.peer(peer.peerId))) + } else { + updatedState.addUpdatePinnedSavedItemIds(operation: .unpin(.peer(peer.peerId))) + } + } + case let .updatePinnedSavedDialogs(_, order): + if let order = order { + updatedState.addUpdatePinnedSavedItemIds(operation: .reorder(order.compactMap { + switch $0 { + case let .dialogPeer(peer): + return .peer(peer.peerId) + case .dialogPeerFolder: + return nil + } + })) + } else { + updatedState.addUpdatePinnedSavedItemIds(operation: .sync) + } case let .updateChannelPinnedTopic(flags, channelId, topicId): let isPinned = (flags & (1 << 0)) != 0 updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned) @@ -3231,7 +3252,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddScheduledMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -4228,6 +4249,44 @@ func replayFinalState( case .sync: addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId) } + case let .UpdatePinnedSavedItemIds(pinnedOperation): + switch pinnedOperation { + case let .pin(itemId): + switch itemId { + case let .peer(peerId): + var currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId) + if !currentItemIds.contains(peerId.toInt64()) { + currentItemIds.insert(peerId.toInt64(), at: 0) + transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: currentItemIds) + } + } + case let .unpin(itemId): + switch itemId { + case let .peer(peerId): + var currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId) + if let index = currentItemIds.firstIndex(of: peerId.toInt64()) { + currentItemIds.remove(at: index) + transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: currentItemIds) + } else { + addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId) + } + } + case let .reorder(itemIds): + let itemIds = itemIds.compactMap({ + switch $0 { + case let .peer(peerId): + return peerId + } + }) + let currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId) + if Set(itemIds) == Set(currentItemIds.map(PeerId.init)) { + transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: itemIds.map { $0.toInt64() }) + } else { + addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId) + } + case .sync: + addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId) + } case let .UpdatePinnedTopic(peerId, threadId, isPinned): var currentThreadIds = transaction.getPeerPinnedThreads(peerId: peerId) if isPinned { diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 05de1c703b..bbd8f0c454 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -69,7 +69,8 @@ final class AccountTaskManager { tasks.add(managedSynchronizeGroupMessageStats(network: self.stateManager.network, postbox: self.stateManager.postbox, stateManager: self.stateManager).start()) tasks.add(managedGlobalNotificationSettings(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) - tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager).start()) + tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager, tag: OperationLogTags.SynchronizePinnedChats).start()) + tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager, tag: OperationLogTags.SynchronizePinnedSavedChats).start()) tasks.add(managedSynchronizeGroupedPeersOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start()) tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .stickers).start()) tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .masks).start()) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index db150f34fb..97a5986320 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1860,23 +1860,15 @@ public final class AccountViewTracker { return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) } |> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in - if let threadInfo = threadInfo { - let anchor: HistoryViewInputAnchor - if threadInfo.maxIncomingReadId <= 1 { - anchor = .message(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: 1)) - } else if threadInfo.incomingUnreadCount > 0 && tagMask == nil { - let customUnreadMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: threadInfo.maxIncomingReadId) - anchor = .message(customUnreadMessageId) - } else { - anchor = .upperBound - } - + if peerId == account.peerId { return account.postbox.aroundMessageHistoryViewForLocation( chatLocation, - anchor: anchor, + anchor: .upperBound, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, - fixedCombinedReadStates: nil, + fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [ + (Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: Int32.max - 1, maxOutgoingReadId: Int32.max - 1, maxKnownId: Int32.max - 1, count: 0, markedUnread: false)) + ])]), topTaggedMessageIdNamespaces: [], tagMask: tagMask, appendMessagesFromTheSameGroup: false, @@ -1884,6 +1876,32 @@ public final class AccountViewTracker { orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData) ) + } else { + if let threadInfo = threadInfo { + let anchor: HistoryViewInputAnchor + if threadInfo.maxIncomingReadId <= 1 { + anchor = .message(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: 1)) + } else if threadInfo.incomingUnreadCount > 0 && tagMask == nil { + let customUnreadMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: threadInfo.maxIncomingReadId) + anchor = .message(customUnreadMessageId) + } else { + anchor = .upperBound + } + + return account.postbox.aroundMessageHistoryViewForLocation( + chatLocation, + anchor: anchor, + ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, + count: count, + fixedCombinedReadStates: nil, + topTaggedMessageIdNamespaces: [], + tagMask: tagMask, + appendMessagesFromTheSameGroup: false, + namespaces: .not(Namespaces.Message.allScheduled), + orderStatistics: orderStatistics, + additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData) + ) + } } return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift index 4dea62753c..5db9adc5e3 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift @@ -4,7 +4,6 @@ import SwiftSignalKit import TelegramApi import MtProtoKit - private final class ManagedSynchronizePinnedChatsOperationsHelper { var operationDisposables: [Int32: Disposable] = [:] @@ -49,10 +48,10 @@ private final class ManagedSynchronizePinnedChatsOperationsHelper { } } -private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal) -> Signal { +private func withTakenOperation(postbox: Postbox, tag: PeerOperationLogTag, peerId: PeerId, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal) -> Signal { return postbox.transaction { transaction -> Signal in var result: PeerMergedOperationLogEntry? - transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: tagLocalIndex, { entry in + transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizePinnedChatsOperation { result = entry.mergedEntry! return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) @@ -65,11 +64,11 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex: } |> switchToLatest } -func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager) -> Signal { +func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, tag: PeerOperationLogTag) -> Signal { return Signal { _ in let helper = Atomic(value: ManagedSynchronizePinnedChatsOperationsHelper()) - let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SynchronizePinnedChats, limit: 10).start(next: { view in + let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in return helper.update(view.entries) } @@ -79,10 +78,14 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, } for (entry, disposable) in beginOperations { - let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in + let signal = withTakenOperation(postbox: postbox, tag: tag, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in if let entry = entry { if let operation = entry.contents as? SynchronizePinnedChatsOperation { - return synchronizePinnedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, groupId: PeerGroupId(rawValue: Int32(entry.peerId.id._internalGetInt64Value())), operation: operation) + if tag == OperationLogTags.SynchronizePinnedChats { + return synchronizePinnedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, groupId: PeerGroupId(rawValue: Int32(entry.peerId.id._internalGetInt64Value())), operation: operation) + } else if tag == OperationLogTags.SynchronizePinnedSavedChats { + return synchronizePinnedSavedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, operation: operation) + } } else { assertionFailure() } @@ -90,7 +93,7 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, return .complete() }) |> then(postbox.transaction { transaction -> Void in - let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: entry.tagLocalIndex) + let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex) }) disposable.set((signal |> delay(2.0, queue: Queue.concurrentDefaultQueue())).start()) @@ -279,3 +282,65 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, |> switchToLatest } } + +private func synchronizePinnedSavedChats(transaction: Transaction, postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, operation: SynchronizePinnedChatsOperation) -> Signal { + return network.request(Api.functions.messages.getPinnedSavedDialogs()) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { dialogs -> Signal in + guard let dialogs = dialogs else { + return .never() + } + + let _ = dialogs + + /*return postbox.transaction { transaction -> Signal in + var storeMessages: [StoreMessage] = [] + var remoteItemIds: [PeerId] = [] + + let parsedPeers: AccumulatedPeers + + switch dialogs { + case .savedDialogs(let dialogs, let messages, let chats, let users), .savedDialogs(_, let dialogs, let messages, let chats, let users): + parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) + + loop: for dialog in dialogs { + switch dialog { + case let .savedDialog(_, peer, _): + remoteItemIds.append(peer.peerId) + } + } + + for message in messages { + var peerIsForum = false + if let peerId = message.peerId, let peer = parsedPeers.get(peerId), peer.isForum { + peerIsForum = true + } + if let storeMessage = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { + storeMessages.append(storeMessage) + } + } + case .savedDialogsNotModified: + parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: []) + } + + let resultingItemIds: [PeerId] = remoteItemIds + + return postbox.transaction { transaction -> Signal in + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + + transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: resultingItemIds.map { $0.toInt64() }) + + let _ = transaction.addMessages(storeMessages, location: .UpperHistoryBlock) + + return .complete() + } + |> switchToLatest + } + |> switchToLatest*/ + + return .complete() + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 6a3dde4997..140799e0b8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -198,6 +198,7 @@ public struct OperationLogTags { public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23) public static let SynchronizeViewStories = PeerOperationLogTag(value: 24) public static let SynchronizePeerStories = PeerOperationLogTag(value: 25) + public static let SynchronizePinnedSavedChats = PeerOperationLogTag(value: 26) } public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizePinnedChatsOperation.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizePinnedChatsOperation.swift index 24dd5195fe..d312d46d36 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizePinnedChatsOperation.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_SynchronizePinnedChatsOperation.swift @@ -61,3 +61,21 @@ public func addSynchronizePinnedChatsOperation(transaction: Transaction, groupId } transaction.operationLogAddEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(Int64(rawId))), tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents) } + +public func addSynchronizePinnedSavedChatsOperation(transaction: Transaction, accountPeerId: PeerId) { + var previousItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId).map { PinnedItemId.peer(PeerId($0)) } + var updateLocalIndex: Int32? + + transaction.operationLogEnumerateEntries(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, { entry in + updateLocalIndex = entry.tagLocalIndex + if let contents = entry.contents as? SynchronizePinnedChatsOperation { + previousItemIds = contents.previousItemIds + } + return false + }) + let operationContents = SynchronizePinnedChatsOperation(previousItemIds: previousItemIds) + if let updateLocalIndex = updateLocalIndex { + let _ = transaction.operationLogRemoveEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, tagLocalIndex: updateLocalIndex) + } + transaction.operationLogAddEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents) +} diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 071119fa71..84a3f55f96 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -296,6 +296,18 @@ func locallyRenderedMessage(message: StoreMessage, peers: AccumulatedPeers, asso public extension Message { func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool { if self.id.peerId == accountPeerId { + if let sourceAuthorInfo = self.sourceAuthorInfo { + if sourceAuthorInfo.originalOutgoing { + return false + } else if let originalAuthor = sourceAuthorInfo.originalAuthor, originalAuthor == accountPeerId { + return false + } + } else if let forwardInfo = self.forwardInfo { + if let author = forwardInfo.author, author.id == accountPeerId { + return false + } + } + if self.forwardInfo != nil { return true } else { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 5199856ec8..e320ebaecf 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -419,6 +419,7 @@ swift_library( "//submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen", "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", + "//submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift index de46ab41e9..180fa18dca 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/Components/ButtonGroupView.swift @@ -15,7 +15,7 @@ final class ButtonGroupView: OverlayMaskContainerView { case end } - case speaker(isActive: Bool) + case speaker(audioOutput: PrivateCallScreen.State.AudioOutput) case flipCamera case video(isActive: Bool) case microphone(isMuted: Bool) @@ -215,10 +215,37 @@ final class ButtonGroupView: OverlayMaskContainerView { let isActive: Bool var isDestructive: Bool = false switch button.content { - case let .speaker(isActiveValue): - title = "speaker" - image = UIImage(bundleImageName: "Call/Speaker") - isActive = isActiveValue + case let .speaker(audioOutput): + switch audioOutput { + case .internalSpeaker, .speaker: + title = "speaker" + default: + title = "audio" + } + + switch audioOutput { + case .internalSpeaker: + image = UIImage(bundleImageName: "Call/Speaker") + isActive = false + case .speaker: + image = UIImage(bundleImageName: "Call/Speaker") + isActive = true + case .airpods: + image = UIImage(bundleImageName: "Call/CallAirpodsButton") + isActive = true + case .airpodsPro: + image = UIImage(bundleImageName: "Call/CallAirpodsProButton") + isActive = true + case .airpodsMax: + image = UIImage(bundleImageName: "Call/CallAirpodsMaxButton") + isActive = true + case .headphones: + image = UIImage(bundleImageName: "Call/CallHeadphonesButton") + isActive = true + case .bluetooth: + image = UIImage(bundleImageName: "Call/CallBluetoothButton") + isActive = true + } case .flipCamera: title = "flip" image = UIImage(bundleImageName: "Call/Flip") diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index db793d3e58..a53cfd48ad 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -60,6 +60,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu public enum AudioOutput: Equatable { case internalSpeaker case speaker + case headphones + case airpods + case airpodsPro + case airpodsMax + case bluetooth } public var lifecycleState: LifecycleState @@ -354,6 +359,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if !self.isUpdating { let wereControlsHidden = self.areControlsHidden self.areControlsHidden = true + self.displayEmojiTooltip = false self.update(transition: .immediate) if !wereControlsHidden { @@ -417,6 +423,24 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil { self.areControlsHidden = !self.areControlsHidden update = true + + if self.areControlsHidden { + self.displayEmojiTooltip = false + self.hideControlsTimer?.invalidate() + self.hideControlsTimer = nil + } else { + self.hideControlsTimer?.invalidate() + self.hideControlsTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + if !self.areControlsHidden { + self.areControlsHidden = true + self.displayEmojiTooltip = false + self.update(transition: .spring(duration: 0.4)) + } + }) + } } if update { @@ -515,7 +539,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if let previousParams = self.params, case .active = params.state.lifecycleState { switch previousParams.state.lifecycleState { case .requesting, .ringing, .connecting, .reconnecting: - if self.hideEmojiTooltipTimer == nil { + if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden { self.displayEmojiTooltip = true self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { [weak self] _ in @@ -592,6 +616,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu } if !self.areControlsHidden { self.areControlsHidden = true + self.displayEmojiTooltip = false self.update(transition: .spring(duration: 0.4)) } }) @@ -704,7 +729,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.flipCameraAction?() }), at: 0) } else { - buttons.insert(ButtonGroupView.Button(content: .speaker(isActive: params.state.audioOutput != .internalSpeaker), isEnabled: !isTerminated, action: { [weak self] in + buttons.insert(ButtonGroupView.Button(content: .speaker(audioOutput: params.state.audioOutput), isEnabled: !isTerminated, action: { [weak self] in guard let self else { return } @@ -1157,9 +1182,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu transition.setPosition(layer: self.blobLayer, position: CGPoint(x: blobFrame.width * 0.5, y: blobFrame.height * 0.5)) transition.setBounds(layer: self.blobLayer, bounds: CGRect(origin: CGPoint(), size: blobFrame.size)) + let displayAudioLevelBlob: Bool let titleString: String switch params.state.lifecycleState { case let .terminated(terminatedState): + displayAudioLevelBlob = false + self.titleView.contentMode = .center switch terminatedState.reason { @@ -1174,6 +1202,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu case .missed: titleString = "Call Missed" } + default: + displayAudioLevelBlob = !params.state.isRemoteAudioMuted + + self.titleView.contentMode = .scaleToFill + titleString = params.state.name + } + + if !displayAudioLevelBlob { genericAlphaTransition.setScale(layer: self.blobLayer, scale: 0.3) genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0) self.canAnimateAudioLevel = false @@ -1181,11 +1217,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.currentAvatarAudioScale = 1.0 transition.setScale(layer: self.avatarTransformLayer, scale: 1.0) transition.setScale(layer: self.blobTransformLayer, scale: 1.0) - default: - self.titleView.contentMode = .scaleToFill - titleString = params.state.name + } else { genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: (expandedEmojiKeyOverlapsAvatar && !havePrimaryVideo) ? 0.0 : 1.0) transition.setScale(layer: self.blobLayer, scale: expandedEmojiKeyOverlapsAvatar ? 0.001 : 1.0) + if !havePrimaryVideo { + self.canAnimateAudioLevel = true + } } let titleSize = self.titleView.update( diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index f006a5ca4d..98ce2ab888 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -183,7 +183,17 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing - let author = message.author + + var author = message.effectiveAuthor + + if let forwardInfo = message.forwardInfo { + if let peer = forwardInfo.author { + author = peer + } else if let authorSignature = forwardInfo.authorSignature { + author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + } + } + let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0, dark: presentationData.theme.theme.overallDarkAppearance) } let mainColor: UIColor diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 519649f255..7f2a940148 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1645,7 +1645,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if case .admin = authorRank { } else if case .owner = authorRank { } else if authorRank == nil { - enableAutoRank = true + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.peerId == item.context.account.peerId { + } else { + enableAutoRank = true + } } if enableAutoRank { if let topicAuthorId = item.associatedData.topicAuthorId, topicAuthorId == message.author?.id { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD new file mode 100644 index 0000000000..0b956ee90f --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD @@ -0,0 +1,38 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageNotificationItem", + module_name = "ChatMessageNotificationItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AvatarNode", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/StickerResources", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift new file mode 100644 index 0000000000..087a18ddaa --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift @@ -0,0 +1,233 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import TelegramUIPreferences +import AvatarNode +import AccountContext +import LocalizedPeerData +import StickerResources +import PhotoResources +import TelegramStringFormatting +import TextFormat +import InvisibleInkDustNode +import TextNodeWithEntities +import AnimationCache +import MultiAnimationRenderer +import ComponentFlow +import MultilineTextComponent +import BundleIconComponent +import PlainButtonComponent + +public final class ChatCallNotificationItem: NotificationItem { + public let context: AccountContext + public let strings: PresentationStrings + public let nameDisplayOrder: PresentationPersonNameOrder + public let peer: EnginePeer + public let isVideo: Bool + public let action: (Bool) -> Void + + public var groupingKey: AnyHashable? { + return nil + } + + public init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peer: EnginePeer, isVideo: Bool, action: @escaping (Bool) -> Void) { + self.context = context + self.strings = strings + self.nameDisplayOrder = nameDisplayOrder + self.peer = peer + self.isVideo = isVideo + self.action = action + } + + public func node(compact: Bool) -> NotificationItemNode { + let node = ChatCallNotificationItemNode() + node.setupItem(self, compact: compact) + return node + } + + public func tapped(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) { + } + + public func canBeExpanded() -> Bool { + return false + } + + public func expand(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) { + } +} + +private let compactAvatarFont = avatarPlaceholderFont(size: 20.0) +private let avatarFont = avatarPlaceholderFont(size: 24.0) + +final class ChatCallNotificationItemNode: NotificationItemNode { + private var item: ChatCallNotificationItem? + + private let avatarNode: AvatarNode + private let title = ComponentView() + private let text = ComponentView() + private let answerButton = ComponentView() + private let declineButton = ComponentView() + + private var compact: Bool? + private var validLayout: CGFloat? + + override init() { + self.avatarNode = AvatarNode(font: avatarFont) + + super.init() + + self.acceptsTouches = true + + self.addSubnode(self.avatarNode) + } + + func setupItem(_ item: ChatCallNotificationItem, compact: Bool) { + self.item = item + + self.compact = compact + if compact { + self.avatarNode.font = compactAvatarFont + } + let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } + + self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: item.peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor) + + if let width = self.validLayout { + let _ = self.updateLayout(width: width, transition: .immediate) + } + } + + override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + self.validLayout = width + + let panelHeight: CGFloat = 66.0 + + guard let item = self.item else { + return panelHeight + } + + let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } + + let leftInset: CGFloat = 14.0 + let rightInset: CGFloat = 14.0 + let avatarSize: CGFloat = 38.0 + let avatarTextSpacing: CGFloat = 10.0 + let buttonSpacing: CGFloat = 14.0 + let titleTextSpacing: CGFloat = 0.0 + + let maxTextWidth: CGFloat = width - leftInset - avatarTextSpacing - rightInset - avatarSize * 2.0 - buttonSpacing - avatarTextSpacing + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: maxTextWidth, height: 100.0) + ) + + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: item.isVideo ? presentationData.strings.Notification_VideoCallIncoming : presentationData.strings.Notification_CallIncoming, font: Font.regular(13.0), textColor: presentationData.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: maxTextWidth, height: 100.0) + ) + + let titleTextHeight = titleSize.height + titleTextSpacing + textSize.height + let titleTextY = floor((panelHeight - titleTextHeight) * 0.5) + let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarSize + avatarTextSpacing, y: titleTextY), size: titleSize) + let textFrame = CGRect(origin: CGPoint(x: leftInset + avatarSize + avatarTextSpacing, y: titleTextY + titleSize.height + titleTextSpacing), size: textSize) + + if let titleView = self.title.view { + if titleView.superview == nil { + self.view.addSubview(titleView) + } + titleView.frame = titleFrame + } + + if let textView = self.text.view { + if textView.superview == nil { + self.view.addSubview(textView) + } + textView.frame = textFrame + } + + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset, y: (panelHeight - avatarSize) / 2.0), size: CGSize(width: avatarSize, height: avatarSize))) + + let answerButtonSize = self.answerButton.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(ZStack([ + AnyComponentWithIdentity(id: 1, component: AnyComponent(Circle( + fillColor: UIColor(rgb: 0x34C759), + size: CGSize(width: avatarSize, height: avatarSize) + ))), + AnyComponentWithIdentity(id: 2, component: AnyComponent(BundleIconComponent( + name: "Call/CallNotificationAnswerIcon", + tintColor: .white + ))) + ])), + effectAlignment: .center, + minSize: CGSize(width: avatarSize, height: avatarSize), + action: { [weak self] in + guard let self, let item = self.item else { + return + } + item.action(true) + } + )), + environment: {}, + containerSize: CGSize(width: avatarSize, height: avatarSize) + ) + let declineButtonSize = self.declineButton.update( + transition: .immediate, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(ZStack([ + AnyComponentWithIdentity(id: 1, component: AnyComponent(Circle( + fillColor: UIColor(rgb: 0xFF3B30), + size: CGSize(width: avatarSize, height: avatarSize) + ))), + AnyComponentWithIdentity(id: 2, component: AnyComponent(BundleIconComponent( + name: "Call/CallNotificationDeclineIcon", + tintColor: .white + ))) + ])), + effectAlignment: .center, + minSize: CGSize(width: avatarSize, height: avatarSize), + action: { [weak self] in + guard let self, let item = self.item else { + return + } + item.action(false) + } + )), + environment: {}, + containerSize: CGSize(width: avatarSize, height: avatarSize) + ) + + let declineButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - avatarSize - buttonSpacing - declineButtonSize.width, y: floor((panelHeight - declineButtonSize.height) * 0.5)), size: declineButtonSize) + if let declineButtonView = self.declineButton.view { + if declineButtonView.superview == nil { + self.view.addSubview(declineButtonView) + } + declineButtonView.frame = declineButtonFrame + } + + let answerButtonFrame = CGRect(origin: CGPoint(x: declineButtonFrame.maxX + buttonSpacing, y: floor((panelHeight - answerButtonSize.height) * 0.5)), size: answerButtonSize) + if let answerButtonView = self.answerButton.view { + if answerButtonView.superview == nil { + self.view.addSubview(answerButtonView) + } + answerButtonView.frame = answerButtonFrame + } + + return panelHeight + } +} diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift similarity index 97% rename from submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift index 6b8b5f050e..1d59686554 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift @@ -20,14 +20,14 @@ import AnimationCache import MultiAnimationRenderer public final class ChatMessageNotificationItem: NotificationItem { - let context: AccountContext - let strings: PresentationStrings - let dateTimeFormat: PresentationDateTimeFormat - let nameDisplayOrder: PresentationPersonNameOrder - let messages: [Message] - let threadData: MessageHistoryThreadData? - let tapAction: () -> Bool - let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void + public let context: AccountContext + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let messages: [Message] + public let threadData: MessageHistoryThreadData? + public let tapAction: () -> Bool + public let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void public var groupingKey: AnyHashable? { return messages.first?.id.peerId @@ -380,7 +380,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { } } - override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = width let compact = self.compact ?? false diff --git a/submodules/TelegramUI/Sources/NotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/NotificationItem.swift similarity index 75% rename from submodules/TelegramUI/Sources/NotificationItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/NotificationItem.swift index 397c7f4faa..03f0542f3c 100644 --- a/submodules/TelegramUI/Sources/NotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/NotificationItem.swift @@ -13,7 +13,9 @@ public protocol NotificationItem { } public class NotificationItemNode: ASDisplayNode { - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { return 32.0 } + + public var acceptsTouches: Bool = false } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 6991eff691..2a69aa8db6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -203,7 +203,15 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { var dashSecondaryColor: UIColor? var dashTertiaryColor: UIColor? - let author = arguments.message?.effectiveAuthor + var author = arguments.message?.effectiveAuthor + + if let forwardInfo = arguments.message?.forwardInfo { + if let peer = forwardInfo.author { + author = peer + } else if let authorSignature = forwardInfo.authorSignature { + author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil) + } + } let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0, dark: arguments.presentationData.theme.theme.overallDarkAppearance) } authorNameColor = colors?.main diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 6a96a5d862..97ed3858db 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -293,6 +293,8 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI } public func scrollToTop() -> Bool { + self.chatListNode.scrollToPosition(.top(adjustForTempInset: false)) + return false } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index 884ac92524..89d0a2a7f0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -87,7 +87,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro } public func scrollToTop() -> Bool { - return false + return self.chatController.performScrollToTop() } public func hitTestResultForScrolling() -> UIView? { diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/Contents.json new file mode 100644 index 0000000000..80053edc1f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "large_hidden 1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/large_hidden 1.pdf b/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/large_hidden 1.pdf new file mode 100644 index 0000000000..933e557a6b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/large_hidden 1.pdf @@ -0,0 +1,134 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.886230 3.427368 cm +1.000000 1.000000 1.000000 scn +38.260708 21.876385 m +41.966354 17.197329 l +42.025131 17.123957 42.070347 17.040751 42.100487 16.951702 c +42.245766 16.522448 42.015556 16.056696 41.586330 15.911341 c +40.312458 15.480631 39.125717 14.924854 38.026104 14.244013 c +36.921986 13.560385 35.998123 12.807886 35.254517 11.986513 c +35.230656 11.961597 35.209145 11.934603 35.189388 11.906336 c +34.929810 11.534874 35.020504 11.023315 35.391689 10.763336 c +36.612873 9.912409 l +36.764828 9.808632 36.876156 9.655817 36.929893 9.479834 c +37.062233 9.046417 36.818161 8.587776 36.384750 8.455406 c +33.044167 7.440701 29.867857 6.143715 26.855820 4.564449 c +24.258381 3.202564 21.947493 1.751854 19.923153 0.212322 c +19.704218 0.045612 19.416248 0.000019 19.156437 0.090702 c +18.728577 0.240036 18.502787 0.707947 18.652121 1.135807 c +18.758976 1.434193 l +19.772457 4.209812 21.285860 6.707634 23.299189 8.927662 c +25.982121 11.886038 29.317558 14.105398 33.305504 15.585741 c +33.305504 20.218485 l +33.304989 20.432394 33.468914 20.610800 33.682098 20.628386 c +34.438572 20.690022 35.148914 20.831165 35.813126 21.051815 c +36.474316 21.271461 37.109425 21.576414 37.718449 21.966673 c +37.895584 22.080223 38.129875 22.041166 38.260708 21.876385 c +h +4.994442 22.027468 m +8.012641 21.104988 l +8.684547 18.079058 9.790257 15.434935 11.329765 13.172619 c +12.755202 11.077932 14.742273 9.031746 17.290981 7.034061 c +17.637016 6.764141 17.708672 6.269619 17.454308 5.911995 c +16.618917 4.741905 l +16.384727 4.413887 15.946983 4.304516 15.586034 4.483837 c +5.774277 9.358383 l +5.648376 9.420931 5.540658 9.514778 5.461451 9.630922 c +5.206123 10.005320 5.302648 10.515812 5.677044 10.771139 c +6.566517 11.377733 l +6.661282 11.442360 6.741444 11.526138 6.801830 11.623659 c +7.040406 12.008947 6.921472 12.514688 6.536184 12.753265 c +0.388562 16.559954 l +0.336297 16.592318 0.287836 16.630453 0.244088 16.673639 c +-0.078415 16.992004 -0.081768 17.511530 0.236598 17.834032 c +4.170660 21.819214 l +4.385132 22.036472 4.702488 22.116701 4.994442 22.027468 c +h +30.742079 20.589767 m +30.998411 20.158377 31.141327 19.678396 31.141327 19.172619 c +31.141327 17.294851 29.171381 15.772619 26.741327 15.772619 c +24.311274 15.772619 22.341328 17.294851 22.341328 19.172619 c +22.341328 19.425110 22.376945 19.671173 22.444510 19.907970 c +25.367607 19.966751 28.125259 20.198212 30.595587 20.567251 c +30.742079 20.589767 l +h +11.389331 20.560408 m +13.907309 20.186129 16.722143 19.955175 19.704977 19.904078 c +19.726641 19.824015 l +19.779579 19.613188 19.807308 19.395405 19.807308 19.172619 c +19.807308 17.294851 17.837360 15.772619 15.407308 15.772619 c +12.977255 15.772619 11.007308 17.294851 11.007308 19.172619 c +11.007308 19.666950 11.143831 20.136642 11.389331 20.560408 c +h +26.181854 41.372620 m +28.509888 41.372620 30.010063 37.754658 30.682379 30.518734 c +35.052368 29.651772 37.913662 28.207664 37.913662 26.572620 c +37.913662 23.921652 30.392046 21.772619 21.113663 21.772619 c +11.835279 21.772619 4.313663 23.921652 4.313663 26.572620 c +4.313663 28.208481 7.177811 29.653212 11.551497 30.519779 c +12.364214 37.755043 13.874769 41.372620 16.083487 41.372620 c +19.449152 41.372620 18.158268 37.207233 21.000959 37.207233 c +23.843653 37.207233 22.634565 41.372620 26.181854 41.372620 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 3357 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 48.000000 48.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000003447 00000 n +0000003470 00000 n +0000003643 00000 n +0000003717 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3776 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/Contents.json new file mode 100644 index 0000000000..0ae3b21323 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "phone.fill.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/phone.fill.svg b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/phone.fill.svg new file mode 100644 index 0000000000..ccfed91796 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/phone.fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/Contents.json new file mode 100644 index 0000000000..47bbf25854 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "phone.down.fill.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/phone.down.fill.svg b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/phone.down.fill.svg new file mode 100644 index 0000000000..4b1eb771db --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/phone.down.fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index b2bdedd8d5..154e3bfeb7 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -896,7 +896,7 @@ private func extractAccountManagerState(records: AccountRecordsView Void)? - let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in + let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in setPresentationCall?(call) }, navigateToChat: { accountId, peerId, messageId in self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil) @@ -1202,6 +1202,7 @@ private func extractAccountManagerState(records: AccountRecordsView 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete } else { if options.contains(.unsendPersonal) { @@ -18035,7 +18041,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ) ) - if canReplyInChat(self.presentationInterfaceState) { + if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) { inputShortcuts.append( KeyShortcut( input: UIKeyCommand.inputUpArrow, @@ -19061,6 +19067,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G public func transferScrollingVelocity(_ velocity: CGFloat) { self.chatDisplayNode.historyNode.transferVelocity(velocity) } + + public func performScrollToTop() -> Bool { + let offset = self.chatDisplayNode.historyNode.visibleContentOffset() + switch offset { + case let .known(value) where value <= CGFloat.ulpOfOne: + return false + default: + self.chatDisplayNode.historyNode.scrollToEndOfHistory() + return true + } + } } final class ChatContextControllerContentSourceImpl: ContextControllerContentSource { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 8e8dfec57b..9a2235765a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -249,7 +249,7 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag return true } -func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool { +func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else { return false } @@ -272,6 +272,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS default: break } + if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation, replyThreadMessage.peerId == accountPeerId { + return false + } if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { if let threadData = chatPresentationInterfaceState.threadData { @@ -611,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - var canReply = canReplyInChat(chatPresentationInterfaceState) + var canReply = canReplyInChat(chatPresentationInterfaceState, accountPeerId: context.account.peerId) var canPin = false let canSelect = !isAction diff --git a/submodules/TelegramUI/Sources/MakeTempAccountContext.swift b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift index 0fc439dd79..89f69e5ddb 100644 --- a/submodules/TelegramUI/Sources/MakeTempAccountContext.swift +++ b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift @@ -27,6 +27,7 @@ public func makeTempContext( encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, + notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 77d57425b1..cddcdac827 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -20,6 +20,7 @@ import MediaEditorScreen import ChatControllerInteraction import SavedMessagesScreen import WallpaperGalleryScreen +import ChatMessageNotificationItem public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { if case let .peer(peer) = params.chatLocation { @@ -75,23 +76,23 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam let controller = ChatListControllerImpl(context: params.context, location: .forum(peerId: peer.id), controlsHistoryPreload: false, enableDebugActions: false) let activateMessageSearch = params.activateMessageSearch + let chatListCompletion = params.chatListCompletion params.navigationController.pushViewController(controller, completion: { [weak controller] in - guard let controller, let activateMessageSearch else { + guard let controller else { return } - controller.activateSearch(query: activateMessageSearch.1) + if let activateMessageSearch { + controller.activateSearch(query: activateMessageSearch.1) + } + + if let chatListCompletion { + chatListCompletion(controller) + } }) return } - /*if case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { - let savedMessagesScreen = SavedMessagesScreen(context: params.context) - params.navigationController.pushViewController(savedMessagesScreen, completion: { - }) - return - }*/ - var found = false var isFirst = true if params.useExisting { diff --git a/submodules/TelegramUI/Sources/NotificationContainerController.swift b/submodules/TelegramUI/Sources/NotificationContainerController.swift index 607f7fc2a8..d8cce2c188 100644 --- a/submodules/TelegramUI/Sources/NotificationContainerController.swift +++ b/submodules/TelegramUI/Sources/NotificationContainerController.swift @@ -6,6 +6,7 @@ import TelegramCore import SwiftSignalKit import TelegramPresentationData import AccountContext +import ChatMessageNotificationItem public final class NotificationContainerController: ViewController { private var controllerNode: NotificationContainerControllerNode { @@ -97,6 +98,10 @@ public final class NotificationContainerController: ViewController { self.controllerNode.enqueue(item) } + public func setBlocking(_ item: NotificationItem?) { + self.controllerNode.setBlocking(item) + } + public func removeItems(_ f: (NotificationItem) -> Bool) { self.controllerNode.removeItems(f) } diff --git a/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift b/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift index d2eebdf635..5870ae8426 100644 --- a/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift +++ b/submodules/TelegramUI/Sources/NotificationContainerControllerNode.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData +import ChatMessageNotificationItem private final class NotificationContainerControllerNodeView: UITracingLayerView { var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? @@ -16,6 +17,7 @@ private final class NotificationContainerControllerNodeView: UITracingLayerView final class NotificationContainerControllerNode: ASDisplayNode { private var validLayout: ContainerViewLayout? private var topItemAndNode: (NotificationItem, NotificationItemContainerNode)? + private var blockingItemAndNode: (NotificationItem, NotificationItemContainerNode)? var displayingItemsUpdated: ((Bool) -> Void)? @@ -49,6 +51,9 @@ final class NotificationContainerControllerNode: ASDisplayNode { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let (_, blockingItemNode) = self.blockingItemAndNode { + return blockingItemNode.hitTest(point, with: event) + } if let (_, topItemNode) = self.topItemAndNode { return topItemNode.hitTest(point, with: event) } @@ -77,6 +82,10 @@ final class NotificationContainerControllerNode: ASDisplayNode { } func enqueue(_ item: NotificationItem) { + if self.blockingItemAndNode != nil { + return + } + if let (_, topItemNode) = self.topItemAndNode { topItemNode.animateOut(completion: { [weak topItemNode] in topItemNode?.removeFromSupernode() @@ -89,9 +98,8 @@ final class NotificationContainerControllerNode: ASDisplayNode { } let itemNode = item.node(compact: useCompactLayout) - let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme) + let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode) containerNode.item = item - containerNode.contentNode = itemNode containerNode.dismissed = { [weak self] item in if let strongSelf = self { if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { @@ -120,7 +128,12 @@ final class NotificationContainerControllerNode: ASDisplayNode { } } self.topItemAndNode = (item, containerNode) - self.addSubnode(containerNode) + + if let blockingItemAndNode = self.blockingItemAndNode { + self.insertSubnode(containerNode, belowSubnode: blockingItemAndNode.1) + } else { + self.addSubnode(containerNode) + } if let validLayout = self.validLayout { containerNode.updateLayout(layout: validLayout, transition: .immediate) @@ -133,6 +146,70 @@ final class NotificationContainerControllerNode: ASDisplayNode { self.resetTimeoutTimer() } + func setBlocking(_ item: NotificationItem?) { + if let (_, blockingItemNode) = self.blockingItemAndNode { + blockingItemNode.animateOut(completion: { [weak blockingItemNode] in + blockingItemNode?.removeFromSupernode() + }) + self.blockingItemAndNode = nil + } + + if let item = item { + if let (_, topItemNode) = self.topItemAndNode { + topItemNode.animateOut(completion: { [weak topItemNode] in + topItemNode?.removeFromSupernode() + }) + } + self.topItemAndNode = nil + + var useCompactLayout = false + if let validLayout = self.validLayout { + useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0 + } + + let itemNode = item.node(compact: useCompactLayout) + let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode) + containerNode.item = item + containerNode.dismissed = { [weak self] item in + if let strongSelf = self { + if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { + topItemNode.removeFromSupernode() + strongSelf.topItemAndNode = nil + + if let strongSelf = self, strongSelf.topItemAndNode == nil { + strongSelf.displayingItemsUpdated?(false) + } + } + } + } + containerNode.cancelTimeout = { [weak self] item in + if let strongSelf = self { + if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { + strongSelf.timeoutTimer?.invalidate() + strongSelf.timeoutTimer = nil + } + } + } + containerNode.resumeTimeout = { [weak self] item in + if let strongSelf = self { + if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey { + strongSelf.resetTimeoutTimer() + } + } + } + self.blockingItemAndNode = (item, containerNode) + self.addSubnode(containerNode) + + if let validLayout = self.validLayout { + containerNode.updateLayout(layout: validLayout, transition: .immediate) + containerNode.frame = CGRect(origin: CGPoint(), size: validLayout.size) + containerNode.animateIn() + } + + self.displayingItemsUpdated?(true) + } + } + func removeItems(_ f: (NotificationItem) -> Bool) { if let (topItem, topItemNode) = self.topItemAndNode { if f(topItem) { diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index f63a5778f6..c5a952f405 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl { return nil }) - sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) + sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil) presentationDataPromise.set(sharedAccountContext!.presentationData) } diff --git a/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift b/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift index 22e32ffe50..aff92e020c 100644 --- a/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift +++ b/submodules/TelegramUI/Sources/NotificationItemContainerNode.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData +import ChatMessageNotificationItem final class NotificationItemContainerNode: ASDisplayNode { private let backgroundNode: ASImageNode @@ -45,7 +46,9 @@ final class NotificationItemContainerNode: ASDisplayNode { var cancelledTimeout = false - init(theme: PresentationTheme) { + init(theme: PresentationTheme, contentNode: NotificationItemNode?) { + self.contentNode = contentNode + self.backgroundNode = ASImageNode() self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false @@ -54,16 +57,21 @@ final class NotificationItemContainerNode: ASDisplayNode { super.init() self.addSubnode(self.backgroundNode) + if let contentNode { + self.addSubnode(contentNode) + } } override func didLoad() { super.didLoad() - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) - let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - panRecognizer.delaysTouchesBegan = false - panRecognizer.cancelsTouchesInView = false - self.view.addGestureRecognizer(panRecognizer) + if let contentNode = self.contentNode, !contentNode.acceptsTouches { + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = false + self.view.addGestureRecognizer(panRecognizer) + } } func animateIn() { @@ -113,6 +121,11 @@ final class NotificationItemContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let contentNode = self.contentNode, contentNode.frame.contains(point) { + if contentNode.acceptsTouches { + if let result = contentNode.view.hitTest(self.view.convert(point, to: contentNode.view), with: event) { + return result + } + } return self.view } return nil diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 2ed91af7e8..e7a561f5ce 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -50,6 +50,7 @@ import ChatRecentActionsController import PeerInfoScreen import ChatQrCodeScreen import UndoUI +import ChatMessageNotificationItem private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -87,6 +88,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let basePath: String public let accountManager: AccountManager public let appLockContext: AppLockContext + public var notificationController: NotificationContainerController? { + didSet { + if self.notificationController !== oldValue { + if let oldValue { + oldValue.setBlocking(nil) + } + } + } + } private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void @@ -137,6 +147,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { public let hasOngoingCall = ValuePromise(false) private let callState = Promise(nil) private var awaitingCallConnectionDisposable: Disposable? + private var callPeerDisposable: Disposable? private var groupCallController: VoiceChatController? public var currentGroupCallController: ViewController? { @@ -216,7 +227,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { private let energyUsageAutomaticDisposable = MetaDisposable() - init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) { + init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, notificationController: NotificationContainerController?, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) { assert(Queue.mainQueue().isCurrent()) precondition(!testHasInstance) @@ -231,6 +242,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.navigateToChatImpl = navigateToChat self.displayUpgradeProgress = displayUpgradeProgress self.appLockContext = appLockContext + self.notificationController = notificationController self.hasInAppPurchases = hasInAppPurchases self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal in @@ -767,18 +779,52 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.callController = nil self.hasOngoingCall.set(false) + self.notificationController?.setBlocking(nil) + + self.callPeerDisposable?.dispose() + self.callPeerDisposable = nil + if let call { self.callState.set(call.state |> map(Optional.init)) self.hasOngoingCall.set(true) setNotificationCall(call) - if !call.isOutgoing && call.isIntegratedWithCallKit { + if call.isOutgoing { + self.presentControllerWithCurrentCall() + } else { + if !call.isIntegratedWithCallKit { + self.callPeerDisposable?.dispose() + self.callPeerDisposable = (call.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId)) + |> deliverOnMainQueue).startStrict(next: { [weak self, weak call] peer in + guard let self, let call, let peer else { + return + } + if self.call !== call { + return + } + + let presentationData = self.currentPresentationData.with { $0 } + self.notificationController?.setBlocking(ChatCallNotificationItem(context: call.context, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, peer: peer, isVideo: call.isVideo, action: { [weak call] answerAction in + guard let call else { + return + } + if answerAction { + call.answer() + } else { + call.rejectBusy() + } + })) + }) + } + self.awaitingCallConnectionDisposable = (call.state |> filter { state in switch state.state { case .ringing: return false + case .terminating, .terminated: + return false default: return true } @@ -788,10 +834,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let self else { return } + self.notificationController?.setBlocking(nil) self.presentControllerWithCurrentCall() + + self.callPeerDisposable?.dispose() + self.callPeerDisposable = nil }) - } else{ - self.presentControllerWithCurrentCall() } } else { self.callState.set(.single(nil)) @@ -861,6 +909,29 @@ public final class SharedAccountContextImpl: SharedAccountContext { let callSignal: Signal = .single(nil) |> then( callManager.currentCallSignal + |> deliverOnMainQueue + |> mapToSignal { call -> Signal in + guard let call else { + return .single(nil) + } + return call.state + |> map { [weak call] state -> PresentationCall? in + guard let call else { + return nil + } + switch state.state { + case .ringing: + return nil + case .terminating, .terminated: + return nil + default: + return call + } + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs === rhs + }) ) let groupCallSignal: Signal = .single(nil) |> then( @@ -1002,6 +1073,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.groupCallDisposable?.dispose() self.callStateDisposable?.dispose() self.awaitingCallConnectionDisposable?.dispose() + self.callPeerDisposable?.dispose() } private var didPerformAccountSettingsImport = false diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 1860d6caee..f8fa7d5abf 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -25,6 +25,17 @@ final class ContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueueWebrtc func isCurrent() -> Bool { return self.queue.isCurrent() } + + func scheduleBlock(_ f: @escaping () -> Void, after timeout: Double) -> GroupCallDisposable { + let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { + f() + }, queue: self.queue) + timer.start() + + return GroupCallDisposable(block: { + timer.invalidate() + }) + } } enum BroadcastPartSubject { diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 1c1270e8c9..5c2aba5e1b 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -213,7 +213,7 @@ public struct OngoingCallContextState: Equatable { public let remoteBatteryLevel: RemoteBatteryLevel } -private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ { +private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc { private let queue: Queue init(queue: Queue) { @@ -235,6 +235,17 @@ private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCal func isCurrent() -> Bool { return self.queue.isCurrent() } + + func scheduleBlock(_ f: @escaping () -> Void, after timeout: Double) -> GroupCallDisposable { + let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { + f() + }, queue: self.queue) + timer.start() + + return GroupCallDisposable(block: { + timer.invalidate() + }) + } } private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType { diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index ae8a533675..e6e9833f1a 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -98,11 +98,20 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { OngoingCallDataSavingAlways }; +@interface GroupCallDisposable : NSObject + +- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block; +- (void)dispose; + +@end + @protocol OngoingCallThreadLocalContextQueueWebrtc - (void)dispatch:(void (^ _Nonnull)())f; - (bool)isCurrent; +- (GroupCallDisposable * _Nonnull)scheduleBlock:(void (^ _Nonnull)())f after:(double)timeout; + @end @interface VoipProxyServerWebrtc : NSObject @@ -133,13 +142,6 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { #endif @end -@interface GroupCallDisposable : NSObject - -- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block; -- (void)dispose; - -@end - @protocol CallVideoFrameBuffer @end diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 06a7924188..f0e739f150 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -925,7 +925,11 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls: std::unique_ptr _tgVoip; bool _didStop; + OngoingCallStateWebrtc _pendingState; OngoingCallStateWebrtc _state; + bool _didPushStateOnce; + GroupCallDisposable *_pushStateDisposable; + OngoingCallVideoStateWebrtc _videoState; bool _connectedOnce; OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel; @@ -1356,6 +1360,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; .directConnectionChannel = directConnectionChannel }); _state = OngoingCallStateInitializing; + _pendingState = OngoingCallStateInitializing; _signalBars = 4; } return self; @@ -1374,6 +1379,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; _currentAudioDeviceModuleThread = nullptr; } + [_pushStateDisposable dispose]; + if (_tgVoip != NULL) { [self stop:nil]; } @@ -1469,6 +1476,18 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } } +- (void)pushPendingState { + _didPushStateOnce = true; + + if (_state != _pendingState) { + _state = _pendingState; + + if (_stateChanged) { + _stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio); + } + } +} + - (void)controllerStateChanged:(tgcalls::State)state { OngoingCallStateWebrtc callState = OngoingCallStateInitializing; switch (state) { @@ -1485,11 +1504,32 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; break; } - if (_state != callState) { - _state = callState; + if (_pendingState != callState) { + _pendingState = callState; - if (_stateChanged) { - _stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio); + [_pushStateDisposable dispose]; + _pushStateDisposable = nil; + + bool maybeDelayPush = false; + if (!_didPushStateOnce) { + maybeDelayPush = false; + } else if (callState == OngoingCallStateReconnecting) { + maybeDelayPush = true; + } else { + maybeDelayPush = false; + } + + if (!maybeDelayPush) { + [self pushPendingState]; + } else { + __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; + _pushStateDisposable = [_queue scheduleBlock:^{ + __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + [strongSelf pushPendingState]; + } after:1.0]; } } }