From bfb4c7bbad30beea2720a04feffec67ba89d69de Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 29 May 2023 23:09:37 +0400 Subject: [PATCH] [Temp] --- .../Sources/ChatListController.swift | 486 +++++++++--------- .../Sources/ChatListControllerNode.swift | 170 +++++- .../Sources/Node/ChatListNode.swift | 1 - .../SearchBarNode/Sources/SearchBarNode.swift | 4 +- .../NavigationBarSearchContentNode.swift | 4 +- .../Components/ChatListHeaderComponent/BUILD | 2 + .../Sources/ChatListNavigationBar.swift | 345 +++++++++++++ 7 files changed, 748 insertions(+), 264 deletions(-) create mode 100644 submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 74c87f5fc5..e6bd6b1ddb 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -230,8 +230,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return super.displayNode as! ChatListControllerNode } - private let headerContentView = ComponentView() - fileprivate private(set) var primaryContext: ChatListLocationContext? private let primaryInfoReady = Promise() private let mainReady = Promise() @@ -275,8 +273,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private let isReorderingTabsValue = ValuePromise(false) - private var searchContentNode: NavigationBarSearchContentNode? - private let navigationSecondaryContentNode: ASDisplayNode private let tabContainerNode: ChatListFilterTabContainerNode private var tabContainerData: ([ChatListFilterTabEntry], Bool, Int32?)? @@ -302,7 +298,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private var powerSavingMonitoringDisposable: Disposable? - private var storySubscriptions: EngineStorySubscriptions? + private(set) var storySubscriptions: EngineStorySubscriptions? private var storySubscriptionsDisposable: Disposable? private var preloadStorySubscriptionsDisposable: Disposable? @@ -346,7 +342,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.storyListHeight = 0.0 - super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) + super.init(context: context, navigationBarPresentationData: nil, mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) self.tabBarItemContextActionType = .always self.automaticallyControlPresentationContextLayout = false @@ -439,9 +435,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.scrollToTop = { [weak self] in if let strongSelf = self { - if let searchContentNode = strongSelf.searchContentNode { + /*if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) - } + }*/ + //TODO:scroll to top strongSelf.chatListDisplayNode.scrollToTop() } } @@ -454,9 +451,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { switch strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.visibleContentOffset() { case .none, .unknown: - if let searchContentNode = strongSelf.searchContentNode { + //TODO:scroll to top + /*if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) - } + }*/ strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.scrollToPosition(.top) case let .known(offset): let isFirstFilter = strongSelf.chatListDisplayNode.effectiveContainerNode.currentItemNode.chatListFilter == strongSelf.chatListDisplayNode.mainContainerNode.availableFilters.first?.filter @@ -474,9 +472,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.selectTab(id: targetTab) } else { - if let searchContentNode = strongSelf.searchContentNode { + //TODO:scroll to top + /*if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) - } + }*/ if let inlineStackContainerNode = strongSelf.chatListDisplayNode.inlineStackContainerNode { inlineStackContainerNode.currentItemNode.scrollToPosition(.top) } else { @@ -513,25 +512,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) if !previewing { - let placeholder: String - let compactPlaceholder: String - - var isForum = false - if case .forum = location { - isForum = true - placeholder = self.presentationData.strings.Common_Search - compactPlaceholder = self.presentationData.strings.Common_Search - } else { - placeholder = self.presentationData.strings.DialogList_SearchLabel - compactPlaceholder = self.presentationData.strings.DialogList_SearchLabelCompact - } - + /* self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder, activate: { [weak self] in self?.chatListDisplayNode.mainContainerNode.currentItemNode.cancelTracking() self?.activateSearch(filter: isForum ? .topics : .chats) }) self.searchContentNode?.updateExpansionProgress(0.0) - self.navigationBar?.setContentNode(self.searchContentNode, animated: false) + self.navigationBar?.setContentNode(self.searchContentNode, animated: false)*/ let tabsIsEmpty: Bool if let (resolvedItems, displayTabsAtBottom, _) = self.tabContainerData { @@ -794,7 +781,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ))) ])) - strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: AnyComponent(Button( + let _ = contentComponent + //TODO:download indicator + /*strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: AnyComponent(Button( content: contentComponent, action: { guard let strongSelf = self else { @@ -802,9 +791,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.activateSearch(filter: .downloads, query: nil) } - ))) + )))*/ } else { - strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: nil) + //strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: nil) } }) } @@ -889,14 +878,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } func findTitleView() -> ChatListTitleView? { - guard let componentView = self.headerContentView.view as? ChatListHeaderComponent.View else { + guard let componentView = self.chatListHeaderView() else { return nil } return componentView.findTitleView() } private var previousEmojiSetupTimestamp: Double? - private func openStatusSetup(sourceView: UIView) { + func openStatusSetup(sourceView: UIView) { let currentTimestamp = CACurrentMediaTime() if let previousTimestamp = self.previousEmojiSetupTimestamp, currentTimestamp < previousTimestamp + 1.0 { return @@ -962,40 +951,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.navigationItem.backBarButtonItem = backBarButtonItem } - let placeholder: String - let compactPlaceholder: String - if case .forum = location { - placeholder = self.presentationData.strings.Common_Search - compactPlaceholder = self.presentationData.strings.Common_Search - } else { - placeholder = self.presentationData.strings.DialogList_SearchLabel - compactPlaceholder = self.presentationData.strings.DialogList_SearchLabelCompact - } - self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder) - - /*let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing - if case .chatList(.root) = self.location { - self.primaryContext?.leftButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), - pressed: { [weak self] in - self?.editPressed() - } - ))) - self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "compose", component: AnyComponent(NavigationButtonComponent( - content: .icon(imageName: "Chat List/Compose Icon"), - pressed: { [weak self] in - self?.composePressed() - } - ))) - } else { - self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), - pressed: { [weak self] in - self?.editPressed() - } - ))) - }*/ - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) @@ -1007,7 +962,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.updatePresentationData(self.presentationData) } - self.requestUpdateHeaderContent(transition: .immediate) + self.requestLayout(transition: .immediate) } override public func loadDisplayNode() { @@ -1299,23 +1254,38 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationController.filterController(strongSelf, animated: true) } - self.chatListDisplayNode.contentOffsetChanged = { [weak self] offset in - if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { + /*self.chatListDisplayNode.contentOffsetChanged = { [weak self] offset in + if let strongSelf = self, let validLayout = strongSelf.validLayout { var offset = offset if validLayout.inVoiceOver { offset = .known(0.0) } - //print("offset: \(offset), additionalHeight: \(searchContentNode.additionalHeight)") - searchContentNode.updateListVisibleContentOffset(offset, transition: strongSelf.chatListDisplayNode.temporaryContentOffsetChangeTransition ?? .immediate) + + let offsetValue: CGFloat + switch offset { + case .none: + offsetValue = 0.0 + case let .known(value): + offsetValue = value + case .unknown: + offsetValue = 10000.0 + } + + if let navigationBarView = strongSelf.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + navigationBarView.applyScroll(offset: offsetValue, transition: .immediate) + } } - } + }*/ self.chatListDisplayNode.contentScrollingEnded = { [weak self] listView in - if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { + let _ = self + /*if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) } else { return false - } + }*/ + //TODO:fix scrolling + return false } self.chatListDisplayNode.emptyListAction = { [weak self] _ in @@ -1851,7 +1821,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.isReorderingFilters = true strongSelf.isReorderingTabsValue.set(true) - strongSelf.searchContentNode?.setIsEnabled(false, animated: true) + + //TODO:update search enabled + //strongSelf.searchContentNode?.setIsEnabled(false, animated: true) + (strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut)) if let layout = strongSelf.validLayout { strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) @@ -1934,6 +1907,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.storySubscriptions = storySubscriptions let isEmpty = storySubscriptions.items.isEmpty + self.storyListHeight = isEmpty ? 0.0 : 94.0 + + let transition: ContainedViewLayoutTransition + if self.didAppear { + transition = .animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + + let _ = wasEmpty + let _ = isEmpty + + self.chatListDisplayNode.temporaryContentOffsetChangeTransition = transition + self.requestLayout(transition: transition) + self.chatListDisplayNode.temporaryContentOffsetChangeTransition = nil + self.chatListDisplayNode.mainContainerNode.currentItemNode.updateState { chatListState in var chatListState = chatListState @@ -1951,23 +1940,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return chatListState } - self.storyListHeight = isEmpty ? 0.0 : 94.0 - - let transition: ContainedViewLayoutTransition - if self.didAppear { - transition = .animated(duration: 0.4, curve: .spring) - } else { - transition = .immediate - } - - if wasEmpty != isEmpty { - self.chatListDisplayNode.temporaryContentOffsetChangeTransition = transition - self.requestLayout(transition: transition) - self.chatListDisplayNode.temporaryContentOffsetChangeTransition = nil - } else { - self.requestUpdateHeaderContent(transition: transition) - } - self.storiesReady.set(.single(true)) }) } @@ -2251,7 +2223,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } if hasEmptyMark { - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let componentView = self.chatListHeaderView() { if let rightButtonView = componentView.rightButtonView { let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view) let text: String = self.presentationData.strings.ChatList_EmptyListTooltip @@ -2402,13 +2374,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - func requestUpdateHeaderContent(transition: ContainedViewLayoutTransition) { - if let validLayout = self.validLayout { - self.updateHeaderContent(layout: validLayout, transition: transition) - } - } - - private func updateHeaderContent(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + func updateHeaderContent(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (primaryContent: ChatListHeaderComponent.Content?, secondaryContent: ChatListHeaderComponent.Content?) { var primaryContent: ChatListHeaderComponent.Content? if let primaryContext = self.primaryContext { var backTitle: String? @@ -2459,8 +2425,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ) } - var storiesFraction: CGFloat = 0.0 - if let searchContentNode = self.searchContentNode, case .chatList(.root) = self.location { + return (primaryContent, secondaryContent) + + /*let storiesFraction: CGFloat = 0.0 + //TODO:move to navigation bar + /*if let searchContentNode = self.searchContentNode, case .chatList(.root) = self.location { if self.storyListHeight > 0.0 { let fraction = navigationBarSearchContentHeight / searchContentNode.nominalHeight @@ -2471,7 +2440,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let visibleProgress: CGFloat = toLow + (searchContentNode.expansionProgress - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) storiesFraction = max(0.0, min(1.0, visibleProgress)) } - } + }*/ let _ = self.headerContentView.update( transition: Transition(transition), @@ -2506,107 +2475,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if self.navigationBar?.customHeaderContentView !== componentView { self.navigationBar?.customHeaderContentView = componentView } - } - if case .chatList(.root) = self.location { - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - componentView.storyPeerAction = { [weak self] peer in - guard let self else { - return - } - - let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peer?.id) - let _ = (storyContent.state - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] storyContentState in - guard let self else { - return - } - - if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil { - var cameraTransitionIn: StoryCameraTransitionIn? - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { - cameraTransitionIn = StoryCameraTransitionIn( - sourceView: transitionView, - sourceRect: transitionView.bounds, - sourceCornerRadius: transitionView.bounds.height * 0.5 - ) - } - } - - if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { - rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in - guard let self else { - return nil - } - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { - return StoryCameraTransitionOut( - destinationView: transitionView, - destinationRect: transitionView.bounds, - destinationCornerRadius: transitionView.bounds.height * 0.5 - ) - } - } - return nil - }) - } - - return - } - - var transitionIn: StoryContainerScreen.TransitionIn? - if let peer, let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) { - transitionIn = StoryContainerScreen.TransitionIn( - sourceView: transitionView, - sourceRect: transitionView.bounds, - sourceCornerRadius: transitionView.bounds.height * 0.5 - ) - } - } - - let storyContainerScreen = StoryContainerScreen( - context: self.context, - content: storyContent, - transitionIn: transitionIn, - transitionOut: { [weak self] peerId, _ in - guard let self else { - return nil - } - - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { - if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) { - return StoryContainerScreen.TransitionOut( - destinationView: transitionView, - destinationRect: transitionView.bounds, - destinationCornerRadius: transitionView.bounds.height * 0.5, - destinationIsAvatar: true, - completed: {} - ) - } - } - - return nil - } - ) - self.push(storyContainerScreen) - }) - } - } - } + }*/ } override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.updateHeaderContent(layout: layout, transition: transition) - super.updateNavigationBarLayout(layout, transition: transition) } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - if case .chatList(.root) = self.location, !self.isSearchActive { - self.searchContentNode?.additionalHeight = (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * self.storyListHeight + private func chatListHeaderView() -> ChatListHeaderComponent.View? { + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + if let componentView = navigationBarView.headerContent.view as? ChatListHeaderComponent.View { + return componentView + } } + return nil + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + //TODO:move to chat list node + /*if case .chatList(.root) = self.location, !self.isSearchActive { + self.searchContentNode?.additionalHeight = (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * self.storyListHeight + }*/ super.containerLayoutUpdated(layout, transition: transition) @@ -2616,14 +2505,100 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.updateLayout(layout: layout, transition: transition) - if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver { - searchContentNode.updateListVisibleContentOffset(.known(0.0)) + if layout.inVoiceOver != wasInVoiceOver { self.chatListDisplayNode.scrollToTop() } + + if case .chatList(.root) = self.location, let componentView = self.chatListHeaderView() { + componentView.storyPeerAction = { [weak self] peer in + guard let self else { + return + } + + let storyContent = StoryContentContextImpl(context: self.context, focusedPeerId: peer?.id) + let _ = (storyContent.state + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] storyContentState in + guard let self else { + return + } + + if let peer, peer.id == self.context.account.peerId, storyContentState.slice == nil { + var cameraTransitionIn: StoryCameraTransitionIn? + if let componentView = self.chatListHeaderView() { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { + cameraTransitionIn = StoryCameraTransitionIn( + sourceView: transitionView, + sourceRect: transitionView.bounds, + sourceCornerRadius: transitionView.bounds.height * 0.5 + ) + } + } + + if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface { + rootController.openStoryCamera(transitionIn: cameraTransitionIn, transitionOut: { [weak self] _ in + guard let self else { + return nil + } + if let componentView = self.chatListHeaderView() { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { + return StoryCameraTransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: transitionView.bounds.height * 0.5 + ) + } + } + return nil + }) + } + + return + } + + var transitionIn: StoryContainerScreen.TransitionIn? + if let peer, let componentView = self.chatListHeaderView() { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) { + transitionIn = StoryContainerScreen.TransitionIn( + sourceView: transitionView, + sourceRect: transitionView.bounds, + sourceCornerRadius: transitionView.bounds.height * 0.5 + ) + } + } + + let storyContainerScreen = StoryContainerScreen( + context: self.context, + content: storyContent, + transitionIn: transitionIn, + transitionOut: { [weak self] peerId, _ in + guard let self else { + return nil + } + + if let componentView = self.chatListHeaderView() { + if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) { + return StoryContainerScreen.TransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: transitionView.bounds.height * 0.5, + destinationIsAvatar: true, + completed: {} + ) + } + } + + return nil + } + ) + self.push(storyContainerScreen) + }) + } + } } public func transitionViewForOwnStoryItem() -> UIView? { - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let componentView = self.chatListHeaderView() { if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { return transitionView } @@ -2632,7 +2607,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public func animateStoryUploadRipple() { - if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View { + if let componentView = self.chatListHeaderView() { if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { let localRect = transitionView.convert(transitionView.bounds, to: self.view) self.animateRipple(centerLocation: localRect.center) @@ -2669,7 +2644,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController tabContainerOffset += 44.0 + 20.0 } - let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0 + let navigationBarHeight: CGFloat = 100.0//self.navigationBar?.frame.maxY ?? 0.0 let secondaryContentHeight = self.navigationBar?.secondaryContentHeight ?? 0.0 transition.updateFrame(node: self.navigationSecondaryContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - secondaryContentHeight + tabContainerOffset), size: CGSize(width: layout.size.width, height: secondaryContentHeight))) @@ -2680,7 +2655,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.mainContainerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.effectiveContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } - self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, storiesInset: self.storyListHeight, transition: transition) + self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: navigationBarHeight, storiesInset: self.storyListHeight, transition: transition) } override public func navigationStackConfigurationUpdated(next: [ViewController]) { @@ -2708,9 +2683,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - self.requestUpdateHeaderContent(transition: .animated(duration: 0.3, curve: .spring)) - - self.searchContentNode?.setIsEnabled(false, animated: true) + //TODO:update search enabled + //self.searchContentNode?.setIsEnabled(false, animated: true) self.chatListDisplayNode.didBeginSelectingChatsWhileEditing = false self.chatListDisplayNode.effectiveContainerNode.updateState { state in @@ -2729,7 +2703,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let skipLayoutUpdate = self.reorderingDonePressed() (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring)) - self.searchContentNode?.setIsEnabled(true, animated: true) + + //TODO:update search enabled + //self.searchContentNode?.setIsEnabled(true, animated: true) + self.chatListDisplayNode.didBeginSelectingChatsWhileEditing = false self.chatListDisplayNode.effectiveContainerNode.updateState { state in var state = state @@ -2776,7 +2753,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.isReorderingFilters = false strongSelf.isReorderingTabsValue.set(false) (strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(true, transition: .animated(duration: 0.2, curve: .easeInOut)) - strongSelf.searchContentNode?.setIsEnabled(true, animated: true) + + //TODO:update search enabled + //strongSelf.searchContentNode?.setIsEnabled(true, animated: true) + if let layout = strongSelf.validLayout { strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) } @@ -3446,24 +3426,43 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public private(set) var isSearchActive: Bool = false - public func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil) { - self.activateSearch(filter: filter, query: query, skipScrolling: false) + + public func activateSearch(filter: ChatListSearchFilter, query: String? = nil) { + var searchContentNode: NavigationBarSearchContentNode? + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + searchContentNode = navigationBarView.searchContentNode + } + + if let searchContentNode { + self.activateSearch(filter: filter, query: query, skipScrolling: false, searchContentNode: searchContentNode) + } + } + + public func activateSearch(query: String? = nil) { + var isForum = false + if case .forum = self.location { + isForum = true + } + + let filter: ChatListSearchFilter = isForum ? .topics : .chats + self.activateSearch(filter: filter, query: query) } - private func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false) { + func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false, searchContentNode: NavigationBarSearchContentNode) { var filter = filter if case .forum = self.chatListDisplayNode.effectiveContainerNode.location { filter = .topics } - if self.displayNavigationBar { - if !skipScrolling, let searchContentNode = self.searchContentNode, searchContentNode.expansionProgress != 1.0 { + if self.chatListDisplayNode.searchDisplayController == nil { + /*if !skipScrolling, let searchContentNode = self.searchContentNode, searchContentNode.expansionProgress != 1.0 { self.scrollToTop?() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { [weak self] in self?.activateSearch(filter: filter, query: query, skipScrolling: true) }) return - } + }*/ + //TODO:scroll to top? let _ = (combineLatest(self.chatListDisplayNode.mainContainerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] _, chatListView in @@ -3492,28 +3491,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -strongSelf.storyListHeight), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } - - if let searchContentNode = strongSelf.searchContentNode { - if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: strongSelf.hasDownloads, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) { - let (filterContainerNode, activate) = filterContainerNodeAndActivate - if displaySearchFilters { - strongSelf.navigationBar?.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight - strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) - } - strongSelf.searchContentNode?.additionalHeight = 0.0 - - activate(filter != .downloads) - - if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { - searchContentNode.search(filter: filter, query: query) - } - - let tabsOffset = 30.0 + strongSelf.storyListHeight - - Queue.mainQueue().justDispatch { - filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: tabsOffset), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } + + if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, hasDownloads: strongSelf.hasDownloads, initialFilter: filter, navigationController: strongSelf.navigationController as? NavigationController) { + let (filterContainerNode, activate) = filterContainerNodeAndActivate + if displaySearchFilters { + //TODO:search filters + strongSelf.navigationBar?.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight + strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false) + } + + activate(filter != .downloads) + + if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { + searchContentNode.search(filter: filter, query: query) + } + + let tabsOffset = 30.0 + strongSelf.storyListHeight + + Queue.mainQueue().justDispatch { + filterContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: tabsOffset), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } } @@ -3552,6 +3549,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var filterContainerNode: ASDisplayNode? + + var searchContentNode: NavigationBarSearchContentNode? + if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View { + searchContentNode = navigationBarView.searchContentNode + } + if animated, let searchContentNode = self.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode { filterContainerNode = searchContentNode.filterContainerNode @@ -3573,7 +3576,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - if let searchContentNode = self.searchContentNode { + if let searchContentNode { let previousFrame = searchContentNode.placeholderNode.frame if case .chatList(.root) = self.location { searchContentNode.placeholderNode.frame = previousFrame.offsetBy(dx: 0.0, dy: 94.0) @@ -3582,10 +3585,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController searchContentNode.placeholderNode.frame = previousFrame } + self.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) + self.navigationBar?.secondaryContentHeight = (!tabsIsEmpty ? NavigationBar.defaultSecondaryContentHeight : 0.0) - if case .chatList(.root) = self.location { + //TODO:move layout to navigation bar + /*if case .chatList(.root) = self.location { self.searchContentNode?.additionalHeight = self.storyListHeight - } + }*/ self.navigationBar?.setSecondaryContentNode(self.navigationSecondaryContentNode, animated: false) let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate @@ -5539,7 +5545,7 @@ private final class ChatListLocationContext { self.ready.set(.single(true)) } - self.parentController?.requestUpdateHeaderContent(transition: .immediate) + self.parentController?.requestLayout(transition: .immediate) } private func updateForum( @@ -5631,7 +5637,7 @@ private final class ChatListLocationContext { navigationController.replaceController(parentController, with: chatController, animated: true) } } else { - self.parentController?.requestUpdateHeaderContent(transition: .immediate) + self.parentController?.requestLayout(transition: .immediate) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 718f35e01c..9a122c6a33 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -18,6 +18,7 @@ import ActionPanelComponent import ComponentDisplayAdapters import ComponentFlow import ChatFolderLinkPreviewScreen +import ChatListHeaderComponent public enum ChatListContainerNodeFilter: Equatable { case all @@ -876,7 +877,11 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self?.updatePeerGrouping?(peerId, group) } itemNode.listNode.contentOffsetChanged = { [weak self] offset in - self?.contentOffsetChanged?(offset) + guard let self else { + return + } + self.contentOffset = offset + self.contentOffsetChanged?(offset) } itemNode.listNode.contentScrollingEnded = { [weak self] listView in return self?.contentScrollingEnded?(listView) ?? false @@ -947,6 +952,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele public var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? + var contentOffset: ListViewVisibleContentOffset? public var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? public var contentScrollingEnded: ((ListView) -> Bool)? var activateChatPreview: ((ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? @@ -1490,12 +1496,15 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { private var tapRecognizer: UITapGestureRecognizer? var navigationBar: NavigationBar? + let navigationBarView = ComponentView() weak var controller: ChatListControllerImpl? var toolbar: Toolbar? private var toolbarNode: ToolbarNode? var toolbarActionSelected: ((ToolbarActionOption) -> Void)? + private var isSearchDisplayControllerActive: Bool = false + private var skipSearchDisplayControllerLayout: Bool = false private(set) var searchDisplayController: SearchDisplayController? var isReorderingFilters: Bool = false @@ -1504,7 +1513,6 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { private var containerLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, storiesInset: CGFloat)? - var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? var contentScrollingEnded: ((ListView) -> Bool)? var requestDeactivateSearch: (() -> Void)? @@ -1695,7 +1703,105 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } + private func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { + let headerContent = self.controller?.updateHeaderContent(layout: layout, transition: transition) + + let navigationBarSize = self.navigationBarView.update( + transition: Transition(transition), + component: AnyComponent(ChatListNavigationBar( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + statusBarHeight: layout.statusBarHeight ?? 0.0, + sideInset: layout.safeInsets.left, + isSearchActive: self.isSearchDisplayControllerActive, + primaryContent: headerContent?.primaryContent, + secondaryContent: headerContent?.secondaryContent, + secondaryTransition: self.inlineStackContainerTransitionFraction, + storySubscriptions: self.controller?.storySubscriptions, + activateSearch: { [weak self] searchContentNode in + guard let self, let controller = self.controller else { + return + } + + var isForum = false + if case .forum = controller.location { + isForum = true + } + + let filter: ChatListSearchFilter = isForum ? .topics : .chats + + controller.activateSearch( + filter: filter, + query: nil, + skipScrolling: false, + searchContentNode: searchContentNode + ) + }, + openStatusSetup: { [weak self] sourceView in + guard let self, let controller = self.controller else { + return + } + controller.openStatusSetup(sourceView: sourceView) + } + )), + environment: {}, + containerSize: layout.size + ) + if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { + navigationBarComponentView.deferScrollApplication = true + + if navigationBarComponentView.superview == nil { + self.view.addSubview(navigationBarComponentView) + } + transition.updateFrame(view: navigationBarComponentView, frame: CGRect(origin: CGPoint(), size: navigationBarSize)) + + return (navigationBarSize.height, navigationBarComponentView.effectiveStoriesInsetHeight) + } else { + return (0.0, 0.0) + } + } + + private func updateNavigationScrolling(transition: ContainedViewLayoutTransition) { + let mainOffset: CGFloat + if let contentOffset = self.mainContainerNode.contentOffset, case let .known(value) = contentOffset { + mainOffset = value + } else { + mainOffset = 1000.0 + } + + let resultingOffset: CGFloat + if let inlineStackContainerNode = self.inlineStackContainerNode { + let inlineOffset: CGFloat + if let contentOffset = inlineStackContainerNode.contentOffset, case let .known(value) = contentOffset { + inlineOffset = value + } else { + inlineOffset = 1000.0 + } + + resultingOffset = mainOffset * (1.0 - self.inlineStackContainerTransitionFraction) + inlineOffset * self.inlineStackContainerTransitionFraction + } else { + resultingOffset = mainOffset + } + + if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { + navigationBarComponentView.applyScroll(offset: resultingOffset, transition: Transition(transition)) + } + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, storiesInset: CGFloat, transition: ContainedViewLayoutTransition) { + var navigationBarHeight = navigationBarHeight + var visualNavigationHeight = visualNavigationHeight + var cleanNavigationBarHeight = cleanNavigationBarHeight + var storiesInset = storiesInset + + let navigationBarLayout = self.updateNavigationBar(layout: layout, transition: transition) + + navigationBarHeight = navigationBarLayout.navigationHeight + visualNavigationHeight = navigationBarLayout.navigationHeight + cleanNavigationBarHeight = navigationBarLayout.navigationHeight + storiesInset = navigationBarLayout.storiesInset + self.containerLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, storiesInset) var insets = layout.insets(options: [.input]) @@ -1789,7 +1895,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var inlineInsets = insets inlineInsets.left = 0.0 - inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0, storiesInset: storiesInset, transition: inlineStackContainerNodeTransition) + let inlineNavigationHeight: CGFloat = navigationBarLayout.navigationHeight - navigationBarLayout.storiesInset + + inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: inlineNavigationHeight, visualNavigationHeight: inlineNavigationHeight, originalNavigationHeight: inlineNavigationHeight, cleanNavigationBarHeight: inlineNavigationHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0, storiesInset: storiesInset, transition: inlineStackContainerNodeTransition) if animateIn { transition.animatePosition(node: inlineStackContainerNode, from: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y)) @@ -1799,12 +1907,21 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.tapRecognizer?.isEnabled = self.isReorderingFilters if let searchDisplayController = self.searchDisplayController { - searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition) + if !self.skipSearchDisplayControllerLayout { + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition) + } + } + + self.updateNavigationScrolling(transition: transition) + + if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { + navigationBarComponentView.deferScrollApplication = false + navigationBarComponentView.applyCurrentScroll(transition: Transition(transition)) } } func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, (Bool) -> Void)? { - guard let (containerLayout, _, _, cleanNavigationBarHeight, _) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else { + guard let (containerLayout, _, _, cleanNavigationBarHeight, _) = self.containerLayout, self.searchDisplayController == nil else { return nil } @@ -1834,7 +1951,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self?.controller?.presentInGlobalOverlay(c, with: a) }, navigationController: navigationController) - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, contentNode: contentNode, cancel: { [weak self] in if let requestDeactivateSearch = self?.requestDeactivateSearch { requestDeactivateSearch() } @@ -1846,29 +1963,42 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { guard let strongSelf = self else { return } + + strongSelf.isSearchDisplayControllerActive = true + strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate) - strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in - if let strongSelf = self, let strongPlaceholderNode = placeholderNode { - if isSearchBar { - strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode) - } else { - strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) + strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in + guard let self else { + return + } + + if isSearchBar { + if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { + navigationBarComponentView.addSubnode(subnode) } + } else { + self.insertSubnode(subnode, aboveSubnode: self.debugListView) } }, placeholder: placeholderNode, focus: focus) + + strongSelf.controller?.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) }) } func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? { if let searchDisplayController = self.searchDisplayController { - searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) + self.isSearchDisplayControllerActive = false self.searchDisplayController = nil self.mainContainerNode.accessibilityElementsHidden = false self.inlineStackContainerNode?.accessibilityElementsHidden = false return { [weak self] in if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight, _) = strongSelf.containerLayout { + searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + + strongSelf.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } } } else { @@ -1884,15 +2014,17 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { 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 } + let _ = containerLayout + if let inlineStackContainerNode = self.inlineStackContainerNode { + let _ = inlineStackContainerNode + } - if !isPrimary { + self.updateNavigationScrolling(transition: .immediate) + + /*if !isPrimary { self.contentOffsetChanged?(offset) if "".isEmpty { return @@ -1933,7 +2065,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { if !isPrimary { self.contentOffsetChanged?(offset) } - } + }*/ } private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 1037b822a1..2d1e050460 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -3258,7 +3258,6 @@ public final class ChatListNode: ListView { if let previousStoriesInset = self.previousStoriesInset { additionalScrollDistance += previousStoriesInset - storiesInset - additionalScrollDistance = 0.0 } self.previousStoriesInset = storiesInset //print("storiesInset: \(storiesInset), additionalScrollDistance: \(additionalScrollDistance)") diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index dedc477309..dc1c0b17bc 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -1012,7 +1012,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } public func animateIn(from node: SearchBarPlaceholderNode, duration: Double, timingFunction: String) { - let initialTextBackgroundFrame = node.convert(node.backgroundNode.frame, to: self) + let initialTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view) let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, initialTextBackgroundFrame.maxY + 8.0))) if let fromBackgroundColor = node.backgroundColor, let toBackgroundColor = self.backgroundNode.backgroundColor { @@ -1060,7 +1060,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { } public func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - let targetTextBackgroundFrame = node.convert(node.backgroundNode.frame, to: self) + let targetTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view) let duration: Double = transition.isAnimated ? 0.5 : 0.0 let timingFunction = kCAMediaTimingFunctionSpring diff --git a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift index f3f2ef7685..4997880732 100644 --- a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift +++ b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift @@ -18,7 +18,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { public var placeholderHeight: CGFloat? private var disabledOverlay: ASDisplayNode? - public private(set) var expansionProgress: CGFloat = 1.0 + public var expansionProgress: CGFloat = 1.0 public var additionalHeight: CGFloat = 0.0 @@ -103,7 +103,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { private func updatePlaceholder(_ progress: CGFloat, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { let padding: CGFloat = 10.0 - let baseWidth = self.bounds.width - padding * 2.0 - leftInset - rightInset + let baseWidth = size.width - padding * 2.0 - leftInset - rightInset let fieldHeight: CGFloat = 36.0 let fraction = fieldHeight / self.nominalHeight diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD index e3178438da..63d1a68cbf 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD @@ -21,6 +21,8 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/AnimationUI", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/SearchUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift new file mode 100644 index 0000000000..ff7e502a14 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -0,0 +1,345 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import TelegramPresentationData +import ComponentDisplayAdapters +import SearchUI +import AccountContext +import TelegramCore + +public final class ChatListNavigationBar: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let statusBarHeight: CGFloat + public let sideInset: CGFloat + public let isSearchActive: Bool + public let primaryContent: ChatListHeaderComponent.Content? + public let secondaryContent: ChatListHeaderComponent.Content? + public let secondaryTransition: CGFloat + public let storySubscriptions: EngineStorySubscriptions? + public let activateSearch: (NavigationBarSearchContentNode) -> Void + public let openStatusSetup: (UIView) -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + statusBarHeight: CGFloat, + sideInset: CGFloat, + isSearchActive: Bool, + primaryContent: ChatListHeaderComponent.Content?, + secondaryContent: ChatListHeaderComponent.Content?, + secondaryTransition: CGFloat, + storySubscriptions: EngineStorySubscriptions?, + activateSearch: @escaping (NavigationBarSearchContentNode) -> Void, + openStatusSetup: @escaping (UIView) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.statusBarHeight = statusBarHeight + self.sideInset = sideInset + self.isSearchActive = isSearchActive + self.primaryContent = primaryContent + self.secondaryContent = secondaryContent + self.secondaryTransition = secondaryTransition + self.storySubscriptions = storySubscriptions + self.activateSearch = activateSearch + self.openStatusSetup = openStatusSetup + } + + public static func ==(lhs: ChatListNavigationBar, rhs: ChatListNavigationBar) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.statusBarHeight != rhs.statusBarHeight { + return false + } + if lhs.sideInset != rhs.sideInset { + return false + } + if lhs.isSearchActive != rhs.isSearchActive { + return false + } + if lhs.primaryContent != rhs.primaryContent { + return false + } + if lhs.secondaryContent != rhs.secondaryContent { + return false + } + if lhs.secondaryTransition != rhs.secondaryTransition { + return false + } + if lhs.storySubscriptions != rhs.storySubscriptions { + return false + } + return true + } + + private struct CurrentLayout { + var size: CGSize + + init(size: CGSize) { + self.size = size + } + } + + public final class View: UIView { + private let backgroundView: BlurredBackgroundView + private let separatorLayer: SimpleLayer + + public let headerContent = ComponentView() + + public private(set) var searchContentNode: NavigationBarSearchContentNode? + + private var component: ChatListNavigationBar? + private weak var state: EmptyComponentState? + + private var scrollTheme: PresentationTheme? + private var scrollStrings: PresentationStrings? + + private var currentLayout: CurrentLayout? + private var rawScrollOffset: CGFloat? + private var clippedScrollOffset: CGFloat? + + public var deferScrollApplication: Bool = false + private var hasDeferredScrollOffset: Bool = false + + public private(set) var effectiveStoriesInsetHeight: CGFloat = 0.0 + + override public init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + self.separatorLayer = SimpleLayer() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.layer.addSublayer(self.separatorLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.backgroundView.frame.contains(point) { + return nil + } + + guard let result = super.hitTest(point, with: event) else { + return nil + } + return result + } + + public func applyCurrentScroll(transition: Transition) { + if let rawScrollOffset = self.rawScrollOffset, self.hasDeferredScrollOffset { + self.applyScroll(offset: rawScrollOffset, transition: transition) + } + } + + public func applyScroll(offset: CGFloat, transition: Transition) { + self.rawScrollOffset = offset + + if self.deferScrollApplication { + self.hasDeferredScrollOffset = true + return + } + + guard let component = self.component, let currentLayout = self.currentLayout else { + return + } + + let themeUpdated = component.theme !== self.scrollTheme || component.strings !== self.scrollStrings + + self.scrollTheme = component.theme + self.scrollStrings = component.strings + + let searchOffsetDistance: CGFloat = navigationBarSearchContentHeight + let defaultStoriesOffsetDistance: CGFloat = 94.0 + let effectiveStoriesOffsetDistance: CGFloat + + var minContentOffset: CGFloat = navigationBarSearchContentHeight + if !component.isSearchActive, let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty { + effectiveStoriesOffsetDistance = defaultStoriesOffsetDistance * (1.0 - component.secondaryTransition) + minContentOffset += effectiveStoriesOffsetDistance + } else { + effectiveStoriesOffsetDistance = 0.0 + } + + let clippedScrollOffset = min(minContentOffset, offset) + if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset { + return + } + self.hasDeferredScrollOffset = false + self.clippedScrollOffset = clippedScrollOffset + + let visibleSize = CGSize(width: currentLayout.size.width, height: max(0.0, currentLayout.size.height - clippedScrollOffset)) + + self.backgroundView.update(size: visibleSize, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: visibleSize)) + transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: UIScreenPixel))) + + let searchContentNode: NavigationBarSearchContentNode + if let current = self.searchContentNode { + searchContentNode = current + + if themeUpdated { + let placeholder: String + let compactPlaceholder: String + + placeholder = component.strings.Common_Search + compactPlaceholder = component.strings.Common_Search + + searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder) + } + } else { + let placeholder: String + let compactPlaceholder: String + + placeholder = component.strings.Common_Search + compactPlaceholder = component.strings.Common_Search + + //TODO:localize + searchContentNode = NavigationBarSearchContentNode( + theme: component.theme, + placeholder: placeholder, + compactPlaceholder: compactPlaceholder, + activate: { [weak self] in + guard let self, let component = self.component, let searchContentNode = self.searchContentNode else { + return + } + component.activateSearch(searchContentNode) + } + ) + self.searchContentNode = searchContentNode + self.addSubview(searchContentNode.view) + } + + let clippedStoriesOffset = max(0.0, min(clippedScrollOffset, defaultStoriesOffsetDistance)) + let storiesOffsetFraction: CGFloat + if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty { + storiesOffsetFraction = clippedStoriesOffset / defaultStoriesOffsetDistance + } else { + storiesOffsetFraction = 1.0 + } + + let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight) + let searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - 0.0 - searchSize.height), size: searchSize) + + let clippedSearchOffset = max(0.0, min(clippedScrollOffset - effectiveStoriesOffsetDistance, searchOffsetDistance)) + let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance + searchContentNode.expansionProgress = 1.0 - searchOffsetFraction + + transition.setFrame(view: searchContentNode.view, frame: searchFrame) + searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition) + + let headerContentSize = self.headerContent.update( + transition: transition, + component: AnyComponent(ChatListHeaderComponent( + sideInset: component.sideInset + 16.0, + primaryContent: component.primaryContent, + secondaryContent: component.secondaryContent, + secondaryTransition: component.secondaryTransition, + networkStatus: nil, + storySubscriptions: component.storySubscriptions, + storiesFraction: 1.0 - storiesOffsetFraction, + context: component.context, + theme: component.theme, + strings: component.strings, + openStatusSetup: { [weak self] sourceView in + guard let self, let component = self.component else { + return + } + component.openStatusSetup(sourceView) + }, + toggleIsLocked: { [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.appLockContext.lock() + } + )), + environment: {}, + containerSize: CGSize(width: currentLayout.size.width, height: 44.0) + ) + let headerContentY: CGFloat + if component.isSearchActive { + headerContentY = -headerContentSize.height - effectiveStoriesOffsetDistance + } else { + if component.statusBarHeight < 1.0 { + headerContentY = 0.0 + } else { + headerContentY = component.statusBarHeight + 12.0 + } + } + let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize) + if let headerContentView = self.headerContent.view { + if headerContentView.superview == nil { + self.addSubview(headerContentView) + } + transition.setFrame(view: headerContentView, frame: headerContentFrame) + } + } + + func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + if themeUpdated { + self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) + self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor + } + + var contentHeight = component.statusBarHeight + + if component.statusBarHeight >= 1.0 { + contentHeight += 10.0 + } + contentHeight += 44.0 + + if component.isSearchActive { + if component.statusBarHeight < 1.0 { + contentHeight += 8.0 + } + self.effectiveStoriesInsetHeight = 0.0 + } else { + if let storySubscriptions = component.storySubscriptions, !storySubscriptions.items.isEmpty { + let storiesHeight: CGFloat = 94.0 * (1.0 - component.secondaryTransition) + contentHeight += storiesHeight + self.effectiveStoriesInsetHeight = storiesHeight + } else { + self.effectiveStoriesInsetHeight = 0.0 + } + + contentHeight += navigationBarSearchContentHeight + } + + let size = CGSize(width: availableSize.width, height: contentHeight) + self.currentLayout = CurrentLayout(size: size) + + self.hasDeferredScrollOffset = true + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +}