diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index 280fd0ad81..329f81d18e 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -4,7 +4,7 @@ import Postbox import Display import TelegramCore -public enum ChatListControllerLocation { +public enum ChatListControllerLocation: Equatable { case chatList(groupId: EngineChatList.Group) case forum(peerId: PeerId) } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index d934d8afb5..0f75fc7316 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -233,15 +233,22 @@ public final class AvatarNode: ASDisplayNode { } set(value) { let updateImage = !value.size.equalTo(super.frame.size) super.frame = value - self.imageNode.frame = CGRect(origin: CGPoint(), size: value.size) - self.editOverlayNode?.frame = self.imageNode.frame - if updateImage && !self.displaySuspended { - self.setNeedsDisplay() - self.editOverlayNode?.setNeedsDisplay() + + if updateImage { + self.updateSize(size: value.size) } } } + public func updateSize(size: CGSize) { + self.imageNode.frame = CGRect(origin: CGPoint(), size: size) + self.editOverlayNode?.frame = self.imageNode.frame + if !self.displaySuspended { + self.setNeedsDisplay() + self.editOverlayNode?.setNeedsDisplay() + } + } + public func playArchiveAnimation() { guard let theme = self.theme else { return diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 459c4d79e4..cade9157be 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -52,7 +52,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa } else { offset = 0.0 } - let _ = listNode.scrollToOffsetFromTop(offset) + let _ = listNode.scrollToOffsetFromTop(offset, animated: true) return true } else if searchNode.expansionProgress == 1.0 { var sortItemNode: ListViewItemNode? @@ -341,6 +341,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private let moreBarButtonItem: UIBarButtonItem private var forumChannelTracker: ForumChannelTopics? + private var backNavigationItem: UIBarButtonItem? + private let selectAddMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable() private let joinForumDisposable = MetaDisposable() @@ -392,6 +394,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) + self.backNavigationItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.navigationBackPressed)) + self.tabBarItemContextActionType = .always self.automaticallyControlPresentationContextLayout = false @@ -742,6 +746,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel { strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) } + } else if strongSelf.chatListDisplayNode.inlineStackContainerNode != nil { } else { let editItem: UIBarButtonItem if stateAndFilterId.state.editing { @@ -1160,10 +1165,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if force { strongSelf.tabContainerNode.cancelAnimations() - strongSelf.chatListDisplayNode.inlineTabContainerNode.cancelAnimations() } strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) - strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) } self.reloadFilters() } @@ -1277,7 +1280,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController editItem = self.moreBarButtonItem } } - if case .chatList(.root) = self.location { + if self.chatListDisplayNode.inlineStackContainerNode != nil { + self.backNavigationItem?.title = self.presentationData.strings.Common_Back + } else if case .chatList(.root) = self.location { self.navigationItem.leftBarButtonItem = editItem let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose @@ -1296,7 +1301,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let layout = self.validLayout { self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) - self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) } if self.isNodeLoaded { @@ -1383,11 +1387,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { - strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) + strongSelf.chatListDisplayNode.clearHighlightAnimated(true) + + if strongSelf.context.sharedContext.immediateExperimentalUISettings.inlineForums { + strongSelf.chatListDisplayNode.setInlineChatList(location: .forum(peerId: channel.id)) + + if strongSelf.navigationItem.leftBarButtonItem !== strongSelf.backNavigationItem { + strongSelf.navigationItem.setLeftBarButton(strongSelf.backNavigationItem, animated: true) + } + } else { + strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) + } } else { if let threadId = threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() - strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) + strongSelf.chatListDisplayNode.clearHighlightAnimated(true) } else { var navigationAnimationOptions: NavigationAnimationOptions = [] var groupId: EngineChatList.Group = .root @@ -1581,7 +1595,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationController.filterController(strongSelf, animated: true) } - self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in + self.chatListDisplayNode.contentOffsetChanged = { [weak self] offset in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { var offset = offset if validLayout.inVoiceOver { @@ -1591,7 +1605,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in + self.chatListDisplayNode.contentScrollingEnded = { [weak self] listView in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) } else { @@ -1885,20 +1899,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.selectTab(id: id) } } - self.chatListDisplayNode.inlineTabContainerNode.tabSelected = { [weak self] id in - self?.selectTab(id: id) - } self.tabContainerNode.tabRequestedDeletion = { [weak self] id in if case let .filter(id) = id { self?.askForFilterRemoval(id: id) } } - self.chatListDisplayNode.inlineTabContainerNode.tabRequestedDeletion = { [weak self] id in - if case let .filter(id) = id { - self?.askForFilterRemoval(id: id) - } - } self.tabContainerNode.presentPremiumTip = { [weak self] in if let strongSelf = self { strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAll, customUndoText: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in @@ -2108,9 +2114,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in tabContextGesture(id, sourceNode, gesture, false, isDisabled) } - self.chatListDisplayNode.inlineTabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in - tabContextGesture(id, sourceNode, gesture, true, isDisabled) - } if case .chatList(.root) = self.location { self.ready.set(.never()) @@ -2499,12 +2502,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - if let tabContainerData = self.tabContainerData { - self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1 - } else { - self.chatListDisplayNode.inlineTabContainerNode.isHidden = true - } - self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition) } @@ -2559,22 +2556,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } @objc private func reorderingDonePressed() { - guard let defaultFilters = self.tabContainerData else { - return - } - let defaultFilterIds = defaultFilters.0.compactMap { entry -> Int32? in - switch entry { - case .all: - return 0 - case let .filter(id, _, _): - return id - } - } - var reorderedFilterIdsValue: [Int32]? - if let reorderedFilterIds = self.chatListDisplayNode.inlineTabContainerNode.reorderedFilterIds, reorderedFilterIds != defaultFilterIds { - reorderedFilterIdsValue = reorderedFilterIds - } else if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { + if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { reorderedFilterIdsValue = reorderedFilterIds } @@ -2620,6 +2603,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) } + @objc private func navigationBackPressed() { + self.chatListDisplayNode.setInlineChatList(location: nil) + + if self.navigationItem.leftBarButtonItem === self.backNavigationItem { + let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + editItem.accessibilityLabel = self.presentationData.strings.Common_Edit + + self.navigationItem.setLeftBarButton(editItem, animated: true) + } + } + public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { peer in @@ -2872,7 +2866,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) } else { strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: filtersLimit, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index e20cb03d29..c44ba07a34 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -173,7 +173,7 @@ private final class ChatListShimmerNode: ASDisplayNode { self.addSubnode(self.maskNode) } - func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, isInlineMode: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData { self.currentParams = (size, presentationData) @@ -186,6 +186,7 @@ private final class ChatListShimmerNode: ASDisplayNode { }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }) + interaction.isInlineMode = isInlineMode let items = (0 ..< 2).map { _ -> ChatListItem in let message = EngineMessage( @@ -251,14 +252,25 @@ private final class ChatListShimmerNode: ASDisplayNode { context.setBlendMode(.copy) context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + if !isInlineMode { + if itemNodes[sampleIndex].avatarNode.isHidden { + context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + } + } + let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) + if isInlineMode { + context.fillEllipse(in: CGRect(origin: CGPoint(x: textFrame.minX, y: titleFrame.minY + 2.0), size: CGSize(width: 16.0, height: 16.0))) + } + + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY) fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) @@ -288,6 +300,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private let becameEmpty: (ChatListFilter?) -> Void private let emptyAction: (ChatListFilter?) -> Void private let secondaryEmptyAction: () -> Void + private let isInlineMode: Bool private var floatingHeaderOffset: CGFloat? @@ -296,9 +309,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { private var shimmerNodeOffset: CGFloat = 0.0 let listNode: ChatListNode - private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? + private var validLayout: (CGSize, UIEdgeInsets, CGFloat, ChatListControllerLocation?)? - init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer @@ -306,8 +319,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.becameEmpty = becameEmpty self.emptyAction = emptyAction self.secondaryEmptyAction = secondaryEmptyAction + self.isInlineMode = isInlineMode - self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true) + self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode) super.init() @@ -361,7 +375,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { }) strongSelf.emptyNode = emptyNode strongSelf.addSubnode(emptyNode) - if let (size, insets, _) = strongSelf.validLayout { + if let (size, insets, _, _) = strongSelf.validLayout { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) emptyNode.frame = emptyNodeFrame emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) @@ -387,7 +401,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { let emptyShimmerEffectNode = ChatListShimmerNode() strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode) - if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { + if let (size, insets, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate) } } @@ -407,14 +421,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { return } strongSelf.floatingHeaderOffset = offset - if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { + if let (size, insets, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition) } } } private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) { - node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, presentationData: self.presentationData, transition: .immediate) + node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, isInlineMode: self.isInlineMode, presentationData: self.presentationData, transition: .immediate) transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size)) } @@ -430,15 +444,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, insets, visualNavigationHeight) + func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, insets, visualNavigationHeight, inlineNavigationLocation) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) - self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) - self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation) if let emptyNode = self.emptyNode { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) @@ -450,8 +463,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext - private let location: ChatListControllerLocation + let location: ChatListControllerLocation private let previewing: Bool + private let isInlineMode: Bool private let controlsHistoryPreload: Bool private let filterBecameEmpty: (ChatListFilter?) -> Void private let filterEmptyAction: (ChatListFilter?) -> Void @@ -473,12 +487,14 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private(set) var transitionFraction: CGFloat = 0.0 private var transitionFractionOffset: CGFloat = 0.0 private var disableItemNodeOperationsWhileAnimating: Bool = false - private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool)? + private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?)? private var enableAdjacentFilterLoading: Bool = false private var panRecognizer: InteractiveTransitionGestureRecognizer? + let leftSeparatorLayer: SimpleLayer + private let _ready = Promise() var ready: Signal { return _ready.get() @@ -624,17 +640,18 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? - var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? - var contentScrollingEnded: ((ListView) -> Bool)? + fileprivate var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? + fileprivate var contentScrollingEnded: ((ListView) -> Bool)? var activateChatPreview: ((ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var didBeginSelectingChats: (() -> Void)? var displayFilterLimit: (() -> Void)? - init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context self.location = location self.previewing = previewing + self.isInlineMode = isInlineMode self.filterBecameEmpty = filterBecameEmpty self.filterEmptyAction = filterEmptyAction self.secondaryEmptyAction = secondaryEmptyAction @@ -646,9 +663,15 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.selectedId = .all + self.leftSeparatorLayer = SimpleLayer() + self.leftSeparatorLayer.isHidden = true + self.leftSeparatorLayer.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor.cgColor + super.init() - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + self.backgroundColor = presentationData.theme.chatList.backgroundColor + + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -685,6 +708,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { panRecognizer.cancelsTouchesInView = true self.panRecognizer = panRecognizer self.view.addGestureRecognizer(panRecognizer) + + self.view.layer.addSublayer(self.leftSeparatorLayer) } deinit { @@ -714,7 +739,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() self.transitionFractionOffset = 0.0 - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { for (id, itemNode) in self.itemNodes { if id != selectedId { itemNode.emptyNode?.restartAnimation() @@ -727,13 +752,13 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { for (_, itemNode) in self.itemNodes { itemNode.layer.removeAllAnimations() } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) } } } case .changed: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / layout.size.width @@ -770,11 +795,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false) } case .cancelled, .ended: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -813,12 +838,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.transitionFraction = 0.0 let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) self.disableItemNodeOperationsWhileAnimating = true - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) DispatchQueue.main.async { self.disableItemNodeOperationsWhileAnimating = false - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } } @@ -827,9 +852,17 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } + func fixContentOffset(offset: CGFloat) { + self.currentItemNode.fixContentOffset(offset: offset) + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + + self.leftSeparatorLayer.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor.cgColor + for (_, itemNode) in self.itemNodes { itemNode.updatePresentationData(presentationData) } @@ -884,8 +917,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.availableFilters = availableFilters strongSelf.filtersLimit = limit - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout { - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout { + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } if !availableFilters.contains(where: { $0.id == self.selectedId }) { @@ -902,8 +935,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { if value != self.enableAdjacentFilterLoading { self.enableAdjacentFilterLoading = value - if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } } @@ -912,7 +945,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { if let itemNode = self.itemNodes[id] { - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout else { return } self.selectedId = id @@ -921,12 +954,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.applyItemNodeAsCurrent(id: id, itemNode: itemNode) let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) itemNode.emptyNode?.restartAnimation() completion?() } else if self.pendingItemNode == nil { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -951,7 +984,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { strongSelf.pendingItemNode = nil - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout else { strongSelf.itemNodes[id] = itemNode strongSelf.addSubnode(itemNode) @@ -1000,7 +1033,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0)) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) strongSelf.selectedId = id if let currentItemNode = strongSelf.currentItemNodeValue { @@ -1008,7 +1041,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode) - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) } @@ -1019,8 +1052,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } - func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, transition: ContainedViewLayoutTransition) { - self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) + func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) self._validLayoutReady.set(.single(true)) @@ -1029,6 +1062,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.panRecognizer?.isEnabled = !isEditing + transition.updateFrame(layer: self.leftSeparatorLayer, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + if let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { var validNodeIds: [ChatListFilterTabEntryId] = [] for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) { @@ -1036,7 +1071,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { validNodeIds.append(id) if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1073,7 +1108,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in }) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: nodeTransition) if wasAdded, case .animated = transition { animateSlidingIds.append(id) @@ -1106,7 +1141,7 @@ final class ChatListControllerNode: ASDisplayNode { private let animationRenderer: MultiAnimationRenderer let containerNode: ChatListContainerNode - let inlineTabContainerNode: ChatListFilterTabInlineContainerNode + private(set) var inlineStackContainerNode: ChatListContainerNode? private var tapRecognizer: UITapGestureRecognizer? var navigationBar: NavigationBar? weak var controller: ChatListControllerImpl? @@ -1121,7 +1156,10 @@ final class ChatListControllerNode: ASDisplayNode { var didBeginSelectingChatsWhileEditing: Bool = false var isEditing: Bool = false - private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)? + private var containerLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat)? + + var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? + var contentScrollingEnded: ((ListView) -> Bool)? var requestDeactivateSearch: (() -> Void)? var requestOpenPeerFromSearch: ((EnginePeer, Int64?, Bool) -> Void)? @@ -1146,7 +1184,7 @@ final class ChatListControllerNode: ASDisplayNode { var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)? var secondaryEmptyAction: (() -> Void)? - self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in + self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in filterBecameEmpty?(filter) }, filterEmptyAction: { filter in filterEmptyAction?(filter) @@ -1154,8 +1192,6 @@ final class ChatListControllerNode: ASDisplayNode { secondaryEmptyAction?() }) - self.inlineTabContainerNode = ChatListFilterTabInlineContainerNode() - self.controller = controller super.init() @@ -1167,7 +1203,13 @@ final class ChatListControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.chatList.backgroundColor self.addSubnode(self.containerNode) - self.addSubnode(self.inlineTabContainerNode) + + self.containerNode.contentOffsetChanged = { [weak self] offset in + self?.contentOffsetChanged(offset: offset, isPrimary: true) + } + self.containerNode.contentScrollingEnded = { [weak self] listView in + return self?.contentScrollingEnded(listView: listView, isPrimary: true) ?? false + } self.addSubnode(self.debugListView) @@ -1292,9 +1334,42 @@ final class ChatListControllerNode: ASDisplayNode { self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition) + var mainNavigationBarHeight = navigationBarHeight + var cleanMainNavigationBarHeight = cleanNavigationBarHeight + var mainInsets = insets + if self.inlineStackContainerNode != nil { + mainNavigationBarHeight = visualNavigationHeight + cleanMainNavigationBarHeight = visualNavigationHeight + mainInsets.top = visualNavigationHeight + } + self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, transition: transition) - transition.updateFrame(node: self.inlineTabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.intrinsicInsets.bottom - 8.0 - 40.0), size: CGSize(width: layout.size.width, height: 40.0))) + if let inlineStackContainerNode = self.inlineStackContainerNode { + var inlineStackContainerNodeTransition = transition + var animateIn = false + if inlineStackContainerNode.supernode == nil { + self.insertSubnode(inlineStackContainerNode, aboveSubnode: self.containerNode) + inlineStackContainerNodeTransition = .immediate + animateIn = true + } + + let inlineSideInset: CGFloat = layout.safeInsets.left + 72.0 + inlineStackContainerNodeTransition.updateFrame(node: inlineStackContainerNode, frame: CGRect(origin: CGPoint(x: inlineSideInset, y: 0.0), size: layout.size)) + var inlineLayout = layout + inlineLayout.size.width -= inlineSideInset + inlineLayout.safeInsets.left = 0.0 + inlineLayout.intrinsicInsets.left = 0.0 + inlineLayout.additionalInsets.left = 0.0 + + var inlineInsets = insets + inlineInsets.left = 0.0 + + inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, transition: inlineStackContainerNodeTransition) + + if animateIn { + transition.animatePosition(node: inlineStackContainerNode, from: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y)) + } + } self.tapRecognizer?.isEnabled = self.isReorderingFilters @@ -1372,6 +1447,150 @@ final class ChatListControllerNode: ASDisplayNode { } } + func clearHighlightAnimated(_ animated: Bool) { + self.containerNode.currentItemNode.clearHighlightAnimated(true) + self.inlineStackContainerNode?.currentItemNode.clearHighlightAnimated(true) + } + + private var contentOffsetSyncLockedIn: Bool = false + + private func contentOffsetChanged(offset: ListViewVisibleContentOffset, isPrimary: Bool) { + guard let inlineStackContainerNode = self.inlineStackContainerNode else { + self.contentOffsetChanged?(offset) + return + } + guard let containerLayout = self.containerLayout else { + return + } + + if !isPrimary { + self.contentOffsetChanged?(offset) + if "".isEmpty { + return + } + } else { + if "".isEmpty { + return + } + } + + let targetNode: ChatListContainerNode + if isPrimary { + targetNode = inlineStackContainerNode + } else { + targetNode = self.containerNode + } + + switch offset { + case let .known(value) where (value <= containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 + UIScreenPixel || self.contentOffsetSyncLockedIn): + if case let .known(otherValue) = targetNode.currentItemNode.visibleContentOffset(), abs(otherValue - value) <= UIScreenPixel { + self.contentOffsetSyncLockedIn = true + } + default: + break + } + + switch offset { + case let .known(value) where self.contentOffsetSyncLockedIn: + var targetValue = value + if targetValue > containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 { + targetValue = containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 + } + + targetNode.fixContentOffset(offset: targetValue) + + self.contentOffsetChanged?(offset) + default: + if !isPrimary { + self.contentOffsetChanged?(offset) + } + } + } + + private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool { + guard let inlineStackContainerNode = self.inlineStackContainerNode else { + return self.contentScrollingEnded?(listView) ?? false + } + + self.contentOffsetSyncLockedIn = false + + if isPrimary { + return false + } + + let _ = inlineStackContainerNode + + return self.contentScrollingEnded?(listView) ?? false + } + + func setInlineChatList(location: ChatListControllerLocation?) { + if let location = location { + if self.inlineStackContainerNode?.location != location { + let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {}) + + inlineStackContainerNode.leftSeparatorLayer.isHidden = false + + inlineStackContainerNode.presentAlert = self.containerNode.presentAlert + inlineStackContainerNode.present = self.containerNode.present + inlineStackContainerNode.push = self.containerNode.push + inlineStackContainerNode.deletePeerChat = self.containerNode.deletePeerChat + inlineStackContainerNode.deletePeerThread = self.containerNode.deletePeerThread + inlineStackContainerNode.setPeerThreadStopped = self.containerNode.setPeerThreadStopped + inlineStackContainerNode.setPeerThreadPinned = self.containerNode.setPeerThreadPinned + inlineStackContainerNode.peerSelected = self.containerNode.peerSelected + inlineStackContainerNode.groupSelected = self.containerNode.groupSelected + inlineStackContainerNode.updatePeerGrouping = self.containerNode.updatePeerGrouping + + inlineStackContainerNode.contentOffsetChanged = { [weak self] offset in + self?.contentOffsetChanged(offset: offset, isPrimary: false) + } + inlineStackContainerNode.contentScrollingEnded = { [weak self] listView in + return self?.contentScrollingEnded(listView: listView, isPrimary: false) ?? false + } + + inlineStackContainerNode.activateChatPreview = self.containerNode.activateChatPreview + inlineStackContainerNode.addedVisibleChatsWithPeerIds = self.containerNode.addedVisibleChatsWithPeerIds + inlineStackContainerNode.didBeginSelectingChats = nil + inlineStackContainerNode.displayFilterLimit = nil + + let previousInlineStackContainerNode = self.inlineStackContainerNode + + self.inlineStackContainerNode = inlineStackContainerNode + + if let containerLayout = self.containerLayout { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + + if let previousInlineStackContainerNode { + transition.updatePosition(node: previousInlineStackContainerNode, position: CGPoint(x: previousInlineStackContainerNode.position.x + previousInlineStackContainerNode.bounds.width + UIScreenPixel, y: previousInlineStackContainerNode.position.y), completion: { [weak previousInlineStackContainerNode] _ in + previousInlineStackContainerNode?.removeFromSupernode() + }) + } + + self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) + } else { + previousInlineStackContainerNode?.removeFromSupernode() + } + } + } else { + if let inlineStackContainerNode = self.inlineStackContainerNode { + self.inlineStackContainerNode = nil + + self.containerNode.contentScrollingEnded = self.contentScrollingEnded + + if let containerLayout = self.containerLayout { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) + + transition.updatePosition(node: inlineStackContainerNode, position: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y), completion: { [weak inlineStackContainerNode] _ in + inlineStackContainerNode?.removeFromSupernode() + }) + } else { + inlineStackContainerNode.removeFromSupernode() + } + } + } + } + func playArchiveAnimation() { self.containerNode.playArchiveAnimation() } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index c8bf77ef63..69cd3d0b1d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -775,6 +775,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private let highlightedBackgroundNode: ASDisplayNode let contextContainer: ContextControllerSourceNode + let mainContentContainerNode: ASDisplayNode let avatarNode: AvatarNode var avatarIconView: ComponentHostView? @@ -784,6 +785,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private let playbackStartDisposable = MetaDisposable() private var videoLoopCount = 0 + private var inlineNavigationMarkLayer: SimpleLayer? + let titleNode: TextNode let authorNode: AuthorNode private var compoundHighlightingNode: LinkHighlightingNode? @@ -1026,6 +1029,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.contextContainer = ContextControllerSourceNode() + self.mainContentContainerNode = ASDisplayNode() + self.mainContentContainerNode.clipsToBounds = true + self.measureNode = TextNode() self.titleNode = TextNode() @@ -1073,19 +1079,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.separatorNode) self.addSubnode(self.contextContainer) + self.contextContainer.addSubnode(self.mainContentContainerNode) self.contextContainer.addSubnode(self.avatarNode) self.contextContainer.addSubnode(self.onlineNode) - self.contextContainer.addSubnode(self.titleNode) - self.contextContainer.addSubnode(self.authorNode) - self.contextContainer.addSubnode(self.textNode.textNode) - self.contextContainer.addSubnode(self.dateNode) - self.contextContainer.addSubnode(self.statusNode) - self.contextContainer.addSubnode(self.pinnedIconNode) - self.contextContainer.addSubnode(self.badgeNode) - self.contextContainer.addSubnode(self.mentionBadgeNode) - self.contextContainer.addSubnode(self.mutedIconNode) + self.mainContentContainerNode.addSubnode(self.titleNode) + self.mainContentContainerNode.addSubnode(self.authorNode) + self.mainContentContainerNode.addSubnode(self.textNode.textNode) + self.mainContentContainerNode.addSubnode(self.dateNode) + self.mainContentContainerNode.addSubnode(self.statusNode) + self.mainContentContainerNode.addSubnode(self.pinnedIconNode) + self.mainContentContainerNode.addSubnode(self.badgeNode) + self.mainContentContainerNode.addSubnode(self.mentionBadgeNode) + self.mainContentContainerNode.addSubnode(self.mutedIconNode) self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in if let strongSelf = self, let layoutParams = strongSelf.layoutParams { @@ -1094,6 +1101,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } }) + self.contextContainer.shouldBegin = { [weak self] location in + guard let strongSelf = self else { + return false + } + if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { + strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundHighlightingNode + } else { + strongSelf.contextContainer.targetNodeForActivationProgress = nil + } + + return true + } + self.contextContainer.activated = { [weak self] gesture, location in guard let strongSelf = self, let item = strongSelf.item else { return @@ -1505,7 +1525,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0)) let avatarLeftInset: CGFloat - if case .forum = item.index { + if item.interaction.isInlineMode { + avatarLeftInset = 12.0 + } else if case .forum = item.index { avatarLeftInset = 50.0 } else { avatarLeftInset = 18.0 + avatarDiameter @@ -2290,8 +2312,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.onlineIsVoiceChat = onlineIsVoiceChat - - strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) if case .groupReference = item.content { strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) @@ -2301,7 +2321,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor } - let revealOffset = strongSelf.revealOffset + let revealOffset = 0.0//strongSelf.revealOffset let transition: ContainedViewLayoutTransition if animated { @@ -2310,6 +2330,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } + let contextContainerFrame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.contextContainer.position = contextContainerFrame.center + transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) + + if item.interaction.inlineNavigationLocation != nil { + let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.size.width, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 0.0) + } else if case .chatList = item.chatListLocation { + let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.origin.x, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + } else { + let mainContentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + } + var crossfadeContent = false if let selectableControlSizeAndApply = selectableControlSizeAndApply { let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height) @@ -2380,9 +2424,51 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) - transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) + var avatarScaleOffset: CGFloat = 0.0 + var avatarScale: CGFloat = 1.0 + if item.interaction.inlineNavigationLocation != nil { + avatarScale = 54.0 / avatarFrame.width + avatarScaleOffset = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 + } + transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.center.offsetBy(dx: avatarScaleOffset, dy: 0.0)) + transition.updateBounds(node: strongSelf.avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) + transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale) + strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() + var itemPeerId: EnginePeer.Id? + if case let .chatList(index) = item.index { + itemPeerId = index.messageIndex.id.peerId + } + + if let itemPeerId = itemPeerId, let inlineNavigationLocation = item.interaction.inlineNavigationLocation, inlineNavigationLocation.location.peerId == itemPeerId { + let inlineNavigationMarkLayer: SimpleLayer + var animateIn = false + if let current = strongSelf.inlineNavigationMarkLayer { + inlineNavigationMarkLayer = current + } else { + inlineNavigationMarkLayer = SimpleLayer() + strongSelf.inlineNavigationMarkLayer = inlineNavigationMarkLayer + inlineNavigationMarkLayer.cornerRadius = 4.0 + animateIn = true + strongSelf.layer.addSublayer(inlineNavigationMarkLayer) + } + inlineNavigationMarkLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor + let markHeight: CGFloat = 50.0 + let markFrame = CGRect(origin: CGPoint(x: -4.0, y: avatarFrame.midY - markHeight * 0.5), size: CGSize(width: 8.0, height: markHeight)) + if animateIn { + inlineNavigationMarkLayer.frame = markFrame + transition.animatePositionAdditive(layer: inlineNavigationMarkLayer, offset: CGPoint(x: -markFrame.width * 0.5, y: 0.0)) + } else { + transition.updateFrame(layer: inlineNavigationMarkLayer, frame: markFrame) + } + } else { + if let inlineNavigationMarkLayer = strongSelf.inlineNavigationMarkLayer { + strongSelf.inlineNavigationMarkLayer = nil + transition.updatePosition(layer: inlineNavigationMarkLayer, position: CGPoint(x: -inlineNavigationMarkLayer.bounds.width * 0.5, y: avatarFrame.midY)) + } + } + if let threadInfo = threadInfo { let avatarIconView: ComponentHostView if let current = strongSelf.avatarIconView { @@ -2390,7 +2476,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { avatarIconView = ComponentHostView() strongSelf.avatarIconView = avatarIconView - strongSelf.contextContainer.view.addSubview(avatarIconView) + strongSelf.mainContentContainerNode.view.addSubview(avatarIconView) } let avatarIconContent: EmojiStatusComponent.Content @@ -2414,9 +2500,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition: .immediate, component: AnyComponent(avatarIconComponent), environment: {}, - containerSize: CGSize(width: 32.0, height: 32.0) + containerSize: item.interaction.isInlineMode ? CGSize(width: 18.0, height: 18.0) : CGSize(width: 32.0, height: 32.0) ) - transition.updateFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize)) + + let avatarIconFrame: CGRect + if item.interaction.isInlineMode { + avatarIconFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + 1.0), size: iconSize) + } else { + avatarIconFrame = CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize) + } + transition.updateFrame(view: avatarIconView, frame: avatarIconFrame) } else if let avatarIconView = strongSelf.avatarIconView { strongSelf.avatarIconView = nil avatarIconView.removeFromSuperview() @@ -2479,7 +2572,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { dateStatusIconNode = ASImageNode() strongSelf.dateStatusIconNode = dateStatusIconNode - strongSelf.contextContainer.addSubnode(dateStatusIconNode) + strongSelf.mainContentContainerNode.addSubnode(dateStatusIconNode) } dateStatusIconNode.image = dateIconImage @@ -2539,6 +2632,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } var titleOffset: CGFloat = 0.0 + if item.interaction.isInlineMode { + titleOffset += 22.0 + } if let currentSecretIconImage = currentSecretIconImage { let iconNode: ASImageNode if let current = strongSelf.secretIconNode { @@ -2548,7 +2644,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { iconNode.isLayerBacked = true iconNode.displaysAsynchronously = false iconNode.displayWithoutProcessing = true - strongSelf.contextContainer.addSubnode(iconNode) + strongSelf.mainContentContainerNode.addSubnode(iconNode) strongSelf.secretIconNode = iconNode } iconNode.image = currentSecretIconImage @@ -2576,7 +2672,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { compoundHighlightingNode.alpha = strongSelf.authorNode.alpha compoundHighlightingNode.useModernPathCalculation = true strongSelf.compoundHighlightingNode = compoundHighlightingNode - strongSelf.contextContainer.insertSubnode(compoundHighlightingNode, at: 0) + strongSelf.mainContentContainerNode.insertSubnode(compoundHighlightingNode, at: 0) } let compoundTextButtonNode: HighlightTrackingButtonNode @@ -2585,7 +2681,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { compoundTextButtonNode = HighlightTrackingButtonNode() strongSelf.compoundTextButtonNode = compoundTextButtonNode - strongSelf.contextContainer.addSubnode(compoundTextButtonNode) + strongSelf.mainContentContainerNode.addSubnode(compoundTextButtonNode) compoundTextButtonNode.addTarget(strongSelf, action: #selector(strongSelf.compoundTextButtonPressed), forControlEvents: .touchUpInside) compoundTextButtonNode.highligthedChanged = { highlighted in guard let strongSelf = self, let compoundHighlightingNode = strongSelf.compoundHighlightingNode else { @@ -2644,7 +2740,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { textArrowNode = ASImageNode() strongSelf.textArrowNode = textArrowNode - compoundTextButtonNode.addSubnode(textArrowNode) + compoundHighlightingNode.addSubnode(textArrowNode) } textArrowNode.image = textArrowImage let arrowScale: CGFloat = 0.75 @@ -2677,7 +2773,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { dustNode = InvisibleInkDustNode(textNode: nil) dustNode.isUserInteractionEnabled = false strongSelf.dustNode = dustNode - strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode) + strongSelf.mainContentContainerNode.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode) } dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) @@ -2699,7 +2795,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let inputActivities = inputActivities, !inputActivities.isEmpty { if strongSelf.inputActivitiesNode.supernode == nil { - strongSelf.contextContainer.addSubnode(strongSelf.inputActivitiesNode) + strongSelf.mainContentContainerNode.addSubnode(strongSelf.inputActivitiesNode) } else { animateInputActivitiesFrame = true } @@ -2768,7 +2864,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { previewNodeAlphaTransition = .immediate previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media) strongSelf.mediaPreviewNodes[mediaId] = previewNode - strongSelf.contextContainer.addSubnode(previewNode) + strongSelf.mainContentContainerNode.addSubnode(previewNode) } previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads) previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0) @@ -2820,7 +2916,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { credibilityIconView = ComponentHostView() strongSelf.credibilityIconView = credibilityIconView - strongSelf.contextContainer.view.addSubview(credibilityIconView) + strongSelf.mainContentContainerNode.view.addSubview(credibilityIconView) } let credibilityIconComponent = EmojiStatusComponent( @@ -2866,11 +2962,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) + transition.updateAlpha(node: strongSelf.separatorNode, alpha: item.interaction.inlineNavigationLocation != nil ? 0.0 : 1.0) transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight))) let backgroundColor: UIColor let highlightedBackgroundColor: UIColor - if item.selected { + if item.interaction.inlineNavigationLocation != nil { + backgroundColor = theme.pinnedItemBackgroundColor + highlightedBackgroundColor = theme.itemHighlightedBackgroundColor + } else if item.selected { backgroundColor = theme.itemSelectedBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor } else if isPinned { @@ -3011,7 +3111,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.playbackStartDisposable.set(nil) videoNode.isHidden = false } - videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0 + videoNode.layer.cornerRadius = self.avatarNode.bounds.size.width / 2.0 if #available(iOS 13.0, *) { videoNode.layer.cornerCurve = .circular } @@ -3019,7 +3119,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { videoNode.canAttachContent = true videoNode.play() -// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode) +// self.mainContentContainerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode) self.avatarNode.addSubnode(videoNode) self.videoNode = videoNode } @@ -3037,7 +3137,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { super.updateRevealOffset(offset: offset, transition: transition) - if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 { + transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0)) + + /*if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 { let editingOffset: CGFloat if let selectableControlNode = self.selectableControlNode { editingOffset = selectableControlNode.bounds.size.width @@ -3188,7 +3290,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let badgeBackgroundFrame = CGRect(x: badgeOffset, y: self.pinnedIconNode.frame.origin.y, width: badgeBackgroundWidth, height: pinnedIconSize.height) transition.updateFrame(node: self.pinnedIconNode, frame: badgeBackgroundFrame) } - } + }*/ } override func touchesToOtherItemsPrevented() { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index d7fce6e645..2737726f3f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -35,7 +35,7 @@ struct ChatListNodeListViewTransition { let animateCrossfade: Bool } -final class ChatListHighlightedLocation { +final class ChatListHighlightedLocation: Equatable { let location: ChatLocation let progress: CGFloat @@ -47,6 +47,16 @@ final class ChatListHighlightedLocation { func withUpdatedProgress(_ progress: CGFloat) -> ChatListHighlightedLocation { return ChatListHighlightedLocation(location: location, progress: progress) } + + static func ==(lhs: ChatListHighlightedLocation, rhs: ChatListHighlightedLocation) -> Bool { + if lhs.location != rhs.location { + return false + } + if lhs.progress != rhs.progress { + return false + } + return true + } } public final class ChatListNodeInteraction { @@ -84,6 +94,9 @@ public final class ChatListNodeInteraction { public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? + var isInlineMode: Bool = false + var inlineNavigationLocation: ChatListHighlightedLocation? + let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer @@ -871,7 +884,9 @@ public final class ChatListNode: ListView { public var selectionLimit: Int32 = 100 public var reachedSelectionLimit: ((Int32) -> Void)? - public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool) { + private var visibleTopInset: CGFloat? + + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) { self.context = context self.location = location self.chatListFilter = chatListFilter @@ -1156,6 +1171,7 @@ public final class ChatListNode: ListView { self.peerSelected?(peer, threadId, true, true, nil) }) }) + nodeInteraction.isInlineMode = isInlineMode let viewProcessingQueue = self.viewProcessingQueue @@ -1929,6 +1945,9 @@ public final class ChatListNode: ListView { guard let strongSelf = self else { return } + if !strongSelf.dequeuedInitialTransitionOnLayout { + return + } let atTop: Bool var revealHiddenItems: Bool = false switch offset { @@ -2255,11 +2274,15 @@ public final class ChatListNode: ListView { var scrollToItem = transition.scrollToItem if transition.adjustScrollToFirstItem { var offset: CGFloat = 0.0 - switch self.visibleContentOffset() { - case let .known(value) where abs(value) < .ulpOfOne: - offset = 0.0 - default: - offset = -navigationBarSearchContentHeight + if let visibleTopInset = self.visibleTopInset { + offset = visibleTopInset - self.insets.top + } else { + switch self.visibleContentOffset() { + case let .known(value) where abs(value) < .ulpOfOne: + offset = 0.0 + default: + offset = -navigationBarSearchContentHeight + } } scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) } @@ -2316,8 +2339,34 @@ public final class ChatListNode: ListView { } } - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + public func fixContentOffset(offset: CGFloat) { + let _ = self.scrollToOffsetFromTop(offset, animated: false) + + /*let scrollToItem: ListViewScrollToItem = ListViewScrollToItem(index: 0, position: .top(-offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })*/ + } + + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, visibleTopInset: CGFloat, inlineNavigationLocation: ChatListControllerLocation?) { + self.visibleTopInset = visibleTopInset + + self.visualInsets = UIEdgeInsets(top: visibleTopInset, left: 0.0, bottom: 0.0, right: 0.0) + + var highlightedLocation: ChatListHighlightedLocation? + if case let .forum(peerId) = inlineNavigationLocation { + highlightedLocation = ChatListHighlightedLocation(location: .peer(id: peerId), progress: 1.0) + } + var navigationLocationUpdated = false + if self.interaction?.inlineNavigationLocation != highlightedLocation { + self.interaction?.inlineNavigationLocation = highlightedLocation + navigationLocationUpdated = true + } + + var options: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency] + if navigationLocationUpdated { + options.insert(.ForceUpdate) + options.insert(.AnimateInsertion) + } + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index e50289ef00..77a086921f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -210,7 +210,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV } var adjustScrollToFirstItem = false - if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 { + if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 1 { adjustScrollToFirstItem = true } diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 55c20a8002..f9c170d984 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -105,7 +105,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa } else { offset = 0.0 } - let _ = listNode.scrollToOffsetFromTop(offset) + let _ = listNode.scrollToOffsetFromTop(offset, animated: true) return true } else if searchNode.expansionProgress == 1.0 { var sortItemNode: ListViewItemNode? diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 88251c7749..ed13290c23 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -86,7 +86,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case enableDebugDataDisplay(Bool) case acceleratedStickers(Bool) case experimentalBackground(Bool) - case inlineStickers(Bool) + case inlineForums(Bool) case localTranscription(Bool) case enableReactionOverrides(Bool) case playerEmbedding(Bool) @@ -111,7 +111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, . enableReactionOverrides, .restorePurchases: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -188,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 30 case .experimentalBackground: return 31 - case .inlineStickers: + case .inlineForums: return 32 case .localTranscription: return 33 @@ -1053,12 +1053,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .inlineStickers(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Inline Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in + case let .inlineForums(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Inline Forums", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.inlineStickers = value + settings.inlineForums = value return PreferencesEntry(settings) }) }).start() @@ -1219,7 +1219,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) - entries.append(.inlineStickers(experimentalSettings.inlineStickers)) + entries.append(.inlineForums(experimentalSettings.inlineForums)) entries.append(.localTranscription(experimentalSettings.localTranscription)) if case .internal = sharedContext.applicationBindings.appBuildType { entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides)) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index d768d2b57a..591ea3c2ba 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1144,6 +1144,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if bottomItemFound { bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY + } else { + bottomItemEdge = self.visibleSize.height } if topItemFound && bottomItemFound { @@ -1734,7 +1736,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { + if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets && !options.contains(.ForceUpdate))) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets @@ -1769,7 +1771,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let widthUpdated: Bool if let updateSizeAndInsets = updateSizeAndInsets { - widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne + widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne || options.contains(.ForceUpdate) state.visibleSize = updateSizeAndInsets.size state.insets = updateSizeAndInsets.insets @@ -2820,7 +2822,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture case let .bottom(additionalOffset): offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset case let .top(additionalOffset): - offset = insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset + offset = (insets.top + additionalOffset + itemNode.scrollPositioningInsets.top) - itemNode.apparentFrame.minY case let .center(overflow): let contentAreaHeight = self.visibleSize.height - insets.bottom - insets.top if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { @@ -2912,7 +2914,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { - offsetFix = updateSizeAndInsets.insets.top - self.insets.top + if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) { + offsetFix = 0.0 + } else { + offsetFix = updateSizeAndInsets.insets.top - self.insets.top + } } offsetFix += additionalScrollDistance @@ -4783,10 +4789,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - public func scrollToOffsetFromTop(_ offset: CGFloat) -> Bool { + public func scrollToOffsetFromTop(_ offset: CGFloat, animated: Bool) -> Bool { for itemNode in self.itemNodes { if itemNode.index == 0 { - self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: true) + if animated { + self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: animated) + } else { + self.scroller.contentOffset = CGPoint(x: 0.0, y: offset) + } return true } } diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index fcc91cc9a6..354018da4e 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -104,6 +104,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64) public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256) + public static let ForceUpdate = ListViewDeleteAndInsertOptions(rawValue: 512) } public struct ListViewUpdateSizeAndInsets { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index e544bb82fe..739f228028 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -206,6 +206,17 @@ final class ViewTracker { let record = (view, ValuePipe()) let index = self.combinedViews.add(record) + if view.views.keys.contains(where: { key in + switch key { + case .messageHistoryThreadIndex: + return true + default: + return false + } + }) { + self.updateTrackedForumTopicListHoles() + } + return (index, record.1.signal()) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 9c5b18ec29..539a285929 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -456,7 +456,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } - dict[-1316338916] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } + dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) } dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 83decd957e..407ec13c3d 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1020,7 +1020,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetMessagesTTL(period: Int32) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) - case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) + case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) case messageActionWebViewDataSent(text: String) case messageActionWebViewDataSentMe(text: String, data: String) @@ -1265,14 +1265,15 @@ public extension Api { serializeInt32(iconColor, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} break - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): if boxed { - buffer.appendInt32(-1316338916) + buffer.appendInt32(-1064024032) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} break case .messageActionWebViewDataSent(let text): if boxed { @@ -1356,8 +1357,8 @@ public extension Api { return ("messageActionSetMessagesTTL", [("period", String(describing: period))]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId))]) - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): - return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]) + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): + return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]) case .messageActionWebViewDataSent(let text): return ("messageActionWebViewDataSent", [("text", String(describing: text))]) case .messageActionWebViewDataSentMe(let text, let data): @@ -1783,12 +1784,17 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Bool } } + var _5: Api.Bool? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Bool + } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4) + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 1ba4add6d3..3499b5f17a 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1524,6 +1524,23 @@ public extension Api.functions.auth { }) } } +public extension Api.functions.auth { + static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(767062953) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(webAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("webAuthToken", String(describing: webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} public extension Api.functions.auth { static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2026,16 +2043,17 @@ public extension Api.functions.channels { } } public extension Api.functions.channels { - static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1820868141) + buffer.appendInt32(-186670715) serializeInt32(flags, buffer: buffer, boxed: false) channel.serialize(buffer, true) serializeInt32(topicId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} - return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -2484,6 +2502,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1471109485) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.channels { static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2537,6 +2571,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1760814315) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.channels { static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 9e1a92a6a8..b5da27157e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) - case let .messageActionTopicEdit(flags, title, iconEmojiId, closed): + case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, _): var components: [TelegramMediaActionType.ForumTopicEditComponent] = [] if let title = title { components.append(.title(title)) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 18b7ad8d16..a007a94b92 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -293,7 +293,8 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId: topicId: Int32(clamping: threadId), title: title, iconEmojiId: iconFileId ?? 0, - closed: nil + closed: nil, + hidden: nil )) |> mapError { _ -> EditForumChannelTopicError in return .generic @@ -338,7 +339,8 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t topicId: Int32(clamping: threadId), title: nil, iconEmojiId: nil, - closed: isClosed ? .boolTrue : .boolFalse + closed: isClosed ? .boolTrue : .boolFalse, + hidden: nil )) |> mapError { _ -> EditForumChannelTopicError in return .generic diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index cdd96329e7..e38ac68f4d 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -96,7 +96,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode { placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle - let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) if let limit = limit { chatListNode.selectionLimit = limit chatListNode.reachedSelectionLimit = reachedSelectionLimit @@ -279,7 +279,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { combinedInsets.right += layout.safeInsets.right let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: combinedInsets, headerInsets: headerInsets, duration: duration, curve: curve) - chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) } self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size) diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift index e2def93ae8..e7e6f19ff1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -144,7 +144,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } func scrollToTop() -> Bool { - if !self.listNode.scrollToOffsetFromTop(0.0) { + if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift index 36d0fe8fb9..e3e3769a6c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift @@ -224,7 +224,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { } func scrollToTop() -> Bool { - if !self.listNode.scrollToOffsetFromTop(0.0) { + if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index d83fb93dcf..958645739a 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -137,7 +137,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { chatListLocation = .chatList(groupId: .root) } - self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) super.init() @@ -576,7 +576,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve) - self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) if let contactListNode = self.contactListNode { contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 5db9e36288..5bda3a0f4a 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -45,6 +45,7 @@ public struct ExperimentalUISettings: Codable, Equatable { public var inlineStickers: Bool public var localTranscription: Bool public var enableReactionOverrides: Bool + public var inlineForums: Bool public var accountReactionEffectOverrides: [AccountReactionOverrides] public var accountStickerEffectOverrides: [AccountReactionOverrides] @@ -69,6 +70,7 @@ public struct ExperimentalUISettings: Codable, Equatable { inlineStickers: false, localTranscription: false, enableReactionOverrides: false, + inlineForums: false, accountReactionEffectOverrides: [], accountStickerEffectOverrides: [] ) @@ -94,6 +96,7 @@ public struct ExperimentalUISettings: Codable, Equatable { inlineStickers: Bool, localTranscription: Bool, enableReactionOverrides: Bool, + inlineForums: Bool, accountReactionEffectOverrides: [AccountReactionOverrides], accountStickerEffectOverrides: [AccountReactionOverrides] ) { @@ -116,6 +119,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.inlineStickers = inlineStickers self.localTranscription = localTranscription self.enableReactionOverrides = enableReactionOverrides + self.inlineForums = inlineForums self.accountReactionEffectOverrides = accountReactionEffectOverrides self.accountStickerEffectOverrides = accountStickerEffectOverrides } @@ -142,6 +146,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0 self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0 self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false + self.inlineForums = try container.decodeIfPresent(Bool.self, forKey: "inlineForums") ?? false self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? [] self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? [] } @@ -168,6 +173,7 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers") try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription") try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides") + try container.encode(self.inlineForums, forKey: "inlineForums") try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides") try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides") }