Global search

This commit is contained in:
Isaac 2025-07-27 15:01:53 +02:00
parent b8ac955792
commit e315e6da6e
20 changed files with 265 additions and 105 deletions

View File

@ -157,6 +157,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let sharedOpenStoryDisposable = MetaDisposable() private let sharedOpenStoryDisposable = MetaDisposable()
private var recentAppsDisposable: Disposable? private var recentAppsDisposable: Disposable?
private var refreshedGlobalPostSearchStateDisposable: Disposable?
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, parentController: @escaping () -> ViewController?) { public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, parentController: @escaping () -> ViewController?) {
var initialFilter = initialFilter var initialFilter = initialFilter
@ -531,6 +532,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
self.recentAppsDisposable = context.engine.peers.managedUpdatedRecentApps().startStrict() self.recentAppsDisposable = context.engine.peers.managedUpdatedRecentApps().startStrict()
self.refreshedGlobalPostSearchStateDisposable = context.engine.messages.refreshGlobalPostSearchState().startStrict()
self._ready.set(self.paneContainerNode.isReady.get() self._ready.set(self.paneContainerNode.isReady.get()
|> map { _ in Void() }) |> map { _ in Void() })
@ -543,6 +545,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.shareStatusDisposable?.dispose() self.shareStatusDisposable?.dispose()
self.sharedOpenStoryDisposable.dispose() self.sharedOpenStoryDisposable.dispose()
self.recentAppsDisposable?.dispose() self.recentAppsDisposable?.dispose()
self.refreshedGlobalPostSearchStateDisposable?.dispose()
self.copyProtectionTooltipController?.dismiss() self.copyProtectionTooltipController?.dismiss()
} }

View File

@ -1298,11 +1298,10 @@ public struct ChatListSearchContainerTransition {
public let isLoading: Bool public let isLoading: Bool
public let query: String? public let query: String?
public let approvedGlobalPostQueryState: ApprovedGlobalPostQueryState? public let approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?
public let remainingGlobalSearches: Int public let globalSearchStateValue: TelegramGlobalPostSearchState?
public let globalSearchUnlockTimestamp: Int32?
public var animated: Bool public var animated: Bool
public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String?, approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?, remainingGlobalSearches: Int, globalSearchUnlockTimestamp: Int32?, animated: Bool) { public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String?, approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?, globalSearchStateValue: TelegramGlobalPostSearchState?, animated: Bool) {
self.deletions = deletions self.deletions = deletions
self.insertions = insertions self.insertions = insertions
self.updates = updates self.updates = updates
@ -1310,8 +1309,7 @@ public struct ChatListSearchContainerTransition {
self.isEmpty = isEmpty self.isEmpty = isEmpty
self.isLoading = isLoading self.isLoading = isLoading
self.approvedGlobalPostQueryState = approvedGlobalPostQueryState self.approvedGlobalPostQueryState = approvedGlobalPostQueryState
self.remainingGlobalSearches = remainingGlobalSearches self.globalSearchStateValue = globalSearchStateValue
self.globalSearchUnlockTimestamp = globalSearchUnlockTimestamp
self.query = query self.query = query
self.animated = animated self.animated = animated
} }
@ -1376,8 +1374,7 @@ public func chatListSearchContainerPreparedTransition(
searchPeer: @escaping (EnginePeer) -> Void, searchPeer: @escaping (EnginePeer) -> Void,
searchQuery: String?, searchQuery: String?,
approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?, approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?,
remainingGlobalSearches: Int, globalSearchStateValue: TelegramGlobalPostSearchState?,
globalSearchUnlockTimestamp: Int32?,
searchOptions: ChatListSearchOptions?, searchOptions: ChatListSearchOptions?,
messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?,
openClearRecentlyDownloaded: @escaping () -> Void, openClearRecentlyDownloaded: @escaping () -> Void,
@ -1393,7 +1390,7 @@ public func chatListSearchContainerPreparedTransition(
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), 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, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), 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, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), 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, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), directionHint: nil) }
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, approvedGlobalPostQueryState: approvedGlobalPostQueryState, remainingGlobalSearches: remainingGlobalSearches, globalSearchUnlockTimestamp: globalSearchUnlockTimestamp, animated: animated) return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, approvedGlobalPostQueryState: approvedGlobalPostQueryState, globalSearchStateValue: globalSearchStateValue, animated: animated)
} }
private struct ChatListSearchListPaneNodeState: Equatable { private struct ChatListSearchListPaneNodeState: Equatable {
@ -1620,16 +1617,6 @@ public struct ApprovedGlobalPostQueryState: Equatable {
} }
} }
struct TelegramGlobalPostSearchState: Equatable {
var remainingSearches: Int
var unlockTimestamp: Int32?
init(remainingSearches: Int, unlockTimestamp: Int32?) {
self.remainingSearches = remainingSearches
self.unlockTimestamp = unlockTimestamp
}
}
final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private let context: AccountContext private let context: AccountContext
private let animationCache: AnimationCache private let animationCache: AnimationCache
@ -1676,7 +1663,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var searchQueryValue: String? private var searchQueryValue: String?
private var searchOptionsValue: ChatListSearchOptions? private var searchOptionsValue: ChatListSearchOptions?
private var approvedGlobalPostQueryStateValue: ApprovedGlobalPostQueryState? private var approvedGlobalPostQueryStateValue: ApprovedGlobalPostQueryState?
private var globalPostSearchStateValue: TelegramGlobalPostSearchState private var globalPostSearchStateValue: TelegramGlobalPostSearchState?
private var globalPostSearchUnlockTimer: Foundation.Timer? private var globalPostSearchUnlockTimer: Foundation.Timer?
private var isPremium: Bool = false private var isPremium: Bool = false
@ -1737,7 +1724,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private let searchScopePromise = ValuePromise<TelegramSearchPeersScope>(.everywhere) private let searchScopePromise = ValuePromise<TelegramSearchPeersScope>(.everywhere)
private let approvedGlobalPostQueryState = ValuePromise<ApprovedGlobalPostQueryState?>(nil, ignoreRepeated: true) private let approvedGlobalPostQueryState = ValuePromise<ApprovedGlobalPostQueryState?>(nil, ignoreRepeated: true)
private let globalPostSearchState = Promise<TelegramGlobalPostSearchState>() private let globalPostSearchState = Promise<TelegramGlobalPostSearchState?>()
private var refreshGlobalPostSearchStateDisposable: Disposable?
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?, parentController: ViewController?, globalPeerSearchContext: GlobalPeerSearchContext?) { init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?, parentController: ViewController?, globalPeerSearchContext: GlobalPeerSearchContext?) {
self.context = context self.context = context
@ -1752,8 +1741,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let globalPeerSearchContext = globalPeerSearchContext ?? GlobalPeerSearchContext() let globalPeerSearchContext = globalPeerSearchContext ?? GlobalPeerSearchContext()
self.globalPeerSearchContext = globalPeerSearchContext self.globalPeerSearchContext = globalPeerSearchContext
self.globalPostSearchStateValue = TelegramGlobalPostSearchState(remainingSearches: 2, unlockTimestamp: nil)
var peersFilter = peersFilter var peersFilter = peersFilter
if case .forum = location { if case .forum = location {
@ -2056,6 +2043,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let globalPostSearchStateType = self.globalPostSearchState.get() let globalPostSearchStateType = self.globalPostSearchState.get()
|> map { state -> Bool in |> map { state -> Bool in
guard let state else {
return false
}
return state.unlockTimestamp != nil return state.unlockTimestamp != nil
} }
|> distinctUntilChanged |> distinctUntilChanged
@ -3624,7 +3614,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
listInteraction?.searchTextHighightState = query listInteraction?.searchTextHighightState = query
chatListInteraction?.searchTextHighightState = query chatListInteraction?.searchTextHighightState = query
}) })
self.globalPostSearchState.set(.single(self.globalPostSearchStateValue)) self.globalPostSearchState.set(context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.GlobalPostSearchState()
))
self.approvedSearchQueryDisposable = (combineLatest(queue: .mainQueue(), self.approvedGlobalPostQueryState.get(), self.globalPostSearchState.get(), isPremium) self.approvedSearchQueryDisposable = (combineLatest(queue: .mainQueue(), self.approvedGlobalPostQueryState.get(), self.globalPostSearchState.get(), isPremium)
|> deliverOnMainQueue).startStrict(next: { [weak self] approvedGlobalPostQueryState, globalPostSearchState, isPremium in |> deliverOnMainQueue).startStrict(next: { [weak self] approvedGlobalPostQueryState, globalPostSearchState, isPremium in
guard let self else { guard let self else {
@ -3634,27 +3626,25 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.globalPostSearchStateValue = globalPostSearchState self.globalPostSearchStateValue = globalPostSearchState
self.isPremium = isPremium self.isPremium = isPremium
if globalPostSearchState.unlockTimestamp != nil { if let globalPostSearchState, globalPostSearchState.unlockTimestamp != nil {
if self.globalPostSearchUnlockTimer == nil { if self.globalPostSearchUnlockTimer == nil {
self.globalPostSearchUnlockTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in self.globalPostSearchUnlockTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
guard let self else { guard let self else {
return return
} }
if let unlockTimestamp = self.globalPostSearchStateValue.unlockTimestamp { if let unlockTimestamp = self.globalPostSearchStateValue?.unlockTimestamp {
var remainingTime: Int32 = unlockTimestamp - Int32(Date().timeIntervalSince1970) var remainingTime: Int32 = unlockTimestamp - Int32(Date().timeIntervalSince1970)
remainingTime = max(0, remainingTime) remainingTime = max(0, remainingTime)
if remainingTime == 0 { if remainingTime == 0 {
if let globalPostSearchUnlockTimer = self.globalPostSearchUnlockTimer { if self.refreshGlobalPostSearchStateDisposable == nil {
self.globalPostSearchUnlockTimer = nil self.refreshGlobalPostSearchStateDisposable = self.context.engine.messages.refreshGlobalPostSearchState().start(completed: { [weak self] in
globalPostSearchUnlockTimer.invalidate() guard let self else {
return
}
self.refreshGlobalPostSearchStateDisposable = nil
})
} }
//TODO:localize
var globalPostSearchState = self.globalPostSearchStateValue
globalPostSearchState.unlockTimestamp = nil
globalPostSearchState.remainingSearches = 2
self.globalPostSearchState.set(.single(globalPostSearchState))
} }
} }
}) })
@ -3816,8 +3806,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, },
searchQuery: strongSelf.searchQueryValue, searchQuery: strongSelf.searchQueryValue,
approvedGlobalPostQueryState: strongSelf.approvedGlobalPostQueryStateValue, approvedGlobalPostQueryState: strongSelf.approvedGlobalPostQueryStateValue,
remainingGlobalSearches: strongSelf.globalPostSearchStateValue.remainingSearches, globalSearchStateValue: strongSelf.globalPostSearchStateValue,
globalSearchUnlockTimestamp: strongSelf.globalPostSearchStateValue.unlockTimestamp,
searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource) interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource)
}, openClearRecentlyDownloaded: { }, openClearRecentlyDownloaded: {
@ -4694,6 +4683,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.approvedSearchQueryDisposable?.dispose() self.approvedSearchQueryDisposable?.dispose()
self.searchOptionsDisposable?.dispose() self.searchOptionsDisposable?.dispose()
self.globalPostSearchUnlockTimer?.invalidate() self.globalPostSearchUnlockTimer?.invalidate()
self.refreshGlobalPostSearchStateDisposable?.dispose()
} }
override func didLoad() { override func didLoad() {
@ -4767,6 +4757,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
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) {
let hadValidLayout = self.currentParams != nil let hadValidLayout = self.currentParams != nil
let layoutChanged = self.currentParams?.size != size || self.currentParams?.sideInset != sideInset || self.currentParams?.bottomInset != bottomInset || self.currentParams?.visibleHeight != visibleHeight
self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData)
var topPanelHeight: CGFloat = 0.0 var topPanelHeight: CGFloat = 0.0
@ -5128,8 +5119,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if let searchQueryValue = self.searchQueryValue, !searchQueryValue.isEmpty, self.approvedGlobalPostQueryStateValue?.query != searchQueryValue { if let searchQueryValue = self.searchQueryValue, !searchQueryValue.isEmpty, self.approvedGlobalPostQueryStateValue?.query != searchQueryValue {
var price: Int? var price: Int?
var globalPostSearchStateValue = self.globalPostSearchStateValue if let globalPostSearchStateValue = self.globalPostSearchStateValue, globalPostSearchStateValue.remainingFreeSearches == 0 {
if globalPostSearchStateValue.remainingSearches == 0 {
//TODO:localize //TODO:localize
price = 10 price = 10
} }
@ -5139,15 +5129,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
price: price price: price
)) ))
if globalPostSearchStateValue.remainingSearches > 0 {
globalPostSearchStateValue.remainingSearches -= 1
if globalPostSearchStateValue.remainingSearches == 0 {
globalPostSearchStateValue.unlockTimestamp = Int32(Date().timeIntervalSince1970) + 30
}
}
self.globalPostSearchState.set(.single(globalPostSearchStateValue))
if let price { if let price {
//TODO:localize //TODO:localize
if let controller = self.navigationController?.topViewController as? ViewController { if let controller = self.navigationController?.topViewController as? ViewController {
@ -5230,7 +5211,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
emptyTitleY = emptyAnimationY emptyTitleY = emptyAnimationY
} }
let textTransition = ContainedViewLayoutTransition.immediate let textTransition: ContainedViewLayoutTransition = layoutChanged ? transition : .immediate
textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.emptyResultsAnimationSize.width) / 2.0, y: emptyAnimationY), size: self.emptyResultsAnimationSize)) textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.emptyResultsAnimationSize.width) / 2.0, y: emptyAnimationY), size: self.emptyResultsAnimationSize))
textTransition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize)) textTransition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize))
textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize))
@ -5240,20 +5221,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if let emptyButtonView = self.emptyResultsButton?.view, let emptyButtonSize { if let emptyButtonView = self.emptyResultsButton?.view, let emptyButtonSize {
nextY += emptyButtonSpacing nextY += emptyButtonSpacing
var emptyButtonTransition = textTransition
if emptyButtonView.superview == nil { if emptyButtonView.superview == nil {
emptyButtonTransition = .immediate
self.view.insertSubview(emptyButtonView, aboveSubview: self.emptyResultsTextNode.view) self.view.insertSubview(emptyButtonView, aboveSubview: self.emptyResultsTextNode.view)
} }
emptyButtonView.frame = CGRect(origin: CGPoint(x: floor((size.width - emptyButtonSize.width) * 0.5), y: nextY), size: emptyButtonSize) emptyButtonTransition.updateFrame(view: emptyButtonView, frame: CGRect(origin: CGPoint(x: floor((size.width - emptyButtonSize.width) * 0.5), y: nextY), size: emptyButtonSize))
nextY += emptyButtonSize.height nextY += emptyButtonSize.height
} }
if let emptyButtonSubtitleView = self.emptyResultsButtonSubtitle?.view, let emptyButtonSubtitleSize { if let emptyButtonSubtitleView = self.emptyResultsButtonSubtitle?.view, let emptyButtonSubtitleSize {
nextY += emptyButtonSubtitleSpacing nextY += emptyButtonSubtitleSpacing
var emptyButtonSubtitleTransition = textTransition
if emptyButtonSubtitleView.superview == nil { if emptyButtonSubtitleView.superview == nil {
emptyButtonSubtitleTransition = .immediate
self.view.insertSubview(emptyButtonSubtitleView, aboveSubview: self.emptyResultsTextNode.view) self.view.insertSubview(emptyButtonSubtitleView, aboveSubview: self.emptyResultsTextNode.view)
} }
emptyButtonSubtitleView.frame = CGRect(origin: CGPoint(x: floor((size.width - emptyButtonSubtitleSize.width) * 0.5), y: nextY), size: emptyButtonSubtitleSize) emptyButtonSubtitleTransition.updateFrame(view: emptyButtonSubtitleView, frame: CGRect(origin: CGPoint(x: floor((size.width - emptyButtonSubtitleSize.width) * 0.5), y: nextY), size: emptyButtonSubtitleSize))
nextY += emptyButtonSubtitleSize.height nextY += emptyButtonSubtitleSize.height
} }
@ -5408,7 +5393,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var emptyResultsButtonContent: EmptyResultsButton.Content? var emptyResultsButtonContent: EmptyResultsButton.Content?
var emptyResultsButtonSubtitleText: String? var emptyResultsButtonSubtitleText: String?
if strongSelf.key == .globalPosts { if strongSelf.key == .globalPosts, let globalSearchStateValue = transition.globalSearchStateValue {
if !strongSelf.isPremium { if !strongSelf.isPremium {
emptyResultsButtonContent = .premiumRequired emptyResultsButtonContent = .premiumRequired
emptyResultsTitle = "Global Search" emptyResultsTitle = "Global Search"
@ -5420,13 +5405,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults
emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(query).string emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(query).string
} else { } else {
if transition.remainingGlobalSearches != 0 { if globalSearchStateValue.remainingFreeSearches != 0 {
emptyResultsTitle = "Global Search" emptyResultsTitle = "Global Search"
emptyResultsText = "Type a keyword to search all posts\nfrom public channels." emptyResultsText = "Type a keyword to search all posts\nfrom public channels."
if transition.remainingGlobalSearches == 1 { if globalSearchStateValue.remainingFreeSearches == 1 {
emptyResultsButtonSubtitleText = "1 free search remaining today." emptyResultsButtonSubtitleText = "1 free search remaining today."
} else { } else {
emptyResultsButtonSubtitleText = "\(transition.remainingGlobalSearches) free searches remaining today." emptyResultsButtonSubtitleText = "\(globalSearchStateValue.remainingFreeSearches) free searches remaining today."
} }
emptyResultsButtonContent = .searchQuery(query) emptyResultsButtonContent = .searchQuery(query)
@ -5435,29 +5420,29 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
emptyResultsText = "You can make up to\n10 search queries per day." emptyResultsText = "You can make up to\n10 search queries per day."
emptyResultsButtonContent = .paidSearch( emptyResultsButtonContent = .paidSearch(
price: 10, price: Int(globalSearchStateValue.price.value),
timestamp: transition.globalSearchUnlockTimestamp timestamp: globalSearchStateValue.unlockTimestamp
) )
} }
} }
} else { } else {
if transition.remainingGlobalSearches != 0 { if globalSearchStateValue.remainingFreeSearches != 0 {
emptyResultsButtonContent = .searchEmpty emptyResultsButtonContent = .searchEmpty
emptyResultsTitle = "Global Search" emptyResultsTitle = "Global Search"
emptyResultsText = "Type a keyword to search all posts\nfrom public channels." emptyResultsText = "Type a keyword to search all posts\nfrom public channels."
if transition.remainingGlobalSearches == 1 { if globalSearchStateValue.remainingFreeSearches == 1 {
emptyResultsButtonSubtitleText = "1 free search remaining today." emptyResultsButtonSubtitleText = "1 free search remaining today."
} else { } else {
emptyResultsButtonSubtitleText = "\(transition.remainingGlobalSearches) free searches remaining today." emptyResultsButtonSubtitleText = "\(globalSearchStateValue.remainingFreeSearches) free searches remaining today."
} }
} else { } else {
emptyResultsTitle = "Limit Reached" emptyResultsTitle = "Limit Reached"
emptyResultsText = "You can make up to\n10 search queries per day." emptyResultsText = "You can make up to\n10 search queries per day."
emptyResultsButtonContent = .paidSearch( emptyResultsButtonContent = .paidSearch(
price: 10, price: Int(globalSearchStateValue.price.value),
timestamp: transition.globalSearchUnlockTimestamp timestamp: globalSearchStateValue.unlockTimestamp
) )
} }
} }

View File

@ -881,6 +881,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) } dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) }
dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) } dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) }
dict[514213599] = { return Api.SavedStarGift.parse_savedStarGift($0) } dict[514213599] = { return Api.SavedStarGift.parse_savedStarGift($0) }
dict[-1810993028] = { return Api.SearchPostsFlood.parse_searchPostsFlood($0) }
dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) } dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) }
dict[2137295719] = { return Api.SearchResultsPosition.parse_searchResultPosition($0) } dict[2137295719] = { return Api.SearchResultsPosition.parse_searchResultPosition($0) }
dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) } dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) }
@ -1381,7 +1382,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-948520370] = { return Api.messages.Messages.parse_channelMessages($0) } dict[-948520370] = { return Api.messages.Messages.parse_channelMessages($0) }
dict[-1938715001] = { return Api.messages.Messages.parse_messages($0) } dict[-1938715001] = { return Api.messages.Messages.parse_messages($0) }
dict[1951620897] = { return Api.messages.Messages.parse_messagesNotModified($0) } dict[1951620897] = { return Api.messages.Messages.parse_messagesNotModified($0) }
dict[978610270] = { return Api.messages.Messages.parse_messagesSlice($0) } dict[1982539325] = { return Api.messages.Messages.parse_messagesSlice($0) }
dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) } dict[-83926371] = { return Api.messages.MyStickers.parse_myStickers($0) }
dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) } dict[863093588] = { return Api.messages.PeerDialogs.parse_peerDialogs($0) }
dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) } dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) }
@ -2099,6 +2100,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.SavedStarGift: case let _1 as Api.SavedStarGift:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.SearchPostsFlood:
_1.serialize(buffer, boxed)
case let _1 as Api.SearchResultsCalendarPeriod: case let _1 as Api.SearchResultsCalendarPeriod:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.SearchResultsPosition: case let _1 as Api.SearchResultsPosition:

View File

@ -294,6 +294,54 @@ public extension Api {
} }
} }
public extension Api {
enum SearchPostsFlood: TypeConstructorDescription {
case searchPostsFlood(flags: Int32, remains: Int32, waitTill: Int32?, starsAmount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchPostsFlood(let flags, let remains, let waitTill, let starsAmount):
if boxed {
buffer.appendInt32(-1810993028)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(remains, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(waitTill!, buffer: buffer, boxed: false)}
serializeInt64(starsAmount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchPostsFlood(let flags, let remains, let waitTill, let starsAmount):
return ("searchPostsFlood", [("flags", flags as Any), ("remains", remains as Any), ("waitTill", waitTill as Any), ("starsAmount", starsAmount as Any)])
}
}
public static func parse_searchPostsFlood(_ reader: BufferReader) -> SearchPostsFlood? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Int64?
_4 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.SearchPostsFlood.searchPostsFlood(flags: _1!, remains: _2!, waitTill: _3, starsAmount: _4!)
}
else {
return nil
}
}
}
}
public extension Api { public extension Api {
enum SearchResultsCalendarPeriod: TypeConstructorDescription { enum SearchResultsCalendarPeriod: TypeConstructorDescription {
case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32)

View File

@ -633,7 +633,7 @@ public extension Api.messages {
case channelMessages(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) case channelMessages(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User])
case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case messages(messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
case messagesNotModified(count: Int32) case messagesNotModified(count: Int32)
case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) case messagesSlice(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, searchFlood: Api.SearchPostsFlood?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -692,14 +692,15 @@ public extension Api.messages {
} }
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
break break
case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let searchFlood, let messages, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(978610270) buffer.appendInt32(1982539325)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextRate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {searchFlood!.serialize(buffer, true)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count)) buffer.appendInt32(Int32(messages.count))
for item in messages { for item in messages {
@ -727,8 +728,8 @@ public extension Api.messages {
return ("messages", [("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) return ("messages", [("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
case .messagesNotModified(let count): case .messagesNotModified(let count):
return ("messagesNotModified", [("count", count as Any)]) return ("messagesNotModified", [("count", count as Any)])
case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let messages, let chats, let users): case .messagesSlice(let flags, let count, let nextRate, let offsetIdOffset, let searchFlood, let messages, let chats, let users):
return ("messagesSlice", [("flags", flags as Any), ("count", count as Any), ("nextRate", nextRate as Any), ("offsetIdOffset", offsetIdOffset as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) return ("messagesSlice", [("flags", flags as Any), ("count", count as Any), ("nextRate", nextRate as Any), ("offsetIdOffset", offsetIdOffset as Any), ("searchFlood", searchFlood as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)])
} }
} }
@ -815,27 +816,32 @@ public extension Api.messages {
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _4: Int32? var _4: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() } if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readInt32() }
var _5: [Api.Message]? var _5: Api.SearchPostsFlood?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood
} }
var _6: [Api.Message]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
} }
var _6: [Api.Chat]? var _7: [Api.Chat]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
} }
var _7: [Api.User]? var _8: [Api.User]?
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = _7 != nil let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { let _c8 = _8 != nil
return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, messages: _5!, chats: _6!, users: _7!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.messages.Messages.messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, searchFlood: _5, messages: _6!, chats: _7!, users: _8!)
} }
else { else {
return nil return nil

View File

@ -2756,6 +2756,21 @@ public extension Api.functions.bots {
}) })
} }
} }
public extension Api.functions.channels {
static func checkSearchPostsFlood() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.SearchPostsFlood>) {
let buffer = Buffer()
buffer.appendInt32(-1146490591)
return (FunctionDescription(name: "channels.checkSearchPostsFlood", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SearchPostsFlood? in
let reader = BufferReader(buffer)
var result: Api.SearchPostsFlood?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood
}
return result
})
}
}
public extension Api.functions.channels { public extension Api.functions.channels {
static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -37,7 +37,7 @@ public func loadedPeerFromMessage(account: Account, peerId: PeerId, messageId: M
switch result { switch result {
case let .messages(_, _, users): case let .messages(_, _, users):
apiUsers = users apiUsers = users
case let .messagesSlice(_, _, _, _, _, _, users): case let .messagesSlice(_, _, _, _, _, _, _, users):
apiUsers = users apiUsers = users
case let .channelMessages(_, _, _, _, _, _, _, users): case let .channelMessages(_, _, _, _, _, _, _, users):
apiUsers = users apiUsers = users

View File

@ -2683,7 +2683,7 @@ private func resolveAssociatedMessages(accountPeerId: PeerId, postbox: Postbox,
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (messages, chats, users) return (messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (messages, chats, users) return (messages, chats, users)
case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users):
let _ = apiTopics let _ = apiTopics
@ -2714,7 +2714,7 @@ private func resolveAssociatedMessages(accountPeerId: PeerId, postbox: Postbox,
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (messages, chats, users) return (messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (messages, chats, users) return (messages, chats, users)
case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users):
let _ = apiTopics let _ = apiTopics

View File

@ -104,7 +104,7 @@ private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int6
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -1087,7 +1087,7 @@ public final class AccountViewTracker {
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .channelMessages(_, _, _, _, messages, _, chats, users): case let .channelMessages(_, _, _, _, messages, _, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)

View File

@ -562,7 +562,7 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -631,7 +631,7 @@ private func validateReplyThreadMessagesBatch(postbox: Postbox, network: Network
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -673,7 +673,7 @@ private func validateScheduledMessagesBatch(postbox: Postbox, network: Network,
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -719,7 +719,7 @@ private func validateQuickReplyMessagesBatch(postbox: Postbox, network: Network,
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -803,7 +803,7 @@ private func validateBatch(postbox: Postbox, network: Network, transaction: Tran
let _ = apiTopics let _ = apiTopics
case let .messages(messages, _, _): case let .messages(messages, _, _):
apiMessages = messages apiMessages = messages
case let .messagesSlice(_, _, _, _, messages, _, _): case let .messagesSlice(_, _, _, _, _, messages, _, _):
apiMessages = messages apiMessages = messages
case .messagesNotModified: case .messagesNotModified:
return Set() return Set()
@ -1052,7 +1052,7 @@ private func validateReplyThreadBatch(postbox: Postbox, network: Network, transa
let _ = apiTopics let _ = apiTopics
case let .messages(messages, _, _): case let .messages(messages, _, _):
apiMessages = messages apiMessages = messages
case let .messagesSlice(_, _, _, _, messages, _, _): case let .messagesSlice(_, _, _, _, _, messages, _, _):
apiMessages = messages apiMessages = messages
case .messagesNotModified: case .messagesNotModified:
return Set() return Set()

View File

@ -256,7 +256,7 @@ func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHis
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users):
let _ = apiTopics let _ = apiTopics
@ -287,7 +287,7 @@ func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHis
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users):
let _ = apiTopics let _ = apiTopics
@ -923,7 +923,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -1221,7 +1221,7 @@ func fetchCallListHole(network: Network, postbox: Postbox, accountPeerId: PeerId
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): case let .messagesSlice(_, _, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers

View File

@ -583,7 +583,7 @@ private func synchronizeMessageHistoryTagSummary(accountPeerId: PeerId, postbox:
case let .messagesNotModified(count): case let .messagesNotModified(count):
apiMessages = [] apiMessages = []
apiCount = count apiCount = count
case let .messagesSlice(_, count, _, _, messages, _, _): case let .messagesSlice(_, count, _, _, _, messages, _, _):
apiMessages = messages apiMessages = messages
apiCount = count apiCount = count
} }

View File

@ -132,7 +132,7 @@ private func synchronizeMarkAllUnseen(transaction: Transaction, postbox: Postbox
return .single(messages.compactMap({ $0.id() })) return .single(messages.compactMap({ $0.id() }))
case .messagesNotModified: case .messagesNotModified:
return .single([]) return .single([])
case let .messagesSlice(_, _, _, _, messages, _, _): case let .messagesSlice(_, _, _, _, _, messages, _, _):
return .single(messages.compactMap({ $0.id() })) return .single(messages.compactMap({ $0.id() }))
} }
} }

View File

@ -51,7 +51,7 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId
apiMessages = messages apiMessages = messages
case let .messages(messages, _, _): case let .messages(messages, _, _):
apiMessages = messages apiMessages = messages
case let .messagesSlice(_, _, _, _, messages, _, _): case let .messagesSlice(_, _, _, _, _, messages, _, _):
apiMessages = messages apiMessages = messages
case .messagesNotModified: case .messagesNotModified:
apiMessages = [] apiMessages = []

View File

@ -317,6 +317,7 @@ private enum PreferencesKeyValues: Int32 {
case secureBotStorageState = 43 case secureBotStorageState = 43
case serverSuggestionInfo = 44 case serverSuggestionInfo = 44
case persistentChatInterfaceData = 45 case persistentChatInterfaceData = 45
case globalPostSearchState = 46
} }
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@ -573,6 +574,12 @@ public struct PreferencesKeys {
key.setInt64(4, value: peerId.toInt64()) key.setInt64(4, value: peerId.toInt64())
return key return key
} }
public static func globalPostSearchState() -> ValueBoxKey {
let key = ValueBoxKey(length: 4 + 8)
key.setInt32(0, value: PreferencesKeyValues.globalPostSearchState.rawValue)
return key
}
} }
private enum SharedDataKeyValues: Int32 { private enum SharedDataKeyValues: Int32 {

View File

@ -478,5 +478,23 @@ public extension TelegramEngine.EngineData.Item {
return view.info?.data.get(MessageHistoryThreadData.self) return view.info?.data.get(MessageHistoryThreadData.self)
} }
} }
public struct GlobalPostSearchState: TelegramEngineDataItem, PostboxViewDataItem {
public typealias Result = TelegramGlobalPostSearchState?
public init() {
}
var key: PostboxViewKey {
return .preferences(keys: Set([PreferencesKeys.globalPostSearchState()]))
}
func extract(view: PostboxView) -> Result {
guard let view = view as? PreferencesView else {
preconditionFailure()
}
return view.values[PreferencesKeys.globalPostSearchState()]?.get(TelegramGlobalPostSearchState.self)
}
}
} }
} }

View File

@ -72,7 +72,7 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po
switch result { switch result {
case let .messages(messages, chats, users): case let .messages(messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .messagesSlice(_, _, _, _, messages, chats, users): case let .messagesSlice(_, _, _, _, _, messages, chats, users):
return (peer, messages, chats, users) return (peer, messages, chats, users)
case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users): case let .channelMessages(_, _, _, _, messages, apiTopics, chats, users):
let _ = apiTopics let _ = apiTopics

View File

@ -101,7 +101,7 @@ private func mergedState(transaction: Transaction, seedConfiguration: SeedConfig
users = apiUsers users = apiUsers
totalCount = Int32(messages.count) totalCount = Int32(messages.count)
nextRate = nil nextRate = nil
case let .messagesSlice(_, count, apiNextRate, _, apiMessages, apiChats, apiUsers): case let .messagesSlice(_, count, apiNextRate, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -288,7 +288,7 @@ func _internal_getSearchMessageCount(account: Account, location: SearchMessagesL
return messages.count return messages.count
case let .messagesNotModified(count): case let .messagesNotModified(count):
return Int(count) return Int(count)
case let .messagesSlice(_, count, _, _, _, _, _): case let .messagesSlice(_, count, _, _, _, _, _, _):
return Int(count) return Int(count)
} }
} }
@ -602,6 +602,26 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
} }
} }
if let result {
switch result {
case let .messagesSlice(_, _, _, _, searchFlood, _, _, _):
if let searchFlood {
transaction.updatePreferencesEntry(key: PreferencesKeys.globalPostSearchState(), { _ in
switch searchFlood {
case let .searchPostsFlood(_, remains, waitTill, starsAmount):
return PreferencesEntry(TelegramGlobalPostSearchState(
remainingFreeSearches: remains,
price: StarsAmount(value: starsAmount, nanos: 0),
unlockTimestamp: waitTill
))
}
})
}
default:
break
}
}
let updatedState = SearchMessagesState(main: mergedState(transaction: transaction, seedConfiguration: account.postbox.seedConfiguration, accountPeerId: account.peerId, state: state?.main, result: result) ?? SearchMessagesPeerState(messages: [], readStates: [:], threadInfo: [:], totalCount: 0, completed: true, nextRate: nil), additional: additional) let updatedState = SearchMessagesState(main: mergedState(transaction: transaction, seedConfiguration: account.postbox.seedConfiguration, accountPeerId: account.peerId, state: state?.main, result: result) ?? SearchMessagesPeerState(messages: [], readStates: [:], threadInfo: [:], totalCount: 0, completed: true, nextRate: nil), additional: additional)
return (mergedResult(updatedState), updatedState) return (mergedResult(updatedState), updatedState)
} }
@ -682,7 +702,7 @@ func _internal_downloadMessage(accountPeerId: PeerId, postbox: Postbox, network:
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, apiMessages, apiChats, apiUsers): case let .messagesSlice(_, _, _, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -773,7 +793,7 @@ func fetchRemoteMessage(accountPeerId: PeerId, postbox: Postbox, source: FetchMe
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, apiMessages, apiChats, apiUsers): case let .messagesSlice(_, _, _, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
@ -839,7 +859,7 @@ func _internal_searchMessageIdByTimestamp(account: Account, peerId: PeerId, thre
messages = apiMessages messages = apiMessages
case let .channelMessages(_, _, _, _, apiMessages, _, _, _): case let .channelMessages(_, _, _, _, apiMessages, _, _, _):
messages = apiMessages messages = apiMessages
case let .messagesSlice(_, _, _, _, apiMessages, _, _): case let .messagesSlice(_, _, _, _, _, apiMessages, _, _):
messages = apiMessages messages = apiMessages
case .messagesNotModified: case .messagesNotModified:
messages = [] messages = []
@ -876,7 +896,7 @@ func _internal_searchMessageIdByTimestamp(account: Account, peerId: PeerId, thre
messages = apiMessages messages = apiMessages
case let .channelMessages(_, _, _, _, apiMessages, _, _, _): case let .channelMessages(_, _, _, _, apiMessages, _, _, _):
messages = apiMessages messages = apiMessages
case let .messagesSlice(_, _, _, _, apiMessages, _, _): case let .messagesSlice(_, _, _, _, _, apiMessages, _, _):
messages = apiMessages messages = apiMessages
case .messagesNotModified: case .messagesNotModified:
messages = [] messages = []
@ -909,7 +929,7 @@ func _internal_searchMessageIdByTimestamp(account: Account, peerId: PeerId, thre
messages = apiMessages messages = apiMessages
case let .channelMessages(_, _, _, _, apiMessages, _, _, _): case let .channelMessages(_, _, _, _, apiMessages, _, _, _):
messages = apiMessages messages = apiMessages
case let .messagesSlice(_, _, _, _, apiMessages, _, _): case let .messagesSlice(_, _, _, _, _, apiMessages, _, _):
messages = apiMessages messages = apiMessages
case .messagesNotModified: case .messagesNotModified:
messages = [] messages = []
@ -933,7 +953,7 @@ func _internal_searchMessageIdByTimestamp(account: Account, peerId: PeerId, thre
messages = apiMessages messages = apiMessages
case let .channelMessages(_, _, _, _, apiMessages, _, _, _): case let .channelMessages(_, _, _, _, apiMessages, _, _, _):
messages = apiMessages messages = apiMessages
case let .messagesSlice(_, _, _, _, apiMessages, _, _): case let .messagesSlice(_, _, _, _, _, apiMessages, _, _):
messages = apiMessages messages = apiMessages
case .messagesNotModified: case .messagesNotModified:
messages = [] messages = []
@ -1067,3 +1087,29 @@ func _internal_updatedRemotePeer(accountPeerId: PeerId, postbox: Postbox, networ
return .fail(.generic) return .fail(.generic)
} }
} }
func _internal_refreshGlobalPostSearchState(account: Account) -> Signal<Never, NoError> {
return account.network.request(Api.functions.channels.checkSearchPostsFlood())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.SearchPostsFlood?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return account.postbox.transaction { transaction -> Void in
guard let result else {
return
}
transaction.updatePreferencesEntry(key: PreferencesKeys.globalPostSearchState(), { _ in
switch result {
case let .searchPostsFlood(_, remains, waitTill, starsAmount):
return PreferencesEntry(TelegramGlobalPostSearchState(
remainingFreeSearches: remains,
price: StarsAmount(value: starsAmount, nanos: 0),
unlockTimestamp: waitTill
))
}
})
}
|> ignoreValues
}
}

View File

@ -37,6 +37,31 @@ public final class StoryPreloadInfo {
} }
} }
public final class TelegramGlobalPostSearchState: Codable, Equatable {
public let remainingFreeSearches: Int32
public let price: StarsAmount
public let unlockTimestamp: Int32?
public init(remainingFreeSearches: Int32, price: StarsAmount, unlockTimestamp: Int32?) {
self.remainingFreeSearches = remainingFreeSearches
self.price = price
self.unlockTimestamp = unlockTimestamp
}
public static func ==(lhs: TelegramGlobalPostSearchState, rhs: TelegramGlobalPostSearchState) -> Bool {
if lhs.remainingFreeSearches != rhs.remainingFreeSearches {
return false
}
if lhs.price != rhs.price {
return false
}
if lhs.unlockTimestamp != rhs.unlockTimestamp {
return false
}
return true
}
}
public extension TelegramEngine { public extension TelegramEngine {
final class Messages { final class Messages {
private let account: Account private let account: Account
@ -514,7 +539,7 @@ public extension TelegramEngine {
signals.append(self.account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: "", fromId: nil, savedPeerId: inputSavedPeer, savedReaction: nil, topMsgId: topMsgId, filter: filter, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0)) signals.append(self.account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: "", fromId: nil, savedPeerId: inputSavedPeer, savedReaction: nil, topMsgId: topMsgId, filter: filter, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0))
|> map { result -> (count: Int32?, topId: Int32?) in |> map { result -> (count: Int32?, topId: Int32?) in
switch result { switch result {
case let .messagesSlice(_, count, _, _, messages, _, _): case let .messagesSlice(_, count, _, _, _, messages, _, _):
return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id) return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id)
case let .channelMessages(_, _, count, _, messages, _, _, _): case let .channelMessages(_, _, count, _, messages, _, _, _):
return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id) return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id)
@ -1597,6 +1622,10 @@ public extension TelegramEngine {
public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal<Never, NoError> { public func monoforumPerformSuggestedPostAction(id: EngineMessage.Id, action: MonoforumSuggestedPostAction) -> Signal<Never, NoError> {
return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, action: action) return _internal_monoforumPerformSuggestedPostAction(account: self.account, id: id, action: action)
} }
public func refreshGlobalPostSearchState() -> Signal<Never, NoError> {
return _internal_refreshGlobalPostSearchState(account: self.account)
}
} }
} }

View File

@ -87,7 +87,7 @@ func _internal_requestPeerPhotos(accountPeerId: PeerId, postbox: Postbox, networ
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers
case let .messagesSlice(_, _, _, _, apiMessages, apiChats, apiUsers): case let .messagesSlice(_, _, _, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages messages = apiMessages
chats = apiChats chats = apiChats
users = apiUsers users = apiUsers