mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[Temp]
This commit is contained in:
parent
fba8f6b9f2
commit
bfb4c7bbad
@ -230,8 +230,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return super.displayNode as! ChatListControllerNode
|
||||
}
|
||||
|
||||
private let headerContentView = ComponentView<Empty>()
|
||||
|
||||
fileprivate private(set) var primaryContext: ChatListLocationContext?
|
||||
private let primaryInfoReady = Promise<Bool>()
|
||||
private let mainReady = Promise<Bool>()
|
||||
@ -275,8 +273,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
private let isReorderingTabsValue = ValuePromise<Bool>(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,9 +2475,41 @@ 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 {
|
||||
|
||||
override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.updateNavigationBarLayout(layout, transition: transition)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
let wasInVoiceOver = self.validLayout?.inVoiceOver ?? false
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
self.updateLayout(layout: layout, transition: transition)
|
||||
|
||||
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
|
||||
@ -2524,7 +2525,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
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 componentView = self.chatListHeaderView() {
|
||||
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
|
||||
cameraTransitionIn = StoryCameraTransitionIn(
|
||||
sourceView: transitionView,
|
||||
@ -2539,7 +2540,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
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 StoryCameraTransitionOut(
|
||||
destinationView: transitionView,
|
||||
@ -2556,7 +2557,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
var transitionIn: StoryContainerScreen.TransitionIn?
|
||||
if let peer, let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
|
||||
if let peer, let componentView = self.chatListHeaderView() {
|
||||
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peer.id) {
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: transitionView,
|
||||
@ -2575,7 +2576,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return nil
|
||||
}
|
||||
|
||||
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
|
||||
if let componentView = self.chatListHeaderView() {
|
||||
if let transitionView = componentView.storyPeerListView()?.transitionViewForItem(peerId: peerId) {
|
||||
return StoryContainerScreen.TransitionOut(
|
||||
destinationView: transitionView,
|
||||
@ -2595,35 +2596,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
let wasInVoiceOver = self.validLayout?.inVoiceOver ?? false
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
self.updateLayout(layout: layout, transition: transition)
|
||||
|
||||
if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver {
|
||||
searchContentNode.updateListVisibleContentOffset(.known(0.0))
|
||||
self.chatListDisplayNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil, skipScrolling: Bool = false) {
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
@ -3493,14 +3492,13 @@ 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 {
|
||||
//TODO:search filters
|
||||
strongSelf.navigationBar?.secondaryContentHeight = NavigationBar.defaultSecondaryContentHeight
|
||||
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
|
||||
}
|
||||
strongSelf.searchContentNode?.additionalHeight = 0.0
|
||||
|
||||
activate(filter != .downloads)
|
||||
|
||||
@ -3515,7 +3513,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
|
||||
strongSelf.setDisplayNavigationBar(false, transition: transition)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Empty>()
|
||||
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 {
|
||||
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 {
|
||||
|
@ -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)")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -21,6 +21,8 @@ swift_library(
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/AnimationUI",
|
||||
"//submodules/TelegramUI/Components/Stories/StoryPeerListComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/SearchUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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<Empty>()
|
||||
|
||||
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<Empty>, 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<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user