mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add emoji search result cache
This commit is contained in:
parent
50ac94a2fe
commit
b3140ffed9
@ -1204,7 +1204,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if query.isSingleEmoji {
|
if query.isSingleEmoji {
|
||||||
signals = .single([context.engine.stickers.searchStickers(query: text.basicEmoji.0)
|
signals = .single([context.engine.stickers.searchStickers(query: [text.basicEmoji.0])
|
||||||
|> map { (nil, $0.items) }])
|
|> map { (nil, $0.items) }])
|
||||||
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
||||||
@ -1226,7 +1226,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
|||||||
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
||||||
let emoticons = keywords.flatMap { $0.emoticons }
|
let emoticons = keywords.flatMap { $0.emoticons }
|
||||||
for emoji in emoticons {
|
for emoji in emoticons {
|
||||||
signals.append(context.engine.stickers.searchStickers(query: emoji.basicEmoji.0)
|
signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0])
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { (emoji, $0.items) })
|
|> map { (emoji, $0.items) })
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,23 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchResult {
|
||||||
|
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||||
|
var id: AnyHashable
|
||||||
|
var version: Int
|
||||||
|
var isPreset: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchState {
|
||||||
|
var result: EmojiSearchResult?
|
||||||
|
var isSearching: Bool
|
||||||
|
|
||||||
|
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||||
|
self.result = result
|
||||||
|
self.isSearching = isSearching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
private let animationCache: AnimationCache
|
private let animationCache: AnimationCache
|
||||||
@ -235,7 +252,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private var emojiContentDisposable: Disposable?
|
private var emojiContentDisposable: Disposable?
|
||||||
|
|
||||||
private let emojiSearchDisposable = MetaDisposable()
|
private let emojiSearchDisposable = MetaDisposable()
|
||||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||||
|
private var emojiSearchStateValue: EmojiSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
private var emptyResultEmojis: [TelegramMediaFile] = []
|
private var emptyResultEmojis: [TelegramMediaFile] = []
|
||||||
private var stableEmptyResultEmoji: TelegramMediaFile?
|
private var stableEmptyResultEmoji: TelegramMediaFile?
|
||||||
private let stableEmptyResultEmojiDisposable = MetaDisposable()
|
private let stableEmptyResultEmojiDisposable = MetaDisposable()
|
||||||
@ -440,14 +459,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.emojiContentDisposable = combineLatest(queue: .mainQueue(),
|
self.emojiContentDisposable = combineLatest(queue: .mainQueue(),
|
||||||
getEmojiContent(self.animationCache, self.animationRenderer),
|
getEmojiContent(self.animationCache, self.animationRenderer),
|
||||||
self.emojiSearchResult.get()
|
self.emojiSearchState.get()
|
||||||
).start(next: { [weak self] emojiContent, emojiSearchResult in
|
).start(next: { [weak self] emojiContent, emojiSearchState in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var emojiContent = emojiContent
|
var emojiContent = emojiContent
|
||||||
if let emojiSearchResult = emojiSearchResult {
|
if let emojiSearchResult = emojiSearchState.result {
|
||||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||||
if strongSelf.stableEmptyResultEmoji == nil {
|
if strongSelf.stableEmptyResultEmoji == nil {
|
||||||
@ -460,7 +479,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: 0), emptySearchResults: emptySearchResults, searchState: .active)
|
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
@ -1340,22 +1359,22 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
strongSelf.requestUpdateOverlayWantsToBeBelowKeyboard(transition.containedViewLayoutTransition)
|
strongSelf.requestUpdateOverlayWantsToBeBelowKeyboard(transition.containedViewLayoutTransition)
|
||||||
},
|
},
|
||||||
updateSearchQuery: { [weak self] query in
|
updateSearchQuery: { [weak self] query in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .none:
|
case .none:
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
case let .text(rawQuery, languageCode):
|
case let .text(rawQuery, languageCode):
|
||||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
if query.isEmpty {
|
if query.isEmpty {
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
} else {
|
} else {
|
||||||
let context = strongSelf.context
|
let context = self.context
|
||||||
|
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
||||||
if !languageCode.lowercased().hasPrefix("en") {
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
@ -1457,18 +1476,22 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
var version = 0
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .category(value):
|
case let .category(value):
|
||||||
let resultSignal = strongSelf.context.engine.stickers.searchEmoji(emojiString: value)
|
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -1488,7 +1511,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
title: nil,
|
title: nil,
|
||||||
@ -1502,16 +1525,26 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: items
|
items: items
|
||||||
)])
|
)], isFinalResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
var version = 0
|
||||||
|> delay(0.15, queue: .mainQueue())
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(value))))
|
|
||||||
|
guard let group = result.items.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -103,6 +103,7 @@ public struct Namespaces {
|
|||||||
public static let attachMenuBots: Int8 = 23
|
public static let attachMenuBots: Int8 = 23
|
||||||
public static let featuredStickersConfiguration: Int8 = 24
|
public static let featuredStickersConfiguration: Int8 = 24
|
||||||
public static let emojiSearchCategories: Int8 = 25
|
public static let emojiSearchCategories: Int8 = 25
|
||||||
|
public static let cachedEmojiQueryResults: Int8 = 26
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct UnorderedItemList {
|
public struct UnorderedItemList {
|
||||||
|
@ -81,14 +81,15 @@ func _internal_randomGreetingSticker(account: Account) -> Signal<FoundStickerIte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_searchStickers(account: Account, query: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
|
func _internal_searchStickers(account: Account, query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
|
||||||
if scope.isEmpty {
|
if scope.isEmpty {
|
||||||
return .single(([], true))
|
return .single(([], true))
|
||||||
}
|
}
|
||||||
var query = query
|
var query = query
|
||||||
if query == "\u{2764}" {
|
if query == ["\u{2764}"] {
|
||||||
query = "\u{2764}\u{FE0F}"
|
query = ["\u{2764}\u{FE0F}"]
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in
|
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in
|
||||||
let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false
|
let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false
|
||||||
|
|
||||||
@ -97,15 +98,17 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
||||||
if let item = entry.contents.get(SavedStickerItem.self) {
|
if let item = entry.contents.get(SavedStickerItem.self) {
|
||||||
for representation in item.stringRepresentations {
|
for representation in item.stringRepresentations {
|
||||||
if representation.hasPrefix(query) {
|
for queryItem in query {
|
||||||
|
if representation.hasPrefix(queryItem) {
|
||||||
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
|
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let currentItems = Set<MediaId>(result.map { $0.file.fileId })
|
var currentItems = Set<MediaId>(result.map { $0.file.fileId })
|
||||||
var recentItems: [TelegramMediaFile] = []
|
var recentItems: [TelegramMediaFile] = []
|
||||||
var recentAnimatedItems: [TelegramMediaFile] = []
|
var recentAnimatedItems: [TelegramMediaFile] = []
|
||||||
var recentItemsIds = Set<MediaId>()
|
var recentItemsIds = Set<MediaId>()
|
||||||
@ -119,9 +122,14 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !currentItems.contains(file.fileId) {
|
if !currentItems.contains(file.fileId) {
|
||||||
|
currentItems.insert(file.fileId)
|
||||||
|
|
||||||
for case let .Sticker(displayText, _, _) in file.attributes {
|
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||||
if displayText.hasPrefix(query) {
|
for queryItem in query {
|
||||||
|
if displayText.hasPrefix(queryItem) {
|
||||||
matchingRecentItemsIds.insert(file.fileId)
|
matchingRecentItemsIds.insert(file.fileId)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
recentItemsIds.insert(file.fileId)
|
recentItemsIds.insert(file.fileId)
|
||||||
if file.isAnimatedSticker || file.isVideoSticker {
|
if file.isAnimatedSticker || file.isVideoSticker {
|
||||||
@ -135,18 +143,23 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchQuery: ItemCollectionSearchQuery = .exact(ValueBoxKey(query))
|
var searchQueries: [ItemCollectionSearchQuery] = query.map { queryItem -> ItemCollectionSearchQuery in
|
||||||
if query == "\u{2764}" {
|
return .exact(ValueBoxKey(queryItem))
|
||||||
searchQuery = .any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])
|
}
|
||||||
|
if query == ["\u{2764}"] {
|
||||||
|
searchQueries = [.any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])]
|
||||||
}
|
}
|
||||||
|
|
||||||
var installedItems: [FoundStickerItem] = []
|
var installedItems: [FoundStickerItem] = []
|
||||||
var installedAnimatedItems: [FoundStickerItem] = []
|
var installedAnimatedItems: [FoundStickerItem] = []
|
||||||
var installedPremiumItems: [FoundStickerItem] = []
|
var installedPremiumItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
for searchQuery in searchQueries {
|
||||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
||||||
if let item = item as? StickerPackItem {
|
if let item = item as? StickerPackItem {
|
||||||
if !currentItems.contains(item.file.fileId) {
|
if !currentItems.contains(item.file.fileId) {
|
||||||
|
currentItems.insert(item.file.fileId)
|
||||||
|
|
||||||
var stringRepresentations: [String] = []
|
var stringRepresentations: [String] = []
|
||||||
for key in item.indexKeys {
|
for key in item.indexKeys {
|
||||||
key.withDataNoCopy { data in
|
key.withDataNoCopy { data in
|
||||||
@ -169,13 +182,14 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for file in recentAnimatedItems {
|
for file in recentAnimatedItems {
|
||||||
if file.isPremiumSticker && !isPremium {
|
if file.isPremiumSticker && !isPremium {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if matchingRecentItemsIds.contains(file.fileId) {
|
if matchingRecentItemsIds.contains(file.fileId) {
|
||||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
result.append(FoundStickerItem(file: file, stringRepresentations: query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +198,7 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if matchingRecentItemsIds.contains(file.fileId) {
|
if matchingRecentItemsIds.contains(file.fileId) {
|
||||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
result.append(FoundStickerItem(file: file, stringRepresentations: query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +206,8 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
result.append(contentsOf: installedItems)
|
result.append(contentsOf: installedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query)))?.get(CachedStickerQueryResult.self)
|
let combinedQuery = query.joined(separator: "")
|
||||||
|
var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)))?.get(CachedStickerQueryResult.self)
|
||||||
|
|
||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||||
@ -278,7 +293,7 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let remote = account.network.request(Api.functions.messages.getStickers(emoticon: query, hash: cached?.hash ?? 0))
|
let remote = account.network.request(Api.functions.messages.getStickers(emoticon: query.joined(separator: ""), hash: cached?.hash ?? 0))
|
||||||
|> `catch` { _ -> Signal<Api.messages.Stickers, NoError> in
|
|> `catch` { _ -> Signal<Api.messages.Stickers, NoError> in
|
||||||
return .single(.stickersNotModified)
|
return .single(.stickersNotModified)
|
||||||
}
|
}
|
||||||
@ -356,7 +371,7 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
|
|
||||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
|
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
|
||||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query)), entry: entry)
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query.joined(separator: ""))), entry: entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result, true)
|
return (result, true)
|
||||||
@ -371,6 +386,191 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_searchEmoji(account: Account, query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
|
||||||
|
if scope.isEmpty {
|
||||||
|
return .single(([], true))
|
||||||
|
}
|
||||||
|
var query = query
|
||||||
|
if query == ["\u{2764}"] {
|
||||||
|
query = ["\u{2764}\u{FE0F}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in
|
||||||
|
let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false
|
||||||
|
|
||||||
|
var result: [FoundStickerItem] = []
|
||||||
|
if scope.contains(.installed) {
|
||||||
|
var currentItems = Set<MediaId>(result.map { $0.file.fileId })
|
||||||
|
var recentItems: [TelegramMediaFile] = []
|
||||||
|
var recentAnimatedItems: [TelegramMediaFile] = []
|
||||||
|
var recentItemsIds = Set<MediaId>()
|
||||||
|
var matchingRecentItemsIds = Set<MediaId>()
|
||||||
|
|
||||||
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.LocalRecentEmoji) {
|
||||||
|
if let item = entry.contents.get(RecentEmojiItem.self), case let .file(file) = item.content {
|
||||||
|
if !currentItems.contains(file.fileId) {
|
||||||
|
currentItems.insert(file.fileId)
|
||||||
|
|
||||||
|
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||||
|
for queryItem in query {
|
||||||
|
if displayText.hasPrefix(queryItem) {
|
||||||
|
matchingRecentItemsIds.insert(file.fileId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recentItemsIds.insert(file.fileId)
|
||||||
|
if file.isAnimatedSticker || file.isVideoSticker {
|
||||||
|
recentAnimatedItems.append(file)
|
||||||
|
} else {
|
||||||
|
recentItems.append(file)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchQueries: [ItemCollectionSearchQuery] = query.map { queryItem -> ItemCollectionSearchQuery in
|
||||||
|
return .exact(ValueBoxKey(queryItem))
|
||||||
|
}
|
||||||
|
if query == ["\u{2764}"] {
|
||||||
|
searchQueries = [.any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])]
|
||||||
|
}
|
||||||
|
|
||||||
|
var installedItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
for searchQuery in searchQueries {
|
||||||
|
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudEmojiPacks, query: searchQuery) {
|
||||||
|
if let item = item as? StickerPackItem {
|
||||||
|
if !currentItems.contains(item.file.fileId) {
|
||||||
|
currentItems.insert(item.file.fileId)
|
||||||
|
|
||||||
|
var stringRepresentations: [String] = []
|
||||||
|
for key in item.indexKeys {
|
||||||
|
key.withDataNoCopy { data in
|
||||||
|
if let string = String(data: data, encoding: .utf8) {
|
||||||
|
stringRepresentations.append(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !recentItemsIds.contains(item.file.fileId) {
|
||||||
|
installedItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations))
|
||||||
|
} else {
|
||||||
|
matchingRecentItemsIds.insert(item.file.fileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(contentsOf: installedItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
let combinedQuery = query.joined(separator: "")
|
||||||
|
var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)))?.get(CachedStickerQueryResult.self)
|
||||||
|
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||||
|
let searchStickersConfiguration = SearchStickersConfiguration.with(appConfiguration: appConfiguration)
|
||||||
|
|
||||||
|
if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout {
|
||||||
|
cached = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result, cached, isPremium, searchStickersConfiguration)
|
||||||
|
}
|
||||||
|
|> mapToSignal { localItems, cached, isPremium, searchStickersConfiguration -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
|
||||||
|
if !scope.contains(.remote) {
|
||||||
|
return .single((localItems, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempResult: [FoundStickerItem] = []
|
||||||
|
let currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
|
||||||
|
|
||||||
|
var otherItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
for item in localItems {
|
||||||
|
otherItems.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let cached = cached {
|
||||||
|
var cachedItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
for file in cached.items {
|
||||||
|
if !currentItemIds.contains(file.fileId) {
|
||||||
|
cachedItems.append(FoundStickerItem(file: file, stringRepresentations: []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otherItems.append(contentsOf: cachedItems)
|
||||||
|
|
||||||
|
let allOtherItems = otherItems
|
||||||
|
|
||||||
|
tempResult.append(contentsOf: allOtherItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
let remote = account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: query.joined(separator: ""), hash: cached?.hash ?? 0))
|
||||||
|
|> `catch` { _ -> Signal<Api.EmojiList, NoError> in
|
||||||
|
return .single(.emojiListNotModified)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<(files: [TelegramMediaFile], hash: Int64)?, NoError> in
|
||||||
|
switch result {
|
||||||
|
case .emojiListNotModified:
|
||||||
|
return .single(nil)
|
||||||
|
case let .emojiList(hash, documentIds):
|
||||||
|
return TelegramEngine(account: account).stickers.resolveInlineStickers(fileIds: documentIds)
|
||||||
|
|> map { fileMap -> (files: [TelegramMediaFile], hash: Int64)? in
|
||||||
|
var files: [TelegramMediaFile] = []
|
||||||
|
for documentId in documentIds {
|
||||||
|
if let file = fileMap[documentId] {
|
||||||
|
files.append(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (files, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
|
||||||
|
return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in
|
||||||
|
if let (fileItems, hash) = result {
|
||||||
|
var result: [FoundStickerItem] = []
|
||||||
|
let currentItemIds = Set<MediaId>(localItems.map { $0.file.fileId })
|
||||||
|
|
||||||
|
var otherItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
for item in localItems {
|
||||||
|
otherItems.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
var foundItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
|
var files: [TelegramMediaFile] = []
|
||||||
|
for file in fileItems {
|
||||||
|
files.append(file)
|
||||||
|
if !currentItemIds.contains(file.fileId) {
|
||||||
|
foundItems.append(FoundStickerItem(file: file, stringRepresentations: []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let allOtherItems = otherItems
|
||||||
|
|
||||||
|
result.append(contentsOf: allOtherItems)
|
||||||
|
|
||||||
|
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) {
|
||||||
|
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(query.joined(separator: ""))), entry: entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result, true)
|
||||||
|
}
|
||||||
|
return (tempResult, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .single((tempResult, false))
|
||||||
|
|> then(remote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct FoundStickerSets {
|
public struct FoundStickerSets {
|
||||||
public var infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)]
|
public var infos: [(ItemCollectionId, ItemCollectionInfo, ItemCollectionItem?, Bool)]
|
||||||
public let entries: [ItemCollectionViewEntry]
|
public let entries: [ItemCollectionViewEntry]
|
||||||
|
@ -30,7 +30,7 @@ public extension TelegramEngine {
|
|||||||
return _internal_randomGreetingSticker(account: self.account)
|
return _internal_randomGreetingSticker(account: self.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func searchStickers(query: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
|
public func searchStickers(query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
|
||||||
return _internal_searchStickers(account: self.account, query: query, scope: scope)
|
return _internal_searchStickers(account: self.account, query: query, scope: scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +184,13 @@ public extension TelegramEngine {
|
|||||||
return _internal_resolveInlineStickers(postbox: self.account.postbox, network: self.account.network, fileIds: fileIds)
|
return _internal_resolveInlineStickers(postbox: self.account.postbox, network: self.account.network, fileIds: fileIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func searchEmoji(emojiString: String) -> Signal<[TelegramMediaFile], NoError> {
|
public func searchEmoji(emojiString: [String]) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> {
|
||||||
return self.account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: emojiString, hash: 0))
|
return _internal_searchEmoji(account: self.account, query: emojiString)
|
||||||
|
|> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in
|
||||||
|
return (items.map(\.file), isFinalResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*return self.account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: emojiString.joined(separator: ""), hash: 0))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.EmojiList?, NoError> in
|
|> `catch` { _ -> Signal<Api.EmojiList?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
@ -209,7 +214,7 @@ public extension TelegramEngine {
|
|||||||
default:
|
default:
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,23 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchResult {
|
||||||
|
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||||
|
var id: AnyHashable
|
||||||
|
var version: Int
|
||||||
|
var isPreset: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchState {
|
||||||
|
var result: EmojiSearchResult?
|
||||||
|
var isSearching: Bool
|
||||||
|
|
||||||
|
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||||
|
self.result = result
|
||||||
|
self.isSearching = isSearching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class View: UIView {
|
class View: UIView {
|
||||||
private let navigationCancelButton = ComponentView<Empty>()
|
private let navigationCancelButton = ComponentView<Empty>()
|
||||||
private let navigationDoneButton = ComponentView<Empty>()
|
private let navigationDoneButton = ComponentView<Empty>()
|
||||||
@ -212,7 +229,8 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
private var data: AvatarKeyboardInputData?
|
private var data: AvatarKeyboardInputData?
|
||||||
|
|
||||||
private let emojiSearchDisposable = MetaDisposable()
|
private let emojiSearchDisposable = MetaDisposable()
|
||||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||||
|
private var emojiSearchStateValue: EmojiSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
||||||
|
|
||||||
@ -263,20 +281,20 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let updateSearchQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void = { [weak self] query in
|
let updateSearchQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void = { [weak self] query in
|
||||||
guard let strongSelf = self, let context = strongSelf.state?.context else {
|
guard let self, let context = self.state?.context else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .none:
|
case .none:
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
case let .text(rawQuery, languageCode):
|
case let .text(rawQuery, languageCode):
|
||||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
if query.isEmpty {
|
if query.isEmpty {
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
} else {
|
} else {
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
||||||
if !languageCode.lowercased().hasPrefix("en") {
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
@ -292,30 +310,30 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|
|> map { peer -> Bool in
|
||||||
|
guard case let .user(user) = peer else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return user.isPremium
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let resultSignal = signal
|
let resultSignal = signal
|
||||||
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||||
combineLatest(keywords.map { context.engine.stickers.searchStickers(query: $0.emoticons.first!)
|
context.engine.stickers.availableReactions(),
|
||||||
|> map { items -> [FoundStickerItem] in
|
hasPremium
|
||||||
return items.items
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|> map { view, stickers -> [EmojiPagerContentComponent.ItemGroup] in
|
|> take(1)
|
||||||
let hasPremium = true
|
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
|
||||||
|
var result: [(String, TelegramMediaFile?, String)] = []
|
||||||
|
|
||||||
var emojis: [(String, TelegramMediaFile?, String)] = []
|
|
||||||
|
|
||||||
var existingEmoticons = Set<String>()
|
|
||||||
var allEmoticons: [String: String] = [:]
|
var allEmoticons: [String: String] = [:]
|
||||||
for keyword in keywords {
|
for keyword in keywords {
|
||||||
for emoticon in keyword.emoticons {
|
for emoticon in keyword.emoticons {
|
||||||
allEmoticons[emoticon] = keyword.keyword
|
allEmoticons[emoticon] = keyword.keyword
|
||||||
|
|
||||||
if !existingEmoticons.contains(emoticon) {
|
|
||||||
existingEmoticons.insert(emoticon)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +346,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
case let .CustomEmoji(_, _, alt, _):
|
case let .CustomEmoji(_, _, alt, _):
|
||||||
if !item.file.isPremiumEmoji || hasPremium {
|
if !item.file.isPremiumEmoji || hasPremium {
|
||||||
if !alt.isEmpty, let keyword = allEmoticons[alt] {
|
if !alt.isEmpty, let keyword = allEmoticons[alt] {
|
||||||
emojis.append((alt, item.file, keyword))
|
result.append((alt, item.file, keyword))
|
||||||
} else if alt == query {
|
} else if alt == query {
|
||||||
emojis.append((alt, item.file, alt))
|
result.append((alt, item.file, alt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -339,9 +357,10 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var emojiItems: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
for item in emojis {
|
for item in result {
|
||||||
if let itemFile = item.1 {
|
if let itemFile = item.1 {
|
||||||
if existingIds.contains(itemFile.fileId) {
|
if existingIds.contains(itemFile.fileId) {
|
||||||
continue
|
continue
|
||||||
@ -351,43 +370,18 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
let item = EmojiPagerContentComponent.Item(
|
let item = EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
itemFile: itemFile,
|
itemFile: itemFile, subgroupId: nil,
|
||||||
subgroupId: nil,
|
|
||||||
icon: .none,
|
icon: .none,
|
||||||
tintMode: animationData.isTemplate ? .primary : .none
|
tintMode: animationData.isTemplate ? .primary : .none
|
||||||
)
|
)
|
||||||
emojiItems.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stickerItems: [EmojiPagerContentComponent.Item] = []
|
return [EmojiPagerContentComponent.ItemGroup(
|
||||||
for stickerResult in stickers {
|
|
||||||
for sticker in stickerResult {
|
|
||||||
if existingIds.contains(sticker.file.fileId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
existingIds.insert(sticker.file.fileId)
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: sticker.file)
|
|
||||||
let item = EmojiPagerContentComponent.Item(
|
|
||||||
animationData: animationData,
|
|
||||||
content: .animation(animationData),
|
|
||||||
itemFile: sticker.file,
|
|
||||||
subgroupId: nil,
|
|
||||||
icon: .none,
|
|
||||||
tintMode: .none
|
|
||||||
)
|
|
||||||
stickerItems.append(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result: [EmojiPagerContentComponent.ItemGroup] = []
|
|
||||||
if !emojiItems.isEmpty {
|
|
||||||
result.append(
|
|
||||||
EmojiPagerContentComponent.ItemGroup(
|
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "emoji",
|
groupId: "search",
|
||||||
title: "Emoji",
|
title: nil,
|
||||||
subtitle: nil,
|
subtitle: nil,
|
||||||
actionButtonTitle: nil,
|
actionButtonTitle: nil,
|
||||||
isFeatured: false,
|
isFeatured: false,
|
||||||
@ -397,46 +391,27 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
collapsedLineCount: nil,
|
collapsedLineCount: nil,
|
||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: emojiItems
|
items: items
|
||||||
)
|
)]
|
||||||
)
|
|
||||||
}
|
|
||||||
if !stickerItems.isEmpty {
|
|
||||||
result.append(
|
|
||||||
EmojiPagerContentComponent.ItemGroup(
|
|
||||||
supergroupId: "search",
|
|
||||||
groupId: "stickers",
|
|
||||||
title: "Stickers",
|
|
||||||
subtitle: nil,
|
|
||||||
actionButtonTitle: nil,
|
|
||||||
isFeatured: false,
|
|
||||||
isPremiumLocked: false,
|
|
||||||
isEmbedded: false,
|
|
||||||
hasClear: false,
|
|
||||||
collapsedLineCount: nil,
|
|
||||||
displayPremiumBadges: false,
|
|
||||||
headerItem: nil,
|
|
||||||
items: stickerItems
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
var version = 0
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .category(value):
|
case let .category(value):
|
||||||
if strongSelf.state?.keyboardContentId == AnyHashable("emoji") {
|
|
||||||
let resultSignal = context.engine.stickers.searchEmoji(emojiString: value)
|
let resultSignal = context.engine.stickers.searchEmoji(emojiString: value)
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -456,7 +431,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
title: nil,
|
title: nil,
|
||||||
@ -470,71 +445,29 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: items
|
items: items
|
||||||
)])
|
)], isFinalResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
let _ = resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|
||||||
|
var version = 0
|
||||||
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(value))))
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
let resultSignal = context.engine.stickers.searchStickers(query: value)
|
|
||||||
|> filter { result -> Bool in
|
|
||||||
return !result.items.isEmpty
|
|
||||||
}
|
|
||||||
|> map { result -> [TelegramMediaFile] in
|
|
||||||
return result.items.map { $0.file }
|
|
||||||
}
|
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
guard let group = result.items.first else {
|
||||||
for itemFile in files {
|
|
||||||
if existingIds.contains(itemFile.fileId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
existingIds.insert(itemFile.fileId)
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: itemFile)
|
|
||||||
let item = EmojiPagerContentComponent.Item(
|
|
||||||
animationData: animationData,
|
|
||||||
content: .animation(animationData),
|
|
||||||
itemFile: itemFile, subgroupId: nil,
|
|
||||||
icon: .none,
|
|
||||||
tintMode: animationData.isTemplate ? .primary : .none
|
|
||||||
)
|
|
||||||
items.append(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
|
||||||
supergroupId: "search",
|
|
||||||
groupId: "search",
|
|
||||||
title: nil,
|
|
||||||
subtitle: nil,
|
|
||||||
actionButtonTitle: nil,
|
|
||||||
isFeatured: false,
|
|
||||||
isPremiumLocked: false,
|
|
||||||
isEmbedded: false,
|
|
||||||
hasClear: false,
|
|
||||||
collapsedLineCount: nil,
|
|
||||||
displayPremiumBadges: false,
|
|
||||||
headerItem: nil,
|
|
||||||
items: items
|
|
||||||
)])
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(value))))
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
}))
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
|
version += 1
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,15 +830,15 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
let context = component.context
|
let context = component.context
|
||||||
let signal = combineLatest(queue: .mainQueue(),
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
controller.inputData |> delay(0.01, queue: .mainQueue()),
|
controller.inputData |> delay(0.01, queue: .mainQueue()),
|
||||||
self.emojiSearchResult.get()
|
self.emojiSearchState.get()
|
||||||
)
|
)
|
||||||
self.dataDisposable = (signal
|
self.dataDisposable = (signal
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
).start(next: { [weak self, weak state] data, searchResult in
|
).start(next: { [weak self, weak state] data, emojiSearchState in
|
||||||
if let self {
|
if let self {
|
||||||
var data = data
|
var data = data
|
||||||
|
|
||||||
if let searchResult = searchResult {
|
if let searchResult = emojiSearchState.result {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||||
if !searchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
if !searchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||||
@ -916,9 +849,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state?.keyboardContentId == AnyHashable("emoji") {
|
if state?.keyboardContentId == AnyHashable("emoji") {
|
||||||
data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: 0), emptySearchResults: emptySearchResults, searchState: .active)
|
data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: .active)
|
||||||
} else {
|
} else {
|
||||||
data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: 0), emptySearchResults: emptySearchResults, searchState: .active)
|
data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: .active)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,7 +426,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
case let .emojiSearch(query):
|
case let .emojiSearch(query):
|
||||||
gifItems = combineLatest(
|
gifItems = combineLatest(
|
||||||
hasRecentGifs,
|
hasRecentGifs,
|
||||||
paneGifSearchForQuery(context: context, query: query, offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil),
|
paneGifSearchForQuery(context: context, query: query.joined(separator: ""), offset: nil, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil),
|
||||||
searchCategories
|
searchCategories
|
||||||
)
|
)
|
||||||
|> map { hasRecentGifs, result, searchCategories -> EntityKeyboardGifContent in
|
|> map { hasRecentGifs, result, searchCategories -> EntityKeyboardGifContent in
|
||||||
@ -503,7 +503,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let gifItems: Signal<EntityKeyboardGifContent, NoError>
|
let gifItems: Signal<EntityKeyboardGifContent, NoError>
|
||||||
gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query, offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil), searchCategories)
|
gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query.joined(separator: ""), offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil), searchCategories)
|
||||||
|> map { hasRecentGifs, result, searchCategories -> EntityKeyboardGifContent in
|
|> map { hasRecentGifs, result, searchCategories -> EntityKeyboardGifContent in
|
||||||
var items: [GifPagerContentComponent.Item] = []
|
var items: [GifPagerContentComponent.Item] = []
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -977,22 +977,22 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateSearchQuery: { [weak self] query in
|
updateSearchQuery: { [weak self] query in
|
||||||
guard let strongSelf = self else {
|
guard let self = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .none:
|
case .none:
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
case let .text(rawQuery, languageCode):
|
case let .text(rawQuery, languageCode):
|
||||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
if query.isEmpty {
|
if query.isEmpty {
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false)
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
} else {
|
} else {
|
||||||
let context = strongSelf.context
|
let context = self.context
|
||||||
|
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
||||||
if !languageCode.lowercased().hasPrefix("en") {
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
@ -1028,17 +1028,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
|
|> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
|
||||||
var result: [(String, TelegramMediaFile?, String)] = []
|
var result: [(String, TelegramMediaFile?, String)] = []
|
||||||
|
|
||||||
var existingEmoticons = Set<String>()
|
|
||||||
var allEmoticonsList: [String] = []
|
|
||||||
var allEmoticons: [String: String] = [:]
|
var allEmoticons: [String: String] = [:]
|
||||||
for keyword in keywords {
|
for keyword in keywords {
|
||||||
for emoticon in keyword.emoticons {
|
for emoticon in keyword.emoticons {
|
||||||
allEmoticons[emoticon] = keyword.keyword
|
allEmoticons[emoticon] = keyword.keyword
|
||||||
|
|
||||||
if !existingEmoticons.contains(emoticon) {
|
|
||||||
allEmoticonsList.append(emoticon)
|
|
||||||
existingEmoticons.insert(emoticon)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1075,8 +1068,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
let item = EmojiPagerContentComponent.Item(
|
let item = EmojiPagerContentComponent.Item(
|
||||||
animationData: animationData,
|
animationData: animationData,
|
||||||
content: .animation(animationData),
|
content: .animation(animationData),
|
||||||
itemFile: itemFile,
|
itemFile: itemFile, subgroupId: nil,
|
||||||
subgroupId: nil,
|
|
||||||
icon: .none,
|
icon: .none,
|
||||||
tintMode: animationData.isTemplate ? .primary : .none
|
tintMode: animationData.isTemplate ? .primary : .none
|
||||||
)
|
)
|
||||||
@ -1084,17 +1076,6 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for emoji in allEmoticonsList {
|
|
||||||
items.append(EmojiPagerContentComponent.Item(
|
|
||||||
animationData: nil,
|
|
||||||
content: .staticEmoji(emoji),
|
|
||||||
itemFile: nil,
|
|
||||||
subgroupId: nil,
|
|
||||||
icon: .none,
|
|
||||||
tintMode: .none
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return [EmojiPagerContentComponent.ItemGroup(
|
return [EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
@ -1114,21 +1095,21 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var version = 0
|
var version = 0
|
||||||
strongSelf.emojiSearchStateValue.isSearching = true
|
self.emojiSearchStateValue.isSearching = true
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
version += 1
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .category(value):
|
case let .category(value):
|
||||||
let resultSignal = strongSelf.context.engine.stickers.searchEmoji(emojiString: value)
|
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -1148,7 +1129,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
title: nil,
|
title: nil,
|
||||||
@ -1162,25 +1143,25 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: items
|
items: items
|
||||||
)])
|
)], isFinalResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
let delayValue: Double
|
|
||||||
/*#if DEBUG
|
|
||||||
delayValue = 2.3
|
|
||||||
#else*/
|
|
||||||
delayValue = 0.0
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
var version = 0
|
var version = 0
|
||||||
strongSelf.emojiSearchStateValue.isSearching = true
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
|
||||||
|> delay(delayValue, queue: .mainQueue())
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
|
||||||
|
guard let group = result.items.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
version += 1
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
|
|
||||||
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if query.isSingleEmoji {
|
if query.isSingleEmoji {
|
||||||
signals = .single([context.engine.stickers.searchStickers(query: text.basicEmoji.0)
|
signals = .single([context.engine.stickers.searchStickers(query: [text.basicEmoji.0])
|
||||||
|> map { (nil, $0.items) }])
|
|> map { (nil, $0.items) }])
|
||||||
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
||||||
@ -360,7 +360,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
||||||
let emoticons = keywords.flatMap { $0.emoticons }
|
let emoticons = keywords.flatMap { $0.emoticons }
|
||||||
for emoji in emoticons {
|
for emoji in emoticons {
|
||||||
signals.append(context.engine.stickers.searchStickers(query: emoji.basicEmoji.0)
|
signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0])
|
||||||
// |> take(1)
|
// |> take(1)
|
||||||
|> map { (emoji, $0.items) })
|
|> map { (emoji, $0.items) })
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,23 @@ public final class EmojiStatusSelectionComponent: Component {
|
|||||||
|
|
||||||
public final class EmojiStatusSelectionController: ViewController {
|
public final class EmojiStatusSelectionController: ViewController {
|
||||||
private final class Node: ViewControllerTracingNode {
|
private final class Node: ViewControllerTracingNode {
|
||||||
|
private struct EmojiSearchResult {
|
||||||
|
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||||
|
var id: AnyHashable
|
||||||
|
var version: Int
|
||||||
|
var isPreset: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchState {
|
||||||
|
var result: EmojiSearchResult?
|
||||||
|
var isSearching: Bool
|
||||||
|
|
||||||
|
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||||
|
self.result = result
|
||||||
|
self.isSearching = isSearching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private weak var controller: EmojiStatusSelectionController?
|
private weak var controller: EmojiStatusSelectionController?
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private weak var sourceView: UIView?
|
private weak var sourceView: UIView?
|
||||||
@ -258,7 +275,9 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
||||||
|
|
||||||
private let emojiSearchDisposable = MetaDisposable()
|
private let emojiSearchDisposable = MetaDisposable()
|
||||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||||
|
private var emojiSearchStateValue: EmojiSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
private var emptyResultEmojis: [TelegramMediaFile] = []
|
private var emptyResultEmojis: [TelegramMediaFile] = []
|
||||||
private var stableEmptyResultEmoji: TelegramMediaFile?
|
private var stableEmptyResultEmoji: TelegramMediaFile?
|
||||||
private let stableEmptyResultEmojiDisposable = MetaDisposable()
|
private let stableEmptyResultEmojiDisposable = MetaDisposable()
|
||||||
@ -349,16 +368,16 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
|
|
||||||
self.emojiContentDisposable = (combineLatest(queue: .mainQueue(),
|
self.emojiContentDisposable = (combineLatest(queue: .mainQueue(),
|
||||||
emojiContent,
|
emojiContent,
|
||||||
self.emojiSearchResult.get()
|
self.emojiSearchState.get()
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] emojiContent, emojiSearchResult in
|
|> deliverOnMainQueue).start(next: { [weak self] emojiContent, emojiSearchState in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.controller?._ready.set(.single(true))
|
strongSelf.controller?._ready.set(.single(true))
|
||||||
|
|
||||||
var emojiContent = emojiContent
|
var emojiContent = emojiContent
|
||||||
if let emojiSearchResult = emojiSearchResult {
|
if let emojiSearchResult = emojiSearchState.result {
|
||||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||||
if strongSelf.stableEmptyResultEmoji == nil {
|
if strongSelf.stableEmptyResultEmoji == nil {
|
||||||
@ -371,7 +390,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: 0), emptySearchResults: emptySearchResults, searchState: .active)
|
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.stableEmptyResultEmoji = nil
|
strongSelf.stableEmptyResultEmoji = nil
|
||||||
}
|
}
|
||||||
@ -433,22 +452,22 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
requestUpdate: { _ in
|
requestUpdate: { _ in
|
||||||
},
|
},
|
||||||
updateSearchQuery: { query in
|
updateSearchQuery: { query in
|
||||||
guard let strongSelf = self else {
|
guard let self = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch query {
|
switch query {
|
||||||
case .none:
|
case .none:
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
case let .text(rawQuery, languageCode):
|
case let .text(rawQuery, languageCode):
|
||||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
if query.isEmpty {
|
if query.isEmpty {
|
||||||
strongSelf.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
strongSelf.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
} else {
|
} else {
|
||||||
let context = strongSelf.context
|
let context = self.context
|
||||||
|
|
||||||
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
|
||||||
if !languageCode.lowercased().hasPrefix("en") {
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
@ -550,18 +569,22 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
var version = 0
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .category(value):
|
case let .category(value):
|
||||||
let resultSignal = strongSelf.context.engine.stickers.searchEmoji(emojiString: value)
|
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -581,7 +604,7 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
title: nil,
|
title: nil,
|
||||||
@ -595,16 +618,26 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: items
|
items: items
|
||||||
)])
|
)], isFinalResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.emojiSearchDisposable.set((resultSignal
|
var version = 0
|
||||||
|> delay(0.15, queue: .mainQueue())
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> deliverOnMainQueue).start(next: { result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(value))))
|
|
||||||
|
guard let group = result.items.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1607,7 +1607,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
private var textField: EmojiSearchTextField?
|
private var textField: EmojiSearchTextField?
|
||||||
|
|
||||||
private var tapRecognizer: UITapGestureRecognizer?
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
private(set) var currentPresetSearchTerm: String?
|
private(set) var currentPresetSearchTerm: [String]?
|
||||||
|
|
||||||
private var params: Params?
|
private var params: Params?
|
||||||
|
|
||||||
@ -2365,7 +2365,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
public enum SearchQuery: Equatable {
|
public enum SearchQuery: Equatable {
|
||||||
case text(value: String, language: String)
|
case text(value: String, language: String)
|
||||||
case category(value: String)
|
case category(value: [String])
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ItemContent: Equatable {
|
public enum ItemContent: Equatable {
|
||||||
|
@ -23,6 +23,23 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
var deviceMetrics: DeviceMetrics
|
var deviceMetrics: DeviceMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchResult {
|
||||||
|
var groups: [EmojiPagerContentComponent.ItemGroup]
|
||||||
|
var id: AnyHashable
|
||||||
|
var version: Int
|
||||||
|
var isPreset: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmojiSearchState {
|
||||||
|
var result: EmojiSearchResult?
|
||||||
|
var isSearching: Bool
|
||||||
|
|
||||||
|
init(result: EmojiSearchResult?, isSearching: Bool) {
|
||||||
|
self.result = result
|
||||||
|
self.isSearching = isSearching
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var initialFocusId: ItemCollectionId?
|
private var initialFocusId: ItemCollectionId?
|
||||||
private let hasPremiumForUse: Bool
|
private let hasPremiumForUse: Bool
|
||||||
@ -41,8 +58,9 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
public var onCancel: (() -> Void)?
|
public var onCancel: (() -> Void)?
|
||||||
|
|
||||||
private let emojiSearchDisposable = MetaDisposable()
|
private let emojiSearchDisposable = MetaDisposable()
|
||||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
private let emojiSearchState = Promise<EmojiSearchState>(EmojiSearchState(result: nil, isSearching: false))
|
||||||
private var emojiSearchResultValue: (groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?
|
private var emojiSearchStateValue: EmojiSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
private var immediateEmojiSearchState: EmojiSearchState = EmojiSearchState(result: nil, isSearching: false)
|
||||||
|
|
||||||
private var dataDisposable: Disposable?
|
private var dataDisposable: Disposable?
|
||||||
|
|
||||||
@ -160,13 +178,13 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
switch query {
|
switch query {
|
||||||
case .none:
|
case .none:
|
||||||
self.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
self.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
case let .text(rawQuery, languageCode):
|
case let .text(rawQuery, languageCode):
|
||||||
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
if query.isEmpty {
|
if query.isEmpty {
|
||||||
self.emojiSearchDisposable.set(nil)
|
self.emojiSearchDisposable.set(nil)
|
||||||
self.emojiSearchResult.set(.single(nil))
|
self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false)))
|
||||||
} else {
|
} else {
|
||||||
let context = self.context
|
let context = self.context
|
||||||
|
|
||||||
@ -270,18 +288,22 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var version = 0
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
self.emojiSearchDisposable.set((resultSignal
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|> delay(0.15, queue: .mainQueue())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emojiSearchResult.set(.single((result, AnyHashable(query))))
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case let .category(value):
|
case let .category(value):
|
||||||
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|
let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value)
|
||||||
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|
|> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
|
||||||
var items: [EmojiPagerContentComponent.Item] = []
|
var items: [EmojiPagerContentComponent.Item] = []
|
||||||
|
|
||||||
var existingIds = Set<MediaId>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -301,7 +323,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .single([EmojiPagerContentComponent.ItemGroup(
|
return .single(([EmojiPagerContentComponent.ItemGroup(
|
||||||
supergroupId: "search",
|
supergroupId: "search",
|
||||||
groupId: "search",
|
groupId: "search",
|
||||||
title: nil,
|
title: nil,
|
||||||
@ -315,16 +337,28 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: nil,
|
headerItem: nil,
|
||||||
items: items
|
items: items
|
||||||
)])
|
)], isFinalResult))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = resultSignal
|
||||||
|
|
||||||
|
var version = 0
|
||||||
self.emojiSearchDisposable.set((resultSignal
|
self.emojiSearchDisposable.set((resultSignal
|
||||||
|> delay(0.15, queue: .mainQueue())
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emojiSearchResult.set(.single((result, AnyHashable(value))))
|
|
||||||
|
guard let group = result.items.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.items.isEmpty && !result.isFinalResult {
|
||||||
|
self.emojiSearchStateValue.isSearching = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: false), isSearching: false)
|
||||||
|
version += 1
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -347,13 +381,13 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.dataDisposable = (
|
self.dataDisposable = (
|
||||||
self.emojiSearchResult.get()
|
self.emojiSearchState.get()
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
).start(next: { [weak self] emojiSearchResult in
|
).start(next: { [weak self] emojiSearchState in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emojiSearchResultValue = emojiSearchResult
|
self.immediateEmojiSearchState = emojiSearchState
|
||||||
self.update(transition: .immediate)
|
self.update(transition: .immediate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -403,7 +437,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
selectedItems: Set()
|
selectedItems: Set()
|
||||||
)
|
)
|
||||||
|
|
||||||
if let emojiSearchResult = self.emojiSearchResultValue {
|
if let emojiSearchResult = self.immediateEmojiSearchState.result {
|
||||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
|
||||||
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||||
@ -411,7 +445,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode
|
|||||||
iconFile: nil
|
iconFile: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: 0), emptySearchResults: emptySearchResults, searchState: .empty(hasResults: true))
|
emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: self.immediateEmojiSearchState.isSearching ? .searching : .empty(hasResults: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.keyboardView.update(
|
let _ = self.keyboardView.update(
|
||||||
|
@ -103,7 +103,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
let useOpaqueTheme: Bool
|
let useOpaqueTheme: Bool
|
||||||
let textInputState: TextInputState
|
let textInputState: TextInputState
|
||||||
let categories: EmojiSearchCategories?
|
let categories: EmojiSearchCategories?
|
||||||
let searchTermUpdated: (String?) -> Void
|
let searchTermUpdated: ([String]?) -> Void
|
||||||
let activateTextInput: () -> Void
|
let activateTextInput: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -113,7 +113,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
useOpaqueTheme: Bool,
|
useOpaqueTheme: Bool,
|
||||||
textInputState: TextInputState,
|
textInputState: TextInputState,
|
||||||
categories: EmojiSearchCategories?,
|
categories: EmojiSearchCategories?,
|
||||||
searchTermUpdated: @escaping (String?) -> Void,
|
searchTermUpdated: @escaping ([String]?) -> Void,
|
||||||
activateTextInput: @escaping () -> Void
|
activateTextInput: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -360,7 +360,7 @@ final class EmojiSearchSearchBarComponent: Component {
|
|||||||
self.componentState?.updated(transition: .easeInOut(duration: 0.2))
|
self.componentState?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
|
||||||
if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) {
|
if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) {
|
||||||
component.searchTermUpdated(group.identifiers.joined(separator: ""))
|
component.searchTermUpdated(group.identifiers)
|
||||||
|
|
||||||
if let itemComponentView = itemView.view.view {
|
if let itemComponentView = itemView.view.view {
|
||||||
var offset = self.scrollView.contentOffset.x
|
var offset = self.scrollView.contentOffset.x
|
||||||
|
@ -139,7 +139,7 @@ public final class GifPagerContentComponent: Component {
|
|||||||
public enum Subject: Equatable {
|
public enum Subject: Equatable {
|
||||||
case recent
|
case recent
|
||||||
case trending
|
case trending
|
||||||
case emojiSearch(String)
|
case emojiSearch([String])
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class InputInteraction {
|
public final class InputInteraction {
|
||||||
@ -147,14 +147,14 @@ public final class GifPagerContentComponent: Component {
|
|||||||
public let openGifContextMenu: (Item, UIView, CGRect, ContextGesture, Bool) -> Void
|
public let openGifContextMenu: (Item, UIView, CGRect, ContextGesture, Bool) -> Void
|
||||||
public let loadMore: (String) -> Void
|
public let loadMore: (String) -> Void
|
||||||
public let openSearch: () -> Void
|
public let openSearch: () -> Void
|
||||||
public let updateSearchQuery: (String?) -> Void
|
public let updateSearchQuery: ([String]?) -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
|
performItemAction: @escaping (Item, UIView, CGRect) -> Void,
|
||||||
openGifContextMenu: @escaping (Item, UIView, CGRect, ContextGesture, Bool) -> Void,
|
openGifContextMenu: @escaping (Item, UIView, CGRect, ContextGesture, Bool) -> Void,
|
||||||
loadMore: @escaping (String) -> Void,
|
loadMore: @escaping (String) -> Void,
|
||||||
openSearch: @escaping () -> Void,
|
openSearch: @escaping () -> Void,
|
||||||
updateSearchQuery: @escaping (String?) -> Void
|
updateSearchQuery: @escaping ([String]?) -> Void
|
||||||
) {
|
) {
|
||||||
self.performItemAction = performItemAction
|
self.performItemAction = performItemAction
|
||||||
self.openGifContextMenu = openGifContextMenu
|
self.openGifContextMenu = openGifContextMenu
|
||||||
|
@ -127,7 +127,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
case .installed:
|
case .installed:
|
||||||
scope = [.installed]
|
scope = [.installed]
|
||||||
}
|
}
|
||||||
return context.engine.stickers.searchStickers(query: query.basicEmoji.0, scope: scope)
|
return context.engine.stickers.searchStickers(query: [query.basicEmoji.0], scope: scope)
|
||||||
|> map { items -> [FoundStickerItem] in
|
|> map { items -> [FoundStickerItem] in
|
||||||
return items.items
|
return items.items
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user