Search filters improvements

This commit is contained in:
Ilya Laktyushin 2020-09-25 19:11:58 +03:00
parent bb27a2ba5d
commit cefc01f28c
28 changed files with 551 additions and 299 deletions

View File

@ -226,6 +226,8 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
var options = ListViewDeleteAndInsertOptions() var options = ListViewDeleteAndInsertOptions()
if transition.firstTime { if transition.firstTime {
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing)
options.insert(.Synchronous) options.insert(.Synchronous)
options.insert(.LowLatency) options.insert(.LowLatency)
} else if transition.animated { } else if transition.animated {

View File

@ -1703,8 +1703,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
public func deactivateSearch(animated: Bool) { public func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar { if !self.displayNavigationBar {
var completion: (() -> Void)?
if let searchContentNode = self.searchContentNode { if let searchContentNode = self.searchContentNode {
self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
} }
let filtersIsEmpty: Bool let filtersIsEmpty: Bool
@ -1722,6 +1723,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
self.setDisplayNavigationBar(true, transition: transition) self.setDisplayNavigationBar(true, transition: transition)
completion?()
(self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring))
} }
} }

View File

@ -1088,7 +1088,6 @@ final class ChatListControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight insets.top += navigationBarHeight
insets.left += layout.safeInsets.left insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right insets.right += layout.safeInsets.right
@ -1195,11 +1194,19 @@ final class ChatListControllerNode: ASDisplayNode {
}) })
} }
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? {
if let searchDisplayController = self.searchDisplayController { if let searchDisplayController = self.searchDisplayController {
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
self.searchDisplayController = nil self.searchDisplayController = nil
self.containerNode.accessibilityElementsHidden = false self.containerNode.accessibilityElementsHidden = false
return { [weak self] in
if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight) = strongSelf.containerLayout {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
} else {
return nil
} }
} }

View File

@ -39,7 +39,9 @@ class ChatListRecentPeersListItem: ListViewItem {
node.contentSize = nodeLayout.contentSize node.contentSize = nodeLayout.contentSize
node.insets = nodeLayout.insets node.insets = nodeLayout.insets
completion(node, nodeApply) completion(node, {
return (nil, { _ in nodeApply(synchronousLoads) })
})
} }
} }
@ -50,8 +52,8 @@ class ChatListRecentPeersListItem: ListViewItem {
async { async {
let (nodeLayout, apply) = layout(self, params, nextItem != nil) let (nodeLayout, apply) = layout(self, params, nextItem != nil)
Queue.mainQueue().async { Queue.mainQueue().async {
completion(nodeLayout, { info in completion(nodeLayout, { _ in
apply().1(info) apply(false)
}) })
} }
} }
@ -86,23 +88,22 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil) let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil)
self.contentSize = nodeLayout.contentSize self.contentSize = nodeLayout.contentSize
self.insets = nodeLayout.insets self.insets = nodeLayout.insets
let _ = nodeApply() let _ = nodeApply(false)
} }
} }
func asyncLayout() -> (_ item: ChatListRecentPeersListItem, _ params: ListViewItemLayoutParams, _ last: Bool) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) { func asyncLayout() -> (_ item: ChatListRecentPeersListItem, _ params: ListViewItemLayoutParams, _ last: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let currentItem = self.item let currentItem = self.item
return { [weak self] item, params, last in return { [weak self] item, params, last in
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 96.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)) let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 96.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
return (nodeLayout, { [weak self] in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme { if currentItem?.theme !== item.theme {
updatedTheme = item.theme updatedTheme = item.theme
} }
return (nil, { _ in return (nodeLayout, { [weak self] synchronousLoads in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
@ -136,7 +137,6 @@ class ChatListRecentPeersListItemNode: ListViewItemNode {
strongSelf.separatorNode.isHidden = true strongSelf.separatorNode.isHidden = true
} }
}) })
})
} }
} }

View File

@ -112,6 +112,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>() private let statePromise = ValuePromise<ChatListSearchContainerNodeSearchState>()
private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id) private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id)
private var selectedFilterKeyPromise = Promise<ChatListSearchFilterEntryId?>(.filter(ChatListSearchFilter.chats.id))
private var transitionFraction: CGFloat = 0.0 private var transitionFraction: CGFloat = 0.0
private var didSetReady: Bool = false private var didSetReady: Bool = false
@ -133,7 +134,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.presentInGlobalOverlay = presentInGlobalOverlay self.presentInGlobalOverlay = presentInGlobalOverlay
self.filterContainerNode = ChatListSearchFiltersContainerNode() self.filterContainerNode = ChatListSearchFiltersContainerNode()
self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, peersFilter: self.peersFilter, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController) self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, peersFilter: self.peersFilter, groupId: groupId, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController)
self.paneContainerNode.clipsToBounds = true self.paneContainerNode.clipsToBounds = true
super.init() super.init()
@ -216,7 +217,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}, dismissInput: { [weak self] in }, dismissInput: { [weak self] in
self?.dismissInput() self?.dismissInput()
}, updateSuggestedPeers: { [weak self] peers, key in }, updateSuggestedPeers: { [weak self] peers, key in
if let strongSelf = self, strongSelf.paneContainerNode.currentPaneKey == key { if let strongSelf = self, key == .chats {
strongSelf.suggestedPeers.set(.single(peers)) strongSelf.suggestedPeers.set(.single(peers))
} }
}, getSelectedMessageIds: { [weak self] () -> Set<MessageId>? in }, getSelectedMessageIds: { [weak self] () -> Set<MessageId>? in
@ -248,6 +249,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
strongSelf.selectedFilterKey = filterKey.flatMap { .filter($0.id) } strongSelf.selectedFilterKey = filterKey.flatMap { .filter($0.id) }
strongSelf.selectedFilterKeyPromise.set(.single(strongSelf.selectedFilterKey))
strongSelf.transitionFraction = transitionFraction strongSelf.transitionFraction = transitionFraction
if let (layout, _) = strongSelf.validLayout { if let (layout, _) = strongSelf.validLayout {
@ -257,7 +259,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} else { } else {
filters = [.chats, .media, .links, .files, .music, .voice] filters = [.chats, .media, .links, .files, .music, .voice]
} }
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilterKey, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition) strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilterKey, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
} }
} }
} }
@ -297,15 +299,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
self.suggestedFiltersDisposable.set((combineLatest(self.suggestedPeers.get(), self.suggestedDates.get()) self.suggestedFiltersDisposable.set((combineLatest(self.suggestedPeers.get(), self.suggestedDates.get(), self.selectedFilterKeyPromise.get())
|> mapToSignal { peers, dates -> Signal<([Peer], [(Date, String?)]), NoError> in |> mapToSignal { peers, dates, selectedFilter -> Signal<([Peer], [(Date, String?)], ChatListSearchFilterEntryId?), NoError> in
if (peers.isEmpty && dates.isEmpty) || peers.isEmpty { if (peers.isEmpty && dates.isEmpty) || peers.isEmpty {
return .single((peers, dates)) return .single((peers, dates, selectedFilter))
} else { } else {
return (.complete() |> delay(0.2, queue: Queue.mainQueue())) return (.complete() |> delay(0.2, queue: Queue.mainQueue()))
|> then(.single((peers, dates))) |> then(.single((peers, dates, selectedFilter)))
} }
} |> map { peers, dates -> [ChatListSearchFilter] in } |> map { peers, dates, selectedFilter -> [ChatListSearchFilter] in
var suggestedFilters: [ChatListSearchFilter] = [] var suggestedFilters: [ChatListSearchFilter] = []
if !dates.isEmpty { if !dates.isEmpty {
let formatter = DateFormatter() let formatter = DateFormatter()
@ -317,7 +319,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
suggestedFilters.append(.date(Int32(date.timeIntervalSince1970), title)) suggestedFilters.append(.date(Int32(date.timeIntervalSince1970), title))
} }
} }
if !peers.isEmpty { if !peers.isEmpty && selectedFilter != .filter(ChatListSearchFilter.chats.id) {
for peer in peers { for peer in peers {
let isGroup: Bool let isGroup: Bool
if let channel = peer as? TelegramChannel, case .group = channel.info { if let channel = peer as? TelegramChannel, case .group = channel.info {
@ -355,6 +357,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
})) }))
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme {
strongSelf.updateTheme(theme: presentationData.theme)
}
}
})
self._ready.set(self.paneContainerNode.isReady.get() self._ready.set(self.paneContainerNode.isReady.get()
|> map { _ in Void() }) |> map { _ in Void() })
} }
@ -429,29 +443,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
// private func updateTheme(theme: PresentationTheme) { private func updateTheme(theme: PresentationTheme) {
// self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor
// self.dimNode.backgroundColor = self.peersFilter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor
// self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor if let (layout, navigationBarHeight) = self.validLayout {
// self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
// }
// self.listNode.forEachItemHeaderNode({ itemHeaderNode in }
// if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
// itemHeaderNode.updateTheme(theme: theme)
// } else if let itemHeaderNode = itemHeaderNode as? ListMessageDateHeaderNode {
// itemHeaderNode.updateThemeAndStrings(theme: theme, strings: self.presentationData.strings)
// }
// })
// self.recentListNode.forEachItemHeaderNode({ itemHeaderNode in
// if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
// itemHeaderNode.updateTheme(theme: theme)
// }
// })
// }
override public func searchTextUpdated(text: String) { override public func searchTextUpdated(text: String) {
let searchQuery: String? = !text.isEmpty ? text : nil let searchQuery: String? = !text.isEmpty ? text : nil
// self.interaction?.searchTextHighightState = searchQuery
self.searchQuery.set(.single(searchQuery)) self.searchQuery.set(.single(searchQuery))
self.searchQueryValue = searchQuery self.searchQueryValue = searchQuery
@ -463,11 +464,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.validLayout = (layout, navigationBarHeight) self.validLayout = (layout, navigationBarHeight)
var topInset = navigationBarHeight let topInset = navigationBarHeight
var topPanelHeight: CGFloat = 0.0
topInset += topPanelHeight
transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0))) transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0)))
let filters: [ChatListSearchFilter] let filters: [ChatListSearchFilter]
@ -477,7 +474,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
filters = [.chats, .media, .links, .files, .music, .voice] filters = [.chats, .media, .links, .files, .music, .voice]
} }
self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilterKey, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilterKey, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
if let selectedMessageIds = self.stateValue.selectedMessageIds { if let selectedMessageIds = self.stateValue.selectedMessageIds {
var wasAdded = false var wasAdded = false
@ -546,7 +543,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}) })
} }
transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset - 48.0, width: layout.size.width, height: layout.size.height - topInset + 48)) transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset, width: layout.size.width, height: layout.size.height - topInset))
self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: layout.inputHeight ?? 0.0, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, transition: transition) self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: layout.inputHeight ?? 0.0, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, transition: transition)
} }

View File

@ -367,7 +367,7 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
} else { } else {
if self.bounds.intersects(self.scrollNode.convert(paneFrame, to: self)) { if self.bounds.intersects(self.scrollNode.convert(paneFrame, to: self)) {
itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame)
} else { } else if paneNode.frame != paneFrame {
paneNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4) { [weak paneNode] _ in paneNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4) { [weak paneNode] _ in
paneNode?.frame = paneFrame paneNode?.frame = paneFrame
} }

View File

@ -24,6 +24,7 @@ import PhoneNumberFormat
import InstantPageUI import InstantPageUI
import GalleryData import GalleryData
import AppBundle import AppBundle
import ShimmerEffect
private enum ChatListRecentEntryStableId: Hashable { private enum ChatListRecentEntryStableId: Hashable {
case topPeers case topPeers
@ -365,7 +366,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} }
} }
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem { public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem {
switch self { switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: Peer let primaryPeer: Peer
@ -503,10 +504,10 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader): case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader):
let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
if let tagMask = tagMask, tagMask != .photoOrVideo { if let tagMask = tagMask, tagMask != .photoOrVideo && (searchQuery?.isEmpty ?? true) {
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true) return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true)
} else { } else {
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction) return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
} }
case let .addContact(phoneNumber, theme, strings): case let .addContact(phoneNumber, theme, strings):
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
@ -529,10 +530,10 @@ public struct ChatListSearchContainerTransition {
public let displayingResults: Bool public let displayingResults: Bool
public let isEmpty: Bool public let isEmpty: Bool
public let isLoading: Bool public let isLoading: Bool
public let query: String public let query: String?
public let animated: Bool public let animated: Bool
public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String, animated: Bool) { public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String?, animated: Bool) {
self.deletions = deletions self.deletions = deletions
self.insertions = insertions self.insertions = insertions
self.updates = updates self.updates = updates
@ -554,12 +555,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
} }
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, searchQuery: String, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition { public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) }
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated)
} }
@ -636,10 +637,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var presentationData: PresentationData private var presentationData: PresentationData
private let key: ChatListSearchPaneKey private let key: ChatListSearchPaneKey
private let tagMask: MessageTags? private let tagMask: MessageTags?
private let groupId: PeerGroupId?
private let navigationController: NavigationController? private let navigationController: NavigationController?
private let recentListNode: ListView private let recentListNode: ListView
private let loadingNode: ASImageNode private let shimmerNode: ChatListSearchShimmerNode
private let listNode: ListView private let listNode: ListView
private let mediaNode: ChatListSearchMediaNode private let mediaNode: ChatListSearchMediaNode
private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = [] private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = []
@ -696,11 +698,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var hiddenMediaDisposable: Disposable? private var hiddenMediaDisposable: Disposable?
init(context: AccountContext, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) { init(context: AccountContext, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId?, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
self.context = context self.context = context
self.interaction = interaction self.interaction = interaction
self.key = key self.key = key
self.peersFilter = peersFilter self.peersFilter = peersFilter
self.groupId = groupId
self.navigationController = navigationController self.navigationController = navigationController
let tagMask: MessageTags? let tagMask: MessageTags?
@ -730,7 +733,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.recentListNode = ListView() self.recentListNode = ListView()
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
self.loadingNode = ASImageNode() self.shimmerNode = ChatListSearchShimmerNode(key: key)
self.shimmerNode.isUserInteractionEnabled = false
self.listNode = ListView() self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
@ -775,7 +779,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.addSubnode(self.recentListNode) self.addSubnode(self.recentListNode)
self.addSubnode(self.listNode) self.addSubnode(self.listNode)
self.addSubnode(self.mediaNode) self.addSubnode(self.mediaNode)
self.addSubnode(self.loadingNode) if key != .chats {
self.addSubnode(self.shimmerNode)
}
self.addSubnode(self.mediaAccessoryPanelContainer) self.addSubnode(self.mediaAccessoryPanelContainer)
self.addSubnode(self.emptyResultsAnimationNode) self.addSubnode(self.emptyResultsAnimationNode)
@ -871,12 +877,19 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if let (peerId, _, _) = options.peer { if let (peerId, _, _) = options.peer {
location = .peer(peerId: peerId, fromId: nil, tags: self.tagMask, topMsgId: nil, minDate: options.minDate?.0, maxDate: options.maxDate?.0) location = .peer(peerId: peerId, fromId: nil, tags: self.tagMask, topMsgId: nil, minDate: options.minDate?.0, maxDate: options.maxDate?.0)
} else { } else {
if let groupId = groupId {
location = .group(groupId: groupId, tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0)
} else {
location = .general(tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0) location = .general(tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0)
} }
}
} else {
if let groupId = groupId {
location = .group(groupId: groupId, tags: self.tagMask, minDate: nil, maxDate: nil)
} else { } else {
location = .general(tags: self.tagMask, minDate: nil, maxDate: nil) location = .general(tags: self.tagMask, minDate: nil, maxDate: nil)
} }
}
let finalQuery = query ?? "" let finalQuery = query ?? ""
updateSearchContext { _ in updateSearchContext { _ in
@ -1272,8 +1285,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let previousSelectedMessages = Atomic<Set<MessageId>?>(value: nil) let previousSelectedMessages = Atomic<Set<MessageId>?>(value: nil)
let _ = (searchQuery let _ = (searchQuery
|> deliverOnMainQueue).start(next: { [weak self] query in |> deliverOnMainQueue).start(next: { [weak self, weak chatListInteraction] query in
self?.searchQueryValue = query self?.searchQueryValue = query
chatListInteraction?.searchTextHighightState = query
}) })
let _ = (searchOptions let _ = (searchOptions
@ -1329,7 +1343,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil) let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil)
let firstTime = previousEntries == nil let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, searchQuery: strongSelf.searchQueryValue ?? "", context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { _, _, _, _ in }, toggleExpandLocalResults: { let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in
interaction.peerContextAction?(message, node, rect, gesture)
}, toggleExpandLocalResults: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1348,13 +1364,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return state return state
} }
}, searchPeer: { peer in }, searchPeer: { peer in
}, searchResults: newEntries.compactMap { entry -> Message? in }, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in
if case let .message(message, _, _, _, _, _, _) = entry {
return message
} else {
return nil
}
}, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in
interaction.messageContextAction(message, node, rect, gesture) interaction.messageContextAction(message, node, rect, gesture)
}) })
strongSelf.enqueueTransition(transition, firstTime: firstTime) strongSelf.enqueueTransition(transition, firstTime: firstTime)
@ -1370,7 +1380,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if !strongSelf.didSetReady { if !strongSelf.didSetReady {
strongSelf.ready.set(.single(true)) strongSelf.ready.set(.single(true))
strongSelf.didSetReady = true strongSelf.didSetReady = true
} else if tagMask != nil { } else if tagMask == nil {
interaction.updateSuggestedPeers(Array(peers.prefix(8)), strongSelf.key) interaction.updateSuggestedPeers(Array(peers.prefix(8)), strongSelf.key)
} }
} }
@ -1485,13 +1495,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.presentationDataDisposable = (context.sharedContext.presentationData self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations))) strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)))
if previousTheme !== presentationData.theme {
// strongSelf.updateTheme(theme: presentationData.theme)
}
} }
}) })
@ -1600,18 +1605,21 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
func scrollToTop() -> Bool { func scrollToTop() -> Bool {
if !self.mediaNode.isHidden {
return self.mediaNode.scrollToTop()
}
let offset = self.listNode.visibleContentOffset() let offset = self.listNode.visibleContentOffset()
switch offset { switch offset {
case let .known(value) where value <= CGFloat.ulpOfOne: case let .known(value) where value <= CGFloat.ulpOfOne:
return false return false
default: default:
// self.listNode.scrollto 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
} }
} }
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
var hadValidLayout = self.currentParams != nil let hadValidLayout = self.currentParams != nil
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
var topPanelHeight: CGFloat = 0.0 var topPanelHeight: CGFloat = 0.0
@ -1814,11 +1822,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight))) transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight)))
var topInset: CGFloat = 0.0 let topInset: CGFloat = topPanelHeight
let navigationBarHeight: CGFloat = 0.0 let overflowInset: CGFloat = 20.0
let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset) let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
self.loadingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: 422.0)) self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height))
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, key: (self.searchQueryValue?.isEmpty ?? true) ? self.key : .chats, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
self.recentListNode.frame = CGRect(origin: CGPoint(), size: size) self.recentListNode.frame = CGRect(origin: CGPoint(), size: size)
@ -1842,7 +1851,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
// } // }
let emptyTextSpacing: CGFloat = 8.0 let emptyTextSpacing: CGFloat = 8.0
let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing
let emptyAnimationY = navigationBarHeight + floorToScreenPixels((visibleHeight - navigationBarHeight - bottomInset - emptyTotalHeight) / 2.0) let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0)
let textTransition = ContainedViewLayoutTransition.immediate let textTransition = ContainedViewLayoutTransition.immediate
textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.animationSize.width) / 2.0, y: emptyAnimationY), size: self.animationSize)) textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.animationSize.width) / 2.0, y: emptyAnimationY), size: self.animationSize))
@ -1909,6 +1918,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var options = ListViewDeleteAndInsertOptions() var options = ListViewDeleteAndInsertOptions()
if firstTime { if firstTime {
options.insert(.PreferSynchronousResourceLoading)
options.insert(.PreferSynchronousDrawing) options.insert(.PreferSynchronousDrawing)
} else { } else {
options.insert(.AnimateInsertion) options.insert(.AnimateInsertion)
@ -1957,9 +1967,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if emptyResults { if emptyResults {
let emptyResultsTitle: String let emptyResultsTitle: String
let emptyResultsText: String let emptyResultsText: String
if !transition.query.isEmpty { if let query = transition.query, !query.isEmpty {
emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults
emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).0 emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(query).0
} else { } else {
if let searchOptions = searchOptions, searchOptions.minDate == nil && searchOptions.maxDate == nil && searchOptions.peer == nil { if let searchOptions = searchOptions, searchOptions.minDate == nil && searchOptions.maxDate == nil && searchOptions.peer == nil {
emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResultsFilter emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResultsFilter
@ -1995,15 +2005,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.emptyResultsTextNode.isHidden = !emptyResults strongSelf.emptyResultsTextNode.isHidden = !emptyResults
strongSelf.emptyResultsAnimationNode.visibility = emptyResults strongSelf.emptyResultsAnimationNode.visibility = emptyResults
if strongSelf.tagMask == .webPage { ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: strongSelf.shimmerNode, alpha: transition.isLoading ? 1.0 : 0.0)
strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Links")
} else if strongSelf.tagMask == .file {
strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Files")
} else if strongSelf.tagMask == .music || strongSelf.tagMask == .voiceOrInstantVideo {
strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Music")
}
strongSelf.loadingNode.isHidden = !transition.isLoading
strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent) strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent)
// strongSelf.dimNode.isHidden = displayingResults // strongSelf.dimNode.isHidden = displayingResults
@ -2058,3 +2060,313 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return nil return nil
} }
} }
private final class ShimmerEffectNode: ASDisplayNode {
private var currentBackgroundColor: UIColor?
private var currentForegroundColor: UIColor?
private let imageNodeContainer: ASDisplayNode
private let imageNode: ASImageNode
private var absoluteLocation: (CGRect, CGSize)?
private var isCurrentlyInHierarchy = false
private var shouldBeAnimating = false
override init() {
self.imageNodeContainer = ASDisplayNode()
self.imageNodeContainer.isLayerBacked = true
self.imageNode = ASImageNode()
self.imageNode.isLayerBacked = true
self.imageNode.displaysAsynchronously = false
self.imageNode.displayWithoutProcessing = true
self.imageNode.contentMode = .scaleToFill
super.init()
self.isLayerBacked = true
self.clipsToBounds = true
self.imageNodeContainer.addSubnode(self.imageNode)
self.addSubnode(self.imageNodeContainer)
}
override func didEnterHierarchy() {
super.didEnterHierarchy()
self.isCurrentlyInHierarchy = true
self.updateAnimation()
}
override func didExitHierarchy() {
super.didExitHierarchy()
self.isCurrentlyInHierarchy = false
self.updateAnimation()
}
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
return
}
self.currentBackgroundColor = backgroundColor
self.currentForegroundColor = foregroundColor
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
context.setFillColor(backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: CGPoint(), size: size))
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
let peakColor = foregroundColor.cgColor
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
return
}
let sizeUpdated = self.absoluteLocation?.1 != containerSize
let frameUpdated = self.absoluteLocation?.0 != rect
self.absoluteLocation = (rect, containerSize)
if sizeUpdated {
if self.shouldBeAnimating {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
self.addImageAnimation()
}
}
if frameUpdated {
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
}
self.updateAnimation()
}
private func updateAnimation() {
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
if shouldBeAnimating != self.shouldBeAnimating {
self.shouldBeAnimating = shouldBeAnimating
if shouldBeAnimating {
self.addImageAnimation()
} else {
self.imageNode.layer.removeAnimation(forKey: "shimmer")
}
}
}
private func addImageAnimation() {
guard let containerSize = self.absoluteLocation?.1 else {
return
}
let gradientHeight: CGFloat = 250.0
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
animation.repeatCount = Float.infinity
animation.beginTime = 1.0
self.imageNode.layer.add(animation, forKey: "shimmer")
}
}
private final class ChatListSearchShimmerNode: ASDisplayNode {
private let backgroundColorNode: ASDisplayNode
private let effectNode: ShimmerEffectNode
private let maskNode: ASImageNode
private var currentParams: (size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey)?
init(key: ChatListSearchPaneKey) {
self.backgroundColorNode = ASDisplayNode()
self.effectNode = ShimmerEffectNode()
self.maskNode = ASImageNode()
super.init()
self.isUserInteractionEnabled = false
self.addSubnode(self.backgroundColorNode)
self.addSubnode(self.effectNode)
self.addSubnode(self.maskNode)
}
func update(context: AccountContext, size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey, transition: ContainedViewLayoutTransition) {
if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData || self.currentParams?.key != key {
self.currentParams = (size, presentationData, key)
let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let timestamp1: Int32 = 100000
let peers = SimpleDictionary<PeerId, Peer>()
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in
gesture?.cancel()
}, present: { _ in })
let items = (0 ..< 1).compactMap { _ -> ListViewItem? in
switch key {
case .chats:
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
case .media:
return nil
case .links:
var media: [Media] = []
media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, attributes: [], instantPage: nil))))
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true)
case .files:
var media: [Media] = []
media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))]))
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
case .music:
var media: [Media] = []
media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))]))
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
case .voice:
var media: [Media] = []
media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: true, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))]))
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
}
}
var itemNodes: [ListViewItemNode] = []
for i in 0 ..< items.count {
items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in
itemNodes.append(node)
apply().1(ListViewItemApply(isOnScreen: true))
})
}
self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
if key == .media {
var currentY: CGFloat = 0.0
var rowIndex: Int = 0
let itemSpacing: CGFloat = 1.0
let itemsInRow = max(3, min(6, Int(size.width / 140.0)))
let itemSize: CGFloat = floor(size.width / CGFloat(itemsInRow))
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
while currentY < size.height {
for i in 0 ..< itemsInRow {
let itemOrigin = CGPoint(x: CGFloat(i) * (itemSize + itemSpacing), y: itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing))
context.fill(CGRect(origin: itemOrigin, size: CGSize(width: itemSize, height: itemSize)))
}
currentY += itemSize
rowIndex += 1
}
} else {
var currentY: CGFloat = 0.0
let fakeLabelPlaceholderHeight: CGFloat = 8.0
func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) {
let startPoint = origin
let diameter = fakeLabelPlaceholderHeight
context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter)))
context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter)))
}
while currentY < size.height {
let sampleIndex = 0
let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode {
context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNode.titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0)
let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
} else if let itemNode = itemNodes[sampleIndex] as? ListMessageFileItemNode {
if let media = itemNode.currentMedia as? TelegramMediaFile {
if media.isMusic || media.isVoice {
context.fillEllipse(in: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0))
} else {
let path = UIBezierPath(roundedRect: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0), cornerRadius: 6.0)
context.addPath(path.cgPath)
context.fillPath()
}
}
let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
let descriptionFrame = itemNode.descriptionNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: descriptionFrame.minX, y: floor(descriptionFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 240.0)
let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
} else if let itemNode = itemNodes[sampleIndex] as? ListMessageSnippetItemNode {
let path = UIBezierPath(roundedRect: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0), cornerRadius: 6.0)
context.addPath(path.cgPath)
context.fillPath()
let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 120.0)
let linkFrame = itemNode.linkNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: linkFrame.minX, y: floor(linkFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 240.0)
let authorFrame = itemNode.authorNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: authorFrame.minX, y: floor(authorFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0)
let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0)
context.setBlendMode(.normal)
context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor)
context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY))
}
currentY += itemHeight
}
}
})
self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
}
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
}
}

View File

@ -13,7 +13,6 @@ import TelegramStringFormatting
import UniversalMediaPlayer import UniversalMediaPlayer
import ListMessageItem import ListMessageItem
import ChatMessageInteractiveMediaBadge import ChatMessageInteractiveMediaBadge
import ShimmerEffect
import GridMessageSelectionNode import GridMessageSelectionNode
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
@ -49,7 +48,6 @@ private final class VisualMediaItemNode: ASDisplayNode {
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var statusNode: RadialStatusNode private var statusNode: RadialStatusNode
private let mediaBadgeNode: ChatMessageInteractiveMediaBadge private let mediaBadgeNode: ChatMessageInteractiveMediaBadge
private var placeholderNode: ShimmerEffectNode?
private var selectionNode: GridMessageSelectionNode? private var selectionNode: GridMessageSelectionNode?
private let fetchStatusDisposable = MetaDisposable() private let fetchStatusDisposable = MetaDisposable()
@ -179,7 +177,6 @@ private final class VisualMediaItemNode: ASDisplayNode {
} }
func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
self.placeholderNode?.updateAbsoluteRect(absoluteRect, within: containerSize)
} }
func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) { func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) {
@ -302,20 +299,9 @@ private final class VisualMediaItemNode: ASDisplayNode {
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size) self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size)
self.updateHiddenMedia() self.updateHiddenMedia()
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
placeholderNode.removeFromSupernode()
}
} else if item.isEmpty, self.placeholderNode == nil {
let placeholderNode = ShimmerEffectNode()
placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: size)
self.addSubnode(placeholderNode)
self.placeholderNode = placeholderNode
} }
let imageFrame = CGRect(origin: CGPoint(), size: size) let imageFrame = CGRect(origin: CGPoint(), size: size)
self.placeholderNode?.frame = imageFrame
if let (item, media, _, mediaDimensions) = self.item { if let (item, media, _, mediaDimensions) = self.item {
self.item = (item, media, size, mediaDimensions) self.item = (item, media, size, mediaDimensions)
@ -435,15 +421,6 @@ private final class VisualMediaItem {
let message: Message? let message: Message?
let dimensions: CGSize let dimensions: CGSize
let aspectRatio: CGFloat let aspectRatio: CGFloat
let isEmpty: Bool
init(index: UInt32) {
self.index = index
self.message = nil
self.dimensions = CGSize(width: 100.0, height: 100.0)
self.aspectRatio = 1.0
self.isEmpty = true
}
init(message: Message) { init(message: Message) {
self.index = nil self.index = nil
@ -461,7 +438,6 @@ private final class VisualMediaItem {
} }
self.aspectRatio = aspectRatio self.aspectRatio = aspectRatio
self.dimensions = dimensions self.dimensions = dimensions
self.isEmpty = false
} }
var stableId: UInt32 { var stableId: UInt32 {
@ -741,23 +717,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
break break
default: default:
self.mediaItems.removeAll() self.mediaItems.removeAll()
let loading: Bool
if let entries = entries { if let entries = entries {
loading = false
for entry in entries { for entry in entries {
if case let .message(message, _, _, _, _, _, _) = entry { if case let .message(message, _, _, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message)) self.mediaItems.append(VisualMediaItem(message: message))
} }
} }
} else {
loading = true
for i in 0 ..< 21 {
self.mediaItems.append(VisualMediaItem(index: UInt32(i)))
}
} }
self.itemsLayout = nil self.itemsLayout = nil
let wasInitialized = self.initialized
self.initialized = true self.initialized = true
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {

View File

@ -36,7 +36,7 @@ final class ChatListSearchPaneWrapper {
} }
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
if let (currentSize, currentSideInset, currentBottomInset, visibleHeight, currentPresentationData) = self.appliedParams { if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams {
if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData { if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData {
return return
} }
@ -76,12 +76,13 @@ private final class ChatListSearchPendingPane {
interaction: ChatListSearchInteraction, interaction: ChatListSearchInteraction,
navigationController: NavigationController?, navigationController: NavigationController?,
peersFilter: ChatListNodePeersFilter, peersFilter: ChatListNodePeersFilter,
groupId: PeerGroupId,
searchQuery: Signal<String?, NoError>, searchQuery: Signal<String?, NoError>,
searchOptions: Signal<ChatListSearchOptions?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>,
key: ChatListSearchPaneKey, key: ChatListSearchPaneKey,
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
) { ) {
let paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController) let paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], groupId: groupId, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode) self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
self.disposable = (paneNode.isReady self.disposable = (paneNode.isReady
@ -100,14 +101,12 @@ private final class ChatListSearchPendingPane {
final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private let context: AccountContext private let context: AccountContext
private let peersFilter: ChatListNodePeersFilter private let peersFilter: ChatListNodePeersFilter
private let groupId: PeerGroupId
private let searchQuery: Signal<String?, NoError> private let searchQuery: Signal<String?, NoError>
private let searchOptions: Signal<ChatListSearchOptions?, NoError> private let searchOptions: Signal<ChatListSearchOptions?, NoError>
private let navigationController: NavigationController? private let navigationController: NavigationController?
var interaction: ChatListSearchInteraction? var interaction: ChatListSearchInteraction?
private let coveringBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode
let isReady = Promise<Bool>() let isReady = Promise<Bool>()
var didSetIsReady = false var didSetIsReady = false
@ -138,23 +137,15 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
private var currentAvailablePanes: [ChatListSearchPaneKey]? private var currentAvailablePanes: [ChatListSearchPaneKey]?
init(context: AccountContext, peersFilter: ChatListNodePeersFilter, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) { init(context: AccountContext, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
self.context = context self.context = context
self.peersFilter = peersFilter self.peersFilter = peersFilter
self.groupId = groupId
self.searchQuery = searchQuery self.searchQuery = searchQuery
self.searchOptions = searchOptions self.searchOptions = searchOptions
self.navigationController = navigationController self.navigationController = navigationController
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
self.coveringBackgroundNode = ASDisplayNode()
self.coveringBackgroundNode.isLayerBacked = true
super.init() super.init()
self.addSubnode(self.separatorNode)
self.addSubnode(self.coveringBackgroundNode)
} }
func requestSelectPane(_ key: ChatListSearchPaneKey) { func requestSelectPane(_ key: ChatListSearchPaneKey) {
@ -256,7 +247,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
directionIsToRight = translation.x > size.width / 2.0 directionIsToRight = translation.x > size.width / 2.0
} }
} }
var updated = false
if let directionIsToRight = directionIsToRight { if let directionIsToRight = directionIsToRight {
var updatedIndex = currentIndex var updatedIndex = currentIndex
if directionIsToRight { if directionIsToRight {
@ -267,7 +258,6 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
let switchToKey = availablePanes[updatedIndex] let switchToKey = availablePanes[updatedIndex]
if switchToKey != self.currentPaneKey && self.currentPanes[switchToKey] != nil{ if switchToKey != self.currentPaneKey && self.currentPanes[switchToKey] != nil{
self.currentPaneKey = switchToKey self.currentPaneKey = switchToKey
updated = true
} }
} }
self.transitionFraction = 0.0 self.transitionFraction = 0.0
@ -339,18 +329,9 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
transition.updateAlpha(node: self.coveringBackgroundNode, alpha: 0.0)
self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
self.coveringBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let tabsHeight: CGFloat = 48.0 let paneFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel)))
let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: tabsHeight), size: CGSize(width: size.width, height: size.height - tabsHeight))
var visiblePaneIndices: [Int] = [] var visiblePaneIndices: [Int] = []
var requiredPendingKeys: [ChatListSearchPaneKey] = [] var requiredPendingKeys: [ChatListSearchPaneKey] = []
@ -386,6 +367,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
interaction: self.interaction!, interaction: self.interaction!,
navigationController: self.navigationController, navigationController: self.navigationController,
peersFilter: self.peersFilter, peersFilter: self.peersFilter,
groupId: self.groupId,
searchQuery: self.searchQuery, searchQuery: self.searchQuery,
searchOptions: self.searchOptions, searchOptions: self.searchOptions,
key: key, key: key,
@ -429,8 +411,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
var paneSwitchAnimationOffset: CGFloat = 0.0 var paneSwitchAnimationOffset: CGFloat = 0.0
var updatedCurrentIndex = currentIndex var updatedCurrentIndex = currentIndex
var animatePaneTransitionOffset: CGFloat? if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, let _ = self.currentPanes[pendingSwitchToPaneKey] {
if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, let pane = self.currentPanes[pendingSwitchToPaneKey] {
self.pendingSwitchToPaneKey = nil self.pendingSwitchToPaneKey = nil
previousPaneKey = self.currentPaneKey previousPaneKey = self.currentPaneKey
self.currentPaneKey = pendingSwitchToPaneKey self.currentPaneKey = pendingSwitchToPaneKey
@ -463,8 +444,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD
return return
} }
pane.isAnimatingOut = false pane.isAnimatingOut = false
if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams { if let _ = strongSelf.currentParams {
if let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 { if let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), paneIndex == 0 || abs(paneIndex - currentIndex) <= 1 {
} else { } else {
if let pane = strongSelf.currentPanes.removeValue(forKey: key) { if let pane = strongSelf.currentPanes.removeValue(forKey: key) {
pane.node.removeFromSupernode() pane.node.removeFromSupernode()

View File

@ -836,10 +836,6 @@ open class NavigationBar: ASDisplayNode {
self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden) self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden)
if let secondaryContentNode = self.secondaryContentNode {
// transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0)
}
let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0
let leftButtonInset: CGFloat = leftInset + 16.0 let leftButtonInset: CGFloat = leftInset + 16.0
@ -1217,8 +1213,8 @@ open class NavigationBar: ASDisplayNode {
public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) {
if self.secondaryContentNode !== secondaryContentNode { if self.secondaryContentNode !== secondaryContentNode {
if let previous = self.secondaryContentNode { if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode {
if animated && previous.supernode === self.clippingNode { if animated {
previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in
if finished { if finished {
previous?.removeFromSupernode() previous?.removeFromSupernode()

View File

@ -96,11 +96,10 @@ public final class HashtagSearchController: TelegramBaseController {
}) })
let firstTime = previousEntries == nil let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: { }, toggleExpandGlobalResults: {
}, searchPeer: { _ in }, searchPeer: { _ in
}, searchQuery: "", searchOptions: nil, messageContextAction: nil)
}, searchResults: [], searchOptions: nil, messageContextAction: nil)
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
} }
}) })

View File

@ -57,7 +57,7 @@ public final class HorizontalPeerItem: ListViewItem {
Queue.mainQueue().async { Queue.mainQueue().async {
completion(node, { completion(node, {
return (nil, { _ in return (nil, { _ in
apply(false) apply(false, synchronousLoads)
}) })
}) })
} }
@ -73,7 +73,7 @@ public final class HorizontalPeerItem: ListViewItem {
let (nodeLayout, apply) = layout(self, params) let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async { Queue.mainQueue().async {
completion(nodeLayout, { _ in completion(nodeLayout, { _ in
apply(animation.isAnimated) apply(animation.isAnimated, false)
}) })
} }
} }
@ -126,7 +126,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} }
public func asyncLayout() -> (HorizontalPeerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { public func asyncLayout() -> (HorizontalPeerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode) let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
let onlineLayout = self.onlineNode.asyncLayout() let onlineLayout = self.onlineNode.asyncLayout()
@ -184,11 +184,11 @@ public final class HorizontalPeerItemNode: ListViewItemNode {
animateContent = true animateContent = true
} }
return (itemLayout, { animated in return (itemLayout, { animated, synchronousLoads in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.peerNode.theme = itemTheme strongSelf.peerNode.theme = itemTheme
strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: RenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: false) strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: RenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: synchronousLoads)
strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size) strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size)
strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false) strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false)

View File

@ -134,14 +134,14 @@ public final class ListMessageFileItemNode: ListMessageNode {
private let offsetContainerNode: ASDisplayNode private let offsetContainerNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode public let separatorNode: ASDisplayNode
private var selectionNode: ItemListSelectableControlNode? private var selectionNode: ItemListSelectableControlNode?
private let titleNode: TextNode public let titleNode: TextNode
private let descriptionNode: TextNode public let descriptionNode: TextNode
private let descriptionProgressNode: ImmediateTextNode private let descriptionProgressNode: ImmediateTextNode
private let dateNode: TextNode public let dateNode: TextNode
private let extensionIconNode: ASImageNode private let extensionIconNode: ASImageNode
private let extensionIconText: TextNode private let extensionIconText: TextNode
@ -149,7 +149,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
private let iconStatusNode: SemanticStatusNode private let iconStatusNode: SemanticStatusNode
private var currentIconImage: FileIconImage? private var currentIconImage: FileIconImage?
private var currentMedia: Media? public var currentMedia: Media?
private let statusDisposable = MetaDisposable() private let statusDisposable = MetaDisposable()
private let fetchControls = Atomic<FetchControls?>(value: nil) private let fetchControls = Atomic<FetchControls?>(value: nil)

View File

@ -28,6 +28,17 @@ public final class ListMessageItemInteraction {
self.longTap = longTap self.longTap = longTap
self.getHiddenMedia = getHiddenMedia self.getHiddenMedia = getHiddenMedia
} }
public static var `default`: ListMessageItemInteraction = ListMessageItemInteraction(openMessage: { _, _ in
return false
}, openMessageContextMenu: { _, _, _, _, _ in
}, toggleMessagesSelection: { _, _ in
}, openUrl: { _, _, _, _ in
}, openInstantPage: { _, _ in
}, longTap: { _, _ in
}, getHiddenMedia: { () -> [MessageId : [Media]] in
return [:]
})
} }
public final class ListMessageItem: ListViewItem { public final class ListMessageItem: ListViewItem {

View File

@ -31,17 +31,17 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
private var nonExtractedRect: CGRect? private var nonExtractedRect: CGRect?
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
private let separatorNode: ASDisplayNode public let separatorNode: ASDisplayNode
private var selectionNode: ItemListSelectableControlNode? private var selectionNode: ItemListSelectableControlNode?
private let titleNode: TextNode public let titleNode: TextNode
private let descriptionNode: TextNode let descriptionNode: TextNode
private let dateNode: TextNode public let dateNode: TextNode
private let instantViewIconNode: ASImageNode private let instantViewIconNode: ASImageNode
private let linkNode: TextNode public let linkNode: TextNode
private var linkHighlightingNode: LinkHighlightingNode? private var linkHighlightingNode: LinkHighlightingNode?
private let authorNode: TextNode public let authorNode: TextNode
private let iconTextBackgroundNode: ASImageNode private let iconTextBackgroundNode: ASImageNode
private let iconTextNode: TextNode private let iconTextNode: TextNode

View File

@ -126,8 +126,16 @@ public final class SearchDisplayController {
let bounds = CGRect(origin: CGPoint(), size: layout.size) let bounds = CGRect(origin: CGPoint(), size: layout.size)
transition.updateFrame(node: self.backgroundNode, frame: bounds.insetBy(dx: -20.0, dy: -20.0)) transition.updateFrame(node: self.backgroundNode, frame: bounds.insetBy(dx: -20.0, dy: -20.0))
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 20.0, y: 20.0), size: layout.size))
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) var size = layout.size
size.width += 20.0 * 2.0
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size))
var safeInsets = layout.safeInsets
safeInsets.left += 20.0
safeInsets.right += 20.0
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
} }
public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) { public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) {
@ -142,29 +150,36 @@ public final class SearchDisplayController {
self.backgroundNode.backgroundColor = .clear self.backgroundNode.backgroundColor = .clear
} }
self.contentNode.frame = CGRect(origin: CGPoint(x: 20.0, y: 20.0), size: layout.size) var size = layout.size
size.width += 20.0 * 2.0
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate) self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size)
var safeInsets = layout.safeInsets
safeInsets.left += 20.0
safeInsets.right += 20.0
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate)
var contentNavigationBarHeight = navigationBarHeight var contentNavigationBarHeight = navigationBarHeight
if layout.statusBarHeight == nil { if layout.statusBarHeight == nil {
contentNavigationBarHeight += 28.0 contentNavigationBarHeight += 28.0
} }
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
if !self.contentNode.hasDim {
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
if let placeholder = placeholder {
self.searchBar.placeholderString = placeholder.placeholderString
}
} else {
if let placeholder = placeholder { if let placeholder = placeholder {
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil) let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
let contentNodePosition = self.backgroundNode.layer.position let contentNodePosition = self.backgroundNode.layer.position
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
if !self.contentNode.hasDim {
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
} else {
self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
self.searchBar.placeholderString = placeholder.placeholderString self.searchBar.placeholderString = placeholder.placeholderString
} }
}
let navigationBarFrame: CGRect let navigationBarFrame: CGRect
switch self.mode { switch self.mode {

View File

@ -114,7 +114,7 @@ public final class SelectablePeerNode: ASDisplayNode {
self.textNode = ASTextNode() self.textNode = ASTextNode()
self.textNode.isUserInteractionEnabled = false self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = true self.textNode.displaysAsynchronously = false
self.onlineNode = PeerOnlineMarkerNode() self.onlineNode = PeerOnlineMarkerNode()

View File

@ -372,10 +372,6 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
return self._isSearching.get() return self._isSearching.get()
} }
public override var hasDim: Bool {
return true
}
init(context: AccountContext, openResult: @escaping (ChatContextResult) -> Void) { init(context: AccountContext, openResult: @escaping (ChatContextResult) -> Void) {
self.context = context self.context = context
self.queryPromise = Promise<WallpaperSearchQuery>(self.queryValue) self.queryPromise = Promise<WallpaperSearchQuery>(self.queryValue)

View File

@ -8,7 +8,7 @@ import SyncCore
public enum SearchMessagesLocation: Equatable { public enum SearchMessagesLocation: Equatable {
case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?)
case group(PeerGroupId) case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?)
case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?) case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?)
case publicForwards(messageId: MessageId, datacenterId: Int?) case publicForwards(messageId: MessageId, datacenterId: Int?)
} }
@ -270,9 +270,15 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
} }
return combineLatest(peerMessages, additionalPeerMessages) return combineLatest(peerMessages, additionalPeerMessages)
} }
case .group: case let .general(tags, minDate, maxDate), let .group(_, tags, minDate, maxDate):
remoteSearchResult = .single((nil, nil)) var flags: Int32 = 0
case let .general(tags, minDate, maxDate): let folderId: Int32?
if case let .group(groupId, _, _, _) = location {
folderId = groupId.rawValue
flags |= (1 << 0)
} else {
folderId = nil
}
let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex? var lowerBound: MessageIndex?
@ -286,7 +292,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
} }
} }
|> mapToSignal { (nextRate, lowerBound, inputPeer) in |> mapToSignal { (nextRate, lowerBound, inputPeer) in
return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) return account.network.request(Api.functions.messages.searchGlobal(flags: flags, folderId: folderId, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in |> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil) return (result, nil)
} }

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Files.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Links.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Music.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -122,7 +122,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
func scrollToTop() -> Bool { func scrollToTop() -> Bool {
if !self.listNode.scrollToOffsetFromTop(0.0) { if !self.listNode.scrollToOffsetFromTop(0.0) {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), 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 {
return false return false

View File

@ -165,7 +165,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
func scrollToTop() -> Bool { func scrollToTop() -> Bool {
if !self.listNode.scrollToOffsetFromTop(0.0) { if !self.listNode.scrollToOffsetFromTop(0.0) {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), 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 {
return false return false

View File

@ -4841,11 +4841,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
private func activateSearch() { private func activateSearch() {
guard let (layout, navigationBarHeight) = self.validLayout else { guard let (layout, navigationBarHeight) = self.validLayout, self.searchDisplayController == nil else {
return
}
if let _ = self.searchDisplayController {
return return
} }
@ -4912,16 +4908,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
transition.updateAlpha(node: navigationBar, alpha: 0.0) transition.updateAlpha(node: navigationBar, alpha: 0.0)
} }
self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight + 10.0, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in
if let strongSelf = self, let navigationBar = strongSelf.controller?.navigationBar { if let strongSelf = self, let navigationBar = strongSelf.controller?.navigationBar {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
} }
}, placeholder: nil) }, placeholder: nil)
if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout: layout, navigationHeight: navigationBarHeight, transition: .immediate)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
}
} }
private func deactivateSearch() { private func deactivateSearch() {
@ -4958,7 +4952,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
if let searchDisplayController = self.searchDisplayController { if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition) searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight + 10.0, transition: transition)
if !searchDisplayController.isDeactivating { if !searchDisplayController.isDeactivating {
//vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta //vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta
} }