diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index ed86535e8b..12914dd1dc 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -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": {}, diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 9d17f95213..29031671ee 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -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", diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 24e109925c..17b1ec5b10 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -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(.everywhere) - private let approvedGlobalPostQuery = ValuePromise(nil, ignoreRepeated: true) + private let approvedGlobalPostQueryState = ValuePromise(nil, ignoreRepeated: true) private let globalPostSearchState = Promise() init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal, searchOptions: Signal, 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() @@ -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(value: nil) let previousSelectedMessages = Atomic?>(value: nil) let previousExpandGlobalSearch = Atomic(value: false) let previousAdsHidden = Atomic(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, 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(_ signal: Signal) -> Signal { + return Signal { subscriber in + let value = Atomic(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() + }) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 3309c17f61..8d58e29bda 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -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) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift index d983b9b1f7..1e924f8faf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift @@ -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> { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift index 40d8136193..d0e013b307 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 794b13e9e7..2782e8b50d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -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 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) } } )), diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift index 30ba8684d0..04d2132065 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index df04ef1521..113a15b558 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -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 } diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index cae06633fc..e408d91083 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -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)?) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index df4a7d6f2c..44be88e59a 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -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)