diff --git a/Telegram/BUILD b/Telegram/BUILD index c40fa37fe1..6ed4339a42 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1785,8 +1785,10 @@ ios_application( #"//submodules/TelegramApi", #"//submodules/TelegramCore", #"//submodules/FFMpegBinding", - "//submodules/Display", + #"//submodules/Display", #"//third-party/webrtc", + "//submodules/AsyncDisplayKit", + "//submodules/ObjCRuntimeUtils", ], ) diff --git a/build-system/bazel-utils/spm.bzl b/build-system/bazel-utils/spm.bzl index 793d804c2a..7cd7038a13 100644 --- a/build-system/bazel-utils/spm.bzl +++ b/build-system/bazel-utils/spm.bzl @@ -95,6 +95,7 @@ _IGNORE_OBJC_LIBRARY_ATTRS = [ "toolchains", "transitive_configs", "visibility", + "package_metadata", ] _IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [ @@ -106,6 +107,8 @@ _IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [ "restricted_to", "textual_hdrs", "sdk_includes", + "conlyopts", + "cxxopts", ] _OBJC_LIBRARY_ATTRS = { @@ -154,6 +157,8 @@ _IGNORE_SWIFT_LIBRARY_ATTRS = [ "toolchains", "transitive_configs", "visibility", + "library_evolution", + "package_metadata", ] _IGNORE_SWIFT_LIBRARY_EMPTY_ATTRS = [ @@ -332,9 +337,9 @@ def _collect_spm_modules_impl(target, ctx): # Extract the path from the label # Example: @//path/ModuleName:ModuleSubname -> path/ModuleName - if not str(ctx.label).startswith("@//"): + if not str(ctx.label).startswith("@@//"): fail("Invalid label: {}".format(ctx.label)) - module_path = str(ctx.label).split(":")[0].split("@//")[1] + module_path = str(ctx.label).split(":")[0].split("@@//")[1] if module_type == "objc_library": module_info = { diff --git a/build-system/decrypt.rb b/build-system/decrypt.rb index ba126e9db3..5f89736a25 100644 --- a/build-system/decrypt.rb +++ b/build-system/decrypt.rb @@ -51,6 +51,11 @@ class EncryptionV2 key = keyIv[0..31] iv = keyIv[32..43] auth_data = keyIv[44..-1] + + puts "key: #{key.inspect}" + puts "iv: #{iv.inspect}" + puts "auth_data: #{auth_data.inspect}" + cipher.key = key cipher.iv = iv cipher.auth_data = auth_data diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 81dd4dd822..8eae4eb612 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1078,6 +1078,7 @@ public protocol SharedAccountContext: AnyObject { selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode ) -> ChatHistoryListNode + func subscribeChatListData(context: AccountContext, location: ChatListControllerLocation) -> Signal func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: ((UIView?, CGPoint?) -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: Peer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool) -> ListViewItem func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader @@ -1120,7 +1121,7 @@ public protocol SharedAccountContext: AnyObject { func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) - func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal + func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 4cab8932e6..fd56cb4fbc 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -949,9 +949,11 @@ public let ChatControllerCount = Atomic(value: 0) public final class PeerInfoNavigationSourceTag { public let peerId: EnginePeer.Id + public let threadId: Int64? - public init(peerId: EnginePeer.Id) { + public init(peerId: EnginePeer.Id, threadId: Int64?) { self.peerId = peerId + self.threadId = threadId } } @@ -1016,6 +1018,13 @@ public protocol ChatControllerCustomNavigationPanelNode: ASDisplayNode { func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, chatController: ChatController) -> LayoutResult } +public enum ChatControllerAnimateInnerChatSwitchDirection { + case up + case down + case left + case right +} + public protocol ChatController: ViewController { var chatLocation: ChatLocation { get } var canReadHistory: ValuePromise { get } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 757e2ed170..2389ead7a8 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1267,7 +1267,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, openBoostToUnrestrict: { }, updateVideoTrimRange: { _, _, _, _ in }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 7bb140e7a8..d7e5cc0247 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -175,7 +175,7 @@ public final class BrowserBookmarksScreen: ViewController { }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6511928025..c995c060e0 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -814,7 +814,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return false } case let .forum(peerId): - self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId) + self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: nil) self.navigationBar?.allowsCustomTransition = { [weak self] in guard let strongSelf = self else { return false @@ -1345,7 +1345,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if case let .channel(channel) = actualPeer, channel.isForumOrMonoForum, let threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), purposefulAction: { if deactivateOnAction { @@ -1378,7 +1378,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if case let .channel(channel) = peer, channel.isForumOrMonoForum, let threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in self?.deactivateSearch(animated: false) @@ -1462,7 +1462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) |> deliverOnMainQueue).startStandalone(next: { topicId in - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone() }, error: { _ in controller?.isInProgress = false }) @@ -3762,6 +3762,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }))) } + var needsSeparatorForCreateTopic = true if let sourceController = sourceController as? ChatController { items.append(.separator) items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in @@ -3771,8 +3772,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController sourceController?.beginMessageSearch("") }))) - } else if channel.hasPermission(.createTopics) { - items.append(.separator) + needsSeparatorForCreateTopic = false + } + if channel.hasPermission(.createTopics) { + if needsSeparatorForCreateTopic { + items.append(.separator) + } items.append(.action(ContextMenuActionItem(text: strings.Chat_CreateTopic, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) @@ -3788,7 +3793,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) |> deliverOnMainQueue).startStandalone(next: { topicId in if let navigationController = (sourceController.navigationController as? NavigationController) { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, scrollToEndIfExists: false, keepStack: .never, animated: true).startStandalone() } }, error: { _ in controller?.isInProgress = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 20fb07f9d8..8c9b818dd9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1023,7 +1023,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let titleTopicIconContent: EmojiStatusComponent.Content? if threadId == 1 { - titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme)) + titleTopicIconContent = .image(image: PresentationResourcesChatList.generalTopicSmallIcon(theme), tintColor: nil) } else if let fileId = iconId, fileId != 0 { titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) } else if let iconColor { @@ -3850,7 +3850,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let avatarIconContent: EmojiStatusComponent.Content if threadInfo.id == 1 { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme)) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(item.presentationData.theme), tintColor: nil) } else if let fileId = threadInfo.info.icon, fileId != 0 { avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) } else { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index dea1d3d354..c999b87ee9 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -6,12 +6,12 @@ import Display import TelegramUIPreferences import AccountContext -enum ChatListNodeLocation: Equatable { +public enum ChatListNodeLocation: Equatable { case initial(count: Int, filter: ChatListFilter?) case navigation(index: EngineChatList.Item.Index, filter: ChatListFilter?) case scroll(index: EngineChatList.Item.Index, sourceIndex: EngineChatList.Item.Index, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilter?) - var filter: ChatListFilter? { + public var filter: ChatListFilter? { switch self { case let .initial(_, filter): return filter @@ -23,10 +23,16 @@ enum ChatListNodeLocation: Equatable { } } -struct ChatListNodeViewUpdate { - let list: EngineChatList - let type: ViewUpdateType - let scrollPosition: ChatListNodeViewScrollPosition? +public struct ChatListNodeViewUpdate { + public let list: EngineChatList + public let type: ViewUpdateType + public let scrollPosition: ChatListNodeViewScrollPosition? + + public init(list: EngineChatList, type: ViewUpdateType, scrollPosition: ChatListNodeViewScrollPosition?) { + self.list = list + self.type = type + self.scrollPosition = scrollPosition + } } public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: EnginePeer.Id) -> ChatListFilterPredicate { @@ -113,7 +119,7 @@ public func chatListFilterPredicate(filter: ChatListFilterData, accountPeerId: E }) } -func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal { +public func chatListViewForLocation(chatListLocation: ChatListControllerLocation, location: ChatListNodeLocation, account: Account, shouldLoadCanMessagePeer: Bool) -> Signal { let accountPeerId = account.peerId switch chatListLocation { diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index 5d10a7a9ed..630edbf917 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -47,7 +47,7 @@ struct ChatListNodeViewTransition { let animateCrossfade: Bool } -enum ChatListNodeViewScrollPosition { +public enum ChatListNodeViewScrollPosition { case index(index: ChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 01370fa96b..45340d8997 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -172,7 +172,7 @@ public final class ChatPanelInterfaceInteraction { public let openStarsPurchase: (Int64?) -> Void public let openMessagePayment: () -> Void public let updateHistoryFilter: ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void - public let updateChatLocationThread: (Int64?) -> Void + public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void public let toggleChatSidebarMode: () -> Void public let updateDisplayHistoryFilterAsList: (Bool) -> Void public let openBoostToUnrestrict: () -> Void @@ -294,7 +294,7 @@ public final class ChatPanelInterfaceInteraction { openBoostToUnrestrict: @escaping () -> Void, updateVideoTrimRange: @escaping (Double, Double, Bool, Bool) -> Void, updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void, - updateChatLocationThread: @escaping (Int64?) -> Void, + updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void, toggleChatSidebarMode: @escaping () -> Void, updateDisplayHistoryFilterAsList: @escaping (Bool) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, @@ -540,7 +540,7 @@ public final class ChatPanelInterfaceInteraction { }, openBoostToUnrestrict: { }, updateVideoTrimRange: { _, _, _, _ in }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 581b4b0238..f88e3ded10 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -2376,16 +2376,24 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel } else { let updateItem = updateIndicesAndItems[0] if let previousNode = previousNodes[updateItem.index] { + let threadId = pthread_self() + var tailRecurse = false self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: crossfade, customAnimationTransition: customAnimationTransition, completion: { _, layout, apply in state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, isAnimated: animated, apply: apply, operations: &operations) updateIndicesAndItems.remove(at: 0) - self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { + tailRecurse = true + } else { + self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + } }) - break + if !tailRecurse { + tailRecurse = true + break + } } else { updateIndicesAndItems.remove(at: 0) - //self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) } } } diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 5392dde83f..0c381ea128 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -458,6 +458,56 @@ open class BlurredBackgroundView: UIView { public protocol NavigationBarHeaderView: UIView { } +private final class NavigationBackgroundCutoutView: UIView { + private let topLayer: SimpleLayer + private let rightLayer: SimpleLayer + + weak var targetNode: ASDisplayNode? + + override init(frame: CGRect) { + self.topLayer = SimpleLayer() + self.topLayer.backgroundColor = UIColor.white.cgColor + + self.rightLayer = SimpleLayer() + self.rightLayer.backgroundColor = UIColor.white.cgColor + + super.init(frame: frame) + + self.layer.addSublayer(self.topLayer) + self.layer.addSublayer(self.rightLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, cutout: CGSize?, transition: ContainedViewLayoutTransition) { + let cutout = cutout ?? CGSize() + + let topFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: cutout.height)) + let rightFrame = CGRect(origin: CGPoint(x: cutout.width, y: 0.0), size: CGSize(width: max(0.0, size.width - cutout.width), height: size.height)) + + if self.topLayer.frame != topFrame || self.rightLayer.frame != rightFrame { + if cutout.width != 0.0 && cutout.height != 0.0 { + self.targetNode?.view.mask = self + //self.targetNode?.view.addSubview(self) + } + + transition.updateFrame(layer: self.topLayer, frame: topFrame) + transition.updateFrame(layer: self.rightLayer, frame: rightFrame, completion: { [weak self] completed in + guard let self, completed else { + return + } + + if !(cutout.width != 0.0 && cutout.height != 0.0) { + self.targetNode?.view.mask = nil + //self.removeFromSuperview() + } + }) + } + } +} + open class NavigationBar: ASDisplayNode { public static var defaultSecondaryContentHeight: CGFloat { return 38.0 @@ -489,7 +539,7 @@ open class NavigationBar: ASDisplayNode { var presentationData: NavigationBarPresentationData - private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)? + private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)? private var requestedLayout: Bool = false var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in } @@ -1013,6 +1063,8 @@ open class NavigationBar: ASDisplayNode { public let leftButtonNode: NavigationButtonNode public let rightButtonNode: NavigationButtonNode public let additionalContentNode: SparseNode + + private let navigationBackgroundCutoutView: NavigationBackgroundCutoutView public func reattachAdditionalContentNode() { if self.additionalContentNode.supernode !== self { @@ -1139,6 +1191,9 @@ open class NavigationBar: ASDisplayNode { self.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight + self.navigationBackgroundCutoutView = NavigationBackgroundCutoutView(frame: CGRect()) + self.navigationBackgroundCutoutView.targetNode = self.backgroundNode + super.init() self.addSubnode(self.backgroundNode) @@ -1246,22 +1301,25 @@ open class NavigationBar: ASDisplayNode { if let validLayout = self.validLayout, self.requestedLayout { self.requestedLayout = false - self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate) + self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, additionalCutout: validLayout.additionalCutout, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate) } } - func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) { if self.layoutSuspended { return } - self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, leftInset, rightInset, appearsHidden, isLandscape) + self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, additionalCutout, leftInset, rightInset, appearsHidden, isLandscape) let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight)) if self.backgroundNode.frame != backgroundFrame { transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) self.backgroundNode.update(size: backgroundFrame.size, transition: transition) + + transition.updateFrame(view: self.navigationBackgroundCutoutView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) } + self.navigationBackgroundCutoutView.update(size: backgroundFrame.size, cutout: additionalCutout, transition: transition) let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? (self.secondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 @@ -1293,7 +1351,7 @@ open class NavigationBar: ASDisplayNode { contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) } - transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height + additionalBackgroundHeight, width: size.width, height: UIScreenPixel)) + transition.updateFrame(node: self.stripeNode, frame: CGRect(x: (additionalCutout?.width ?? 0.0), y: size.height + additionalBackgroundHeight, width: size.width - (additionalCutout?.width ?? 0.0), height: UIScreenPixel)) let nominalHeight: CGFloat = defaultHeight let contentVerticalOrigin = additionalTopHeight diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 90c53ba32c..7d3f2f8be3 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -424,10 +424,10 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { } open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: 0.0, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition) } - public func applyNavigationBarLayout(_ layout: ContainerViewLayout, navigationLayout: NavigationLayout, additionalBackgroundHeight: CGFloat, transition: ContainedViewLayoutTransition) { + public func applyNavigationBarLayout(_ layout: ContainerViewLayout, navigationLayout: NavigationLayout, additionalBackgroundHeight: CGFloat, additionalCutout: CGSize?, transition: ContainedViewLayoutTransition) { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: navigationLayout.navigationFrame.maxY)) @@ -451,7 +451,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { navigationBarFrame.size.height += navigationBar.secondaryContentHeight } - navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition) + navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, additionalCutout: additionalCutout, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition) if !transition.isAnimated { navigationBar.layer.removeAnimation(forKey: "bounds") navigationBar.layer.removeAnimation(forKey: "position") diff --git a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m index 4435e0b5dd..82b42731c5 100644 --- a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m +++ b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m @@ -1,4 +1,5 @@ #import "RuntimeUtils.h" +#include #import diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 1aefe3e225..48d031b145 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -977,7 +977,7 @@ public class ReplaceBoostScreen: ViewController { layout.statusBarHeight = nil - self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 7872e0a3cc..3f87d69206 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -394,13 +394,14 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { private var suspendNavigationBarLayout: Bool = false private var suspendedNavigationBarLayout: ContainerViewLayout? private var additionalNavigationBarBackgroundHeight: CGFloat = 0.0 + private var additionalNavigationBarCutout: CGSize? override open func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if self.suspendNavigationBarLayout { self.suspendedNavigationBarLayout = layout return } - self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -969,7 +970,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { self.suspendNavigationBarLayout = false if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout { self.suspendedNavigationBarLayout = suspendedNavigationBarLayout - self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) + self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition) } self.accessoryPanelContainerHeight = additionalHeight diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index 105ce3a39c..24bc6ed9ed 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -71,6 +71,7 @@ public final class ChatInlineSearchResultsListComponent: Component { case empty case tag(MemoryBuffer) case search(query: String, includeSavedPeers: Bool) + case monoforumChats(query: String) } public let context: AccountContext @@ -85,6 +86,7 @@ public final class ChatInlineSearchResultsListComponent: Component { public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? public let getSearchResult: () -> Signal? public let getSavedPeers: (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>? + public let getChats: (String) -> Signal? public let loadMoreSearchResults: () -> Void public init( @@ -100,6 +102,7 @@ public final class ChatInlineSearchResultsListComponent: Component { loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, getSearchResult: @escaping () -> Signal?, getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?, + getChats: @escaping (String) -> Signal?, loadMoreSearchResults: @escaping () -> Void ) { self.context = context @@ -114,6 +117,7 @@ public final class ChatInlineSearchResultsListComponent: Component { self.loadTagMessages = loadTagMessages self.getSearchResult = getSearchResult self.getSavedPeers = getSavedPeers + self.getChats = getChats self.loadMoreSearchResults = loadMoreSearchResults } @@ -146,10 +150,12 @@ public final class ChatInlineSearchResultsListComponent: Component { enum Id: Hashable { case peer(EnginePeer.Id) case message(EngineMessage.Id) + case chat(EngineChatList.Item.Id) } case peer(EnginePeer) case message(EngineMessage) + case chat(EngineChatList.Item) var id: Id { switch self { @@ -157,6 +163,8 @@ public final class ChatInlineSearchResultsListComponent: Component { return .peer(peer.id) case let .message(message): return .message(message.id) + case let .chat(chat): + return .chat(chat.id) } } @@ -174,6 +182,12 @@ public final class ChatInlineSearchResultsListComponent: Component { } else { return false } + case let .chat(chat): + if case .chat(chat) = rhs { + return true + } else { + return false + } } } @@ -188,14 +202,27 @@ public final class ChatInlineSearchResultsListComponent: Component { return lhsPeer.id < rhsPeer.id case .message: return true + case .chat: + return true } case let .message(lhsMessage): switch rhs { case .peer: return false + case .chat: + return false case let .message(rhsMessage): return lhsMessage.index > rhsMessage.index } + case let .chat(lhsChat): + switch rhs { + case let .chat(rhsChat): + return lhsChat.index > rhsChat.index + case .peer: + return false + case .message: + return true + } } } } @@ -418,6 +445,8 @@ public final class ChatInlineSearchResultsListComponent: Component { } case .search: break + case .monoforumChats: + break } } } else if let (currentIndex, disposable) = self.searchContents { @@ -431,6 +460,8 @@ public final class ChatInlineSearchResultsListComponent: Component { self.searchContents = (loadAroundIndex, disposable) component.loadMoreSearchResults() + case .monoforumChats: + break } } } @@ -581,6 +612,127 @@ public final class ChatInlineSearchResultsListComponent: Component { })) } } + case let .monoforumChats(query): + let _ = query + + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let disposable = MetaDisposable() + self.searchContents = (nil, disposable) + + let savedPeers: Signal + if let savedPeersSignal = component.getChats(query) { + savedPeers = savedPeersSignal + } else { + savedPeers = .single(nil) + } + + disposable.set((savedPeers + |> deliverOnMainQueue).startStrict(next: { [weak self] chatList in + guard let self else { + return + } + + let messages: [EngineMessage] = [] /*result?.messages.map { entry in + return EngineMessage(entry) + } ?? []*/ + + var entries: [Entry] = [] + if let chatList { + for item in chatList.items { + entries.append(.chat(item)) + } + } + for message in messages { + entries.append(.message(message)) + } + entries.sort() + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .search(query), + entries: entries, + messages: messages, + hasEarlier: false, //!(result?.completed ?? true), + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + + /*if !query.isEmpty, let savedPeersSignal = component.getSavedPeers(query) { + savedPeers = savedPeersSignal + } else { + savedPeers = .single([]) + }*/ + + /*if let historySignal = component.getSearchResult() { + disposable.set((savedPeers + |> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in + if savedPeers.isEmpty { + return historySignal + |> map { result in + return ([], result) + } + } else { + return (.single(nil) |> then(historySignal)) + |> map { result in + return (savedPeers, result) + } + } + } + |> deliverOnMainQueue).startStrict(next: { [weak self] savedPeers, result in + guard let self else { + return + } + + let messages: [EngineMessage] = result?.messages.map { entry in + return EngineMessage(entry) + } ?? [] + + var entries: [Entry] = [] + for (peer, _) in savedPeers { + entries.append(.peer(peer)) + } + for message in messages { + entries.append(.message(message)) + } + entries.sort() + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .search(query), + entries: entries, + messages: messages, + hasEarlier: !(result?.completed ?? true), + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + }*/ + } } if let contentsState = self.contentsState, self.contentsState != self.appliedContentsState { @@ -607,13 +759,17 @@ public final class ChatInlineSearchResultsListComponent: Component { }, additionalCategorySelected: { _ in }, - messageSelected: { [weak self] _, _, message, _ in - guard let self else { + messageSelected: { [weak self] peer, _, message, _ in + guard let self, let component = self.component else { return } self.listNode.clearHighlightAnimated(true) - self.component?.messageSelected(message) + if case .monoforumChats = component.contents { + component.peerSelected(peer) + } else { + component.messageSelected(message) + } }, groupSelected: { _ in }, @@ -845,6 +1001,45 @@ public final class ChatInlineSearchResultsListComponent: Component { hiddenOffset: false, interaction: chatListNodeInteraction ) + case let .chat(item): + return ChatListItem( + presentationData: chatListPresentationData, + context: component.context, + chatListLocation: component.peerId.flatMap { peerId in .savedMessagesChats(peerId: peerId) } ?? .chatList(groupId: .root), + filterData: nil, + index: item.index, + content: .peer(ChatListItemContent.PeerData( + messages: item.messages, + peer: item.renderedPeer, + threadInfo: nil, + combinedReadState: nil, + isRemovedFromTotalUnreadCount: false, + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + draftState: nil, + mediaDraftContentType: nil, + inputActivities: nil, + promoInfo: nil, + ignoreUnreadBadge: false, + displayAsMessage: component.peerId != component.context.account.peerId && !component.showEmptyResults, + hasFailedMessages: false, + forumTopicData: nil, + topForumTopicItems: [], + autoremoveTimeout: nil, + storyState: nil, + requiresPremiumForMessaging: false, + displayAsTopicList: false, + tags: [] + )), + editing: false, + hasActiveRevealControls: false, + selected: false, + header: nil, + enableContextActions: false, + hiddenOffset: false, + interaction: chatListNodeInteraction + ) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 289c647211..1b059bc3c9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -816,7 +816,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) - let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) + //let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let currentShareButtonNode = self.shareButtonNode let currentForwardInfo = self.appliedForwardInfo @@ -1107,7 +1107,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) var viaBotApply: (TextNodeLayout, () -> TextNode)? - var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? + let threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? = nil var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)? var replyMarkup: ReplyMarkupMessageAttribute? @@ -1173,7 +1173,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { hasReply = false } - threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( + /*threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( presentationData: item.presentationData, strings: item.presentationData.strings, context: item.context, @@ -1185,7 +1185,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) + ))*/ } if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index a62523895c..ec521589b0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2413,8 +2413,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var boostNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var viaWidth: CGFloat = 0.0 - var threadInfoOriginY: CGFloat = 0.0 - var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil }) + let threadInfoOriginY: CGFloat = 0.0 + let threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil }) var replyInfoOriginY: CGFloat = 0.0 var replyInfoSizeApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode?) = (CGSize(), { _, _, _ in nil }) @@ -2625,8 +2625,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } - var hasThreadInfo = false - if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForum, item.message.associatedThreadInfo != nil { + let hasThreadInfo = !"".isEmpty + /*if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForum, item.message.associatedThreadInfo != nil { hasThreadInfo = true } else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { @@ -2634,7 +2634,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { hasThreadInfo = true } - } + }*/ var hasReply = replyMessage != nil || replyForward != nil || replyStory != nil if !isInstantVideo, hasThreadInfo { @@ -2642,7 +2642,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI hasReply = false } - if !mergedTop.merged { + /*if !mergedTop.merged { if headerSize.height.isZero { headerSize.height += 14.0 } else { @@ -2666,7 +2666,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI threadInfoOriginY = headerSize.height headerSize.width = max(headerSize.width, threadInfoSizeApply.0.width + bubbleWidthInsets) headerSize.height += threadInfoSizeApply.0.height + 5.0 - } + }*/ } if !isInstantVideo, hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) { @@ -3549,7 +3549,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI guard let strongSelf, let item = strongSelf.item else { return } - item.controllerInteraction.updateChatLocationThread(item.content.firstMessage.threadId) + item.controllerInteraction.updateChatLocationThread(item.content.firstMessage.threadId, nil) } } @@ -6000,7 +6000,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } } else if let channel = item.message.peers[item.message.id.peerId], channel.isMonoForum, case .peer = item.chatLocation { - item.controllerInteraction.updateChatLocationThread(item.message.threadId) + item.controllerInteraction.updateChatLocationThread(item.message.threadId, nil) } else { if !self.disablesComments { if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD index e704fe5675..a5e2f3cdcd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/BUILD @@ -35,6 +35,8 @@ swift_library( "//submodules/Components/HierarchyTrackingLayer", "//submodules/WallpaperBackgroundNode", "//submodules/AvatarVideoNode", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 290c6dcfa4..bf7e26de8b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -17,6 +17,8 @@ import ChatControllerInteraction import AvatarVideoNode import ChatMessageItem import AvatarNode +import ComponentFlow +import EmojiStatusComponent private let timezoneOffset: Int32 = { let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) @@ -39,18 +41,23 @@ public final class ChatMessageDateHeader: ListViewItemHeader { } } - public final class PeerData { - public let peer: EnginePeer + public final class HeaderData { + public enum Contents { + case peer(EnginePeer) + case thread(id: Int64, info: Message.AssociatedThreadInfo) + } - public init(peer: EnginePeer) { - self.peer = peer + public let contents: Contents + + public init(contents: Contents) { + self.contents = contents } } private let timestamp: Int32 private let roundedTimestamp: Int32 private let scheduled: Bool - public let displayPeer: PeerData? + public let displayHeader: HeaderData? public let id: ListViewItemNode.HeaderId public let stackingId: ListViewItemNode.HeaderId? @@ -60,16 +67,16 @@ public final class ChatMessageDateHeader: ListViewItemHeader { public let context: AccountContext public let action: ((Int32, Bool) -> Void)? - public init(timestamp: Int32, separableThreadId: Int64?, scheduled: Bool, displayPeer: PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + public init(timestamp: Int32, separableThreadId: Int64?, scheduled: Bool, displayHeader: HeaderData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.timestamp = timestamp self.scheduled = scheduled - self.displayPeer = displayPeer + self.displayHeader = displayHeader self.presentationData = presentationData self.controllerInteraction = controllerInteraction self.context = context self.action = action self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp) - if let _ = self.displayPeer { + if let _ = self.displayHeader { self.idValue = ChatMessageDateHeader.Id(roundedTimestamp: 0, separableThreadId: separableThreadId) self.id = ListViewItemNode.HeaderId(space: 3, id: self.idValue) self.stackingId = ListViewItemNode.HeaderId(space: 2, id: ChatMessageDateHeader.Id(roundedTimestamp: Int64(self.roundedTimestamp), separableThreadId: nil)) @@ -98,7 +105,7 @@ public final class ChatMessageDateHeader: ListViewItemHeader { } public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { - return ChatMessageDateHeaderNodeImpl(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, displayPeer: self.displayPeer, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action) + return ChatMessageDateHeaderNodeImpl(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, displayHeader: self.displayHeader, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action) } public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { @@ -362,9 +369,11 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { private let context: AccountContext private var presentationData: ChatPresentationData private let controllerInteraction: ChatControllerInteraction? - private let peer: EnginePeer + private let displayHeader: ChatMessageDateHeader.HeaderData + + private var avatarNode: AvatarNode? + private var icon: ComponentView? - private let avatarNode: AvatarNode public let labelNode: TextNode public let backgroundNode: NavigationBackgroundNode public let stickBackgroundNode: ASImageNode @@ -372,13 +381,11 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { private var backgroundContent: WallpaperBubbleBackgroundNode? private var text: String - init(context: AccountContext, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, peer: EnginePeer) { + init(context: AccountContext, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, displayHeader: ChatMessageDateHeader.HeaderData) { self.context = context self.presentationData = presentationData self.controllerInteraction = controllerInteraction - self.peer = peer - - self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0)) + self.displayHeader = displayHeader self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false @@ -397,7 +404,13 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { self.stickBackgroundNode.displayWithoutProcessing = true self.stickBackgroundNode.displaysAsynchronously = false - let text = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let text: String + switch displayHeader.contents { + case let .peer(peer): + text = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + case let .thread(_, info): + text = info.title + } self.text = text super.init() @@ -415,7 +428,50 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { } else { self.addSubnode(self.backgroundNode) } - self.addSubnode(self.avatarNode) + + switch displayHeader.contents { + case let .peer(peer): + let avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0)) + self.avatarNode = avatarNode + self.addSubnode(avatarNode) + + if peer.smallProfileImage != nil { + avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: CGSize(width: 16.0, height: 16.0)) + } else { + avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: CGSize(width: 16.0, height: 16.0)) + } + case let .thread(id, info): + let avatarIconContent: EmojiStatusComponent.Content + if id != 1 { + if let fileId = info.icon, fileId != 0 { + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 16.0, height: 16.0), placeholderColor: presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: presentationData.theme.theme.list.itemAccentColor, loopMode: .count(0)) + } else { + avatarIconContent = .topic(title: String(info.title.prefix(1)), color: info.iconColor, size: CGSize(width: 16.0, height: 16.0)) + } + } else { + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(presentationData.theme.theme)?.withRenderingMode(.alwaysTemplate), tintColor: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper)) + } + + let avatarIconComponent = EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: avatarIconContent, + isVisibleForAnimations: false, + action: nil + ) + let icon = ComponentView() + self.icon = icon + let _ = icon.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent), + environment: {}, + containerSize: CGSize(width: 16.0, height: 16.0) + ) + if let iconView = icon.view { + self.view.addSubview(iconView) + } + } self.addSubnode(self.labelNode) let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))) @@ -461,7 +517,13 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { let chatDateInset: CGFloat = 6.0 let avatarDiameter: CGFloat = 16.0 - let avatarInset: CGFloat = 2.0 + let avatarInset: CGFloat + switch self.displayHeader.contents { + case .peer: + avatarInset = 2.0 + case .thread: + avatarInset = 4.0 + } let avatarSpacing: CGFloat = 4.0 let labelSize = self.labelNode.bounds.size @@ -469,14 +531,14 @@ private final class ChatMessagePeerContentNode: ASDisplayNode { let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((size.width - leftInset - rightInset - backgroundSize.width) / 2.0), y: (size.height - chatDateSize) / 2.0), size: backgroundSize) - let avatarFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) - transition.updateFrame(node: self.avatarNode, frame: avatarFrame) - self.avatarNode.updateSize(size: avatarFrame.size) + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) - if self.peer.smallProfileImage != nil { - self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size) - } else { - self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size) + if let avatarNode = self.avatarNode { + transition.updateFrame(node: avatarNode, frame: iconFrame) + avatarNode.updateSize(size: iconFrame.size) + } + if let iconView = self.icon?.view { + transition.updateFrame(view: iconView, frame: iconFrame) } transition.updateFrame(node: self.stickBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) @@ -517,7 +579,7 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe private let context: AccountContext private let localTimestamp: Int32 private let scheduled: Bool - private let displayPeer: ChatMessageDateHeader.PeerData? + private let displayHeader: ChatMessageDateHeader.HeaderData? private var presentationData: ChatPresentationData private let controllerInteraction: ChatControllerInteraction? @@ -528,14 +590,14 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe private var params: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)? private var absolutePosition: (CGRect, CGSize)? - public init(localTimestamp: Int32, scheduled: Bool, displayPeer: ChatMessageDateHeader.PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + public init(localTimestamp: Int32, scheduled: Bool, displayHeader: ChatMessageDateHeader.HeaderData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.context = context self.presentationData = presentationData self.controllerInteraction = controllerInteraction self.localTimestamp = localTimestamp self.scheduled = scheduled - self.displayPeer = displayPeer + self.displayHeader = displayHeader self.action = action let isRotated = controllerInteraction?.chatIsRotated ?? true @@ -546,13 +608,13 @@ public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMe self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) } - if let displayPeer { + if let displayHeader { if self.peerContentNode == nil { let sectionSeparator = ChatMessageDateSectionSeparatorNode(controllerInteraction: controllerInteraction, presentationData: presentationData) self.sectionSeparator = sectionSeparator self.addSubnode(sectionSeparator) - let peerContentNode = ChatMessagePeerContentNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, peer: displayPeer.peer) + let peerContentNode = ChatMessagePeerContentNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, displayHeader: displayHeader) self.peerContentNode = peerContentNode self.addSubnode(peerContentNode) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index b7c110d4e6..4dc0005076 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -92,10 +92,10 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs isPaid = true } - var sameThread = true - if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.isForumOrMonoForum, lhs.threadId != rhs.threadId { + let sameThread = true + /*if let lhsPeer = lhs.peers[lhs.id.peerId], let rhsPeer = rhs.peers[rhs.id.peerId], arePeersEqual(lhsPeer, rhsPeer), let channel = lhsPeer as? TelegramChannel, channel.isForumOrMonoForum, lhs.threadId != rhs.threadId { sameThread = false - } + }*/ var sameAuthor = false if lhsEffectiveAuthor?.id == rhsEffectiveAuthor?.id && lhs.effectivelyIncoming(accountPeerId) == rhs.effectivelyIncoming(accountPeerId) { @@ -288,7 +288,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId var headerSeparableThreadId: Int64? - var headerDisplayPeer: ChatMessageDateHeader.PeerData? + var headerDisplayPeer: ChatMessageDateHeader.HeaderData? do { let peerId = messagePeerId @@ -320,15 +320,22 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible } displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil - if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum { + if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum { if case .replyThread = chatLocation { displayAuthorInfo = false } else { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { - headerSeparableThreadId = content.firstMessage.threadId - - if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { - headerDisplayPeer = ChatMessageDateHeader.PeerData(peer: EnginePeer(peer)) + if channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + headerSeparableThreadId = content.firstMessage.threadId + + if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] { + headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .peer(EnginePeer(peer))) + } + } + } else if let threadId = content.firstMessage.threadId { + if let threadInfo = content.firstMessage.associatedThreadInfo { + headerSeparableThreadId = content.firstMessage.threadId + headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .thread(id: threadId, info: threadInfo)) } } } @@ -343,7 +350,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible isScheduledMessages = true } - self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: nil, scheduled: isScheduledMessages, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in + self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: nil, scheduled: isScheduledMessages, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in var calendar = NSCalendar.current calendar.timeZone = TimeZone(abbreviation: "UTC")! let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) @@ -355,8 +362,8 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible }) if let headerSeparableThreadId, let headerDisplayPeer { - self.topicHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: headerSeparableThreadId, scheduled: false, displayPeer: headerDisplayPeer, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { _, _ in - controllerInteraction.updateChatLocationThread(headerSeparableThreadId) + self.topicHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: headerSeparableThreadId, scheduled: false, displayHeader: headerDisplayPeer, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { _, _ in + controllerInteraction.updateChatLocationThread(headerSeparableThreadId, nil) }) } else { self.topicHeader = nil diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift index 3a193abfdd..70311fed90 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift @@ -25,7 +25,7 @@ public class ChatReplyCountItem: ListViewItem { self.isComments = isComments self.count = count self.presentationData = presentationData - self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) + self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) self.controllerInteraction = controllerInteraction } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift index 0865ae6cfc..bd516b177b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift @@ -22,7 +22,7 @@ public class ChatUnreadItem: ListViewItem { self.index = index self.presentationData = presentationData self.controllerInteraction = controllerInteraction - self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) + self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 21d4c80c12..d3f6727a94 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -430,7 +430,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) - let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) + //let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let currentShareButtonNode = self.shareButtonNode let currentForwardInfo = self.appliedForwardInfo @@ -669,7 +669,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) var viaBotApply: (TextNodeLayout, () -> TextNode)? - var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? + let threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)? = nil var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)? var replyMarkup: ReplyMarkupMessageAttribute? @@ -739,7 +739,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { hasReply = false } - threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( + /*threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments( presentationData: item.presentationData, strings: item.presentationData.strings, context: item.context, @@ -751,7 +751,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer - )) + ))*/ } if hasReply, (replyMessage != nil || replyForward != nil || replyStory != nil) { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift index 0421d39818..7f82b824f6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift @@ -514,7 +514,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { var containerSize: CGSize = CGSize(width: 22.0, height: 22.0) var iconX: CGFloat = 0.0 if arguments.threadId == 1 { - titleTopicIconContent = .image(image: generalThreadIcon) + titleTopicIconContent = .image(image: generalThreadIcon, tintColor: nil) containerSize = CGSize(width: 18.0, height: 18.0) iconX = 3.0 } else if let fileId = topicIconId, fileId != 0 { diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift index ef42321985..115eece0f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift @@ -526,7 +526,7 @@ final class AvatarComponent: Component { let avatarIconContent: EmojiStatusComponent.Content if threadData.id == 1 { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme)) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: nil) } else if let fileId = threadData.data.info.icon, fileId != 0 { avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 0a8000addc..ab6f27bced 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -171,7 +171,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, openBoostToUnrestrict: { }, updateVideoTrimRange: { _, _, _, _ in }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 616846fe62..8d5710fe52 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -313,7 +313,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, navigateToMessageStandalone: { _ in }, navigateToThreadMessage: { [weak self] peerId, threadId, _ in if let context = self?.context, let navigationController = self?.getNavigationController() { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { [weak self] messageId, _, _, _ in @@ -646,7 +646,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode)) self.controllerInteraction = controllerInteraction @@ -1208,7 +1208,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } case let .replyThread(messageId): if let navigationController = strongSelf.getNavigationController() { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } case let .stickerPack(name, type): let _ = type diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index fb77a9c14a..a5a00e7cde 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -501,7 +501,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode)) diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index 0e05b95c73..06dcda5e87 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -37,23 +37,26 @@ public final class ChatSideTopicsPanel: Component { let theme: PresentationTheme let strings: PresentationStrings let peerId: EnginePeer.Id + let isMonoforum: Bool let topicId: Int64? let togglePanel: () -> Void - let updateTopicId: (Int64?) -> Void + let updateTopicId: (Int64?, Bool) -> Void public init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peerId: EnginePeer.Id, + isMonoforum: Bool, topicId: Int64?, togglePanel: @escaping () -> Void, - updateTopicId: @escaping (Int64?) -> Void + updateTopicId: @escaping (Int64?, Bool) -> Void ) { self.context = context self.theme = theme self.strings = strings self.peerId = peerId + self.isMonoforum = isMonoforum self.topicId = topicId self.togglePanel = togglePanel self.updateTopicId = updateTopicId @@ -72,6 +75,9 @@ public final class ChatSideTopicsPanel: Component { if lhs.peerId != rhs.peerId { return false } + if lhs.isMonoforum != rhs.isMonoforum { + return false + } if lhs.topicId != rhs.topicId { return false } @@ -111,7 +117,7 @@ public final class ChatSideTopicsPanel: Component { private let containerButton: HighlightTrackingButton - private let icon = ComponentView() + private var icon: ComponentView? private var avatarNode: AvatarNode? private let title = ComponentView() @@ -179,34 +185,78 @@ public final class ChatSideTopicsPanel: Component { func update(context: AccountContext, item: Item, isSelected: Bool, theme: PresentationTheme, width: CGFloat, transition: ComponentTransition) -> CGSize { let spacing: CGFloat = 3.0 + let iconSize = CGSize(width: 30.0, height: 30.0) - let avatarIconContent: EmojiStatusComponent.Content - if case let .forum(topicId) = item.item.id, topicId != 1, let threadData = item.item.threadData { - if let fileId = threadData.info.icon, fileId != 0 { - avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) + var avatarIconContent: EmojiStatusComponent.Content? + if case let .forum(topicId) = item.item.id { + if topicId != 1, let threadData = item.item.threadData { + if let fileId = threadData.info.icon, fileId != 0 { + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) + } else { + avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize) + } } else { - avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: CGSize(width: 18.0, height: 18.0)) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: theme.rootController.navigationBar.secondaryTextColor) } - } else { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme)) } - let avatarIconComponent = EmojiStatusComponent( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - content: avatarIconContent, - isVisibleForAnimations: false, - action: nil - ) - let iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent(avatarIconComponent), - environment: {}, - containerSize: CGSize(width: 30.0, height: 30.0) - ) + if let avatarIconContent { + let avatarIconComponent = EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: avatarIconContent, + isVisibleForAnimations: false, + action: nil + ) + let icon: ComponentView + if let current = self.icon { + icon = current + } else { + icon = ComponentView() + self.icon = icon + } + let _ = icon.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent), + environment: {}, + containerSize: iconSize + ) + } else if let icon = self.icon { + self.icon = nil + icon.view?.removeFromSuperview() + } + + let titleText: String + if case let .forum(topicId) = item.item.id { + let _ = topicId + if let threadData = item.item.threadData { + titleText = threadData.info.title + } else { + //TODO:localize + titleText = "General" + } + } else { + titleText = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " + } + + if let avatarIconContent, let icon = self.icon { + let avatarIconComponent = EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: avatarIconContent, + isVisibleForAnimations: false, + action: nil + ) + let _ = icon.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent), + environment: {}, + containerSize: iconSize + ) + } - let titleText: String = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -224,39 +274,38 @@ public final class ChatSideTopicsPanel: Component { let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) * 0.5), y: 0.0), size: iconSize) let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: iconFrame.maxY + spacing), size: titleSize) - if let iconView = self.icon.view { - if iconView.superview == nil { - iconView.isUserInteractionEnabled = false - self.containerButton.addSubview(iconView) - } - iconView.frame = iconFrame - - if "".isEmpty { - iconView.isHidden = true - - let avatarNode: AvatarNode - if let current = self.avatarNode { - avatarNode = current - } else { - avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0)) - avatarNode.isUserInteractionEnabled = false - self.avatarNode = avatarNode - self.containerButton.addSubview(avatarNode.view) - } - avatarNode.frame = iconFrame - avatarNode.updateSize(size: iconFrame.size) - - if let peer = item.item.renderedPeer.chatMainPeer { - if peer.smallProfileImage != nil { - avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) - } else { - avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) - } - } - } else if let avatarNode = self.avatarNode { + if let icon = self.icon { + if let avatarNode = self.avatarNode { self.avatarNode = nil avatarNode.view.removeFromSuperview() - iconView.isHidden = false + } + + if let iconView = icon.view { + if iconView.superview == nil { + iconView.isUserInteractionEnabled = false + self.containerButton.addSubview(iconView) + } + iconView.frame = iconFrame + } + } else { + let avatarNode: AvatarNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0)) + avatarNode.isUserInteractionEnabled = false + self.avatarNode = avatarNode + self.containerButton.addSubview(avatarNode.view) + } + avatarNode.frame = iconFrame + avatarNode.updateSize(size: iconFrame.size) + + if let peer = item.item.renderedPeer.chatMainPeer { + if peer.smallProfileImage != nil { + avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) + } else { + avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) + } } } @@ -600,7 +649,15 @@ public final class ChatSideTopicsPanel: Component { public func topicIndex(threadId: Int64?) -> Int? { if let threadId { - if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) { + if let value = self.items.firstIndex(where: { item in + if item.id == .chatList(PeerId(threadId)) { + return true + } else if item.id == .forum(threadId) { + return true + } else { + return false + } + }) { return value + 1 } else { return nil @@ -619,77 +676,7 @@ public final class ChatSideTopicsPanel: Component { self.state = state if self.component == nil { - let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: component.peerId) - let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: component.peerId) - - let accountPeerId = component.context.account.peerId - let threadListSignal: Signal = component.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey]) - |> map { views -> EngineChatList in - guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else { - preconditionFailure() - } - - var draft: EngineChatList.Draft? - if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView { - if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp { - if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { - if let text = opaqueState.synchronizeableInputState?.text { - draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) - } - } - } - } - - var items: [EngineChatList.Item] = [] - for item in view.items { - guard let sourcePeer = item.peer else { - continue - } - - let sourceId = PeerId(item.id) - - var messages: [EngineMessage] = [] - if let topMessage = item.topMessage { - messages.append(EngineMessage(topMessage)) - } - - let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp) - - items.append(EngineChatList.Item( - id: .chatList(sourceId), - index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), - messages: messages, - readCounters: nil, - isMuted: false, - draft: sourceId == accountPeerId ? draft : nil, - threadData: nil, - renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), - presence: nil, - hasUnseenMentions: false, - hasUnseenReactions: false, - forumTopicData: nil, - topForumTopicItems: [], - hasFailed: false, - isContact: false, - autoremoveTimeout: nil, - storyStats: nil, - displayAsTopicList: false, - isPremiumRequiredToMessage: false, - mediaDraftContentType: nil - )) - } - - let list = EngineChatList( - items: items.reversed(), - groupItems: [], - additionalItems: [], - hasEarlier: false, - hasLater: false, - isLoading: view.isLoading - ) - - return list - } + let threadListSignal: Signal = component.context.sharedContext.subscribeChatListData(context: component.context, location: component.isMonoforum ? .savedMessagesChats(peerId: component.peerId) : .forum(peerId: component.peerId)) self.itemsDisposable = (threadListSignal |> deliverOnMainQueue).startStrict(next: { [weak self] chatList in @@ -814,7 +801,8 @@ public final class ChatSideTopicsPanel: Component { guard let self, let component = self.component else { return } - component.updateTopicId(nil) + + component.updateTopicId(nil, false) }) self.allItemView = itemView self.scrollView.addSubview(itemView) @@ -864,8 +852,20 @@ public final class ChatSideTopicsPanel: Component { guard let self, let component = self.component else { return } - let topicId = chatListItem.renderedPeer.peerId.toInt64() - component.updateTopicId(topicId) + + let topicId: Int64 + if case let .forum(topicIdValue) = chatListItem.id { + topicId = topicIdValue + } else { + topicId = chatListItem.renderedPeer.peerId.toInt64() + } + + var direction = true + if let lhsIndex = self.topicIndex(threadId: component.topicId), let rhsIndex = self.topicIndex(threadId: topicId) { + direction = lhsIndex < rhsIndex + } + + component.updateTopicId(topicId, direction) }, contextGesture: { gesture, sourceNode in }) self.itemViews[itemId] = itemView @@ -873,8 +873,10 @@ public final class ChatSideTopicsPanel: Component { } var isSelected = false - if component.topicId == item.item.renderedPeer.peerId.toInt64() { - isSelected = true + if case let .forum(topicId) = item.item.id { + isSelected = component.topicId == topicId + } else { + isSelected = component.topicId == item.item.renderedPeer.peerId.toInt64() } let itemSize = itemView.update(context: component.context, item: item, isSelected: isSelected, theme: component.theme, width: panelWidth, transition: .immediate) let itemFrame = CGRect(origin: CGPoint(x: containerInsets.left, y: contentSize.height), size: itemSize) @@ -893,6 +895,9 @@ public final class ChatSideTopicsPanel: Component { contentSize.height += itemSize.height } + + contentSize.height += 12.0 + var removedIds: [Item.Id] = [] for (id, itemView) in self.itemViews { if !validIds.contains(id) { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index ec77cd376e..871e2b3caa 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -280,7 +280,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let forceUpdateWarpContents: () -> Void public let playShakeAnimation: () -> Void public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void - public let updateChatLocationThread: (Int64?) -> Void + public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void public var canPlayMedia: Bool = false public var hiddenMedia: [MessageId: [Media]] = [:] @@ -443,7 +443,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol forceUpdateWarpContents: @escaping () -> Void, playShakeAnimation: @escaping () -> Void, displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void, - updateChatLocationThread: @escaping (Int64?) -> Void, + updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings, diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 80bcc2c849..f7a7d09dcd 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -155,6 +155,13 @@ private enum ChatTitleCredibilityIcon: Equatable { } public final class ChatTitleView: UIView, NavigationBarTitleView { + public enum AnimateFromSnapshotDirection { + case up + case down + case left + case right + } + private let context: AccountContext private var theme: PresentationTheme @@ -1111,25 +1118,40 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } - public func prepareSnapshotState() -> SnapshotState { - let snapshotView = self.snapshotView(afterScreenUpdates: false)! + public func prepareSnapshotState() -> SnapshotState? { + guard let snapshotView = self.snapshotView(afterScreenUpdates: false) else { + return nil + } return SnapshotState( snapshotView: snapshotView ) } - public func animateFromSnapshot(_ snapshotState: SnapshotState) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + public func animateFromSnapshot(_ snapshotState: SnapshotState, direction: AnimateFromSnapshotDirection = .up) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + var offset = CGPoint() + switch direction { + case .up: + offset.y = -20.0 + case .down: + offset.y = 20.0 + case .left: + offset.x = -20.0 + case .right: + offset.x = 20.0 + } + + self.layer.animatePosition(from: offset, to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) snapshotState.snapshotView.frame = self.frame self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self) let snapshotView = snapshotState.snapshotView - snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) - snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -offset.x, y: -offset.y), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } } diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index c5d3a8ab92..437de99742 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -50,7 +50,7 @@ public final class EmojiStatusComponent: Component { case text(color: UIColor, string: String) case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode) case topic(title: String, color: Int32, size: CGSize) - case image(image: UIImage?) + case image(image: UIImage?, tintColor: UIColor?) } public let postbox: Postbox @@ -335,8 +335,9 @@ public final class EmojiStatusComponent: Component { } else { iconImage = nil } - case let .image(image): + case let .image(image, tintColor): iconImage = image + iconTintColor = tintColor case let .verified(fillColor, foregroundColor, sizeType): let imageNamePrefix: String switch sizeType { diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index bf412d075f..182c9ed0f9 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -169,7 +169,7 @@ private final class TitleFieldComponent: Component { let iconContent: EmojiStatusComponent.Content if component.isGeneral { - iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor)) + iconContent = .image(image: generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: component.placeholderColor), tintColor: nil) self.iconButton.isUserInteractionEnabled = false } else if component.fileId == 0 { iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift index 2d0d30fdb7..1b987ae877 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift @@ -294,7 +294,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } let content: EmojiStatusComponent.Content if threadId == 1 { - content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme)) + content = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(theme), tintColor: nil) } else if let iconFileId = threadInfo.icon { content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .forever) } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 06048392af..f476b65173 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -436,7 +436,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, openBoostToUnrestrict: { }, updateVideoTrimRange: { _, _, _, _ in }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in @@ -3302,7 +3302,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { @@ -3464,7 +3464,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default).startStandalone() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { @@ -3806,7 +3806,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in @@ -13120,7 +13120,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() { return nil } - if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId { + if let tag = other.userInfo as? PeerInfoNavigationSourceTag, (tag.peerId == peerId || tag.threadId == peerId.toInt64()) { return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode) } return nil diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 670d144cc5..a58875b292 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -785,7 +785,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, openBoostToUnrestrict: { }, updateVideoTrimRange: { _, _, _, _ in }, updateHistoryFilter: { _ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, toggleChatSidebarMode: { }, updateDisplayHistoryFilterAsList: { _ in }, requestLayout: { _ in diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 708980cfba..22e1eb1b63 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -127,10 +127,21 @@ import PostSuggestionsSettingsScreen import ChatSendStarsScreen extension ChatControllerImpl { - func reloadChatLocation() { + func reloadChatLocation(chatLocation: ChatLocation, isReady: Promise?) { + self._chatLocationInfoReady.set(.single(false)) + self.didSetChatLocationInfoReady = false + + if let peerId = chatLocation.peerId, peerId != self.context.account.peerId { + switch subject { + case .pinnedMessages, .scheduledMessages, .messageOptions: + break + default: + self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: chatLocation.threadId) + } + } + let context = self.context - let chatLocation = self.chatLocation - let chatLocationPeerId: PeerId? = self.chatLocation.peerId + let chatLocationPeerId: PeerId? = chatLocation.peerId let mode = self.mode let subject = self.subject let peerId = chatLocationPeerId @@ -157,7 +168,7 @@ extension ChatControllerImpl { } let managingBot: Signal - if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { + if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { managingBot = self.context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId) ) @@ -505,7 +516,7 @@ extension ChatControllerImpl { })) let threadInfo: Signal - if let threadId = self.chatLocation.threadId { + if let threadId = chatLocation.threadId { let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) threadInfo = context.account.postbox.combinedView(keys: [viewKey]) |> map { views -> EngineMessageHistoryThread.Info? in @@ -523,9 +534,9 @@ extension ChatControllerImpl { } let hasSearchTags: Signal - if let peerId = self.chatLocation.peerId, peerId == context.account.peerId { + if let peerId = chatLocation.peerId, peerId == context.account.peerId { hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId) + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) ) |> map { tags -> Bool in return !tags.isEmpty @@ -536,14 +547,14 @@ extension ChatControllerImpl { } let hasSavedChats: Signal - if case .peer(context.account.peerId) = self.chatLocation { + if case .peer(context.account.peerId) = chatLocation { hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() } else { hasSavedChats = .single(false) } let isPremiumRequiredForMessaging: Signal - if let peerId = self.chatLocation.peerId { + if let peerId = chatLocation.peerId { isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) |> distinctUntilChanged } else { @@ -558,7 +569,7 @@ extension ChatControllerImpl { } let displayedPeerVerification: Signal - if let peerId = self.chatLocation.peerId { + if let peerId = chatLocation.peerId { displayedPeerVerification = ApplicationSpecificNotice.displayedPeerVerification(accountManager: context.sharedContext.accountManager, peerId: peerId) |> take(1) } else { @@ -906,7 +917,7 @@ extension ChatControllerImpl { boostsToUnrestrict = cachedChannelData.boostsToUnrestrict } - if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = strongSelf.chatLocation.peerId { + if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() } @@ -992,7 +1003,7 @@ extension ChatControllerImpl { } else { isRegularChat = true } - if strongSelf.nextChannelToReadDisposable == nil, let peerId = strongSelf.chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack { + if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack = strongSelf.customChatNavigationStack { if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 { let nextPeerId = customChatNavigationStack[index + 1] strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(), @@ -1074,6 +1085,7 @@ extension ChatControllerImpl { strongSelf.didSetChatLocationInfoReady = true strongSelf._chatLocationInfoReady.set(.single(true)) } + isReady?.set(.single(true)) strongSelf.updateReminderActivity() if let upgradedToPeerId = upgradedToPeerId { if let navigationController = strongSelf.effectiveNavigationController { @@ -1267,9 +1279,9 @@ extension ChatControllerImpl { } let hasSearchTags: Signal - if let peerId = self.chatLocation.peerId, peerId == context.account.peerId { + if let peerId = chatLocation.peerId, peerId == context.account.peerId { hasSearchTags = context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: self.chatLocation.threadId) + TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId) ) |> map { tags -> Bool in return !tags.isEmpty @@ -1280,14 +1292,14 @@ extension ChatControllerImpl { } let hasSavedChats: Signal - if case .peer(context.account.peerId) = self.chatLocation { + if case .peer(context.account.peerId) = chatLocation { hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved() } else { hasSavedChats = .single(false) } let isPremiumRequiredForMessaging: Signal - if let peerId = self.chatLocation.peerId { + if let peerId = chatLocation.peerId { isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId) |> distinctUntilChanged } else { @@ -1456,8 +1468,8 @@ extension ChatControllerImpl { strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) let avatarContent: EmojiStatusComponent.Content - if strongSelf.chatLocation.threadId == 1 { - avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme)) + if chatLocation.threadId == 1 { + avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme), tintColor: nil) } else if let fileId = threadInfo.icon { avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1)) } else { @@ -1633,7 +1645,7 @@ extension ChatControllerImpl { boostsToUnrestrict = cachedChannelData.boostsToUnrestrict } - if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = strongSelf.chatLocation.peerId { + if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId { strongSelf.premiumOrStarsRequiredDisposable = ((strongSelf.context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone() } @@ -1709,6 +1721,7 @@ extension ChatControllerImpl { strongSelf.didSetChatLocationInfoReady = true strongSelf._chatLocationInfoReady.set(.single(true)) } + isReady?.set(.single(true)) } })) } else if case .customChatContents = self.chatLocationInfoData { @@ -1785,6 +1798,7 @@ extension ChatControllerImpl { self.didSetChatLocationInfoReady = true self._chatLocationInfoReady.set(.single(true)) } + isReady?.set(.single(true)) })) } } @@ -2729,9 +2743,10 @@ extension ChatControllerImpl { } mappedTransition = (ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: deleteItems, insertItems: insertItems, updateItems: transition.updateItems, options: options, scrollToItem: scrollToItem, stationaryItemRange: stationaryItemRange, initialData: transition.initialData, keyboardButtonsMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData, scrolledToIndex: transition.scrolledToIndex, scrolledToSomeIndex: transition.scrolledToSomeIndex, peerType: transition.peerType, networkType: transition.networkType, animateIn: false, reason: transition.reason, flashIndicators: transition.flashIndicators, animateFromPreviousFilter: false), updateSizeAndInsets) - }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, _ in + }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, cutout, _ in strongSelf.additionalNavigationBarBackgroundHeight = value strongSelf.additionalNavigationBarHitTestSlop = hitTestSlop + strongSelf.additionalNavigationBarCutout = cutout }) if let mappedTransition = mappedTransition { @@ -6041,44 +6056,14 @@ extension ChatControllerImpl { } else { apply() } - }, updateChatLocationThread: { [weak self] threadId in + }, updateChatLocationThread: { [weak self] threadId, animationDirection in guard let self else { return } - guard let peerId = self.chatLocation.peerId else { - return + let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in + return direction ? .right : .left } - guard let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer else { - return - } - let updatedChatLocation: ChatLocation - if let threadId { - var isMonoforum = false - if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { - isMonoforum = true - } - - updatedChatLocation = .replyThread(message: ChatReplyThreadMessage( - peerId: peerId, - threadId: threadId, - channelMessageId: nil, - isChannelPost: false, - isForumPost: true, - isMonoforumPost: isMonoforum, - maxMessage: nil, - maxReadIncomingMessageId: nil, - maxReadOutgoingMessageId: nil, - unreadCount: 0, - initialFilledHoles: IndexSet(), - initialAnchor: .automatic, - isNotAvailable: false - )) - } else { - updatedChatLocation = .peer(id: peerId) - } - self.updateChatPresentationInterfaceState(animated: true, interactive: false, { presentationInterfaceState in - return presentationInterfaceState.updatedChatLocation(updatedChatLocation) - }) + self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection) }, toggleChatSidebarMode: { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift index 8e4780d163..bffd8e3466 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift @@ -426,6 +426,23 @@ extension ChatControllerImpl { if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .forumTopic(thread: replyThreadMessage), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { self.effectiveNavigationController?.pushViewController(infoController) } + } else if let peer = self.presentationInterfaceState.renderedPeer?.peer, case let .replyThread(replyThreadMessage) = self.chatLocation, peer.isMonoForum { + let context = self.context + if #available(iOS 13.0, *) { + Task { @MainActor [weak self] in + guard let peer = await context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(replyThreadMessage.threadId)) + ).get() else { + return + } + guard let self else { + return + } + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { + self.effectiveNavigationController?.pushViewController(infoController) + } + } + } } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, case let .replyThread(message) = self.chatLocation { if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.inviteRequestsContext) { self.effectiveNavigationController?.pushViewController(infoController) diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index de8f854626..5f4dae9e2a 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -437,7 +437,10 @@ func updateChatPresentationInterfaceStateImpl( selfController.presentationInterfaceState = updatedChatPresentationInterfaceState if selfController.chatDisplayNode.chatLocation != selfController.presentationInterfaceState.chatLocation { - let tabSwitchDirection = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatDisplayNode.chatLocation, to: selfController.presentationInterfaceState.chatLocation) + let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatLocation.threadId, to: selfController.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in + return direction ? .right : .left + } + let tabSwitchDirection = selfController.currentChatSwitchDirection ?? defaultDirection selfController.chatDisplayNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation, transition: transition, tabSwitchDirection: tabSwitchDirection) } @@ -488,17 +491,18 @@ func updateChatPresentationInterfaceStateImpl( animated = false } animated = false - selfController.navigationItem.setLeftBarButton(button.buttonItem, animated: animated) + selfController.navigationItem.setLeftBarButton(button.buttonItem, animated: animated && selfController.currentChatSwitchDirection == nil) selfController.leftNavigationButton = button } } else if let _ = selfController.leftNavigationButton { - selfController.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated) + selfController.navigationItem.setLeftBarButton(nil, animated: transition.isAnimated && selfController.currentChatSwitchDirection == nil) selfController.leftNavigationButton = nil } - /*if let channel = selfController.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum { - } else {*/ var buttonsAnimated = transition.isAnimated + if selfController.currentChatSwitchDirection != nil { + buttonsAnimated = false + } if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) { if selfController.rightNavigationButton != button { if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action { @@ -601,7 +605,6 @@ func updateChatPresentationInterfaceStateImpl( if previousChatLocation != selfController.presentationInterfaceState.chatLocation { selfController.chatLocation = selfController.presentationInterfaceState.chatLocation - selfController.reloadChatLocation() selfController.reloadCachedData() selfController.setupChatHistoryNode() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b737f92b7f..92b0fbed73 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -233,7 +233,7 @@ struct ScrolledToMessageId: Equatable { var allowedReplacementDirection: AllowedReplacementDirections } -public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate { +public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate { var validLayout: ContainerViewLayout? public weak var parentController: ViewController? @@ -2059,7 +2059,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false) }, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in if let context = self?.context, let navigationController = self?.effectiveNavigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } }, tapMessage: nil, clickThroughMessage: { [weak self] view, location in self?.chatDisplayNode.dismissInput(view: view, location: location) @@ -4828,38 +4828,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.displayQuickShare(id: messageId, node: node, gesture: gesture) - }, updateChatLocationThread: { [weak self] threadId in + }, updateChatLocationThread: { [weak self] threadId, animationDirection in guard let self else { return } - self.interfaceInteraction?.updateChatLocationThread(threadId) + let defaultDirection: ChatControllerAnimateInnerChatSwitchDirection? = self.chatDisplayNode.chatLocationTabSwitchDirection(from: self.chatLocation.threadId, to: self.presentationInterfaceState.chatLocation.threadId).flatMap { direction -> ChatControllerAnimateInnerChatSwitchDirection in + return direction ? .right : .left + } + self.updateChatLocationThread(threadId: threadId, animationDirection: animationDirection ?? defaultDirection) }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode)) controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency self.controllerInteraction = controllerInteraction - //if chatLocation.threadId == nil { - if let peerId = chatLocation.peerId, peerId != context.account.peerId { - switch subject { - case .pinnedMessages, .scheduledMessages, .messageOptions: - break - default: - self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId) - } + self.navigationBar?.allowsCustomTransition = { [weak self] in + guard let strongSelf = self else { + return false } - self.navigationBar?.allowsCustomTransition = { [weak self] in - guard let strongSelf = self else { - return false - } - if strongSelf.navigationBar?.userInfo == nil { - return false - } - if strongSelf.navigationBar?.contentNode != nil { - return false - } - return true + if strongSelf.navigationBar?.userInfo == nil { + return false } - //} + if strongSelf.navigationBar?.contentNode != nil { + return false + } + return true + } self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) @@ -5306,7 +5299,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - self.reloadChatLocation() + self.reloadChatLocation(chatLocation: self.chatLocation, isReady: nil) self.botCallbackAlertMessageDisposable = (self.botCallbackAlertMessage.get() |> deliverOnMainQueue).startStrict(next: { [weak self] message in @@ -5910,6 +5903,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.networkSpeedEventsDisposable?.dispose() self.postedScheduledMessagesEventsDisposable?.dispose() self.premiumOrStarsRequiredDisposable?.dispose() + self.updateChatLocationThreadDisposable?.dispose() } deallocate() } @@ -7076,13 +7070,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var suspendedNavigationBarLayout: ContainerViewLayout? var additionalNavigationBarBackgroundHeight: CGFloat = 0.0 var additionalNavigationBarHitTestSlop: CGFloat = 0.0 + var additionalNavigationBarCutout: CGSize? override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if self.suspendNavigationBarLayout { self.suspendedNavigationBarLayout = layout return } - self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: transition) } override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? { @@ -7120,10 +7115,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var navigationBarTransition = transition self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) - }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, extraNavigationTransition in + }, updateExtraNavigationBarBackgroundHeight: { value, hitTestSlop, cutout, extraNavigationTransition in navigationBarTransition = extraNavigationTransition self.additionalNavigationBarBackgroundHeight = value self.additionalNavigationBarHitTestSlop = hitTestSlop + self.additionalNavigationBarCutout = cutout }) if case .compact = layout.metrics.widthClass { @@ -7142,7 +7138,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.suspendNavigationBarLayout = false if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout { self.suspendedNavigationBarLayout = suspendedNavigationBarLayout - self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: navigationBarTransition) + self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: self.additionalNavigationBarCutout, transition: navigationBarTransition) } self.navigationBar?.additionalContentNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: 0.0, bottom: self.additionalNavigationBarHitTestSlop, right: 0.0) } @@ -9819,6 +9815,122 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + private var updateChatLocationThreadDisposable: Disposable? + private var isUpdatingChatLocationThread: Bool = false + var currentChatSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection? + + public func updateChatLocationThread(threadId: Int64?, animationDirection: ChatControllerAnimateInnerChatSwitchDirection? = nil) { + if self.isUpdatingChatLocationThread { + return + } + + guard let peerId = self.chatLocation.peerId else { + return + } + if self.chatLocation.threadId == threadId { + return + } + guard let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer else { + return + } + + let navigationSnapshot = self.chatTitleView?.prepareSnapshotState() + + let rightBarButtonItemSnapshots: [(UIView, CGRect)] = (self.navigationItem.rightBarButtonItems ?? []).compactMap { item -> (UIView, CGRect)? in + guard let view = item.customDisplayNode?.view, let snapshotView = view.snapshotView(afterScreenUpdates: false) else { + return nil + } + return (snapshotView, view.convert(view.bounds, to: self.view)) + } + + let updatedChatLocation: ChatLocation + if let threadId { + var isMonoforum = false + if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { + isMonoforum = true + } + + updatedChatLocation = .replyThread(message: ChatReplyThreadMessage( + peerId: peerId, + threadId: threadId, + channelMessageId: nil, + isChannelPost: false, + isForumPost: true, + isMonoforumPost: isMonoforum, + maxMessage: nil, + maxReadIncomingMessageId: nil, + maxReadOutgoingMessageId: nil, + unreadCount: 0, + initialFilledHoles: IndexSet(), + initialAnchor: .automatic, + isNotAvailable: false + )) + } else { + updatedChatLocation = .peer(id: peerId) + } + + let isReady = Promise() + self.reloadChatLocation(chatLocation: updatedChatLocation, isReady: isReady) + + self.isUpdatingChatLocationThread = true + self.updateChatLocationThreadDisposable?.dispose() + self.updateChatLocationThreadDisposable = (isReady.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + self.isUpdatingChatLocationThread = false + + self.currentChatSwitchDirection = animationDirection + self.updateChatPresentationInterfaceState(animated: animationDirection != nil, interactive: false, { presentationInterfaceState in + return presentationInterfaceState.updatedChatLocation(updatedChatLocation) + }) + + if let navigationSnapshot, let animationDirection { + let mappedAnimationDirection: ChatTitleView.AnimateFromSnapshotDirection + switch animationDirection { + case .up: + mappedAnimationDirection = .up + case .down: + mappedAnimationDirection = .down + case .left: + mappedAnimationDirection = .left + case .right: + mappedAnimationDirection = .right + } + + self.chatTitleView?.animateFromSnapshot(navigationSnapshot, direction: mappedAnimationDirection) + if let rightBarButtonItems = self.navigationItem.rightBarButtonItems { + for i in 0 ..< rightBarButtonItems.count { + let item = rightBarButtonItems[i] + if let customDisplayNode = item.customDisplayNode { + customDisplayNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + //customDisplayNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + + let _ = rightBarButtonItemSnapshots + /*if i < rightBarButtonItemSnapshots.count { + let (snapshotItem, snapshotFrame) = rightBarButtonItemSnapshots[i] + if let targetSuperview = customDisplayNode.view.superview { + snapshotItem.frame = targetSuperview.convert(snapshotFrame, from: self.view) + targetSuperview.addSubview(snapshotItem) + + snapshotItem.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, completion: { [weak snapshotItem] _ in + snapshotItem?.removeFromSuperview() + }) + snapshotItem.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + }*/ + } + } + } + } + + self.currentChatSwitchDirection = nil + }) + } + public var contentContainerNode: ASDisplayNode { return self.chatDisplayNode.contentContainerNode } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 5cdf3810e6..43fd6d9cb3 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -351,7 +351,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate, listViewTransaction: { _, _, _, _ in - }, updateExtraNavigationBarBackgroundHeight: { _, _, _ in + }, updateExtraNavigationBarBackgroundHeight: { _, _, _, _ in }) } } @@ -1033,7 +1033,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.wrappingNode.update(size: layout.size, cornerRadius: layout.deviceMetrics.screenCornerRadius, transition: .immediate) } - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, ContainedViewLayoutTransition) -> Void) { + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction: (ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void, updateExtraNavigationBarBackgroundHeight: (CGFloat, CGFloat, CGSize?, ContainedViewLayoutTransition) -> Void) { let transition: ContainedViewLayoutTransition if let _ = self.scheduledAnimateInAsOverlayFromNode { transition = .immediate @@ -1302,6 +1302,28 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleTopicsAccessoryPanelNode = nil } + var defaultLeftPanelWidth: CGFloat = 72.0 + defaultLeftPanelWidth += layout.safeInsets.left + let leftPanelLeftInset = defaultLeftPanelWidth - 72.0 + + var leftPanelSize: CGSize? + var dismissedLeftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? + var immediatelyLayoutLeftPanelNodeAndAnimateAppearance = false + if let leftPanelComponent = sidePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.leftPanel?.component, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { + if self.leftPanel?.component.id != leftPanelComponent.id { + dismissedLeftPanel = self.leftPanel + self.leftPanel = (leftPanelComponent, ComponentView()) + immediatelyLayoutLeftPanelNodeAndAnimateAppearance = true + } else if let leftPanel = self.leftPanel { + self.leftPanel = (leftPanelComponent, leftPanel.view) + } + + leftPanelSize = CGSize(width: defaultLeftPanelWidth, height: layout.size.height) + } else if let leftPanel = self.leftPanel { + dismissedLeftPanel = leftPanel + self.leftPanel = nil + } + var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode? var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false var titleAccessoryPanelHeight: CGFloat? @@ -1321,7 +1343,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) + let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) titleAccessoryPanelHeight = layoutResult.insetHeight titleAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight titleAccessoryPanelHitTestSlop = layoutResult.hitTestSlop @@ -1348,6 +1370,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { hasTranslationPanel = true } } + + #if DEBUG + if "".isEmpty { + hasTranslationPanel = true + } + #endif + if hasTranslationPanel { let translationPanelNode: ChatTranslationPanelNode if let current = self.chatTranslationPanel { @@ -1369,7 +1398,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) + let height = translationPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState) translationPanelHeight = height if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { translationPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1433,7 +1462,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleAccessoryPanelContainer.addSubnode(adPanelNode) } - let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) + let height = adPanelNode.updateLayout(width: layout.size.width, leftInset: leftPanelSize?.width ?? layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition, interfaceState: self.chatPresentationInterfaceState) if let adMessage = self.chatPresentationInterfaceState.adMessage, let opaqueId = adMessage.adAttribute?.opaqueId { self.historyNode.markAdAsSeen(opaqueId: opaqueId) } @@ -1489,30 +1518,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.feePanelNode = nil } - var defaultLeftPanelWidth: CGFloat = 72.0 - if case .landscapeRight = layout.metrics.orientation { - defaultLeftPanelWidth += layout.safeInsets.left - } - let leftPanelLeftInset = defaultLeftPanelWidth - 72.0 - - var leftPanelSize: CGSize? - var dismissedLeftPanel: (component: AnyComponentWithIdentity, view: ComponentView)? - var immediatelyLayoutLeftPanelNodeAndAnimateAppearance = false - if let leftPanelComponent = sidePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.leftPanel?.component, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) { - if self.leftPanel?.component.id != leftPanelComponent.id { - dismissedLeftPanel = self.leftPanel - self.leftPanel = (leftPanelComponent, ComponentView()) - immediatelyLayoutLeftPanelNodeAndAnimateAppearance = true - } else if let leftPanel = self.leftPanel { - self.leftPanel = (leftPanelComponent, leftPanel.view) - } - - leftPanelSize = CGSize(width: defaultLeftPanelWidth, height: layout.size.height) - } else if let leftPanel = self.leftPanel { - dismissedLeftPanel = leftPanel - self.leftPanel = nil - } - self.controllerInteraction.isSidePanelOpen = self.leftPanel != nil var inputPanelNodeBaseHeight: CGFloat = 0.0 @@ -1793,6 +1798,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.historyNode.isSelectionGestureEnabled = isSelectionEnabled transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 200.0))) + self.titleAccessoryPanelContainer.hitTestExcludeInsets = UIEdgeInsets(top: 0.0, left: leftPanelSize?.width ?? 0.0, bottom: 0.0, right: 0.0) transition.updateFrame(node: self.inputContextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) transition.updateFrame(node: self.inputContextOverTextPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) @@ -1848,8 +1854,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { insets.top += panelHeight extraNavigationBarHeight += panelHeight } + + var extraNavigationBarLeftCutout: CGSize? + if let leftPanelSize { + extraNavigationBarLeftCutout = CGSize(width: leftPanelSize.width, height: navigationBarHeight) + } else { + extraNavigationBarLeftCutout = CGSize(width: 0.0, height: navigationBarHeight) + } - updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraTransition) + updateExtraNavigationBarBackgroundHeight(extraNavigationBarHeight, extraNavigationBarHitTestSlop, extraNavigationBarLeftCutout, extraTransition) let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom) @@ -2250,28 +2263,32 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let leftPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height)) transition.updateFrame(node: self.leftPanelContainer, frame: leftPanelContainerFrame) if let leftPanel = self.leftPanel, let leftPanelSize { - let _ = leftPanel.view.update( + let leftPanelSize = leftPanel.view.update( transition: immediatelyLayoutLeftPanelNodeAndAnimateAppearance ? .immediate :ComponentTransition(transition), component: leftPanel.component.component, environment: { ChatSidePanelEnvironment(insets: UIEdgeInsets( - top: sidePanelTopInset, + top: 0.0, left: leftPanelLeftInset, - bottom: containerInsets.bottom + contentBottomInset, + bottom: 0.0, right: 0.0 )) }, - containerSize: leftPanelSize + containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight)) ) - let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: leftPanelSize) + + let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: sidePanelTopInset), size: leftPanelSize) if let leftPanelView = leftPanel.view.view { if leftPanelView.superview == nil { self.leftPanelContainer.view.addSubview(leftPanelView) } if immediatelyLayoutLeftPanelNodeAndAnimateAppearance { leftPanelView.frame = leftPanelFrame.offsetBy(dx: -leftPanelSize.width, dy: 0.0) - if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View { - leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition)) + + if self.titleTopicsAccessoryPanelNode != nil || dismissedTitleTopicsAccessoryPanelNode != nil { + if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View { + leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition)) + } } } transition.updateFrame(view: leftPanelView, frame: leftPanelFrame) @@ -2286,19 +2303,21 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { component: dismissedLeftPanel.component.component, environment: { ChatSidePanelEnvironment(insets: UIEdgeInsets( - top: sidePanelTopInset, + top: 0.0, left: leftPanelLeftInset, - bottom: containerInsets.bottom + contentBottomInset, + bottom: 0.0, right: 0.0 )) }, - containerSize: CGSize(width: defaultLeftPanelWidth, height: layout.size.height) + containerSize: CGSize(width: defaultLeftPanelWidth, height: layout.size.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight)) ) - transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -dismissedLeftPanelSize.width, y: 0.0), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in + transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -dismissedLeftPanelSize.width, y: sidePanelTopInset), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in dismissedLeftPanelView?.removeFromSuperview() }) if let dismissedLeftPanelView = dismissedLeftPanelView as? ChatSideTopicsPanel.View { - dismissedLeftPanelView.updateGlobalOffset(globalOffset: -dismissedLeftPanelSize.width, transition: ComponentTransition(transition)) + if self.titleTopicsAccessoryPanelNode != nil { + dismissedLeftPanelView.updateGlobalOffset(globalOffset: -dismissedLeftPanelSize.width, transition: ComponentTransition(transition)) + } } } @@ -2352,7 +2371,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode, let titleTopicsAccessoryPanelFrame, (immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || !titleTopicsAccessoryPanelNode.frame.equalTo(titleTopicsAccessoryPanelFrame)) { if immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance { titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame.offsetBy(dx: 0.0, dy: -titleTopicsAccessoryPanelFrame.height) - titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate) + if self.leftPanel != nil || dismissedLeftPanel != nil { + titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate) + } let topPanelTransition = ComponentTransition(transition) /*switch topPanelTransition.animation { @@ -2378,8 +2399,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let previousFrame = titleAccessoryPanelNode.frame titleAccessoryPanelNode.frame = titleAccessoryPanelFrame if transition.isAnimated && previousFrame.width != titleAccessoryPanelFrame.width { - } else { + } else if immediatelyLayoutAccessoryPanelAndAnimateAppearance { transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height)) + } else if previousFrame.minY != titleAccessoryPanelFrame.minY { + transition.animatePositionAdditive(node: titleAccessoryPanelNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - titleAccessoryPanelFrame.minY)) } } @@ -2387,14 +2410,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let previousFrame = chatTranslationPanel.frame chatTranslationPanel.frame = translationPanelFrame if transition.isAnimated && previousFrame.width != translationPanelFrame.width { - } else { + } else if immediatelyLayoutTranslationPanelNodeAndAnimateAppearance { transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: -translationPanelFrame.height)) + } else if previousFrame.minY != translationPanelFrame.minY { + transition.animatePositionAdditive(node: chatTranslationPanel, offset: CGPoint(x: 0.0, y: previousFrame.minY - translationPanelFrame.minY)) } } if let chatImportStatusPanel = self.chatImportStatusPanel, let importStatusPanelFrame, !chatImportStatusPanel.frame.equalTo(importStatusPanelFrame) { chatImportStatusPanel.frame = importStatusPanelFrame - //transition.animatePositionAdditive(node: chatImportStatusPanel, offset: CGPoint(x: 0.0, y: -titleAccessoryPanelFrame.height)) } if let adPanelNode = self.adPanelNode, let adPanelFrame, !adPanelNode.frame.equalTo(adPanelFrame) { @@ -2502,7 +2526,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateFrame(node: dismissedTitleTopicsAccessoryPanelNode, frame: dismissedTopPanelFrame, completion: { [weak dismissedTitleTopicsAccessoryPanelNode] _ in dismissedTitleTopicsAccessoryPanelNode?.removeFromSupernode() }) - dismissedTitleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -dismissedTopPanelFrame.height, transition: ComponentTransition(transition)) + if self.leftPanel != nil { + dismissedTitleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -dismissedTopPanelFrame.height, transition: ComponentTransition(transition)) + } } if let dismissedTitleAccessoryPanelNode { @@ -2802,6 +2828,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { displayInlineSearch = true } } + if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if self.chatPresentationInterfaceState.search != nil { + displayInlineSearch = true + } + } if displayInlineSearch { let peerId = self.chatPresentationInterfaceState.chatLocation.peerId @@ -2827,6 +2858,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { mappedContents = .empty } + } else if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + mappedContents = .monoforumChats(query: self.chatPresentationInterfaceState.search?.query ?? "") } else if case .peer(self.context.account.peerId) = self.chatPresentationInterfaceState.chatLocation { mappedContents = .tag(MemoryBuffer()) } else { @@ -2952,30 +2985,39 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { guard let self else { return } - guard let navigationController = self.controller?.navigationController as? NavigationController else { - return + + if let channel = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { + self.interfaceInteraction?.updateChatLocationThread(peer.id.toInt64(), nil) + + self.controller?.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in + return current.updatedSearch(nil) + }) + } else { + guard let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: self.context, + chatLocation: .replyThread(ChatReplyThreadMessage( + peerId: self.context.account.peerId, + threadId: peer.id.toInt64(), + channelMessageId: nil, + isChannelPost: false, + isForumPost: false, + isMonoforumPost: false, + maxMessage: nil, + maxReadIncomingMessageId: nil, + maxReadOutgoingMessageId: nil, + unreadCount: 0, + initialFilledHoles: IndexSet(), + initialAnchor: .automatic, + isNotAvailable: false + )), + subject: nil, + keepStack: .always + )) } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( - navigationController: navigationController, - context: self.context, - chatLocation: .replyThread(ChatReplyThreadMessage( - peerId: self.context.account.peerId, - threadId: peer.id.toInt64(), - channelMessageId: nil, - isChannelPost: false, - isForumPost: false, - isMonoforumPost: false, - maxMessage: nil, - maxReadIncomingMessageId: nil, - maxReadOutgoingMessageId: nil, - unreadCount: 0, - initialFilledHoles: IndexSet(), - initialAnchor: .automatic, - isNotAvailable: false - )), - subject: nil, - keepStack: .always - )) }, loadTagMessages: { tag, index in let input: ChatHistoryLocationInput @@ -3048,6 +3090,89 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } return foundLocalPeers }, + getChats: { [weak self] query in + guard let self else { + return nil + } + guard let peerId = self.chatPresentationInterfaceState.chatLocation.peerId else { + return nil + } + + let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId) + let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId) + + let accountPeerId = self.context.account.peerId + let threadListSignal: Signal = self.context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey]) + |> map { views -> EngineChatList? in + guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else { + preconditionFailure() + } + + var draft: EngineChatList.Draft? + if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView { + if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp { + if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { + if let text = opaqueState.synchronizeableInputState?.text { + draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) + } + } + } + } + + var items: [EngineChatList.Item] = [] + for item in view.items { + guard let sourcePeer = item.peer else { + continue + } + + let sourceId = PeerId(item.id) + + var messages: [EngineMessage] = [] + if let topMessage = item.topMessage { + messages.append(EngineMessage(topMessage)) + } + + let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp) + + items.append(EngineChatList.Item( + id: .chatList(sourceId), + index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), + messages: messages, + readCounters: EnginePeerReadCounters( + incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false), + isMuted: false, + draft: sourceId == accountPeerId ? draft : nil, + threadData: nil, + renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), + presence: nil, + hasUnseenMentions: false, + hasUnseenReactions: false, + forumTopicData: nil, + topForumTopicItems: [], + hasFailed: false, + isContact: false, + autoremoveTimeout: nil, + storyStats: nil, + displayAsTopicList: false, + isPremiumRequiredToMessage: false, + mediaDraftContentType: nil + )) + } + + let list = EngineChatList( + items: items.reversed(), + groupItems: [], + additionalItems: [], + hasEarlier: false, + hasLater: false, + isLoading: view.isLoading + ) + + return list + } + + return threadListSignal + }, loadMoreSearchResults: { [weak self] in guard let self, let controller = self.controller else { return @@ -4052,7 +4177,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion) - }, updateExtraNavigationBarBackgroundHeight: { _, _, _ in + }, updateExtraNavigationBarBackgroundHeight: { _, _, _, _ in }) } } @@ -4900,15 +5025,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { }) } - func chatLocationTabSwitchDirection(from fromLocation: ChatLocation, to toLocation: ChatLocation) -> Bool? { + func chatLocationTabSwitchDirection(from fromLocation: Int64?, to toLocation: Int64?) -> Bool? { var leftIndex: Int? var rightIndex: Int? if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode { - leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation.threadId) - rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation.threadId) + leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation) + rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation) } else if let leftPanelView = self.leftPanel?.view.view as? ChatSideTopicsPanel.View { - leftIndex = leftPanelView.topicIndex(threadId: fromLocation.threadId) - rightIndex = leftPanelView.topicIndex(threadId: toLocation.threadId) + leftIndex = leftPanelView.topicIndex(threadId: fromLocation) + rightIndex = leftPanelView.topicIndex(threadId: toLocation) } guard let leftIndex, let rightIndex else { return nil @@ -4916,7 +5041,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { return leftIndex < rightIndex } - func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: Bool?) { + func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: ChatControllerAnimateInnerChatSwitchDirection?) { if chatLocation == self.chatLocation { return } @@ -4988,10 +5113,25 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if let validLayout = self.validLayout, transition.isAnimated, let tabSwitchDirection { - let offsetMultiplier: CGFloat = tabSwitchDirection ? 1.0 : -1.0 - transition.animatePosition(layer: historyNode.layer, from: CGPoint(x: offsetMultiplier * validLayout.0.size.width, y: 0.0), to: CGPoint(), removeOnCompletion: true, additive: true) - transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier * validLayout.0.size.width, y: 0.0), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode] _ in + var offsetMultiplier = CGPoint() + switch tabSwitchDirection { + case .up: + offsetMultiplier.y = -1.0 + case .down: + offsetMultiplier.y = 1.0 + case .left: + offsetMultiplier.x = -1.0 + case .right: + offsetMultiplier.x = 1.0 + } + + previousHistoryNode.clipsToBounds = true + historyNode.clipsToBounds = true + + transition.animatePosition(layer: historyNode.layer, from: CGPoint(x: offsetMultiplier.x * validLayout.0.size.width, y: offsetMultiplier.y * validLayout.0.size.height), to: CGPoint(), removeOnCompletion: true, additive: true) + transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier.x * validLayout.0.size.width, y: -offsetMultiplier.y * validLayout.0.size.height), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode, weak historyNode] _ in previousHistoryNode?.removeFromSupernode() + historyNode?.clipsToBounds = false }) } else { previousHistoryNode.removeFromSupernode() diff --git a/submodules/TelegramUI/Sources/ChatControllerTitlePanelNodeContainer.swift b/submodules/TelegramUI/Sources/ChatControllerTitlePanelNodeContainer.swift index f6bf81862e..e73a848319 100644 --- a/submodules/TelegramUI/Sources/ChatControllerTitlePanelNodeContainer.swift +++ b/submodules/TelegramUI/Sources/ChatControllerTitlePanelNodeContainer.swift @@ -3,7 +3,13 @@ import UIKit import AsyncDisplayKit final class ChatControllerTitlePanelNodeContainer: ASDisplayNode { + var hitTestExcludeInsets = UIEdgeInsets() + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if point.x < self.hitTestExcludeInsets.left { + return nil + } + for subview in self.view.subviews { if let result = subview.hitTest(self.view.convert(point, to: subview), with: event) { return result diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index f4231397ae..4b1907421b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -232,13 +232,24 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? { - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode { return currentPanel } else { - let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId) + let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId, isMonoforum: true) + panel.interfaceInteraction = interfaceInteraction + return panel + } + } + } else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil { + let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top + if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId { + if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode { + return currentPanel + } else { + let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId, isMonoforum: false) panel.interfaceInteraction = interfaceInteraction return panel } @@ -253,7 +264,7 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState return nil } - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.adminRights != nil, chatPresentationInterfaceState.search == nil { let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top if case .side = topicListDisplayMode { return AnyComponentWithIdentity( @@ -263,12 +274,34 @@ func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, peerId: peerId, + isMonoforum: true, topicId: chatPresentationInterfaceState.chatLocation.threadId, togglePanel: { [weak interfaceInteraction] in interfaceInteraction?.toggleChatSidebarMode() }, - updateTopicId: { [weak interfaceInteraction] topicId in - interfaceInteraction?.updateChatLocationThread(topicId) + updateTopicId: { [weak interfaceInteraction] topicId, direction in + interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) + } + )) + ) + } + } else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForum, chatPresentationInterfaceState.search == nil { + let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top + if case .side = topicListDisplayMode { + return AnyComponentWithIdentity( + id: "topics", + component: AnyComponent(ChatSideTopicsPanel( + context: context, + theme: chatPresentationInterfaceState.theme, + strings: chatPresentationInterfaceState.strings, + peerId: peerId, + isMonoforum: false, + topicId: chatPresentationInterfaceState.chatLocation.threadId, + togglePanel: { [weak interfaceInteraction] in + interfaceInteraction?.toggleChatSidebarMode() + }, + updateTopicId: { [weak interfaceInteraction] topicId, direction in + interfaceInteraction?.updateChatLocationThread(topicId, direction ? .down : .up) } )) ) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 9262bd3f0d..35f4f9a047 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -517,12 +517,13 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset || messageUpdated || themeUpdated || currentTranslateToLanguageUpdated { self.currentLayout = (width, leftInset, rightInset) + let messageUpdated = self.currentMessage?.message.id != interfaceState.pinnedMessage?.message.id let previousMessageWasNil = self.currentMessage == nil self.currentMessage = interfaceState.pinnedMessage if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout { self.dustNode?.update(revealed: false, animated: false) - self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread, translateToLanguage: translateToLanguage?.toLang) + self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: messageUpdated ? .immediate : transition, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread, translateToLanguage: translateToLanguage?.toLang) } } @@ -565,6 +566,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { self.textNode.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true) self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } + } else { + animationTransition = transition } let makeTitleLayout = self.titleNode.asyncLayout() @@ -575,13 +578,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let previousMediaReference = self.previousMediaReference let context = self.context - let targetQueue: Queue - if firstTime { - targetQueue = Queue.mainQueue() - } else { - targetQueue = self.queue - } - let contentLeftInset: CGFloat = leftInset + 10.0 var textLineInset: CGFloat = 10.0 var rightInset: CGFloat = 14.0 + rightInset @@ -592,293 +588,288 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { rightInset += self.actionButton.bounds.width - 14.0 } - targetQueue.async { [weak self] in - var updatedMediaReference: AnyMediaReference? - var imageDimensions: CGSize? - - let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway - - var titleStrings: [AnimatedCountLabelNode.Segment] = [] - if let _ = giveaway { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - } else { - if pinnedMessage.totalCount == 2 { - if pinnedMessage.index == 0 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - } else { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - } - } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 { - titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) - titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + var updatedMediaReference: AnyMediaReference? + var imageDimensions: CGSize? + + let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway + + var titleStrings: [AnimatedCountLabelNode.Segment] = [] + if let _ = giveaway { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + } else { + if pinnedMessage.totalCount == 2 { + if pinnedMessage.index == 0 { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } else { titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } + } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) + } else { + titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))) } - - if !message.containsSecretMedia { - for media in message.media { - if let image = media as? TelegramMediaImage { - updatedMediaReference = .message(message: MessageReference(message), media: image) - if let representation = largestRepresentationForPhoto(image) { - imageDimensions = representation.dimensions.cgSize - } - break - } else if let file = media as? TelegramMediaFile { - updatedMediaReference = .message(message: MessageReference(message), media: file) - if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) { - imageDimensions = representation.dimensions.cgSize - } else if file.isAnimated, let dimensions = file.dimensions { + } + + if !message.containsSecretMedia { + for media in message.media { + if let image = media as? TelegramMediaImage { + updatedMediaReference = .message(message: MessageReference(message), media: image) + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.cgSize + } + break + } else if let file = media as? TelegramMediaFile { + updatedMediaReference = .message(message: MessageReference(message), media: file) + if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) { + imageDimensions = representation.dimensions.cgSize + } else if file.isAnimated, let dimensions = file.dimensions { + imageDimensions = dimensions.cgSize + } + break + } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first { + switch firstMedia { + case let .preview(dimensions, immediateThumbnailData, _): + let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + if let dimensions { imageDimensions = dimensions.cgSize } - break - } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first { - switch firstMedia { - case let .preview(dimensions, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - if let dimensions { + updatedMediaReference = .standalone(media: thumbnailMedia) + case let .full(fullMedia): + updatedMediaReference = .message(message: MessageReference(message), media: fullMedia) + if let image = fullMedia as? TelegramMediaImage { + if let representation = largestRepresentationForPhoto(image) { + imageDimensions = representation.dimensions.cgSize + } + break + } else if let file = fullMedia as? TelegramMediaFile { + if let dimensions = file.dimensions { imageDimensions = dimensions.cgSize } - updatedMediaReference = .standalone(media: thumbnailMedia) - case let .full(fullMedia): - updatedMediaReference = .message(message: MessageReference(message), media: fullMedia) - if let image = fullMedia as? TelegramMediaImage { - if let representation = largestRepresentationForPhoto(image) { - imageDimensions = representation.dimensions.cgSize - } - break - } else if let file = fullMedia as? TelegramMediaFile { - if let dimensions = file.dimensions { - imageDimensions = dimensions.cgSize - } - break - } - } - } - } - } - - if isReplyThread { - let titleString: String - if let author = message.effectiveAuthor { - titleString = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) - } else { - titleString = "" - } - titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] - } else { - for media in message.media { - if let media = media as? TelegramMediaInvoice { - titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] - break - } - } - } - - var applyImage: (() -> Void)? - if let imageDimensions = imageDimensions { - let boundingSize = CGSize(width: 35.0, height: 35.0) - applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) - - textLineInset += 9.0 + 35.0 - } - - var mediaUpdated = false - if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference { - mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) - } else if (updatedMediaReference != nil) != (previousMediaReference != nil) { - mediaUpdated = true - } - - let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) - - var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchMediaSignal: Signal? - if mediaUpdated { - if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { - if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { - if imageReference.media.representations.isEmpty { - updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true) - } else { - updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler) - } - } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) { - if fileReference.media.isAnimatedSticker { - let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512) - updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))) - updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)) - } else if fileReference.media.isVideo || fileReference.media.isAnimated { - updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler) - } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation) - } - } - } else { - updateImageSignal = .single({ _ in return nil }) - } - } - let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), .zero, titleStrings) - - let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) - - let messageText: NSAttributedString - let textFont = Font.regular(15.0) - if let giveaway { - let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings) - let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - let isFinished = currentTime >= giveaway.untilDate - let text: String - if isFinished { - let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity) - text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string - } else { - let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity) - text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string - } - messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor) - } else if isText { - var text = message.text - var messageEntities = message.textEntitiesAttribute?.entities ?? [] - - if let translateToLanguage = translateToLanguage, !text.isEmpty { - for attribute in message.attributes { - if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { - text = attribute.text - messageEntities = attribute.entities break } } } - - let entities = messageEntities.filter { entity in - switch entity.type { - case .Spoiler, .CustomEmoji: - return true - default: - return false - } - } - let textColor = theme.chat.inputPanel.primaryTextColor - if entities.count > 0 { - messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message) - } else { - messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor) - } - } else { - messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) } - - let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude) - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) - - let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)? - if !textLayout.spoilers.isEmpty { - spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true)) + } + + if isReplyThread { + let titleString: String + if let author = message.effectiveAuthor { + titleString = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) } else { - spoilerTextLayoutAndApply = nil + titleString = "" } + titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] + } else { + for media in message.media { + if let media = media as? TelegramMediaInvoice { + titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))] + break + } + } + } + + var applyImage: (() -> Void)? + if let imageDimensions = imageDimensions { + let boundingSize = CGSize(width: 35.0, height: 35.0) + applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) - Queue.mainQueue().async { - if let strongSelf = self { - let _ = titleApply(animation != nil) - - var textArguments: TextNodeWithEntities.Arguments? - if let cache = strongSelf.animationCache, let renderer = strongSelf.animationRenderer { - textArguments = TextNodeWithEntities.Arguments( - context: strongSelf.context, - cache: cache, - renderer: renderer, - placeholderColor: theme.list.mediaPlaceholderColor, - attemptSynchronous: false - ) - } - let _ = textApply(textArguments) - - strongSelf.previousMediaReference = updatedMediaReference - - animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight))) - - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size) - - let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) - strongSelf.textNode.textNode.frame = textFrame - - if let (_, spoilerTextApply) = spoilerTextLayoutAndApply { - let spoilerTextNode = spoilerTextApply(textArguments) - if strongSelf.spoilerTextNode == nil { - spoilerTextNode.textNode.alpha = 0.0 - spoilerTextNode.textNode.isUserInteractionEnabled = false - spoilerTextNode.textNode.contentMode = .topLeft - spoilerTextNode.textNode.contentsScale = UIScreenScale - spoilerTextNode.textNode.displaysAsynchronously = false - strongSelf.contentTextContainer.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textNode.textNode) - - strongSelf.spoilerTextNode = spoilerTextNode - } - - strongSelf.spoilerTextNode?.textNode.frame = textFrame - - let dustNode: InvisibleInkDustNode - if let current = strongSelf.dustNode { - dustNode = current - } else { - dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency) - strongSelf.dustNode = dustNode - strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode) - } - dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) - dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) - } else if let spoilerTextNode = strongSelf.spoilerTextNode { - strongSelf.spoilerTextNode = nil - spoilerTextNode.textNode.removeFromSupernode() - - if let dustNode = strongSelf.dustNode { - strongSelf.dustNode = nil - dustNode.removeFromSupernode() - } - } - - strongSelf.textNode.visibilityRect = CGRect.infinite - strongSelf.spoilerTextNode?.visibilityRect = CGRect.infinite - - let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight)) - animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame) - strongSelf.lineNode.update( - colors: AnimatedNavigationStripeNode.Colors( - foreground: theme.chat.inputPanel.panelControlAccentColor, - background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5), - clearBackground: theme.chat.inputPanel.panelBackgroundColor - ), - configuration: AnimatedNavigationStripeNode.Configuration( - height: panelHeight, - index: pinnedMessage.index, - count: pinnedMessage.totalCount - ), - transition: animationTransition - ) - - strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0)) - strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0)) - - if let applyImage = applyImage { - applyImage() - - animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 1.0) - animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true) + textLineInset += 9.0 + 35.0 + } + + var mediaUpdated = false + if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference { + mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media) + } else if (updatedMediaReference != nil) != (previousMediaReference != nil) { + mediaUpdated = true + } + + let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) + + var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? + var updatedFetchMediaSignal: Signal? + if mediaUpdated { + if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { + if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { + if imageReference.media.representations.isEmpty { + updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true) } else { - animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 0.1) - animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true) + updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler) } - - if let updateImageSignal = updateImageSignal { - strongSelf.imageNode.setSignal(updateImageSignal) + } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) { + if fileReference.media.isAnimatedSticker { + let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512) + updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))) + updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)) + } else if fileReference.media.isVideo || fileReference.media.isAnimated { + updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler) + } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation) } - if let updatedFetchMediaSignal = updatedFetchMediaSignal { - strongSelf.fetchDisposable.set(updatedFetchMediaSignal.startStrict()) + } + } else { + updateImageSignal = .single({ _ in return nil }) + } + } + let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), .zero, titleStrings) + + let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) + + let messageText: NSAttributedString + let textFont = Font.regular(15.0) + if let giveaway { + let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings) + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let isFinished = currentTime >= giveaway.untilDate + let text: String + if isFinished { + let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity) + text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string + } else { + let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity) + text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string + } + messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor) + } else if isText { + var text = message.text + var messageEntities = message.textEntitiesAttribute?.entities ?? [] + + if let translateToLanguage = translateToLanguage, !text.isEmpty { + for attribute in message.attributes { + if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage { + text = attribute.text + messageEntities = attribute.entities + break } } } + + let entities = messageEntities.filter { entity in + switch entity.type { + case .Spoiler, .CustomEmoji: + return true + default: + return false + } + } + let textColor = theme.chat.inputPanel.primaryTextColor + if entities.count > 0 { + messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message) + } else { + messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor) + } + } else { + messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) + } + + let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) + + let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)? + if !textLayout.spoilers.isEmpty { + spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true)) + } else { + spoilerTextLayoutAndApply = nil + } + + let strongSelf = self + let _ = titleApply(animation != nil) + + var textArguments: TextNodeWithEntities.Arguments? + if let cache = strongSelf.animationCache, let renderer = strongSelf.animationRenderer { + textArguments = TextNodeWithEntities.Arguments( + context: strongSelf.context, + cache: cache, + renderer: renderer, + placeholderColor: theme.list.mediaPlaceholderColor, + attemptSynchronous: false + ) + } + let _ = textApply(textArguments) + + strongSelf.previousMediaReference = updatedMediaReference + + animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight))) + + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size) + + let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) + strongSelf.textNode.textNode.frame = textFrame + + if let (_, spoilerTextApply) = spoilerTextLayoutAndApply { + let spoilerTextNode = spoilerTextApply(textArguments) + if strongSelf.spoilerTextNode == nil { + spoilerTextNode.textNode.alpha = 0.0 + spoilerTextNode.textNode.isUserInteractionEnabled = false + spoilerTextNode.textNode.contentMode = .topLeft + spoilerTextNode.textNode.contentsScale = UIScreenScale + spoilerTextNode.textNode.displaysAsynchronously = false + strongSelf.contentTextContainer.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textNode.textNode) + + strongSelf.spoilerTextNode = spoilerTextNode + } + + strongSelf.spoilerTextNode?.textNode.frame = textFrame + + let dustNode: InvisibleInkDustNode + if let current = strongSelf.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency) + strongSelf.dustNode = dustNode + strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode) + } + dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) + dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + } else if let spoilerTextNode = strongSelf.spoilerTextNode { + strongSelf.spoilerTextNode = nil + spoilerTextNode.textNode.removeFromSupernode() + + if let dustNode = strongSelf.dustNode { + strongSelf.dustNode = nil + dustNode.removeFromSupernode() + } + } + + strongSelf.textNode.visibilityRect = CGRect.infinite + strongSelf.spoilerTextNode?.visibilityRect = CGRect.infinite + + let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight)) + animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame) + strongSelf.lineNode.update( + colors: AnimatedNavigationStripeNode.Colors( + foreground: theme.chat.inputPanel.panelControlAccentColor, + background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5), + clearBackground: theme.chat.inputPanel.panelBackgroundColor + ), + configuration: AnimatedNavigationStripeNode.Configuration( + height: panelHeight, + index: pinnedMessage.index, + count: pinnedMessage.totalCount + ), + transition: animationTransition + ) + + strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0)) + strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0)) + + if let applyImage = applyImage { + applyImage() + + animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 1.0) + animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true) + } else { + animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 0.1) + animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true) + } + + if let updateImageSignal = updateImageSignal { + strongSelf.imageNode.setSignal(updateImageSignal) + } + if let updatedFetchMediaSignal = updatedFetchMediaSignal { + strongSelf.fetchDisposable.set(updatedFetchMediaSignal.startStrict()) } } diff --git a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift index 9f529a7cd2..a5fb16ae2d 100644 --- a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift @@ -246,7 +246,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C private let containerButton: HighlightTrackingButton - private let icon = ComponentView() + private var icon: ComponentView? private var avatarNode: AvatarNode? private let title = ComponentView() private var badge: ComponentView? @@ -319,33 +319,60 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C let spacing: CGFloat = 3.0 let badgeSpacing: CGFloat = 4.0 - let avatarIconContent: EmojiStatusComponent.Content - if case let .forum(topicId) = item.item.id, topicId != 1, let threadData = item.item.threadData { - if let fileId = threadData.info.icon, fileId != 0 { - avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) + let iconSize = CGSize(width: 18.0, height: 18.0) + + var avatarIconContent: EmojiStatusComponent.Content? + if case let .forum(topicId) = item.item.id { + if topicId != 1, let threadData = item.item.threadData { + if let fileId = threadData.info.icon, fileId != 0 { + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(0)) + } else { + avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize) + } } else { - avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: CGSize(width: 18.0, height: 18.0)) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme), tintColor: theme.rootController.navigationBar.secondaryTextColor) } - } else { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(theme)) } - let avatarIconComponent = EmojiStatusComponent( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - content: avatarIconContent, - isVisibleForAnimations: false, - action: nil - ) - let iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent(avatarIconComponent), - environment: {}, - containerSize: CGSize(width: 18.0, height: 18.0) - ) + if let avatarIconContent { + let avatarIconComponent = EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: avatarIconContent, + isVisibleForAnimations: false, + action: nil + ) + let icon: ComponentView + if let current = self.icon { + icon = current + } else { + icon = ComponentView() + self.icon = icon + } + let _ = icon.update( + transition: .immediate, + component: AnyComponent(avatarIconComponent), + environment: {}, + containerSize: CGSize(width: 18.0, height: 18.0) + ) + } else if let icon = self.icon { + self.icon = nil + icon.view?.removeFromSuperview() + } - let titleText: String = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " + let titleText: String + if case let .forum(topicId) = item.item.id { + let _ = topicId + if let threadData = item.item.threadData { + titleText = threadData.info.title + } else { + //TODO:localize + titleText = "General" + } + } else { + titleText = item.item.renderedPeer.chatMainPeer?.compactDisplayTitle ?? " " + } let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -391,38 +418,37 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: 5.0 + floor((size.height - iconSize.height) * 0.5)), size: iconSize) let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: 5.0 + floor((size.height - titleSize.height) * 0.5)), size: titleSize) - if let iconView = self.icon.view { - if iconView.superview == nil { - iconView.isUserInteractionEnabled = false - self.containerButton.addSubview(iconView) + if let icon = self.icon { + if let iconView = icon.view { + if iconView.superview == nil { + iconView.isUserInteractionEnabled = false + self.containerButton.addSubview(iconView) + } + iconView.frame = iconFrame } - iconView.frame = iconFrame - if "".isEmpty { - iconView.isHidden = true - - let avatarNode: AvatarNode - if let current = self.avatarNode { - avatarNode = current - } else { - avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 7.0)) - self.avatarNode = avatarNode - self.containerButton.addSubview(avatarNode.view) - } - avatarNode.frame = iconFrame - avatarNode.updateSize(size: iconFrame.size) - - if let peer = item.item.renderedPeer.chatMainPeer { - if peer.smallProfileImage != nil { - avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) - } else { - avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) - } - } - } else if let avatarNode = self.avatarNode { + if let avatarNode = self.avatarNode { self.avatarNode = nil avatarNode.view.removeFromSuperview() - iconView.isHidden = false + } + } else { + let avatarNode: AvatarNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 7.0)) + self.avatarNode = avatarNode + self.containerButton.addSubview(avatarNode.view) + } + avatarNode.frame = iconFrame + avatarNode.updateSize(size: iconFrame.size) + + if let peer = item.item.renderedPeer.chatMainPeer { + if peer.smallProfileImage != nil { + avatarNode.setPeerV2(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) + } else { + avatarNode.setPeer(context: context, theme: theme, peer: peer, overrideImage: nil, emptyColor: .gray, clipStyle: .round, synchronousLoad: false, displayDimensions: iconFrame.size) + } } } @@ -707,6 +733,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C } private let context: AccountContext + private let isMonoforum: Bool private let scrollView: ScrollView @@ -722,8 +749,9 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C private var appliedScrollToId: ScrollId? - init(context: AccountContext, peerId: EnginePeer.Id) { + init(context: AccountContext, peerId: EnginePeer.Id, isMonoforum: Bool) { self.context = context + self.isMonoforum = isMonoforum self.selectedLineView = UIImageView() @@ -751,78 +779,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C self.scrollView.disablesInteractiveTransitionGestureRecognizer = true - let viewKey: PostboxViewKey = .savedMessagesIndex(peerId: peerId) - let interfaceStateKey: PostboxViewKey = .chatInterfaceState(peerId: peerId) - - let accountPeerId = context.account.peerId - let threadListSignal: Signal = context.account.postbox.combinedView(keys: [viewKey, interfaceStateKey]) - |> map { views -> EngineChatList in - guard let view = views.views[viewKey] as? MessageHistorySavedMessagesIndexView else { - preconditionFailure() - } - - var draft: EngineChatList.Draft? - if let interfaceStateView = views.views[interfaceStateKey] as? ChatInterfaceStateView { - if let embeddedState = interfaceStateView.value, let _ = embeddedState.overrideChatTimestamp { - if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { - if let text = opaqueState.synchronizeableInputState?.text { - draft = EngineChatList.Draft(text: text, entities: opaqueState.synchronizeableInputState?.entities ?? []) - } - } - } - } - - var items: [EngineChatList.Item] = [] - for item in view.items { - guard let sourcePeer = item.peer else { - continue - } - - let sourceId = PeerId(item.id) - - var messages: [EngineMessage] = [] - if let topMessage = item.topMessage { - messages.append(EngineMessage(topMessage)) - } - - let mappedMessageIndex = MessageIndex(id: MessageId(peerId: sourceId, namespace: item.index.id.namespace, id: item.index.id.id), timestamp: item.index.timestamp) - - items.append(EngineChatList.Item( - id: .chatList(sourceId), - index: .chatList(ChatListIndex(pinningIndex: item.pinnedIndex.flatMap(UInt16.init), messageIndex: mappedMessageIndex)), - messages: messages, - readCounters: EnginePeerReadCounters( - incomingReadId: 0, outgoingReadId: 0, count: Int32(item.unreadCount), markedUnread: false), - isMuted: false, - draft: sourceId == accountPeerId ? draft : nil, - threadData: nil, - renderedPeer: EngineRenderedPeer(peer: EnginePeer(sourcePeer)), - presence: nil, - hasUnseenMentions: false, - hasUnseenReactions: false, - forumTopicData: nil, - topForumTopicItems: [], - hasFailed: false, - isContact: false, - autoremoveTimeout: nil, - storyStats: nil, - displayAsTopicList: false, - isPremiumRequiredToMessage: false, - mediaDraftContentType: nil - )) - } - - let list = EngineChatList( - items: items.reversed(), - groupItems: [], - additionalItems: [], - hasEarlier: false, - hasLater: false, - isLoading: view.isLoading - ) - - return list - } + let threadListSignal: Signal = context.sharedContext.subscribeChatListData(context: context, location: isMonoforum ? .savedMessagesChats(peerId: peerId) : .forum(peerId: peerId)) self.itemsDisposable = (threadListSignal |> deliverOnMainQueue).startStrict(next: { [weak self] chatList in @@ -951,7 +908,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C guard let self else { return } - self.interfaceInteraction?.updateChatLocationThread(nil) + self.interfaceInteraction?.updateChatLocationThread(nil, .left) }) self.allItemView = itemView self.scrollView.addSubview(itemView) @@ -1001,8 +958,20 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C guard let self else { return } - let topicId = chatListItem.renderedPeer.peerId.toInt64() - self.interfaceInteraction?.updateChatLocationThread(topicId) + + let topicId: Int64 + if case let .forum(topicIdValue) = chatListItem.id { + topicId = topicIdValue + } else { + topicId = chatListItem.renderedPeer.peerId.toInt64() + } + + var direction = true + if let params = self.params, let lhsIndex = self.topicIndex(threadId: params.interfaceState.chatLocation.threadId), let rhsIndex = self.topicIndex(threadId: topicId) { + direction = lhsIndex < rhsIndex + } + + self.interfaceInteraction?.updateChatLocationThread(topicId, direction ? .right : .left) }, contextGesture: { gesture, sourceNode in }) self.itemViews[itemId] = itemView @@ -1010,8 +979,10 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C } var isSelected = false - if params.interfaceState.chatLocation.threadId == item.item.renderedPeer.peerId.toInt64() { - isSelected = true + if case let .forum(topicId) = item.item.id { + isSelected = params.interfaceState.chatLocation.threadId == topicId + } else { + isSelected = params.interfaceState.chatLocation.threadId == item.item.renderedPeer.peerId.toInt64() } let itemSize = itemView.update(context: self.context, item: item, isSelected: isSelected, theme: params.interfaceState.theme, height: panelHeight, transition: .immediate) let itemFrame = CGRect(origin: CGPoint(x: contentSize.width, y: -5.0), size: itemSize) @@ -1100,7 +1071,15 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C public func topicIndex(threadId: Int64?) -> Int? { if let threadId { - if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) { + if let value = self.items.firstIndex(where: { item in + if item.id == .chatList(PeerId(threadId)) { + return true + } else if item.id == .forum(threadId) { + return true + } else { + return false + } + }) { return value + 1 } else { return nil diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index b6d0f1c4d2..043b097745 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -805,7 +805,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.suspendedNavigationBarLayout = layout return } - self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: nil, transition: transition) } override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -818,7 +818,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.suspendNavigationBarLayout = false if let suspendedNavigationBarLayout = self.suspendedNavigationBarLayout { self.suspendedNavigationBarLayout = suspendedNavigationBarLayout - self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, transition: transition) + self.applyNavigationBarLayout(suspendedNavigationBarLayout, navigationLayout: self.navigationLayout(layout: layout), additionalBackgroundHeight: self.additionalNavigationBarBackgroundHeight, additionalCutout: nil, transition: transition) } } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 03cf76aa7f..23a121900f 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -138,7 +138,15 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam isFirst = false continue } - if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) { + + var canMatchThread = controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId + var switchToThread = false + if !canMatchThread && controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.subject == nil { + canMatchThread = true + switchToThread = true + } + + if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && canMatchThread && (controller.subject != .scheduledMessages || controller.subject == params.subject) { if let updateTextInputState = params.updateTextInputState { controller.updateTextInputState(updateTextInputState) } @@ -164,6 +172,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam controller.beginReportSelection(reason: reportReason) } + if switchToThread { + controller.updateChatLocationThread(threadId: params.chatLocation.threadId, animationDirection: nil) + } + if popAndComplete { if let _ = params.navigationController.viewControllers.last as? AttachmentController, let controller = params.navigationController.viewControllers[params.navigationController.viewControllers.count - 2] as? ChatControllerImpl, controller.chatLocation == params.chatLocation.asChatLocation { @@ -376,7 +388,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll return false } -public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal { +public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal { return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) |> deliverOnMainQueue |> beforeNext { [weak context, weak navigationController] result in @@ -399,7 +411,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee activateInput: actualActivateInput, keepStack: keepStack, scrollToEndIfExists: scrollToEndIfExists, - animated: !scrollToEndIfExists + animated: !scrollToEndIfExists && animated ) ) } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 706cd0751d..7cdcc9e4da 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -220,7 +220,7 @@ func openResolvedUrlImpl( } case let .replyThread(messageId): if let navigationController = navigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).startStandalone() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } case let .stickerPack(name, _): dismissInput() diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 292d8ae989..e21416010d 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -193,7 +193,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.dimNode = ASDisplayNode() diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index cfdb30d267..b830f4c93b 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2127,8 +2127,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) } - public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack) -> Signal { - return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, scrollToEndIfExists: scrollToEndIfExists, keepStack: keepStack) + public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal { + return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, scrollToEndIfExists: scrollToEndIfExists, keepStack: keepStack, animated: animated) } public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal { @@ -2239,6 +2239,13 @@ public final class SharedAccountContextImpl: SharedAccountContext { ) } + public func subscribeChatListData(context: AccountContext, location: ChatListControllerLocation) -> Signal { + return chatListViewForLocation(chatListLocation: location, location: .initial(count: 100, filter: nil), account: context.account, shouldLoadCanMessagePeer: false) + |> map { update -> EngineChatList in + return update.list + } + } + public func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? { return nil } @@ -2389,7 +2396,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, forceUpdateWarpContents: { }, playShakeAnimation: { }, displayQuickShare: { _, _ ,_ in - }, updateChatLocationThread: { _ in + }, updateChatLocationThread: { _, _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode)) @@ -2410,7 +2417,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { - return ChatMessageDateHeader(timestamp: timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context) + return ChatMessageDateHeader(timestamp: timestamp, separableThreadId: nil, scheduled: false, displayHeader: nil, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context) } public func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index 81b4b239a9..b6e53647a7 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -91,7 +91,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n } case let .replyThread(messageId): if let navigationController = controller.navigationController as? NavigationController { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always).start() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).start() } case let .stickerPack(name, _): let packReference: StickerPackReference = .name(name) diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index 776763d2b5..5ed5c5c428 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -1151,7 +1151,7 @@ public class TranslateScreen: ViewController { layout.statusBarHeight = nil - self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition) + self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, additionalCutout: nil, transition: transition) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {