mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Search filters improvements
This commit is contained in:
parent
bb27a2ba5d
commit
cefc01f28c
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,56 +88,54 @@ 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 (nodeLayout, { [weak self] synchronousLoads in
|
||||||
return (nil, { _ in
|
if let strongSelf = self {
|
||||||
if let strongSelf = self {
|
strongSelf.item = item
|
||||||
strongSelf.item = item
|
|
||||||
|
if let _ = updatedTheme {
|
||||||
if let _ = updatedTheme {
|
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
|
||||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
|
|
||||||
}
|
|
||||||
|
|
||||||
let peersNode: ChatListSearchRecentPeersNode
|
|
||||||
if let currentPeersNode = strongSelf.peersNode {
|
|
||||||
peersNode = currentPeersNode
|
|
||||||
peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings)
|
|
||||||
} else {
|
|
||||||
peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in
|
|
||||||
self?.item?.peerSelected(peer)
|
|
||||||
}, peerContextAction: { peer, node, gesture in
|
|
||||||
self?.item?.peerContextAction(peer, node, gesture)
|
|
||||||
}, isPeerSelected: { _ in
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
strongSelf.peersNode = peersNode
|
|
||||||
strongSelf.addSubnode(peersNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
peersNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize)
|
|
||||||
peersNode.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
|
||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
|
||||||
strongSelf.separatorNode.isHidden = true
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
let peersNode: ChatListSearchRecentPeersNode
|
||||||
|
if let currentPeersNode = strongSelf.peersNode {
|
||||||
|
peersNode = currentPeersNode
|
||||||
|
peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings)
|
||||||
|
} else {
|
||||||
|
peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in
|
||||||
|
self?.item?.peerSelected(peer)
|
||||||
|
}, peerContextAction: { peer, node, gesture in
|
||||||
|
self?.item?.peerContextAction(peer, node, gesture)
|
||||||
|
}, isPeerSelected: { _ in
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
strongSelf.peersNode = peersNode
|
||||||
|
strongSelf.addSubnode(peersNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
peersNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize)
|
||||||
|
peersNode.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||||
|
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||||
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||||
|
strongSelf.separatorNode.isHidden = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,8 +733,9 @@ 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,11 +877,18 @@ 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 = .general(tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
location = .general(tags: self.tagMask, minDate: nil, maxDate: nil)
|
if let groupId = groupId {
|
||||||
|
location = .group(groupId: groupId, tags: self.tagMask, minDate: nil, maxDate: nil)
|
||||||
|
} else {
|
||||||
|
location = .general(tags: self.tagMask, minDate: nil, maxDate: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalQuery = query ?? ""
|
let finalQuery = query ?? ""
|
||||||
@ -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
|
||||||
@ -1994,16 +2004,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
|
strongSelf.emptyResultsTitleNode.isHidden = !emptyResults
|
||||||
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
@ -338,19 +328,10 @@ 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()
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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,28 +150,35 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if let placeholder = placeholder {
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||||
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
if !self.contentNode.hasDim {
|
||||||
|
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
let contentNodePosition = self.backgroundNode.layer.position
|
if let placeholder = placeholder {
|
||||||
|
self.searchBar.placeholderString = placeholder.placeholderString
|
||||||
|
}
|
||||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
} else {
|
||||||
if !self.contentNode.hasDim {
|
if let placeholder = placeholder {
|
||||||
self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
||||||
} else {
|
let contentNodePosition = self.backgroundNode.layer.position
|
||||||
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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -371,11 +371,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode {
|
|||||||
override var isSearching: Signal<Bool, NoError> {
|
override var isSearching: Signal<Bool, NoError> {
|
||||||
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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 |
@ -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 |
@ -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 |
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user