Implement status emoji search

This commit is contained in:
Ali 2022-09-20 13:26:13 +02:00
parent 5b1f01ce75
commit 4be4770776
5 changed files with 440 additions and 39 deletions

View File

@ -235,7 +235,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var emojiContentDisposable: Disposable?
private let emojiSearchDisposable = MetaDisposable()
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable, emptyResultEmoji: TelegramMediaFile?)?>(nil)
private var stableEmptyResultEmoji: TelegramMediaFile?
private var horizontalExpandRecognizer: UIPanGestureRecognizer?
private var horizontalExpandStartLocation: CGPoint?
@ -419,7 +420,22 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
var emojiContent = emojiContent
if let emojiSearchResult = emojiSearchResult {
emojiContent = emojiContent.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id)
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
if strongSelf.stableEmptyResultEmoji == nil {
strongSelf.stableEmptyResultEmoji = emojiSearchResult.emptyResultEmoji
}
//TODO:localize
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
text: "No emoji found",
iconFile: strongSelf.stableEmptyResultEmoji
)
} else {
strongSelf.stableEmptyResultEmoji = nil
}
emojiContent = emojiContent.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
} else {
strongSelf.stableEmptyResultEmoji = nil
}
strongSelf.emojiContent = emojiContent
@ -1315,12 +1331,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|> distinctUntilChanged
let resultSignal = signal
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|> mapToSignal { keywords -> Signal<(result: [EmojiPagerContentComponent.ItemGroup], emptyResultEmoji: TelegramMediaFile?), NoError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
context.engine.stickers.availableReactions(),
hasPremium
)
|> map { view, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in
|> take(1)
|> map { view, availableReactions, hasPremium -> (result: [EmojiPagerContentComponent.ItemGroup], emptyResultEmoji: TelegramMediaFile?) in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
@ -1371,8 +1389,42 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
}
return [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)]
var emptyResultEmoji: TelegramMediaFile?
if let availableReactions = availableReactions {
//let reactionFilter: [String] = ["😖", "😫", "🫠", "😨", ""]
let filteredReactions: [TelegramMediaFile] = availableReactions.reactions.compactMap { reaction -> TelegramMediaFile? in
switch reaction.value {
case let .builtin(value):
let _ = value
//if reactionFilter.contains(value) {
return reaction.selectAnimation
/*} else {
return nil
}*/
case .custom:
return nil
}
}
emptyResultEmoji = filteredReactions.randomElement()
}
return (result: [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
)],
emptyResultEmoji: emptyResultEmoji
)
}
}
@ -1382,7 +1434,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
guard let strongSelf = self else {
return
}
strongSelf.emojiSearchResult.set(.single((result, AnyHashable(query))))
strongSelf.emojiSearchResult.set(.single((result.result, AnyHashable(query), result.emptyResultEmoji)))
}))
}
},

View File

@ -210,6 +210,7 @@ public final class EmojiStatusSelectionComponent: Component {
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: component.hideTopPanel ? -UIScreenPixel : topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
transition.setAlpha(view: self.panelSeparatorView, alpha: component.hideTopPanel ? 0.0 : 1.0)
}
return availableSize
@ -250,6 +251,10 @@ public final class EmojiStatusSelectionController: ViewController {
private var freezeUpdates: Bool = false
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
private let emojiSearchDisposable = MetaDisposable()
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable, emptyResultEmoji: TelegramMediaFile?)?>(nil)
private var stableEmptyResultEmoji: TelegramMediaFile?
private var previewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)?
private var dismissedPreviewItem: (groupId: AnyHashable, item: EmojiPagerContentComponent.Item)?
private var previewScreenView: ComponentView<Empty>?
@ -265,6 +270,8 @@ public final class EmojiStatusSelectionController: ViewController {
private var isAnimatingOut: Bool = false
private var isDismissed: Bool = false
private var isReactionSearchActive: Bool = false
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?) {
self.controller = controller
self.context = context
@ -306,12 +313,36 @@ public final class EmojiStatusSelectionController: ViewController {
self.layer.addSublayer(self.cloudLayer0)
self.layer.addSublayer(self.cloudLayer1)
self.emojiContentDisposable = (emojiContent
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
self.emojiContentDisposable = (combineLatest(queue: .mainQueue(),
emojiContent,
self.emojiSearchResult.get()
)
|> deliverOnMainQueue).start(next: { [weak self] emojiContent, emojiSearchResult in
guard let strongSelf = self else {
return
}
strongSelf.controller?._ready.set(.single(true))
var emojiContent = emojiContent
if let emojiSearchResult = emojiSearchResult {
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) {
if strongSelf.stableEmptyResultEmoji == nil {
strongSelf.stableEmptyResultEmoji = emojiSearchResult.emptyResultEmoji
}
//TODO:localize
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
text: "No emoji found",
iconFile: strongSelf.stableEmptyResultEmoji
)
} else {
strongSelf.stableEmptyResultEmoji = nil
}
emojiContent = emojiContent.withUpdatedItemGroups(itemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults)
} else {
strongSelf.stableEmptyResultEmoji = nil
}
if strongSelf.emojiContent == nil || !strongSelf.freezeUpdates {
strongSelf.emojiContent = emojiContent
}
@ -366,7 +397,150 @@ public final class EmojiStatusSelectionController: ViewController {
},
requestUpdate: { _ in
},
updateSearchQuery: { _ in
updateSearchQuery: { rawQuery in
guard let strongSelf = self else {
return
}
let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isEmpty {
strongSelf.emojiSearchDisposable.set(nil)
strongSelf.emojiSearchResult.set(.single(nil))
} else {
let context = strongSelf.context
let languageCode = "en"
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false)
if !languageCode.lowercased().hasPrefix("en") {
signal = signal
|> mapToSignal { keywords in
return .single(keywords)
|> then(
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|> map { englishKeywords in
return keywords + englishKeywords
}
)
}
}
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
|> mapToSignal { keywords -> Signal<(result: [EmojiPagerContentComponent.ItemGroup], emptyResultEmoji: TelegramMediaFile?), NoError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
context.engine.stickers.availableReactions(),
hasPremium
)
|> take(1)
|> map { view, availableReactions, hasPremium -> (result: [EmojiPagerContentComponent.ItemGroup], emptyResultEmoji: TelegramMediaFile?) in
var result: [(String, TelegramMediaFile?, String)] = []
var allEmoticons: [String: String] = [:]
for keyword in keywords {
for emoticon in keyword.emoticons {
allEmoticons[emoticon] = keyword.keyword
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
for attribute in item.file.attributes {
switch attribute {
case let .CustomEmoji(_, alt, _):
if !item.file.isPremiumEmoji || hasPremium {
if !alt.isEmpty, let keyword = allEmoticons[alt] {
result.append((alt, item.file, keyword))
} else if alt == query {
result.append((alt, item.file, alt))
}
}
default:
break
}
}
}
var items: [EmojiPagerContentComponent.Item] = []
var existingIds = Set<MediaId>()
for item in result {
if let itemFile = item.1 {
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,
accentTint: false
)
items.append(item)
}
}
var emptyResultEmoji: TelegramMediaFile?
if let availableReactions = availableReactions {
//let reactionFilter: [String] = ["😖", "😫", "🫠", "😨", ""]
let filteredReactions: [TelegramMediaFile] = availableReactions.reactions.compactMap { reaction -> TelegramMediaFile? in
switch reaction.value {
case let .builtin(value):
let _ = value
//if reactionFilter.contains(value) {
return reaction.selectAnimation
/*} else {
return nil
}*/
case .custom:
return nil
}
}
emptyResultEmoji = filteredReactions.randomElement()
}
return (result: [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
)],
emptyResultEmoji: emptyResultEmoji
)
}
}
strongSelf.emojiSearchDisposable.set((resultSignal
|> delay(0.15, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
strongSelf.emojiSearchResult.set(.single((result.result, AnyHashable(query), result.emptyResultEmoji)))
}))
}
},
chatPeerId: nil,
peekBehavior: nil,
@ -398,6 +572,7 @@ public final class EmojiStatusSelectionController: ViewController {
self.emojiContentDisposable?.dispose()
self.availableReactionsDisposable?.dispose()
self.genericReactionEffectDisposable?.dispose()
self.emojiSearchDisposable.dispose()
}
private func refreshLayout(transition: Transition) {
@ -664,8 +839,14 @@ public final class EmojiStatusSelectionController: ViewController {
emojiContent: emojiContent,
backgroundColor: listBackgroundColor,
separatorColor: separatorColor,
hideTopPanel: false,
hideTopPanelUpdated: { _, _ in }
hideTopPanel: self.isReactionSearchActive,
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
guard let strongSelf = self else {
return
}
strongSelf.isReactionSearchActive = hideTopPanel
strongSelf.refreshLayout(transition: transition)
}
)),
environment: {},
containerSize: CGSize(width: componentWidth, height: min(308.0, layout.size.height))
@ -1045,6 +1226,10 @@ public final class EmojiStatusSelectionController: ViewController {
return self._ready
}
override public var overlayWantsToBeBelowKeyboard: Bool {
return true
}
public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, currentSelection: Int64?, destinationItemView: @escaping () -> UIView?) {
self.context = context
self.mode = mode

View File

@ -29,14 +29,12 @@ swift_library(
"//submodules/TelegramUI/Components/VideoAnimationCache:VideoAnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
"//submodules/SoftwareVideo:SoftwareVideo",
"//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/PhotoResources:PhotoResources",
"//submodules/StickerResources:StickerResources",
"//submodules/AppBundle:AppBundle",
#"//submodules/ContextUI:ContextUI",
#"//submodules/PremiumUI:PremiumUI",
#"//submodules/StickerPeekUI:StickerPeekUI",
"//submodules/UndoUI:UndoUI",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",

View File

@ -22,6 +22,7 @@ import UndoUI
import AudioToolbox
import SolidRoundedButtonComponent
import EmojiTextAttachmentView
import EmojiStatusComponent
private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white)
private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white)
@ -1512,6 +1513,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
private struct Params: Equatable {
var theme: PresentationTheme
var strings: PresentationStrings
var text: String
var useOpaqueTheme: Bool
var isActive: Bool
var size: CGSize
@ -1523,6 +1525,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
if lhs.strings !== rhs.strings {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.useOpaqueTheme != rhs.useOpaqueTheme {
return false
}
@ -1733,13 +1738,14 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
return
}
self.params = nil
self.update(theme: params.theme, strings: params.strings, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, transition: transition)
self.update(theme: params.theme, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, transition: transition)
}
public func update(theme: PresentationTheme, strings: PresentationStrings, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, transition: Transition) {
public func update(theme: PresentationTheme, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, transition: Transition) {
let params = Params(
theme: theme,
strings: strings,
text: text,
useOpaqueTheme: useOpaqueTheme,
isActive: isActive,
size: size
@ -1776,11 +1782,10 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
self.backgroundLayer.cornerRadius = inputHeight * 0.5
self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5
//TODO:localize
let textSize = self.textView.update(
transition: .immediate,
component: AnyComponent(Text(
text: "Search Reactions",
text: text,
font: Font.regular(17.0),
color: theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
)),
@ -1790,7 +1795,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
let _ = self.tintTextView.update(
transition: .immediate,
component: AnyComponent(Text(
text: "Search Reactions",
text: text,
font: Font.regular(17.0),
color: .white
)),
@ -1892,6 +1897,99 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate {
}
}
private final class EmptySearchResultsView: UIView {
override public static var layerClass: AnyClass {
return PassthroughLayer.self
}
let tintContainerView: UIView
let titleLabel: ComponentView<Empty>
let titleTintLabel: ComponentView<Empty>
let icon: ComponentView<Empty>
override init(frame: CGRect) {
self.tintContainerView = UIView()
self.titleLabel = ComponentView()
self.titleTintLabel = ComponentView()
self.icon = ComponentView()
super.init(frame: frame)
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, transition: Transition) {
let titleColor: UIColor
if useOpaqueTheme {
titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor
} else {
titleColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor
}
let iconSize: CGSize
if let file = file {
iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever),
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 32.0, height: 32.0)
)
} else {
iconSize = CGSize()
}
let titleSize = self.titleLabel.update(
transition: .immediate,
component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)),
environment: {},
containerSize: CGSize(width: size.width, height: 100.0)
)
let _ = self.titleTintLabel.update(
transition: .immediate,
component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)),
environment: {},
containerSize: CGSize(width: size.width, height: 100.0)
)
let spacing: CGFloat = 4.0
let contentHeight = iconSize.height + spacing + titleSize.height
let contentOriginY = floor((size.height - contentHeight) / 2.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize)
if let iconView = self.icon.view {
if iconView.superview == nil {
self.addSubview(iconView)
}
transition.setFrame(view: iconView, frame: iconFrame)
}
if let titleLabelView = self.titleLabel.view {
if titleLabelView.superview == nil {
self.addSubview(titleLabelView)
}
transition.setFrame(view: titleLabelView, frame: titleFrame)
}
if let titleTintLabelView = self.titleTintLabel.view {
if titleTintLabelView.superview == nil {
self.tintContainerView.addSubview(titleTintLabelView)
}
transition.setFrame(view: titleTintLabelView, frame: titleFrame)
}
}
}
public protocol EmojiContentPeekBehavior: AnyObject {
func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, EmojiPagerContentComponent.View.ItemLayer, TelegramMediaFile)?)
}
@ -2211,6 +2309,26 @@ public final class EmojiPagerContentComponent: Component {
case detailed
}
public final class EmptySearchResults: Equatable {
public let text: String
public let iconFile: TelegramMediaFile?
public init(text: String, iconFile: TelegramMediaFile?) {
self.text = text
self.iconFile = iconFile
}
public static func ==(lhs: EmptySearchResults, rhs: EmptySearchResults) -> Bool {
if lhs.text != rhs.text {
return false
}
if lhs.iconFile?.fileId != rhs.iconFile?.fileId {
return false
}
return true
}
}
public let id: AnyHashable
public let context: AccountContext
public let avatarPeer: EnginePeer?
@ -2221,7 +2339,8 @@ public final class EmojiPagerContentComponent: Component {
public let itemLayoutType: ItemLayoutType
public let itemContentUniqueId: AnyHashable?
public let warpContentsOnEdges: Bool
public let displaySearch: Bool
public let displaySearchWithPlaceholder: String?
public let emptySearchResults: EmptySearchResults?
public let enableLongPress: Bool
public let selectedItems: Set<MediaId>
@ -2236,7 +2355,8 @@ public final class EmojiPagerContentComponent: Component {
itemLayoutType: ItemLayoutType,
itemContentUniqueId: AnyHashable?,
warpContentsOnEdges: Bool,
displaySearch: Bool,
displaySearchWithPlaceholder: String?,
emptySearchResults: EmptySearchResults?,
enableLongPress: Bool,
selectedItems: Set<MediaId>
) {
@ -2250,12 +2370,13 @@ public final class EmojiPagerContentComponent: Component {
self.itemLayoutType = itemLayoutType
self.itemContentUniqueId = itemContentUniqueId
self.warpContentsOnEdges = warpContentsOnEdges
self.displaySearch = displaySearch
self.displaySearchWithPlaceholder = displaySearchWithPlaceholder
self.emptySearchResults = emptySearchResults
self.enableLongPress = enableLongPress
self.selectedItems = selectedItems
}
public func withUpdatedItemGroups(itemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?) -> EmojiPagerContentComponent {
public func withUpdatedItemGroups(itemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?) -> EmojiPagerContentComponent {
return EmojiPagerContentComponent(
id: self.id,
context: self.context,
@ -2267,7 +2388,8 @@ public final class EmojiPagerContentComponent: Component {
itemLayoutType: self.itemLayoutType,
itemContentUniqueId: itemContentUniqueId,
warpContentsOnEdges: self.warpContentsOnEdges,
displaySearch: self.displaySearch,
displaySearchWithPlaceholder: self.displaySearchWithPlaceholder,
emptySearchResults: emptySearchResults,
enableLongPress: self.enableLongPress,
selectedItems: self.selectedItems
)
@ -2304,7 +2426,10 @@ public final class EmojiPagerContentComponent: Component {
if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges {
return false
}
if lhs.displaySearch != rhs.displaySearch {
if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder {
return false
}
if lhs.emptySearchResults != rhs.emptySearchResults {
return false
}
if lhs.enableLongPress != rhs.enableLongPress {
@ -3079,6 +3204,7 @@ public final class EmojiPagerContentComponent: Component {
private let placeholdersContainerView: UIView
private var visibleSearchHeader: EmojiSearchHeaderView?
private var visibleEmptySearchResultsView: EmptySearchResultsView?
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:]
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
@ -5703,7 +5829,7 @@ public final class EmojiPagerContentComponent: Component {
itemGroups: itemGroups,
expandedGroupIds: self.expandedGroupIds,
curveNearBounds: component.warpContentsOnEdges,
displaySearch: component.displaySearch,
displaySearch: component.displaySearchWithPlaceholder != nil,
isSearchActivated: self.isSearchActivated,
customLayout: component.inputInteractionHolder.inputInteraction?.customLayout
)
@ -5730,7 +5856,7 @@ public final class EmojiPagerContentComponent: Component {
transition.setPosition(view: self.scrollView, position: CGPoint(x: 0.0, y: scrollOriginY))
let previousSize = self.scrollView.bounds.size
var resetScrolling = false
if self.scrollView.bounds.isEmpty && component.displaySearch {
if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil {
resetScrolling = true
}
if previousComponent?.itemContentUniqueId != component.itemContentUniqueId {
@ -5829,7 +5955,7 @@ public final class EmojiPagerContentComponent: Component {
}
if resetScrolling {
if component.displaySearch && !self.isSearchActivated {
if component.displaySearchWithPlaceholder != nil && !self.isSearchActivated {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 50.0), size: scrollSize)
} else {
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize)
@ -5879,7 +6005,9 @@ public final class EmojiPagerContentComponent: Component {
}
}
if component.displaySearch {
let useOpaqueTheme = component.inputInteractionHolder.inputInteraction?.useOpaqueTheme ?? false
if let displaySearchWithPlaceholder = component.displaySearchWithPlaceholder {
let visibleSearchHeader: EmojiSearchHeaderView
if let current = self.visibleSearchHeader {
visibleSearchHeader = current
@ -5924,12 +6052,10 @@ public final class EmojiPagerContentComponent: Component {
}
}
let useOpaqueTheme = component.inputInteractionHolder.inputInteraction?.useOpaqueTheme ?? false
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] _ in
guard let strongSelf = self, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
visibleSearchHeader.update(theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, transition: transition)
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
@ -5946,6 +6072,37 @@ public final class EmojiPagerContentComponent: Component {
}
}
if let emptySearchResults = component.emptySearchResults {
let visibleEmptySearchResultsView: EmptySearchResultsView
var emptySearchResultsTransition = transition
if let current = self.visibleEmptySearchResultsView {
visibleEmptySearchResultsView = current
} else {
emptySearchResultsTransition = .immediate
visibleEmptySearchResultsView = EmptySearchResultsView(frame: CGRect())
self.visibleEmptySearchResultsView = visibleEmptySearchResultsView
self.addSubview(visibleEmptySearchResultsView)
self.mirrorContentClippingView?.addSubview(visibleEmptySearchResultsView.tintContainerView)
}
let emptySearchResultsSize = CGSize(width: availableSize.width, height: availableSize.height - itemLayout.searchInsets.top - itemLayout.searchHeight)
visibleEmptySearchResultsView.update(
context: component.context,
theme: keyboardChildEnvironment.theme,
useOpaqueTheme: useOpaqueTheme,
text: emptySearchResults.text,
file: emptySearchResults.iconFile,
size: emptySearchResultsSize,
transition: emptySearchResultsTransition
)
emptySearchResultsTransition.setFrame(view: visibleEmptySearchResultsView, frame: CGRect(origin: CGPoint(x: 0.0, y: itemLayout.searchInsets.top + itemLayout.searchHeight), size: emptySearchResultsSize))
} else {
if let visibleEmptySearchResultsView = self.visibleEmptySearchResultsView {
self.visibleEmptySearchResultsView = nil
visibleEmptySearchResultsView.removeFromSuperview()
visibleEmptySearchResultsView.tintContainerView.removeFromSuperview()
}
}
self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame)
return availableSize
@ -6623,6 +6780,13 @@ public final class EmojiPagerContentComponent: Component {
}
}
var displaySearchWithPlaceholder: String?
if isReactionSelection {
displaySearchWithPlaceholder = "Search Reactions"
} else if isStatusSelection {
displaySearchWithPlaceholder = "Search Statuses"
}
return EmojiPagerContentComponent(
id: "emoji",
context: context,
@ -6667,7 +6831,8 @@ public final class EmojiPagerContentComponent: Component {
itemLayoutType: .compact,
itemContentUniqueId: nil,
warpContentsOnEdges: isReactionSelection || isStatusSelection,
displaySearch: isReactionSelection,
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
emptySearchResults: nil,
enableLongPress: (isReactionSelection && !isQuickReactionSelection) || isStatusSelection,
selectedItems: selectedItems
)

View File

@ -537,7 +537,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
itemLayoutType: .detailed,
itemContentUniqueId: nil,
warpContentsOnEdges: false,
displaySearch: false,
displaySearchWithPlaceholder: nil,
emptySearchResults: nil,
enableLongPress: false,
selectedItems: Set()
)
@ -1714,9 +1715,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
private func processInputData(inputData: InputData) -> InputData {
return InputData(
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: nil),
emoji: inputData.emoji.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: inputData.emoji.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil),
stickers: inputData.stickers.flatMap { stickers in
return stickers.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.itemGroups), itemContentUniqueId: nil)
return stickers.withUpdatedItemGroups(itemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.itemGroups), itemContentUniqueId: nil, emptySearchResults: nil)
},
gifs: inputData.gifs,
availableGifSearchEmojies: inputData.availableGifSearchEmojies