[WIP] Inline forums

This commit is contained in:
Ali 2022-11-16 01:43:02 +04:00
parent f2b0d699d9
commit 152fd01567
22 changed files with 631 additions and 175 deletions

View File

@ -4,7 +4,7 @@ import Postbox
import Display import Display
import TelegramCore import TelegramCore
public enum ChatListControllerLocation { public enum ChatListControllerLocation: Equatable {
case chatList(groupId: EngineChatList.Group) case chatList(groupId: EngineChatList.Group)
case forum(peerId: PeerId) case forum(peerId: PeerId)
} }

View File

@ -233,15 +233,22 @@ public final class AvatarNode: ASDisplayNode {
} set(value) { } set(value) {
let updateImage = !value.size.equalTo(super.frame.size) let updateImage = !value.size.equalTo(super.frame.size)
super.frame = value super.frame = value
self.imageNode.frame = CGRect(origin: CGPoint(), size: value.size)
self.editOverlayNode?.frame = self.imageNode.frame if updateImage {
if updateImage && !self.displaySuspended { self.updateSize(size: value.size)
self.setNeedsDisplay()
self.editOverlayNode?.setNeedsDisplay()
} }
} }
} }
public func updateSize(size: CGSize) {
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
self.editOverlayNode?.frame = self.imageNode.frame
if !self.displaySuspended {
self.setNeedsDisplay()
self.editOverlayNode?.setNeedsDisplay()
}
}
public func playArchiveAnimation() { public func playArchiveAnimation() {
guard let theme = self.theme else { guard let theme = self.theme else {
return return

View File

@ -52,7 +52,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa
} else { } else {
offset = 0.0 offset = 0.0
} }
let _ = listNode.scrollToOffsetFromTop(offset) let _ = listNode.scrollToOffsetFromTop(offset, animated: true)
return true return true
} else if searchNode.expansionProgress == 1.0 { } else if searchNode.expansionProgress == 1.0 {
var sortItemNode: ListViewItemNode? var sortItemNode: ListViewItemNode?
@ -341,6 +341,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private let moreBarButtonItem: UIBarButtonItem private let moreBarButtonItem: UIBarButtonItem
private var forumChannelTracker: ForumChannelTopics? private var forumChannelTracker: ForumChannelTopics?
private var backNavigationItem: UIBarButtonItem?
private let selectAddMemberDisposable = MetaDisposable() private let selectAddMemberDisposable = MetaDisposable()
private let addMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable()
private let joinForumDisposable = MetaDisposable() private let joinForumDisposable = MetaDisposable()
@ -392,6 +394,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource)
self.backNavigationItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.navigationBackPressed))
self.tabBarItemContextActionType = .always self.tabBarItemContextActionType = .always
self.automaticallyControlPresentationContextLayout = false self.automaticallyControlPresentationContextLayout = false
@ -742,6 +746,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel { if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel {
strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true)
} }
} else if strongSelf.chatListDisplayNode.inlineStackContainerNode != nil {
} else { } else {
let editItem: UIBarButtonItem let editItem: UIBarButtonItem
if stateAndFilterId.state.editing { if stateAndFilterId.state.editing {
@ -1160,10 +1165,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
if force { if force {
strongSelf.tabContainerNode.cancelAnimations() strongSelf.tabContainerNode.cancelAnimations()
strongSelf.chatListDisplayNode.inlineTabContainerNode.cancelAnimations()
} }
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
} }
self.reloadFilters() self.reloadFilters()
} }
@ -1277,7 +1280,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
editItem = self.moreBarButtonItem editItem = self.moreBarButtonItem
} }
} }
if case .chatList(.root) = self.location { if self.chatListDisplayNode.inlineStackContainerNode != nil {
self.backNavigationItem?.title = self.presentationData.strings.Common_Back
} else if case .chatList(.root) = self.location {
self.navigationItem.leftBarButtonItem = editItem self.navigationItem.leftBarButtonItem = editItem
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose
@ -1296,7 +1301,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let layout = self.validLayout { if let layout = self.validLayout {
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
} }
if self.isNodeLoaded { if self.isNodeLoaded {
@ -1383,11 +1387,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil {
strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) strongSelf.chatListDisplayNode.clearHighlightAnimated(true)
if strongSelf.context.sharedContext.immediateExperimentalUISettings.inlineForums {
strongSelf.chatListDisplayNode.setInlineChatList(location: .forum(peerId: channel.id))
if strongSelf.navigationItem.leftBarButtonItem !== strongSelf.backNavigationItem {
strongSelf.navigationItem.setLeftBarButton(strongSelf.backNavigationItem, animated: true)
}
} else {
strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController)
}
} else { } else {
if let threadId = threadId { if let threadId = threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) strongSelf.chatListDisplayNode.clearHighlightAnimated(true)
} else { } else {
var navigationAnimationOptions: NavigationAnimationOptions = [] var navigationAnimationOptions: NavigationAnimationOptions = []
var groupId: EngineChatList.Group = .root var groupId: EngineChatList.Group = .root
@ -1581,7 +1595,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationController.filterController(strongSelf, animated: true) navigationController.filterController(strongSelf, animated: true)
} }
self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in self.chatListDisplayNode.contentOffsetChanged = { [weak self] offset in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout {
var offset = offset var offset = offset
if validLayout.inVoiceOver { if validLayout.inVoiceOver {
@ -1591,7 +1605,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
} }
self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in self.chatListDisplayNode.contentScrollingEnded = { [weak self] listView in
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
return fixListNodeScrolling(listView, searchNode: searchContentNode) return fixListNodeScrolling(listView, searchNode: searchContentNode)
} else { } else {
@ -1885,20 +1899,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.selectTab(id: id) strongSelf.selectTab(id: id)
} }
} }
self.chatListDisplayNode.inlineTabContainerNode.tabSelected = { [weak self] id in
self?.selectTab(id: id)
}
self.tabContainerNode.tabRequestedDeletion = { [weak self] id in self.tabContainerNode.tabRequestedDeletion = { [weak self] id in
if case let .filter(id) = id { if case let .filter(id) = id {
self?.askForFilterRemoval(id: id) self?.askForFilterRemoval(id: id)
} }
} }
self.chatListDisplayNode.inlineTabContainerNode.tabRequestedDeletion = { [weak self] id in
if case let .filter(id) = id {
self?.askForFilterRemoval(id: id)
}
}
self.tabContainerNode.presentPremiumTip = { [weak self] in self.tabContainerNode.presentPremiumTip = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAll, customUndoText: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAll, customUndoText: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
@ -2108,9 +2114,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in
tabContextGesture(id, sourceNode, gesture, false, isDisabled) tabContextGesture(id, sourceNode, gesture, false, isDisabled)
} }
self.chatListDisplayNode.inlineTabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in
tabContextGesture(id, sourceNode, gesture, true, isDisabled)
}
if case .chatList(.root) = self.location { if case .chatList(.root) = self.location {
self.ready.set(.never()) self.ready.set(.never())
@ -2499,12 +2502,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
if let tabContainerData = self.tabContainerData {
self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1
} else {
self.chatListDisplayNode.inlineTabContainerNode.isHidden = true
}
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition) self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
} }
@ -2559,22 +2556,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
@objc private func reorderingDonePressed() { @objc private func reorderingDonePressed() {
guard let defaultFilters = self.tabContainerData else {
return
}
let defaultFilterIds = defaultFilters.0.compactMap { entry -> Int32? in
switch entry {
case .all:
return 0
case let .filter(id, _, _):
return id
}
}
var reorderedFilterIdsValue: [Int32]? var reorderedFilterIdsValue: [Int32]?
if let reorderedFilterIds = self.chatListDisplayNode.inlineTabContainerNode.reorderedFilterIds, reorderedFilterIds != defaultFilterIds { if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
reorderedFilterIdsValue = reorderedFilterIds
} else if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
reorderedFilterIdsValue = reorderedFilterIds reorderedFilterIdsValue = reorderedFilterIds
} }
@ -2620,6 +2603,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
} }
@objc private func navigationBackPressed() {
self.chatListDisplayNode.setInlineChatList(location: nil)
if self.navigationItem.leftBarButtonItem === self.backNavigationItem {
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
self.navigationItem.setLeftBarButton(editItem, animated: true)
}
}
public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { peer in
@ -2872,7 +2866,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
(strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition)
} else { } else {
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: filtersLimit, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: filtersLimit, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
} }
} }

View File

@ -173,7 +173,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
self.addSubnode(self.maskNode) self.addSubnode(self.maskNode)
} }
func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, isInlineMode: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData { if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData {
self.currentParams = (size, presentationData) self.currentParams = (size, presentationData)
@ -186,6 +186,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }) }, present: { _ in }, openForumThread: { _, _ in })
interaction.isInlineMode = isInlineMode
let items = (0 ..< 2).map { _ -> ChatListItem in let items = (0 ..< 2).map { _ -> ChatListItem in
let message = EngineMessage( let message = EngineMessage(
@ -251,14 +252,25 @@ private final class ChatListShimmerNode: ASDisplayNode {
context.setBlendMode(.copy) context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor) context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) if !isInlineMode {
if itemNodes[sampleIndex].avatarNode.isHidden {
context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
}
}
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) if isInlineMode {
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) context.fillEllipse(in: CGRect(origin: CGPoint(x: textFrame.minX, y: titleFrame.minY + 2.0), size: CGSize(width: 16.0, height: 16.0)))
}
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0)
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0)
let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY) let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
@ -288,6 +300,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
private let becameEmpty: (ChatListFilter?) -> Void private let becameEmpty: (ChatListFilter?) -> Void
private let emptyAction: (ChatListFilter?) -> Void private let emptyAction: (ChatListFilter?) -> Void
private let secondaryEmptyAction: () -> Void private let secondaryEmptyAction: () -> Void
private let isInlineMode: Bool
private var floatingHeaderOffset: CGFloat? private var floatingHeaderOffset: CGFloat?
@ -296,9 +309,9 @@ private final class ChatListContainerItemNode: ASDisplayNode {
private var shimmerNodeOffset: CGFloat = 0.0 private var shimmerNodeOffset: CGFloat = 0.0
let listNode: ChatListNode let listNode: ChatListNode
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? private var validLayout: (CGSize, UIEdgeInsets, CGFloat, ChatListControllerLocation?)?
init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) {
self.context = context self.context = context
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
@ -306,8 +319,9 @@ private final class ChatListContainerItemNode: ASDisplayNode {
self.becameEmpty = becameEmpty self.becameEmpty = becameEmpty
self.emptyAction = emptyAction self.emptyAction = emptyAction
self.secondaryEmptyAction = secondaryEmptyAction self.secondaryEmptyAction = secondaryEmptyAction
self.isInlineMode = isInlineMode
self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true) self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode)
super.init() super.init()
@ -361,7 +375,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
}) })
strongSelf.emptyNode = emptyNode strongSelf.emptyNode = emptyNode
strongSelf.addSubnode(emptyNode) strongSelf.addSubnode(emptyNode)
if let (size, insets, _) = strongSelf.validLayout { if let (size, insets, _, _) = strongSelf.validLayout {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
emptyNode.frame = emptyNodeFrame emptyNode.frame = emptyNodeFrame
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
@ -387,7 +401,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
let emptyShimmerEffectNode = ChatListShimmerNode() let emptyShimmerEffectNode = ChatListShimmerNode()
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode) strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { if let (size, insets, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate) strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate)
} }
} }
@ -407,14 +421,14 @@ private final class ChatListContainerItemNode: ASDisplayNode {
return return
} }
strongSelf.floatingHeaderOffset = offset strongSelf.floatingHeaderOffset = offset
if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { if let (size, insets, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode {
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition) strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition)
} }
} }
} }
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) { private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, presentationData: self.presentationData, transition: .immediate) node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, isInlineMode: self.isInlineMode, presentationData: self.presentationData, transition: .immediate)
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size)) transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
} }
@ -430,15 +444,14 @@ private final class ChatListContainerItemNode: ASDisplayNode {
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
} }
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets, visualNavigationHeight) self.validLayout = (size, insets, visualNavigationHeight, inlineNavigationLocation)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve)
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation)
self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
if let emptyNode = self.emptyNode { if let emptyNode = self.emptyNode {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
@ -450,8 +463,9 @@ private final class ChatListContainerItemNode: ASDisplayNode {
final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let context: AccountContext private let context: AccountContext
private let location: ChatListControllerLocation let location: ChatListControllerLocation
private let previewing: Bool private let previewing: Bool
private let isInlineMode: Bool
private let controlsHistoryPreload: Bool private let controlsHistoryPreload: Bool
private let filterBecameEmpty: (ChatListFilter?) -> Void private let filterBecameEmpty: (ChatListFilter?) -> Void
private let filterEmptyAction: (ChatListFilter?) -> Void private let filterEmptyAction: (ChatListFilter?) -> Void
@ -473,12 +487,14 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private(set) var transitionFraction: CGFloat = 0.0 private(set) var transitionFraction: CGFloat = 0.0
private var transitionFractionOffset: CGFloat = 0.0 private var transitionFractionOffset: CGFloat = 0.0
private var disableItemNodeOperationsWhileAnimating: Bool = false private var disableItemNodeOperationsWhileAnimating: Bool = false
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool)? private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?)?
private var enableAdjacentFilterLoading: Bool = false private var enableAdjacentFilterLoading: Bool = false
private var panRecognizer: InteractiveTransitionGestureRecognizer? private var panRecognizer: InteractiveTransitionGestureRecognizer?
let leftSeparatorLayer: SimpleLayer
private let _ready = Promise<Bool>() private let _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> { var ready: Signal<Bool, NoError> {
return _ready.get() return _ready.get()
@ -624,17 +640,18 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)?
var groupSelected: ((EngineChatList.Group) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)?
var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)?
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? fileprivate var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
var contentScrollingEnded: ((ListView) -> Bool)? fileprivate var contentScrollingEnded: ((ListView) -> Bool)?
var activateChatPreview: ((ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var activateChatPreview: ((ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
var didBeginSelectingChats: (() -> Void)? var didBeginSelectingChats: (() -> Void)?
var displayFilterLimit: (() -> Void)? var displayFilterLimit: (() -> Void)?
init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) {
self.context = context self.context = context
self.location = location self.location = location
self.previewing = previewing self.previewing = previewing
self.isInlineMode = isInlineMode
self.filterBecameEmpty = filterBecameEmpty self.filterBecameEmpty = filterBecameEmpty
self.filterEmptyAction = filterEmptyAction self.filterEmptyAction = filterEmptyAction
self.secondaryEmptyAction = secondaryEmptyAction self.secondaryEmptyAction = secondaryEmptyAction
@ -646,9 +663,15 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.selectedId = .all self.selectedId = .all
self.leftSeparatorLayer = SimpleLayer()
self.leftSeparatorLayer.isHidden = true
self.leftSeparatorLayer.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor.cgColor
super.init() super.init()
let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self.backgroundColor = presentationData.theme.chatList.backgroundColor
let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter) self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in }, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter) self?.filterEmptyAction(filter)
@ -685,6 +708,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
panRecognizer.cancelsTouchesInView = true panRecognizer.cancelsTouchesInView = true
self.panRecognizer = panRecognizer self.panRecognizer = panRecognizer
self.view.addGestureRecognizer(panRecognizer) self.view.addGestureRecognizer(panRecognizer)
self.view.layer.addSublayer(self.leftSeparatorLayer)
} }
deinit { deinit {
@ -714,7 +739,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.onFilterSwitch?() self.onFilterSwitch?()
self.transitionFractionOffset = 0.0 self.transitionFractionOffset = 0.0
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] {
for (id, itemNode) in self.itemNodes { for (id, itemNode) in self.itemNodes {
if id != selectedId { if id != selectedId {
itemNode.emptyNode?.restartAnimation() itemNode.emptyNode?.restartAnimation()
@ -727,13 +752,13 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
for (_, itemNode) in self.itemNodes { for (_, itemNode) in self.itemNodes {
itemNode.layer.removeAllAnimations() itemNode.layer.removeAllAnimations()
} }
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true)
} }
} }
} }
case .changed: case .changed:
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) {
let translation = recognizer.translation(in: self.view) let translation = recognizer.translation(in: self.view)
var transitionFraction = translation.x / layout.size.width var transitionFraction = translation.x / layout.size.width
@ -770,11 +795,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
} }
} }
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false)
} }
case .cancelled, .ended: case .cancelled, .ended:
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) {
let translation = recognizer.translation(in: self.view) let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view) let velocity = recognizer.velocity(in: self.view)
var directionIsToRight: Bool? var directionIsToRight: Bool?
@ -813,12 +838,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.transitionFraction = 0.0 self.transitionFraction = 0.0
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
self.disableItemNodeOperationsWhileAnimating = true self.disableItemNodeOperationsWhileAnimating = true
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
DispatchQueue.main.async { DispatchQueue.main.async {
self.disableItemNodeOperationsWhileAnimating = false self.disableItemNodeOperationsWhileAnimating = false
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout {
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
} }
} }
} }
@ -827,9 +852,17 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
} }
func fixContentOffset(offset: CGFloat) {
self.currentItemNode.fixContentOffset(offset: offset)
}
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData self.presentationData = presentationData
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.leftSeparatorLayer.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor.cgColor
for (_, itemNode) in self.itemNodes { for (_, itemNode) in self.itemNodes {
itemNode.updatePresentationData(presentationData) itemNode.updatePresentationData(presentationData)
} }
@ -884,8 +917,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
strongSelf.availableFilters = availableFilters strongSelf.availableFilters = availableFilters
strongSelf.filtersLimit = limit strongSelf.filtersLimit = limit
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout { if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout {
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
} }
} }
if !availableFilters.contains(where: { $0.id == self.selectedId }) { if !availableFilters.contains(where: { $0.id == self.selectedId }) {
@ -902,8 +935,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
if value != self.enableAdjacentFilterLoading { if value != self.enableAdjacentFilterLoading {
self.enableAdjacentFilterLoading = value self.enableAdjacentFilterLoading = value
if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout {
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
} }
} }
} }
@ -912,7 +945,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.onFilterSwitch?() self.onFilterSwitch?()
if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) {
if let itemNode = self.itemNodes[id] { if let itemNode = self.itemNodes[id] {
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout else { guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout else {
return return
} }
self.selectedId = id self.selectedId = id
@ -921,12 +954,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
self.applyItemNodeAsCurrent(id: id, itemNode: itemNode) self.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition)
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
itemNode.emptyNode?.restartAnimation() itemNode.emptyNode?.restartAnimation()
completion?() completion?()
} else if self.pendingItemNode == nil { } else if self.pendingItemNode == nil {
let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter) self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in }, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter) self?.filterEmptyAction(filter)
@ -951,7 +984,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
strongSelf.pendingItemNode = nil strongSelf.pendingItemNode = nil
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout else { guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout else {
strongSelf.itemNodes[id] = itemNode strongSelf.itemNodes[id] = itemNode
strongSelf.addSubnode(itemNode) strongSelf.addSubnode(itemNode)
@ -1000,7 +1033,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0)) transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0))
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate) itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
strongSelf.selectedId = id strongSelf.selectedId = id
if let currentItemNode = strongSelf.currentItemNodeValue { if let currentItemNode = strongSelf.currentItemNodeValue {
@ -1008,7 +1041,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode) strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate)
strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false)
} }
@ -1019,8 +1052,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
} }
} }
func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, transition: ContainedViewLayoutTransition) { func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation)
self._validLayoutReady.set(.single(true)) self._validLayoutReady.set(.single(true))
@ -1029,6 +1062,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
self.panRecognizer?.isEnabled = !isEditing self.panRecognizer?.isEnabled = !isEditing
transition.updateFrame(layer: self.leftSeparatorLayer, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)))
if let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { if let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) {
var validNodeIds: [ChatListFilterTabEntryId] = [] var validNodeIds: [ChatListFilterTabEntryId] = []
for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) { for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) {
@ -1036,7 +1071,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
validNodeIds.append(id) validNodeIds.append(id)
if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating {
let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in
self?.filterBecameEmpty(filter) self?.filterBecameEmpty(filter)
}, emptyAction: { [weak self] filter in }, emptyAction: { [weak self] filter in
self?.filterEmptyAction(filter) self?.filterEmptyAction(filter)
@ -1073,7 +1108,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in
}) })
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition) itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: nodeTransition)
if wasAdded, case .animated = transition { if wasAdded, case .animated = transition {
animateSlidingIds.append(id) animateSlidingIds.append(id)
@ -1106,7 +1141,7 @@ final class ChatListControllerNode: ASDisplayNode {
private let animationRenderer: MultiAnimationRenderer private let animationRenderer: MultiAnimationRenderer
let containerNode: ChatListContainerNode let containerNode: ChatListContainerNode
let inlineTabContainerNode: ChatListFilterTabInlineContainerNode private(set) var inlineStackContainerNode: ChatListContainerNode?
private var tapRecognizer: UITapGestureRecognizer? private var tapRecognizer: UITapGestureRecognizer?
var navigationBar: NavigationBar? var navigationBar: NavigationBar?
weak var controller: ChatListControllerImpl? weak var controller: ChatListControllerImpl?
@ -1121,7 +1156,10 @@ final class ChatListControllerNode: ASDisplayNode {
var didBeginSelectingChatsWhileEditing: Bool = false var didBeginSelectingChatsWhileEditing: Bool = false
var isEditing: Bool = false var isEditing: Bool = false
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)? private var containerLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat)?
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
var contentScrollingEnded: ((ListView) -> Bool)?
var requestDeactivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((EnginePeer, Int64?, Bool) -> Void)? var requestOpenPeerFromSearch: ((EnginePeer, Int64?, Bool) -> Void)?
@ -1146,7 +1184,7 @@ final class ChatListControllerNode: ASDisplayNode {
var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterBecameEmpty: ((ChatListFilter?) -> Void)?
var filterEmptyAction: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)?
var secondaryEmptyAction: (() -> Void)? var secondaryEmptyAction: (() -> Void)?
self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in
filterBecameEmpty?(filter) filterBecameEmpty?(filter)
}, filterEmptyAction: { filter in }, filterEmptyAction: { filter in
filterEmptyAction?(filter) filterEmptyAction?(filter)
@ -1154,8 +1192,6 @@ final class ChatListControllerNode: ASDisplayNode {
secondaryEmptyAction?() secondaryEmptyAction?()
}) })
self.inlineTabContainerNode = ChatListFilterTabInlineContainerNode()
self.controller = controller self.controller = controller
super.init() super.init()
@ -1167,7 +1203,13 @@ final class ChatListControllerNode: ASDisplayNode {
self.backgroundColor = presentationData.theme.chatList.backgroundColor self.backgroundColor = presentationData.theme.chatList.backgroundColor
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.addSubnode(self.inlineTabContainerNode)
self.containerNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged(offset: offset, isPrimary: true)
}
self.containerNode.contentScrollingEnded = { [weak self] listView in
return self?.contentScrollingEnded(listView: listView, isPrimary: true) ?? false
}
self.addSubnode(self.debugListView) self.addSubnode(self.debugListView)
@ -1292,9 +1334,42 @@ final class ChatListControllerNode: ASDisplayNode {
self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition) var mainNavigationBarHeight = navigationBarHeight
var cleanMainNavigationBarHeight = cleanNavigationBarHeight
var mainInsets = insets
if self.inlineStackContainerNode != nil {
mainNavigationBarHeight = visualNavigationHeight
cleanMainNavigationBarHeight = visualNavigationHeight
mainInsets.top = visualNavigationHeight
}
self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, transition: transition)
transition.updateFrame(node: self.inlineTabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.intrinsicInsets.bottom - 8.0 - 40.0), size: CGSize(width: layout.size.width, height: 40.0))) if let inlineStackContainerNode = self.inlineStackContainerNode {
var inlineStackContainerNodeTransition = transition
var animateIn = false
if inlineStackContainerNode.supernode == nil {
self.insertSubnode(inlineStackContainerNode, aboveSubnode: self.containerNode)
inlineStackContainerNodeTransition = .immediate
animateIn = true
}
let inlineSideInset: CGFloat = layout.safeInsets.left + 72.0
inlineStackContainerNodeTransition.updateFrame(node: inlineStackContainerNode, frame: CGRect(origin: CGPoint(x: inlineSideInset, y: 0.0), size: layout.size))
var inlineLayout = layout
inlineLayout.size.width -= inlineSideInset
inlineLayout.safeInsets.left = 0.0
inlineLayout.intrinsicInsets.left = 0.0
inlineLayout.additionalInsets.left = 0.0
var inlineInsets = insets
inlineInsets.left = 0.0
inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, transition: inlineStackContainerNodeTransition)
if animateIn {
transition.animatePosition(node: inlineStackContainerNode, from: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y))
}
}
self.tapRecognizer?.isEnabled = self.isReorderingFilters self.tapRecognizer?.isEnabled = self.isReorderingFilters
@ -1372,6 +1447,150 @@ final class ChatListControllerNode: ASDisplayNode {
} }
} }
func clearHighlightAnimated(_ animated: Bool) {
self.containerNode.currentItemNode.clearHighlightAnimated(true)
self.inlineStackContainerNode?.currentItemNode.clearHighlightAnimated(true)
}
private var contentOffsetSyncLockedIn: Bool = false
private func contentOffsetChanged(offset: ListViewVisibleContentOffset, isPrimary: Bool) {
guard let inlineStackContainerNode = self.inlineStackContainerNode else {
self.contentOffsetChanged?(offset)
return
}
guard let containerLayout = self.containerLayout else {
return
}
if !isPrimary {
self.contentOffsetChanged?(offset)
if "".isEmpty {
return
}
} else {
if "".isEmpty {
return
}
}
let targetNode: ChatListContainerNode
if isPrimary {
targetNode = inlineStackContainerNode
} else {
targetNode = self.containerNode
}
switch offset {
case let .known(value) where (value <= containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 + UIScreenPixel || self.contentOffsetSyncLockedIn):
if case let .known(otherValue) = targetNode.currentItemNode.visibleContentOffset(), abs(otherValue - value) <= UIScreenPixel {
self.contentOffsetSyncLockedIn = true
}
default:
break
}
switch offset {
case let .known(value) where self.contentOffsetSyncLockedIn:
var targetValue = value
if targetValue > containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 {
targetValue = containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0
}
targetNode.fixContentOffset(offset: targetValue)
self.contentOffsetChanged?(offset)
default:
if !isPrimary {
self.contentOffsetChanged?(offset)
}
}
}
private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool {
guard let inlineStackContainerNode = self.inlineStackContainerNode else {
return self.contentScrollingEnded?(listView) ?? false
}
self.contentOffsetSyncLockedIn = false
if isPrimary {
return false
}
let _ = inlineStackContainerNode
return self.contentScrollingEnded?(listView) ?? false
}
func setInlineChatList(location: ChatListControllerLocation?) {
if let location = location {
if self.inlineStackContainerNode?.location != location {
let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {})
inlineStackContainerNode.leftSeparatorLayer.isHidden = false
inlineStackContainerNode.presentAlert = self.containerNode.presentAlert
inlineStackContainerNode.present = self.containerNode.present
inlineStackContainerNode.push = self.containerNode.push
inlineStackContainerNode.deletePeerChat = self.containerNode.deletePeerChat
inlineStackContainerNode.deletePeerThread = self.containerNode.deletePeerThread
inlineStackContainerNode.setPeerThreadStopped = self.containerNode.setPeerThreadStopped
inlineStackContainerNode.setPeerThreadPinned = self.containerNode.setPeerThreadPinned
inlineStackContainerNode.peerSelected = self.containerNode.peerSelected
inlineStackContainerNode.groupSelected = self.containerNode.groupSelected
inlineStackContainerNode.updatePeerGrouping = self.containerNode.updatePeerGrouping
inlineStackContainerNode.contentOffsetChanged = { [weak self] offset in
self?.contentOffsetChanged(offset: offset, isPrimary: false)
}
inlineStackContainerNode.contentScrollingEnded = { [weak self] listView in
return self?.contentScrollingEnded(listView: listView, isPrimary: false) ?? false
}
inlineStackContainerNode.activateChatPreview = self.containerNode.activateChatPreview
inlineStackContainerNode.addedVisibleChatsWithPeerIds = self.containerNode.addedVisibleChatsWithPeerIds
inlineStackContainerNode.didBeginSelectingChats = nil
inlineStackContainerNode.displayFilterLimit = nil
let previousInlineStackContainerNode = self.inlineStackContainerNode
self.inlineStackContainerNode = inlineStackContainerNode
if let containerLayout = self.containerLayout {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
if let previousInlineStackContainerNode {
transition.updatePosition(node: previousInlineStackContainerNode, position: CGPoint(x: previousInlineStackContainerNode.position.x + previousInlineStackContainerNode.bounds.width + UIScreenPixel, y: previousInlineStackContainerNode.position.y), completion: { [weak previousInlineStackContainerNode] _ in
previousInlineStackContainerNode?.removeFromSupernode()
})
}
self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition)
} else {
previousInlineStackContainerNode?.removeFromSupernode()
}
}
} else {
if let inlineStackContainerNode = self.inlineStackContainerNode {
self.inlineStackContainerNode = nil
self.containerNode.contentScrollingEnded = self.contentScrollingEnded
if let containerLayout = self.containerLayout {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition)
transition.updatePosition(node: inlineStackContainerNode, position: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y), completion: { [weak inlineStackContainerNode] _ in
inlineStackContainerNode?.removeFromSupernode()
})
} else {
inlineStackContainerNode.removeFromSupernode()
}
}
}
}
func playArchiveAnimation() { func playArchiveAnimation() {
self.containerNode.playArchiveAnimation() self.containerNode.playArchiveAnimation()
} }

View File

@ -775,6 +775,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
let contextContainer: ContextControllerSourceNode let contextContainer: ContextControllerSourceNode
let mainContentContainerNode: ASDisplayNode
let avatarNode: AvatarNode let avatarNode: AvatarNode
var avatarIconView: ComponentHostView<Empty>? var avatarIconView: ComponentHostView<Empty>?
@ -784,6 +785,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
private let playbackStartDisposable = MetaDisposable() private let playbackStartDisposable = MetaDisposable()
private var videoLoopCount = 0 private var videoLoopCount = 0
private var inlineNavigationMarkLayer: SimpleLayer?
let titleNode: TextNode let titleNode: TextNode
let authorNode: AuthorNode let authorNode: AuthorNode
private var compoundHighlightingNode: LinkHighlightingNode? private var compoundHighlightingNode: LinkHighlightingNode?
@ -1026,6 +1029,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.contextContainer = ContextControllerSourceNode() self.contextContainer = ContextControllerSourceNode()
self.mainContentContainerNode = ASDisplayNode()
self.mainContentContainerNode.clipsToBounds = true
self.measureNode = TextNode() self.measureNode = TextNode()
self.titleNode = TextNode() self.titleNode = TextNode()
@ -1073,19 +1079,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.addSubnode(self.separatorNode) self.addSubnode(self.separatorNode)
self.addSubnode(self.contextContainer) self.addSubnode(self.contextContainer)
self.contextContainer.addSubnode(self.mainContentContainerNode)
self.contextContainer.addSubnode(self.avatarNode) self.contextContainer.addSubnode(self.avatarNode)
self.contextContainer.addSubnode(self.onlineNode) self.contextContainer.addSubnode(self.onlineNode)
self.contextContainer.addSubnode(self.titleNode) self.mainContentContainerNode.addSubnode(self.titleNode)
self.contextContainer.addSubnode(self.authorNode) self.mainContentContainerNode.addSubnode(self.authorNode)
self.contextContainer.addSubnode(self.textNode.textNode) self.mainContentContainerNode.addSubnode(self.textNode.textNode)
self.contextContainer.addSubnode(self.dateNode) self.mainContentContainerNode.addSubnode(self.dateNode)
self.contextContainer.addSubnode(self.statusNode) self.mainContentContainerNode.addSubnode(self.statusNode)
self.contextContainer.addSubnode(self.pinnedIconNode) self.mainContentContainerNode.addSubnode(self.pinnedIconNode)
self.contextContainer.addSubnode(self.badgeNode) self.mainContentContainerNode.addSubnode(self.badgeNode)
self.contextContainer.addSubnode(self.mentionBadgeNode) self.mainContentContainerNode.addSubnode(self.mentionBadgeNode)
self.contextContainer.addSubnode(self.mutedIconNode) self.mainContentContainerNode.addSubnode(self.mutedIconNode)
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
if let strongSelf = self, let layoutParams = strongSelf.layoutParams { if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
@ -1094,6 +1101,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
}) })
self.contextContainer.shouldBegin = { [weak self] location in
guard let strongSelf = self else {
return false
}
if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view {
strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundHighlightingNode
} else {
strongSelf.contextContainer.targetNodeForActivationProgress = nil
}
return true
}
self.contextContainer.activated = { [weak self] gesture, location in self.contextContainer.activated = { [weak self] gesture, location in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
@ -1505,7 +1525,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0)) let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0))
let avatarLeftInset: CGFloat let avatarLeftInset: CGFloat
if case .forum = item.index { if item.interaction.isInlineMode {
avatarLeftInset = 12.0
} else if case .forum = item.index {
avatarLeftInset = 50.0 avatarLeftInset = 50.0
} else { } else {
avatarLeftInset = 18.0 + avatarDiameter avatarLeftInset = 18.0 + avatarDiameter
@ -2290,8 +2312,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.cachedChatListSearchResult = chatListSearchResult
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
if case .groupReference = item.content { if case .groupReference = item.content {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
@ -2301,7 +2321,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor
} }
let revealOffset = strongSelf.revealOffset let revealOffset = 0.0//strongSelf.revealOffset
let transition: ContainedViewLayoutTransition let transition: ContainedViewLayoutTransition
if animated { if animated {
@ -2310,6 +2330,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition = .immediate transition = .immediate
} }
let contextContainerFrame = CGRect(origin: CGPoint(), size: layout.contentSize)
strongSelf.contextContainer.position = contextContainerFrame.center
transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0))
if item.interaction.inlineNavigationLocation != nil {
let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center)
transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.size.width, y: 0.0), size: mainContentFrame.size))
transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 0.0)
} else if case .chatList = item.chatListLocation {
let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center)
transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.origin.x, y: 0.0), size: mainContentFrame.size))
transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0)
} else {
let mainContentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center)
transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mainContentFrame.size))
transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0)
}
var crossfadeContent = false var crossfadeContent = false
if let selectableControlSizeAndApply = selectableControlSizeAndApply { if let selectableControlSizeAndApply = selectableControlSizeAndApply {
let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height) let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height)
@ -2380,9 +2424,51 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0)
let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) var avatarScaleOffset: CGFloat = 0.0
var avatarScale: CGFloat = 1.0
if item.interaction.inlineNavigationLocation != nil {
avatarScale = 54.0 / avatarFrame.width
avatarScaleOffset = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5
}
transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.center.offsetBy(dx: avatarScaleOffset, dy: 0.0))
transition.updateBounds(node: strongSelf.avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale)
strongSelf.avatarNode.updateSize(size: avatarFrame.size)
strongSelf.updateVideoVisibility() strongSelf.updateVideoVisibility()
var itemPeerId: EnginePeer.Id?
if case let .chatList(index) = item.index {
itemPeerId = index.messageIndex.id.peerId
}
if let itemPeerId = itemPeerId, let inlineNavigationLocation = item.interaction.inlineNavigationLocation, inlineNavigationLocation.location.peerId == itemPeerId {
let inlineNavigationMarkLayer: SimpleLayer
var animateIn = false
if let current = strongSelf.inlineNavigationMarkLayer {
inlineNavigationMarkLayer = current
} else {
inlineNavigationMarkLayer = SimpleLayer()
strongSelf.inlineNavigationMarkLayer = inlineNavigationMarkLayer
inlineNavigationMarkLayer.cornerRadius = 4.0
animateIn = true
strongSelf.layer.addSublayer(inlineNavigationMarkLayer)
}
inlineNavigationMarkLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor
let markHeight: CGFloat = 50.0
let markFrame = CGRect(origin: CGPoint(x: -4.0, y: avatarFrame.midY - markHeight * 0.5), size: CGSize(width: 8.0, height: markHeight))
if animateIn {
inlineNavigationMarkLayer.frame = markFrame
transition.animatePositionAdditive(layer: inlineNavigationMarkLayer, offset: CGPoint(x: -markFrame.width * 0.5, y: 0.0))
} else {
transition.updateFrame(layer: inlineNavigationMarkLayer, frame: markFrame)
}
} else {
if let inlineNavigationMarkLayer = strongSelf.inlineNavigationMarkLayer {
strongSelf.inlineNavigationMarkLayer = nil
transition.updatePosition(layer: inlineNavigationMarkLayer, position: CGPoint(x: -inlineNavigationMarkLayer.bounds.width * 0.5, y: avatarFrame.midY))
}
}
if let threadInfo = threadInfo { if let threadInfo = threadInfo {
let avatarIconView: ComponentHostView<Empty> let avatarIconView: ComponentHostView<Empty>
if let current = strongSelf.avatarIconView { if let current = strongSelf.avatarIconView {
@ -2390,7 +2476,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
avatarIconView = ComponentHostView<Empty>() avatarIconView = ComponentHostView<Empty>()
strongSelf.avatarIconView = avatarIconView strongSelf.avatarIconView = avatarIconView
strongSelf.contextContainer.view.addSubview(avatarIconView) strongSelf.mainContentContainerNode.view.addSubview(avatarIconView)
} }
let avatarIconContent: EmojiStatusComponent.Content let avatarIconContent: EmojiStatusComponent.Content
@ -2414,9 +2500,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition: .immediate, transition: .immediate,
component: AnyComponent(avatarIconComponent), component: AnyComponent(avatarIconComponent),
environment: {}, environment: {},
containerSize: CGSize(width: 32.0, height: 32.0) containerSize: item.interaction.isInlineMode ? CGSize(width: 18.0, height: 18.0) : CGSize(width: 32.0, height: 32.0)
) )
transition.updateFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize))
let avatarIconFrame: CGRect
if item.interaction.isInlineMode {
avatarIconFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + 1.0), size: iconSize)
} else {
avatarIconFrame = CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize)
}
transition.updateFrame(view: avatarIconView, frame: avatarIconFrame)
} else if let avatarIconView = strongSelf.avatarIconView { } else if let avatarIconView = strongSelf.avatarIconView {
strongSelf.avatarIconView = nil strongSelf.avatarIconView = nil
avatarIconView.removeFromSuperview() avatarIconView.removeFromSuperview()
@ -2479,7 +2572,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
dateStatusIconNode = ASImageNode() dateStatusIconNode = ASImageNode()
strongSelf.dateStatusIconNode = dateStatusIconNode strongSelf.dateStatusIconNode = dateStatusIconNode
strongSelf.contextContainer.addSubnode(dateStatusIconNode) strongSelf.mainContentContainerNode.addSubnode(dateStatusIconNode)
} }
dateStatusIconNode.image = dateIconImage dateStatusIconNode.image = dateIconImage
@ -2539,6 +2632,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
var titleOffset: CGFloat = 0.0 var titleOffset: CGFloat = 0.0
if item.interaction.isInlineMode {
titleOffset += 22.0
}
if let currentSecretIconImage = currentSecretIconImage { if let currentSecretIconImage = currentSecretIconImage {
let iconNode: ASImageNode let iconNode: ASImageNode
if let current = strongSelf.secretIconNode { if let current = strongSelf.secretIconNode {
@ -2548,7 +2644,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
iconNode.isLayerBacked = true iconNode.isLayerBacked = true
iconNode.displaysAsynchronously = false iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true iconNode.displayWithoutProcessing = true
strongSelf.contextContainer.addSubnode(iconNode) strongSelf.mainContentContainerNode.addSubnode(iconNode)
strongSelf.secretIconNode = iconNode strongSelf.secretIconNode = iconNode
} }
iconNode.image = currentSecretIconImage iconNode.image = currentSecretIconImage
@ -2576,7 +2672,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
compoundHighlightingNode.alpha = strongSelf.authorNode.alpha compoundHighlightingNode.alpha = strongSelf.authorNode.alpha
compoundHighlightingNode.useModernPathCalculation = true compoundHighlightingNode.useModernPathCalculation = true
strongSelf.compoundHighlightingNode = compoundHighlightingNode strongSelf.compoundHighlightingNode = compoundHighlightingNode
strongSelf.contextContainer.insertSubnode(compoundHighlightingNode, at: 0) strongSelf.mainContentContainerNode.insertSubnode(compoundHighlightingNode, at: 0)
} }
let compoundTextButtonNode: HighlightTrackingButtonNode let compoundTextButtonNode: HighlightTrackingButtonNode
@ -2585,7 +2681,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
compoundTextButtonNode = HighlightTrackingButtonNode() compoundTextButtonNode = HighlightTrackingButtonNode()
strongSelf.compoundTextButtonNode = compoundTextButtonNode strongSelf.compoundTextButtonNode = compoundTextButtonNode
strongSelf.contextContainer.addSubnode(compoundTextButtonNode) strongSelf.mainContentContainerNode.addSubnode(compoundTextButtonNode)
compoundTextButtonNode.addTarget(strongSelf, action: #selector(strongSelf.compoundTextButtonPressed), forControlEvents: .touchUpInside) compoundTextButtonNode.addTarget(strongSelf, action: #selector(strongSelf.compoundTextButtonPressed), forControlEvents: .touchUpInside)
compoundTextButtonNode.highligthedChanged = { highlighted in compoundTextButtonNode.highligthedChanged = { highlighted in
guard let strongSelf = self, let compoundHighlightingNode = strongSelf.compoundHighlightingNode else { guard let strongSelf = self, let compoundHighlightingNode = strongSelf.compoundHighlightingNode else {
@ -2644,7 +2740,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
textArrowNode = ASImageNode() textArrowNode = ASImageNode()
strongSelf.textArrowNode = textArrowNode strongSelf.textArrowNode = textArrowNode
compoundTextButtonNode.addSubnode(textArrowNode) compoundHighlightingNode.addSubnode(textArrowNode)
} }
textArrowNode.image = textArrowImage textArrowNode.image = textArrowImage
let arrowScale: CGFloat = 0.75 let arrowScale: CGFloat = 0.75
@ -2677,7 +2773,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
dustNode = InvisibleInkDustNode(textNode: nil) dustNode = InvisibleInkDustNode(textNode: nil)
dustNode.isUserInteractionEnabled = false dustNode.isUserInteractionEnabled = false
strongSelf.dustNode = dustNode strongSelf.dustNode = dustNode
strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode) strongSelf.mainContentContainerNode.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode)
} }
dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) })
dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
@ -2699,7 +2795,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let inputActivities = inputActivities, !inputActivities.isEmpty { if let inputActivities = inputActivities, !inputActivities.isEmpty {
if strongSelf.inputActivitiesNode.supernode == nil { if strongSelf.inputActivitiesNode.supernode == nil {
strongSelf.contextContainer.addSubnode(strongSelf.inputActivitiesNode) strongSelf.mainContentContainerNode.addSubnode(strongSelf.inputActivitiesNode)
} else { } else {
animateInputActivitiesFrame = true animateInputActivitiesFrame = true
} }
@ -2768,7 +2864,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
previewNodeAlphaTransition = .immediate previewNodeAlphaTransition = .immediate
previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media) previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media)
strongSelf.mediaPreviewNodes[mediaId] = previewNode strongSelf.mediaPreviewNodes[mediaId] = previewNode
strongSelf.contextContainer.addSubnode(previewNode) strongSelf.mainContentContainerNode.addSubnode(previewNode)
} }
previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads) previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads)
previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0) previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0)
@ -2820,7 +2916,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else { } else {
credibilityIconView = ComponentHostView<Empty>() credibilityIconView = ComponentHostView<Empty>()
strongSelf.credibilityIconView = credibilityIconView strongSelf.credibilityIconView = credibilityIconView
strongSelf.contextContainer.view.addSubview(credibilityIconView) strongSelf.mainContentContainerNode.view.addSubview(credibilityIconView)
} }
let credibilityIconComponent = EmojiStatusComponent( let credibilityIconComponent = EmojiStatusComponent(
@ -2866,11 +2962,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight)))
transition.updateAlpha(node: strongSelf.separatorNode, alpha: item.interaction.inlineNavigationLocation != nil ? 0.0 : 1.0)
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight))) transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight)))
let backgroundColor: UIColor let backgroundColor: UIColor
let highlightedBackgroundColor: UIColor let highlightedBackgroundColor: UIColor
if item.selected { if item.interaction.inlineNavigationLocation != nil {
backgroundColor = theme.pinnedItemBackgroundColor
highlightedBackgroundColor = theme.itemHighlightedBackgroundColor
} else if item.selected {
backgroundColor = theme.itemSelectedBackgroundColor backgroundColor = theme.itemSelectedBackgroundColor
highlightedBackgroundColor = theme.itemHighlightedBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor
} else if isPinned { } else if isPinned {
@ -3011,7 +3111,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.playbackStartDisposable.set(nil) self.playbackStartDisposable.set(nil)
videoNode.isHidden = false videoNode.isHidden = false
} }
videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0 videoNode.layer.cornerRadius = self.avatarNode.bounds.size.width / 2.0
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
videoNode.layer.cornerCurve = .circular videoNode.layer.cornerCurve = .circular
} }
@ -3019,7 +3119,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
videoNode.canAttachContent = true videoNode.canAttachContent = true
videoNode.play() videoNode.play()
// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode) // self.mainContentContainerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
self.avatarNode.addSubnode(videoNode) self.avatarNode.addSubnode(videoNode)
self.videoNode = videoNode self.videoNode = videoNode
} }
@ -3037,7 +3137,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 { transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0))
/*if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 {
let editingOffset: CGFloat let editingOffset: CGFloat
if let selectableControlNode = self.selectableControlNode { if let selectableControlNode = self.selectableControlNode {
editingOffset = selectableControlNode.bounds.size.width editingOffset = selectableControlNode.bounds.size.width
@ -3188,7 +3290,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let badgeBackgroundFrame = CGRect(x: badgeOffset, y: self.pinnedIconNode.frame.origin.y, width: badgeBackgroundWidth, height: pinnedIconSize.height) let badgeBackgroundFrame = CGRect(x: badgeOffset, y: self.pinnedIconNode.frame.origin.y, width: badgeBackgroundWidth, height: pinnedIconSize.height)
transition.updateFrame(node: self.pinnedIconNode, frame: badgeBackgroundFrame) transition.updateFrame(node: self.pinnedIconNode, frame: badgeBackgroundFrame)
} }
} }*/
} }
override func touchesToOtherItemsPrevented() { override func touchesToOtherItemsPrevented() {

View File

@ -35,7 +35,7 @@ struct ChatListNodeListViewTransition {
let animateCrossfade: Bool let animateCrossfade: Bool
} }
final class ChatListHighlightedLocation { final class ChatListHighlightedLocation: Equatable {
let location: ChatLocation let location: ChatLocation
let progress: CGFloat let progress: CGFloat
@ -47,6 +47,16 @@ final class ChatListHighlightedLocation {
func withUpdatedProgress(_ progress: CGFloat) -> ChatListHighlightedLocation { func withUpdatedProgress(_ progress: CGFloat) -> ChatListHighlightedLocation {
return ChatListHighlightedLocation(location: location, progress: progress) return ChatListHighlightedLocation(location: location, progress: progress)
} }
static func ==(lhs: ChatListHighlightedLocation, rhs: ChatListHighlightedLocation) -> Bool {
if lhs.location != rhs.location {
return false
}
if lhs.progress != rhs.progress {
return false
}
return true
}
} }
public final class ChatListNodeInteraction { public final class ChatListNodeInteraction {
@ -84,6 +94,9 @@ public final class ChatListNodeInteraction {
public var searchTextHighightState: String? public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation? var highlightedChatLocation: ChatListHighlightedLocation?
var isInlineMode: Bool = false
var inlineNavigationLocation: ChatListHighlightedLocation?
let animationCache: AnimationCache let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer let animationRenderer: MultiAnimationRenderer
@ -871,7 +884,9 @@ public final class ChatListNode: ListView {
public var selectionLimit: Int32 = 100 public var selectionLimit: Int32 = 100
public var reachedSelectionLimit: ((Int32) -> Void)? public var reachedSelectionLimit: ((Int32) -> Void)?
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool) { private var visibleTopInset: CGFloat?
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) {
self.context = context self.context = context
self.location = location self.location = location
self.chatListFilter = chatListFilter self.chatListFilter = chatListFilter
@ -1156,6 +1171,7 @@ public final class ChatListNode: ListView {
self.peerSelected?(peer, threadId, true, true, nil) self.peerSelected?(peer, threadId, true, true, nil)
}) })
}) })
nodeInteraction.isInlineMode = isInlineMode
let viewProcessingQueue = self.viewProcessingQueue let viewProcessingQueue = self.viewProcessingQueue
@ -1929,6 +1945,9 @@ public final class ChatListNode: ListView {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if !strongSelf.dequeuedInitialTransitionOnLayout {
return
}
let atTop: Bool let atTop: Bool
var revealHiddenItems: Bool = false var revealHiddenItems: Bool = false
switch offset { switch offset {
@ -2255,11 +2274,15 @@ public final class ChatListNode: ListView {
var scrollToItem = transition.scrollToItem var scrollToItem = transition.scrollToItem
if transition.adjustScrollToFirstItem { if transition.adjustScrollToFirstItem {
var offset: CGFloat = 0.0 var offset: CGFloat = 0.0
switch self.visibleContentOffset() { if let visibleTopInset = self.visibleTopInset {
case let .known(value) where abs(value) < .ulpOfOne: offset = visibleTopInset - self.insets.top
offset = 0.0 } else {
default: switch self.visibleContentOffset() {
offset = -navigationBarSearchContentHeight case let .known(value) where abs(value) < .ulpOfOne:
offset = 0.0
default:
offset = -navigationBarSearchContentHeight
}
} }
scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
} }
@ -2316,8 +2339,34 @@ public final class ChatListNode: ListView {
} }
} }
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { public func fixContentOffset(offset: CGFloat) {
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) let _ = self.scrollToOffsetFromTop(offset, animated: false)
/*let scrollToItem: ListViewScrollToItem = ListViewScrollToItem(index: 0, position: .top(-offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })*/
}
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, visibleTopInset: CGFloat, inlineNavigationLocation: ChatListControllerLocation?) {
self.visibleTopInset = visibleTopInset
self.visualInsets = UIEdgeInsets(top: visibleTopInset, left: 0.0, bottom: 0.0, right: 0.0)
var highlightedLocation: ChatListHighlightedLocation?
if case let .forum(peerId) = inlineNavigationLocation {
highlightedLocation = ChatListHighlightedLocation(location: .peer(id: peerId), progress: 1.0)
}
var navigationLocationUpdated = false
if self.interaction?.inlineNavigationLocation != highlightedLocation {
self.interaction?.inlineNavigationLocation = highlightedLocation
navigationLocationUpdated = true
}
var options: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency]
if navigationLocationUpdated {
options.insert(.ForceUpdate)
options.insert(.AnimateInsertion)
}
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !self.dequeuedInitialTransitionOnLayout { if !self.dequeuedInitialTransitionOnLayout {
self.dequeuedInitialTransitionOnLayout = true self.dequeuedInitialTransitionOnLayout = true

View File

@ -210,7 +210,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
} }
var adjustScrollToFirstItem = false var adjustScrollToFirstItem = false
if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 { if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 1 {
adjustScrollToFirstItem = true adjustScrollToFirstItem = true
} }

View File

@ -105,7 +105,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa
} else { } else {
offset = 0.0 offset = 0.0
} }
let _ = listNode.scrollToOffsetFromTop(offset) let _ = listNode.scrollToOffsetFromTop(offset, animated: true)
return true return true
} else if searchNode.expansionProgress == 1.0 { } else if searchNode.expansionProgress == 1.0 {
var sortItemNode: ListViewItemNode? var sortItemNode: ListViewItemNode?

View File

@ -86,7 +86,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableDebugDataDisplay(Bool) case enableDebugDataDisplay(Bool)
case acceleratedStickers(Bool) case acceleratedStickers(Bool)
case experimentalBackground(Bool) case experimentalBackground(Bool)
case inlineStickers(Bool) case inlineForums(Bool)
case localTranscription(Bool) case localTranscription(Bool)
case enableReactionOverrides(Bool) case enableReactionOverrides(Bool)
case playerEmbedding(Bool) case playerEmbedding(Bool)
@ -111,7 +111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, . enableReactionOverrides, .restorePurchases: case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -188,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 30 return 30
case .experimentalBackground: case .experimentalBackground:
return 31 return 31
case .inlineStickers: case .inlineForums:
return 32 return 32
case .localTranscription: case .localTranscription:
return 33 return 33
@ -1053,12 +1053,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .inlineStickers(value): case let .inlineForums(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Inline Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Inline Forums", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.inlineStickers = value settings.inlineForums = value
return PreferencesEntry(settings) return PreferencesEntry(settings)
}) })
}).start() }).start()
@ -1219,7 +1219,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
entries.append(.inlineStickers(experimentalSettings.inlineStickers)) entries.append(.inlineForums(experimentalSettings.inlineForums))
entries.append(.localTranscription(experimentalSettings.localTranscription)) entries.append(.localTranscription(experimentalSettings.localTranscription))
if case .internal = sharedContext.applicationBindings.appBuildType { if case .internal = sharedContext.applicationBindings.appBuildType {
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides)) entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))

View File

@ -1144,6 +1144,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if bottomItemFound { if bottomItemFound {
bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY
} else {
bottomItemEdge = self.visibleSize.height
} }
if topItemFound && bottomItemFound { if topItemFound && bottomItemFound {
@ -1734,7 +1736,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) {
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil {
if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets && !options.contains(.ForceUpdate))) {
self.visibleSize = updateSizeAndInsets.size self.visibleSize = updateSizeAndInsets.size
self.insets = updateSizeAndInsets.insets self.insets = updateSizeAndInsets.insets
self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets
@ -1769,7 +1771,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
let widthUpdated: Bool let widthUpdated: Bool
if let updateSizeAndInsets = updateSizeAndInsets { if let updateSizeAndInsets = updateSizeAndInsets {
widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne || options.contains(.ForceUpdate)
state.visibleSize = updateSizeAndInsets.size state.visibleSize = updateSizeAndInsets.size
state.insets = updateSizeAndInsets.insets state.insets = updateSizeAndInsets.insets
@ -2820,7 +2822,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
case let .bottom(additionalOffset): case let .bottom(additionalOffset):
offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset
case let .top(additionalOffset): case let .top(additionalOffset):
offset = insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset offset = (insets.top + additionalOffset + itemNode.scrollPositioningInsets.top) - itemNode.apparentFrame.minY
case let .center(overflow): case let .center(overflow):
let contentAreaHeight = self.visibleSize.height - insets.bottom - insets.top let contentAreaHeight = self.visibleSize.height - insets.bottom - insets.top
if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne {
@ -2912,7 +2914,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} else if self.snapToBottomInsetUntilFirstInteraction { } else if self.snapToBottomInsetUntilFirstInteraction {
offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom
} else { } else {
offsetFix = updateSizeAndInsets.insets.top - self.insets.top if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) {
offsetFix = 0.0
} else {
offsetFix = updateSizeAndInsets.insets.top - self.insets.top
}
} }
offsetFix += additionalScrollDistance offsetFix += additionalScrollDistance
@ -4783,10 +4789,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
public func scrollToOffsetFromTop(_ offset: CGFloat) -> Bool { public func scrollToOffsetFromTop(_ offset: CGFloat, animated: Bool) -> Bool {
for itemNode in self.itemNodes { for itemNode in self.itemNodes {
if itemNode.index == 0 { if itemNode.index == 0 {
self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: true) if animated {
self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: animated)
} else {
self.scroller.contentOffset = CGPoint(x: 0.0, y: offset)
}
return true return true
} }
} }

View File

@ -104,6 +104,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet {
public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64) public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64)
public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128)
public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256) public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256)
public static let ForceUpdate = ListViewDeleteAndInsertOptions(rawValue: 512)
} }
public struct ListViewUpdateSizeAndInsets { public struct ListViewUpdateSizeAndInsets {

View File

@ -206,6 +206,17 @@ final class ViewTracker {
let record = (view, ValuePipe<CombinedView>()) let record = (view, ValuePipe<CombinedView>())
let index = self.combinedViews.add(record) let index = self.combinedViews.add(record)
if view.views.keys.contains(where: { key in
switch key {
case .messageHistoryThreadIndex:
return true
default:
return false
}
}) {
self.updateTrackedForumTopicListHoles()
}
return (index, record.1.signal()) return (index, record.1.signal())
} }

View File

@ -456,7 +456,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
dict[-1316338916] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) } dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) }
dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) } dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) }
dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) }

View File

@ -1020,7 +1020,7 @@ public extension Api {
case messageActionSetChatTheme(emoticon: String) case messageActionSetChatTheme(emoticon: String)
case messageActionSetMessagesTTL(period: Int32) case messageActionSetMessagesTTL(period: Int32)
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
case messageActionWebViewDataSent(text: String) case messageActionWebViewDataSent(text: String)
case messageActionWebViewDataSentMe(text: String, data: String) case messageActionWebViewDataSentMe(text: String, data: String)
@ -1265,14 +1265,15 @@ public extension Api {
serializeInt32(iconColor, buffer: buffer, boxed: false) serializeInt32(iconColor, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)}
break break
case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden):
if boxed { if boxed {
buffer.appendInt32(-1316338916) buffer.appendInt32(-1064024032)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)}
break break
case .messageActionWebViewDataSent(let text): case .messageActionWebViewDataSent(let text):
if boxed { if boxed {
@ -1356,8 +1357,8 @@ public extension Api {
return ("messageActionSetMessagesTTL", [("period", String(describing: period))]) return ("messageActionSetMessagesTTL", [("period", String(describing: period))])
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId))]) return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId))])
case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden):
return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]) return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))])
case .messageActionWebViewDataSent(let text): case .messageActionWebViewDataSent(let text):
return ("messageActionWebViewDataSent", [("text", String(describing: text))]) return ("messageActionWebViewDataSent", [("text", String(describing: text))])
case .messageActionWebViewDataSentMe(let text, let data): case .messageActionWebViewDataSentMe(let text, let data):
@ -1783,12 +1784,17 @@ public extension Api {
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.Bool _4 = Api.parse(reader, signature: signature) as? Api.Bool
} } } }
var _5: Api.Bool?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Bool
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5)
} }
else { else {
return nil return nil

View File

@ -1524,6 +1524,23 @@ public extension Api.functions.auth {
}) })
} }
} }
public extension Api.functions.auth {
static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.Authorization>) {
let buffer = Buffer()
buffer.appendInt32(767062953)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(apiHash, buffer: buffer, boxed: false)
serializeString(webAuthToken, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("webAuthToken", String(describing: webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in
let reader = BufferReader(buffer)
var result: Api.auth.Authorization?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.Authorization
}
return result
})
}
}
public extension Api.functions.auth { public extension Api.functions.auth {
static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoggedOut>) { static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoggedOut>) {
let buffer = Buffer() let buffer = Buffer()
@ -2026,16 +2043,17 @@ public extension Api.functions.channels {
} }
} }
public extension Api.functions.channels { public extension Api.functions.channels {
static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1820868141) buffer.appendInt32(-186670715)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
channel.serialize(buffer, true) channel.serialize(buffer, true)
serializeInt32(topicId, buffer: buffer, boxed: false) serializeInt32(topicId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)}
return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)}
return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -2484,6 +2502,22 @@ public extension Api.functions.channels {
}) })
} }
} }
public extension Api.functions.channels {
static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1471109485)
channel.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.channels { public extension Api.functions.channels {
static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
@ -2537,6 +2571,22 @@ public extension Api.functions.channels {
}) })
} }
} }
public extension Api.functions.channels {
static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1760814315)
channel.serialize(buffer, true)
enabled.serialize(buffer, true)
return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
}
public extension Api.functions.channels { public extension Api.functions.channels {
static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months))
case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId):
return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId))
case let .messageActionTopicEdit(flags, title, iconEmojiId, closed): case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, _):
var components: [TelegramMediaActionType.ForumTopicEditComponent] = [] var components: [TelegramMediaActionType.ForumTopicEditComponent] = []
if let title = title { if let title = title {
components.append(.title(title)) components.append(.title(title))

View File

@ -293,7 +293,8 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId:
topicId: Int32(clamping: threadId), topicId: Int32(clamping: threadId),
title: title, title: title,
iconEmojiId: iconFileId ?? 0, iconEmojiId: iconFileId ?? 0,
closed: nil closed: nil,
hidden: nil
)) ))
|> mapError { _ -> EditForumChannelTopicError in |> mapError { _ -> EditForumChannelTopicError in
return .generic return .generic
@ -338,7 +339,8 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t
topicId: Int32(clamping: threadId), topicId: Int32(clamping: threadId),
title: nil, title: nil,
iconEmojiId: nil, iconEmojiId: nil,
closed: isClosed ? .boolTrue : .boolFalse closed: isClosed ? .boolTrue : .boolFalse,
hidden: nil
)) ))
|> mapError { _ -> EditForumChannelTopicError in |> mapError { _ -> EditForumChannelTopicError in
return .generic return .generic

View File

@ -96,7 +96,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode { if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode {
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
if let limit = limit { if let limit = limit {
chatListNode.selectionLimit = limit chatListNode.selectionLimit = limit
chatListNode.reachedSelectionLimit = reachedSelectionLimit chatListNode.reachedSelectionLimit = reachedSelectionLimit
@ -279,7 +279,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
combinedInsets.right += layout.safeInsets.right combinedInsets.right += layout.safeInsets.right
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: combinedInsets, headerInsets: headerInsets, duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: combinedInsets, headerInsets: headerInsets, duration: duration, curve: curve)
chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil)
} }
self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size) self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size)

View File

@ -144,7 +144,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
} }
func scrollToTop() -> Bool { func scrollToTop() -> Bool {
if !self.listNode.scrollToOffsetFromTop(0.0) { if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
return true return true
} else { } else {

View File

@ -224,7 +224,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
} }
func scrollToTop() -> Bool { func scrollToTop() -> Bool {
if !self.listNode.scrollToOffsetFromTop(0.0) { if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
return true return true
} else { } else {

View File

@ -137,7 +137,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
chatListLocation = .chatList(groupId: .root) chatListLocation = .chatList(groupId: .root)
} }
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
super.init() super.init()
@ -576,7 +576,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil)
if let contactListNode = self.contactListNode { if let contactListNode = self.contactListNode {
contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)

View File

@ -45,6 +45,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var inlineStickers: Bool public var inlineStickers: Bool
public var localTranscription: Bool public var localTranscription: Bool
public var enableReactionOverrides: Bool public var enableReactionOverrides: Bool
public var inlineForums: Bool
public var accountReactionEffectOverrides: [AccountReactionOverrides] public var accountReactionEffectOverrides: [AccountReactionOverrides]
public var accountStickerEffectOverrides: [AccountReactionOverrides] public var accountStickerEffectOverrides: [AccountReactionOverrides]
@ -69,6 +70,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
inlineStickers: false, inlineStickers: false,
localTranscription: false, localTranscription: false,
enableReactionOverrides: false, enableReactionOverrides: false,
inlineForums: false,
accountReactionEffectOverrides: [], accountReactionEffectOverrides: [],
accountStickerEffectOverrides: [] accountStickerEffectOverrides: []
) )
@ -94,6 +96,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
inlineStickers: Bool, inlineStickers: Bool,
localTranscription: Bool, localTranscription: Bool,
enableReactionOverrides: Bool, enableReactionOverrides: Bool,
inlineForums: Bool,
accountReactionEffectOverrides: [AccountReactionOverrides], accountReactionEffectOverrides: [AccountReactionOverrides],
accountStickerEffectOverrides: [AccountReactionOverrides] accountStickerEffectOverrides: [AccountReactionOverrides]
) { ) {
@ -116,6 +119,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.inlineStickers = inlineStickers self.inlineStickers = inlineStickers
self.localTranscription = localTranscription self.localTranscription = localTranscription
self.enableReactionOverrides = enableReactionOverrides self.enableReactionOverrides = enableReactionOverrides
self.inlineForums = inlineForums
self.accountReactionEffectOverrides = accountReactionEffectOverrides self.accountReactionEffectOverrides = accountReactionEffectOverrides
self.accountStickerEffectOverrides = accountStickerEffectOverrides self.accountStickerEffectOverrides = accountStickerEffectOverrides
} }
@ -142,6 +146,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0 self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0
self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0 self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0
self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false
self.inlineForums = try container.decodeIfPresent(Bool.self, forKey: "inlineForums") ?? false
self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? [] self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? []
self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? [] self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? []
} }
@ -168,6 +173,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers") try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers")
try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription") try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription")
try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides") try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides")
try container.encode(self.inlineForums, forKey: "inlineForums")
try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides") try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides")
try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides") try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides")
} }