This commit is contained in:
Isaac 2025-07-25 17:40:09 +02:00
parent b20652120e
commit b8ac955792
11 changed files with 230 additions and 121 deletions

20
MODULE.bazel.lock generated
View File

@ -81,12 +81,12 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
@ -100,8 +100,8 @@
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
"https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad",
"https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544",
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
@ -151,15 +151,15 @@
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/MODULE.bazel": "49276599207dae3df1e4336c2067505323dfb0606b53ef63e144087d1226e0eb",
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/source.json": "bbbb718187dcbdfbb3a9a0ec7d49446cdf48c67657cafd79b5cf33aa8918f608",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=",
"bzlTransitiveDigest": "IK7QnlhcNBu2jc4wZoGZeDTu3keF2LldFiFUINRcKvo=",
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -190,7 +190,7 @@
},
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": {
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=",
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -290,7 +290,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%internal": {
"general": {
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
"usagesDigest": "fvsnMonVwKDYnBfww4bXuYie3WU0d9VSqT2gePSdQco=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@ -312,7 +312,7 @@
},
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%non_module_deps": {
"general": {
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
"usagesDigest": "jzxYhnOC9BE0dJ0biFLfxWXi/+R19uAAZkJ0p9CY0JI=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},

View File

@ -116,6 +116,7 @@ swift_library(
"//submodules/TelegramUI/Components/Ads/AdsInfoScreen",
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
],
visibility = [
"//visibility:public",

View File

@ -37,6 +37,7 @@ import ComponentFlow
import MultilineTextComponent
import ButtonComponent
import BundleIconComponent
import AnimatedTextComponent
private enum ChatListRecentEntryStableId: Hashable {
case topPeers
@ -1296,19 +1297,19 @@ public struct ChatListSearchContainerTransition {
public let isEmpty: Bool
public let isLoading: Bool
public let query: String?
public let approvedGlobalPostQuery: String?
public let approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?
public let remainingGlobalSearches: Int
public let globalSearchUnlockTimestamp: Int32?
public var animated: Bool
public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String?, approvedGlobalPostQuery: String?, remainingGlobalSearches: Int, globalSearchUnlockTimestamp: Int32?, 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) {
self.deletions = deletions
self.insertions = insertions
self.updates = updates
self.displayingResults = displayingResults
self.isEmpty = isEmpty
self.isLoading = isLoading
self.approvedGlobalPostQuery = approvedGlobalPostQuery
self.approvedGlobalPostQueryState = approvedGlobalPostQueryState
self.remainingGlobalSearches = remainingGlobalSearches
self.globalSearchUnlockTimestamp = globalSearchUnlockTimestamp
self.query = query
@ -1374,7 +1375,7 @@ public func chatListSearchContainerPreparedTransition(
toggleExpandGlobalResults: @escaping () -> Void,
searchPeer: @escaping (EnginePeer) -> Void,
searchQuery: String?,
approvedGlobalPostQuery: String?,
approvedGlobalPostQueryState: ApprovedGlobalPostQueryState?,
remainingGlobalSearches: Int,
globalSearchUnlockTimestamp: Int32?,
searchOptions: ChatListSearchOptions?,
@ -1392,7 +1393,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 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, approvedGlobalPostQuery: approvedGlobalPostQuery, remainingGlobalSearches: remainingGlobalSearches, globalSearchUnlockTimestamp: globalSearchUnlockTimestamp, animated: animated)
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, approvedGlobalPostQueryState: approvedGlobalPostQueryState, remainingGlobalSearches: remainingGlobalSearches, globalSearchUnlockTimestamp: globalSearchUnlockTimestamp, animated: animated)
}
private struct ChatListSearchListPaneNodeState: Equatable {
@ -1609,6 +1610,16 @@ final class GlobalPeerSearchContext {
}
}
public struct ApprovedGlobalPostQueryState: Equatable {
public var query: String
public var price: Int?
public init(query: String, price: Int?) {
self.query = query
self.price = price
}
}
struct TelegramGlobalPostSearchState: Equatable {
var remainingSearches: Int
var unlockTimestamp: Int32?
@ -1664,7 +1675,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private var searchQueryValue: String?
private var searchOptionsValue: ChatListSearchOptions?
private var approvedGlobalPostQueryValue: String?
private var approvedGlobalPostQueryStateValue: ApprovedGlobalPostQueryState?
private var globalPostSearchStateValue: TelegramGlobalPostSearchState
private var globalPostSearchUnlockTimer: Foundation.Timer?
private var isPremium: Bool = false
@ -1725,7 +1736,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
private let searchScopePromise = ValuePromise<TelegramSearchPeersScope>(.everywhere)
private let approvedGlobalPostQuery = ValuePromise<String?>(nil, ignoreRepeated: true)
private let approvedGlobalPostQueryState = ValuePromise<ApprovedGlobalPostQueryState?>(nil, ignoreRepeated: true)
private let globalPostSearchState = Promise<TelegramGlobalPostSearchState>()
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?) {
@ -2065,7 +2076,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var defaultFoundRemoteMessagesSignal: Signal<([FoundRemoteMessages], Bool), NoError> = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false))
if key == .globalPosts {
let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts, tags: nil, minDate: nil, maxDate: nil), query: "", state: nil, limit: 50)
let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), tags: nil, minDate: nil, maxDate: nil), query: "", state: nil, limit: 50)
|> map { resultData -> ChatListSearchMessagesResult in
let (result, updatedState) = resultData
@ -2086,8 +2097,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let defaultFoundRemoteMessages = Promise<([FoundRemoteMessages], Bool)>()
defaultFoundRemoteMessages.set(defaultFoundRemoteMessagesSignal)
let foundItems: Signal<([ChatListSearchEntry], Bool)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, self.approvedGlobalPostQuery.get(), searchOptions, self.searchScopePromise.get(), downloadItems, globalPostSearchStateType)
|> mapToSignal { [weak self] query, approvedGlobalPostQuery, options, searchScope, downloadItems, _ -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
let foundItems: Signal<([ChatListSearchEntry], Bool, String?)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, self.approvedGlobalPostQueryState.get(), searchOptions, self.searchScopePromise.get(), downloadItems, globalPostSearchStateType)
|> debounceOnMainThread
|> mapToSignal { [weak self] query, approvedGlobalPostQueryState, options, searchScope, downloadItems, _ -> Signal<([ChatListSearchEntry], Bool, String?)?, NoError> in
if query == nil && options == nil && [.chats, .topics, .channels, .apps].contains(key) {
let _ = currentRemotePeers.swap(nil)
return .single(nil)
@ -2114,7 +2126,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
return combineLatest(queue: .mainQueue(), presentationDataPromise.get(), selectionPromise.get())
|> map { presentationData, selectionState -> ([ChatListSearchEntry], Bool)? in
|> map { presentationData, selectionState -> ([ChatListSearchEntry], Bool, String?)? in
var entries: [ChatListSearchEntry] = []
var existingMessageIds = Set<MessageId>()
@ -2182,7 +2194,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
entries.append(.message(message, peer, nil, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false, nil, false, .everywhere))
}
return (entries.sorted(), false)
return (entries.sorted(), false, query)
}
}
@ -2685,7 +2697,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
foundRemoteMessages = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false))
} else if key == .apps {
foundRemoteMessages = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false))
} else if key == .globalPosts && (finalQuery.isEmpty || approvedGlobalPostQuery != finalQuery) {
} else if key == .globalPosts && (finalQuery.isEmpty || approvedGlobalPostQueryState?.query != finalQuery) {
if finalQuery.isEmpty {
foundRemoteMessages = defaultFoundRemoteMessages.get()
} else {
@ -2699,11 +2711,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>]
if key == .globalPosts {
#if DEBUG && false
searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil), query: finalQuery, state: nil, limit: 50)]
#else
searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts, tags: nil, minDate: nil, maxDate: nil), query: finalQuery, state: nil, limit: 50)]
#endif
searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil), query: finalQuery, state: nil, limit: 50)]
} else {
searchSignals = searchLocations.map { searchLocation in
return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50)
@ -2863,7 +2871,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
foundThreads,
adsHiddenPromise.get()
)
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, foundPublicMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads, adsHidden -> ([ChatListSearchEntry], Bool)? in
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, foundPublicMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads, adsHidden -> ([ChatListSearchEntry], Bool, String?)? in
let isSearching = foundRemotePeers.3 || foundRemoteMessages.1 || foundPublicMessages.1
var entries: [ChatListSearchEntry] = []
var index = 0
@ -3309,7 +3317,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings))
}
return (entries, isSearching)
return (entries, isSearching, query)
}
}
@ -3597,6 +3605,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
listInteraction.preferredStoryHighQuality = context.sharedContext.currentAutomaticMediaDownloadSettings.highQualityStories
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
let previousSearchQuery = Atomic<String?>(value: nil)
let previousSelectedMessages = Atomic<Set<EngineMessage.Id>?>(value: nil)
let previousExpandGlobalSearch = Atomic<Bool>(value: false)
let previousAdsHidden = Atomic<Bool>(value: false)
@ -3607,8 +3616,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return
}
if let searchQueryValue = self.searchQueryValue, searchQueryValue == self.approvedGlobalPostQueryValue {
self.approvedGlobalPostQuery.set(nil)
if let searchQueryValue = self.searchQueryValue, searchQueryValue == self.approvedGlobalPostQueryStateValue?.query {
self.approvedGlobalPostQueryState.set(nil)
}
self.searchQueryValue = query
@ -3616,12 +3625,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
chatListInteraction?.searchTextHighightState = query
})
self.globalPostSearchState.set(.single(self.globalPostSearchStateValue))
self.approvedSearchQueryDisposable = (combineLatest(queue: .mainQueue(), self.approvedGlobalPostQuery.get(), self.globalPostSearchState.get(), isPremium)
|> deliverOnMainQueue).startStrict(next: { [weak self] approvedGlobalPostQuery, globalPostSearchState, isPremium in
self.approvedSearchQueryDisposable = (combineLatest(queue: .mainQueue(), self.approvedGlobalPostQueryState.get(), self.globalPostSearchState.get(), isPremium)
|> deliverOnMainQueue).startStrict(next: { [weak self] approvedGlobalPostQueryState, globalPostSearchState, isPremium in
guard let self else {
return
}
self.approvedGlobalPostQueryValue = approvedGlobalPostQuery
self.approvedGlobalPostQueryStateValue = approvedGlobalPostQueryState
self.globalPostSearchStateValue = globalPostSearchState
self.isPremium = isPremium
@ -3664,8 +3673,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
})
self.searchDisposable.set((foundItems |> mapToSignal { items -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
guard let (items, isSearching) = items else {
self.searchDisposable.set((foundItems |> mapToSignal { items -> Signal<([ChatListSearchEntry], Bool, String?)?, NoError> in
guard let (items, isSearching, query) = items else {
return .single(nil)
}
var storyStatsIds: [EnginePeer.Id] = []
@ -3708,7 +3717,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
requiresPremiumForMessagingPeerIds.map(TelegramEngine.EngineData.Item.Peer.IsPremiumRequiredForMessaging.init(id:))
)
)
|> map { stats, requiresPremiumForMessaging -> ([ChatListSearchEntry], Bool)? in
|> map { stats, requiresPremiumForMessaging -> ([ChatListSearchEntry], Bool, String?)? in
var requiresPremiumForMessaging = requiresPremiumForMessaging
if !peersFilter.contains(.onlyWriteable) {
requiresPremiumForMessaging = [:]
@ -3731,7 +3740,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
break
}
}
return (mappedItems, isSearching)
return (mappedItems, isSearching, query)
}
}
|> deliverOnMainQueue).startStrict(next: { [weak self] foundItems in
@ -3743,6 +3752,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var entriesAndFlags = foundItems?.0
let isSearching = foundItems?.1 ?? false
let currentQuery = foundItems?.2
strongSelf._isSearching.set(isSearching)
if strongSelf.tagMask == .photoOrVideo {
@ -3773,6 +3783,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
let previousEntries = previousSearchItems.swap(entriesAndFlags)
let previousQuery = previousSearchQuery.swap(currentQuery)
let newEntries = entriesAndFlags ?? []
let selectionChanged = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil)
@ -3780,7 +3791,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let adsHiddenChanged = previousAdsHidden != strongSelf.adsHidden
let animated = selectionChanged || expandGlobalSearchChanged || adsHiddenChanged
let firstTime = previousEntries == nil
let firstTime = previousEntries == nil || previousQuery != currentQuery
var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags != nil, isEmpty: !isSearching && (entriesAndFlags?.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, requestPeerType: requestPeerType, location: location, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in
interaction.peerContextAction?(message, node, rect, gesture, location)
}, toggleExpandLocalResults: {
@ -3804,7 +3815,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, searchPeer: { peer in
},
searchQuery: strongSelf.searchQueryValue,
approvedGlobalPostQuery: strongSelf.approvedGlobalPostQueryValue,
approvedGlobalPostQueryState: strongSelf.approvedGlobalPostQueryStateValue,
remainingGlobalSearches: strongSelf.globalPostSearchStateValue.remainingSearches,
globalSearchUnlockTimestamp: strongSelf.globalPostSearchStateValue.unlockTimestamp,
searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
@ -5114,17 +5125,47 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
navigationController.pushViewController(controller)
} else {
if let searchQueryValue = self.searchQueryValue, !searchQueryValue.isEmpty, self.approvedGlobalPostQueryValue != searchQueryValue {
self.approvedGlobalPostQuery.set(searchQueryValue)
if let searchQueryValue = self.searchQueryValue, !searchQueryValue.isEmpty, self.approvedGlobalPostQueryStateValue?.query != searchQueryValue {
var price: Int?
var globalPostSearchStateValue = self.globalPostSearchStateValue
if globalPostSearchStateValue.remainingSearches == 0 {
//TODO:localize
price = 10
}
self.approvedGlobalPostQueryState.set(ApprovedGlobalPostQueryState(
query: searchQueryValue,
price: price
))
if globalPostSearchStateValue.remainingSearches > 0 {
globalPostSearchStateValue.remainingSearches -= 1
}
if globalPostSearchStateValue.remainingSearches == 0 {
globalPostSearchStateValue.unlockTimestamp = Int32(Date().timeIntervalSince1970) + 30
if globalPostSearchStateValue.remainingSearches == 0 {
globalPostSearchStateValue.unlockTimestamp = Int32(Date().timeIntervalSince1970) + 30
}
}
self.globalPostSearchState.set(.single(globalPostSearchStateValue))
if let price {
//TODO:localize
if let controller = self.navigationController?.topViewController as? ViewController {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .starsSent(context: self.context, title: "", text: [AnimatedTextComponent.Item(
id: AnyHashable(0),
isUnbreakable: true,
content: .text("\(price) Stars spent on extra search."),
)], hasUndo: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { action in
return true
}
), in: .current)
}
}
}
}
}
@ -5336,7 +5377,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions()
if isFirstTime && [.chats, .topics, .channels, .apps].contains(self.key) {
if isFirstTime && [.chats, .topics, .channels, .apps, .globalPosts].contains(self.key) {
options.insert(.PreferSynchronousDrawing)
options.insert(.PreferSynchronousResourceLoading)
} else if transition.animated {
@ -5374,7 +5415,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
emptyResultsText = "Type a keyword to search all posts\nfrom public channels."
emptyResultsButtonSubtitleText = "Global search is a Premium feature."
} else if let query = transition.query, !query.isEmpty {
if transition.approvedGlobalPostQuery == query {
if transition.approvedGlobalPostQueryState?.query == query {
emptyResultsButtonContent = nil
emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults
emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(query).string
@ -6288,6 +6329,8 @@ private final class EmptyResultsButtonPaidSearchContent: Component {
private var timer: Foundation.Timer?
private weak var state: EmptyComponentState?
private var cachedStarImage: UIImage?
override init(frame: CGRect) {
super.init(frame: frame)
}
@ -6299,14 +6342,25 @@ private final class EmptyResultsButtonPaidSearchContent: Component {
func update(component: EmptyResultsButtonPaidSearchContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let subtitleSpacing: CGFloat = 1.0
if self.cachedStarImage == nil || self.component?.theme !== component.theme {
self.cachedStarImage = generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: component.theme.list.itemCheckColors.foregroundColor)
}
self.component = component
self.state = state
let attributedString = NSMutableAttributedString(attributedString: NSAttributedString(string: "Search for * \(component.price)", font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
if let range = attributedString.string.range(of: "*"), let starImage = self.cachedStarImage {
attributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.foregroundColor, value: component.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
}
//TODO:localize
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Search for \(component.price) Stars", font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
text: .plain(attributedString)
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
@ -6349,8 +6403,9 @@ private final class EmptyResultsButtonPaidSearchContent: Component {
contentSize.width = max(titleSize.width, subtitleSize.width)
contentSize.height = titleSize.height + subtitleSpacing + subtitleSize.height
}
contentSize.width = max(contentSize.width, availableSize.width)
let titleFrame = CGRect(origin: CGPoint(x: floor((contentSize.width - titleSize.width)), y: 0.0), size: titleSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((contentSize.width - titleSize.width) * 0.5), y: 0.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
@ -6358,7 +6413,7 @@ private final class EmptyResultsButtonPaidSearchContent: Component {
titleView.frame = titleFrame
}
let subtitleFrame = CGRect(origin: CGPoint(x: floor((contentSize.width - subtitleSize.width)), y: titleFrame.maxY + subtitleSpacing), size: subtitleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: floor((contentSize.width - subtitleSize.width) * 0.5), y: titleFrame.maxY + subtitleSpacing), size: subtitleSize)
if let subtitleView = self.subtitle.view {
if subtitleView.superview == nil {
self.addSubview(subtitleView)
@ -6512,3 +6567,30 @@ private func stringForRemainingTime(_ duration: Int32) -> String {
}
return durationString
}
func debounceOnMainThread<T, E>(_ signal: Signal<T, E>) -> Signal<T, E> {
return Signal { subscriber in
let value = Atomic<T?>(value: nil)
let flushValue: () -> Void = {
let v = value.swap(nil)
if let v {
subscriber.putNext(v)
}
}
return signal.start(next: { v in
let previous = value.swap(v)
if previous == nil {
DispatchQueue.main.async {
flushValue()
}
}
}, error: { e in
subscriber.putError(e)
}, completed: {
flushValue()
subscriber.putCompletion()
})
}
}

View File

@ -473,7 +473,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
folderId = nil
}
if case let .general(scope, _, _, _) = location, case .globalPosts = scope {
if case let .general(scope, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope {
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
@ -488,7 +488,10 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
|> mapToSignal { (nextRate, lowerBound, inputPeer) in
var flags: Int32 = 0
flags |= 1 << 1
return account.network.request(Api.functions.channels.searchPosts(flags: flags, hashtag: nil, query: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit, allowPaidStars: nil), automaticFloodWait: false)
if allowPaidStars != nil {
flags |= 1 << 2
}
return account.network.request(Api.functions.channels.searchPosts(flags: flags, hashtag: nil, query: query, offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit, allowPaidStars: allowPaidStars.flatMap(Int64.init)), automaticFloodWait: false)
|> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in
return (result, nil)
}

View File

@ -18,12 +18,12 @@ public struct FoundPeer: Equatable {
}
}
public enum TelegramSearchPeersScope {
public enum TelegramSearchPeersScope: Equatable {
case everywhere
case channels
case groups
case privateChats
case globalPosts
case globalPosts(allowPaidStars: Int?)
}
public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, network: Network, query: String, scope: TelegramSearchPeersScope) -> Signal<([FoundPeer], [FoundPeer]), NoError> {

View File

@ -152,8 +152,17 @@ public final class PeerInfoRatingComponent: Component {
let baseHeight: CGFloat = 20.0
let innerInset: CGFloat = 2.0
let compactLabelSize = self.compactLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.compactLabel, font: Font.medium(11.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let expandedSize = CGSize(width: 174.0, height: baseHeight)
let collapsedSize = CGSize(width: baseHeight, height: baseHeight)
let collapsedSize = CGSize(width: max(baseHeight, compactLabelSize.width + 6.0 * 2.0), height: baseHeight)
if self.backgroundView.image == nil {
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: baseHeight, color: .white)?.withRenderingMode(.alwaysTemplate)
@ -189,19 +198,11 @@ public final class PeerInfoRatingComponent: Component {
self.foregroundClippedView.backgroundColor = component.foregroundColor
transition.setFrame(view: self.foregroundClippedShapeView, frame: foregroundFrame)
let compactLabelSize = self.compactLabel.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.compactLabel, font: Font.medium(11.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
if let compactLabelView = self.compactLabel.view {
if compactLabelView.superview == nil {
self.foregroundMaskView.addSubview(compactLabelView)
}
compactLabelView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.width) * 0.5), y: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.height) * 0.5) + UIScreenPixel), size: compactLabelSize)
compactLabelView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((collapsedSize.width - innerInset * 2.0 - compactLabelSize.width) * 0.5), y: floorToScreenPixels((baseHeight - innerInset * 2.0 - compactLabelSize.height) * 0.5) + UIScreenPixel), size: compactLabelSize)
alphaTransition.setAlpha(view: compactLabelView, alpha: component.isExpanded ? 0.0 : 1.0)
}
@ -284,50 +285,57 @@ public final class PeerInfoRatingComponent: Component {
})
}
let tooltipController: TooltipScreen
if let current = self.tooltipController {
tooltipController = current
if !component.tooltipLabel.isEmpty {
let tooltipController: TooltipScreen
if let current = self.tooltipController {
tooltipController = current
} else {
tooltipController = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .attributedString(text: NSAttributedString(string: component.tooltipLabel, font: Font.semibold(11.0), textColor: .white)),
style: .customBlur(component.tooltipBackgroundColor, -4.0),
arrowStyle: .small,
location: .point(CGRect(origin: CGPoint(x: 100.0, y: 100.0), size: CGSize()), .bottom),
displayDuration: .infinite,
isShimmering: true,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
tooltipController.containerLayoutUpdated(ContainerViewLayout(
size: CGSize(width: 200.0, height: 200.0),
metrics: LayoutMetrics(),
deviceMetrics: DeviceMetrics.iPhoneXSMax,
intrinsicInsets: UIEdgeInsets(),
safeInsets: UIEdgeInsets(),
additionalInsets: UIEdgeInsets(),
statusBarHeight: nil,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
), transition: .immediate)
self.layer.addSublayer(tooltipController.view.layer)
tooltipController.viewWillAppear(false)
tooltipController.viewDidAppear(false)
tooltipController.setIgnoreAppearanceMethodInvocations(true)
tooltipController.view.isUserInteractionEnabled = false
}
transition.setFrame(view: tooltipController.view, frame: CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -200.0 * 0.5 + foregroundFrame.width + 2.0, dy: -200.0 * 0.5))
alphaTransition.setAlpha(view: tooltipController.view, alpha: component.isExpanded ? 1.0 : 0.0)
} else {
tooltipController = TooltipScreen(
context: component.context,
account: component.context.account,
sharedContext: component.context.sharedContext,
text: .attributedString(text: NSAttributedString(string: component.tooltipLabel, font: Font.semibold(11.0), textColor: .white)),
style: .customBlur(component.tooltipBackgroundColor, -4.0),
arrowStyle: .small,
location: .point(CGRect(origin: CGPoint(x: 100.0, y: 100.0), size: CGSize()), .bottom),
displayDuration: .infinite,
isShimmering: true,
cornerRadius: 10.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
tooltipController.containerLayoutUpdated(ContainerViewLayout(
size: CGSize(width: 200.0, height: 200.0),
metrics: LayoutMetrics(),
deviceMetrics: DeviceMetrics.iPhoneXSMax,
intrinsicInsets: UIEdgeInsets(),
safeInsets: UIEdgeInsets(),
additionalInsets: UIEdgeInsets(),
statusBarHeight: nil,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
), transition: .immediate)
self.layer.addSublayer(tooltipController.view.layer)
tooltipController.viewWillAppear(false)
tooltipController.viewDidAppear(false)
tooltipController.setIgnoreAppearanceMethodInvocations(true)
tooltipController.view.isUserInteractionEnabled = false
if let tooltipController = self.tooltipController {
self.tooltipController = nil
tooltipController.view.layer.removeFromSuperlayer()
}
}
transition.setFrame(view: tooltipController.view, frame: CGRect(origin: CGPoint(), size: CGSize(width: 200.0, height: 200.0)).offsetBy(dx: -200.0 * 0.5 + foregroundFrame.width + 2.0, dy: -200.0 * 0.5))
alphaTransition.setAlpha(view: tooltipController.view, alpha: component.isExpanded ? 1.0 : 0.0)
return size
}
}

View File

@ -1917,7 +1917,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
var subtitleRatingSize: CGSize?
if !"".isEmpty, let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
let subtitleRating: ComponentView<Empty>
var subtitleRatingTransition = ComponentTransition(transition)
if let current = self.subtitleRating {
@ -1978,7 +1978,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.subtitleRatingIsExpanded, let controller = self.controller, let presentationData = self.presentationData, !self.didDisplayRatingTooltip {
self.didDisplayRatingTooltip = true
controller.presentInGlobalOverlay(UndoOverlayController(
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .info(
title: nil,
@ -1987,10 +1987,21 @@ final class PeerInfoHeaderNode: ASDisplayNode {
customUndoText: "Learn More"
),
position: .top,
action: { _ in
action: { [weak self] action in
guard let self else {
return true
}
if case .undo = action {
var infoUrl = "https://telegram.org/blog/telegram-stars"
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["stars_rating_learnmore_url"] as? String {
infoUrl = value
}
self.context.sharedContext.applicationBindings.openUrl(infoUrl)
}
return true
}
))
), in: .current)
}
}
)),

View File

@ -115,7 +115,7 @@ extension ChatControllerImpl {
let textItems: [AnimatedTextComponent.Item] = [
AnimatedTextComponent.Item(id: 0, content: .text(text))
]
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak self] action in
guard let self else {
return false
}

View File

@ -535,9 +535,9 @@ extension ChatControllerImpl {
self.currentSendStarsUndoMessageId = messageId
if let current = self.currentSendStarsUndoController {
current.content = .starsSent(context: self.context, title: title, text: textItems)
current.content = .starsSent(context: self.context, title: title, text: textItems, hasUndo: true)
} else {
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in
let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems, hasUndo: true), elevatedLayout: false, position: .top, action: { [weak self] action in
guard let self else {
return false
}

View File

@ -40,7 +40,7 @@ public enum UndoOverlayContent {
case copy(text: String)
case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String)
case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item])
case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item], hasUndo: Bool)
case inviteRequestSent(title: String, text: String)
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)

View File

@ -452,7 +452,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = string
displayUndo = false
self.originalRemainingSeconds = 5
case let .starsSent(_, title, textItems):
case let .starsSent(_, title, textItems, hasUndo):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
@ -478,11 +478,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.animatedTextItems = textItems
displayUndo = true
self.originalRemainingSeconds = 4.9
displayUndo = hasUndo
self.originalRemainingSeconds = hasUndo ? 4.9 : 3.0
isUserInteractionEnabled = true
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
if hasUndo {
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
}
case let .messagesUnpinned(title, text, undo, isHidden):
self.avatarNode = nil
self.iconNode = nil
@ -1562,8 +1564,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .starsSent:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case let .starsSent(_, _, _, hasUndo):
if hasUndo {
self.panelWrapperNode.addSubnode(self.timerTextNode)
}
if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true
@ -1835,7 +1839,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
}
self.textNode.attributedText = attributedText
case let .starsSent(_, title, textItems):
case let .starsSent(_, title, textItems, _):
self.animatedTextItems = textItems
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)