Animated emoji autocompletion

This commit is contained in:
Ali 2022-07-15 16:05:37 +02:00
parent eaf0b74f1b
commit 89ecb672c7
21 changed files with 235 additions and 100 deletions

View File

@ -469,7 +469,7 @@ public enum ChatPresentationInputQueryResult: Equatable {
case hashtags([String])
case mentions([EnginePeer])
case commands([PeerCommand])
case emojis([(String, String)], NSRange)
case emojis([(String, TelegramMediaFile?, String)], NSRange)
case contextRequestResult(EnginePeer?, ChatContextResultCollection?)
public static func ==(lhs: ChatPresentationInputQueryResult, rhs: ChatPresentationInputQueryResult) -> Bool {
@ -513,7 +513,13 @@ public enum ChatPresentationInputQueryResult: Equatable {
return false
}
for i in 0 ..< lhsValue.count {
if lhsValue[i] != rhsValue[i] {
if lhsValue[i].0 != rhsValue[i].0 {
return false
}
if lhsValue[i].1?.fileId != rhsValue[i].1?.fileId {
return false
}
if lhsValue[i].2 != rhsValue[i].2 {
return false
}
}

View File

@ -412,7 +412,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
return UIView()
}
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: strongSelf.animationCache, renderer: strongSelf.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12))
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: strongSelf.animationCache, renderer: strongSelf.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12), pointSize: CGSize(width: 24.0, height: 24.0))
}
self.updateSendButtonEnabled(isCaption || isAttachment, animated: false)

View File

@ -249,8 +249,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
public final class EmojiTextAttachmentView: UIView {
private let contentLayer: InlineStickerItemLayer
public init(context: AccountContext, emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.contentLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: true, emoji: emoji, file: file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: 24.0, height: 24.0))
public init(context: AccountContext, emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor, pointSize: CGSize) {
self.contentLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: true, emoji: emoji, file: file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: pointSize)
super.init(frame: CGRect())

View File

@ -350,7 +350,7 @@ public final class EmojiPagerContentComponent: Component {
var premiumButtonInset: CGFloat
var premiumButtonHeight: CGFloat
init(width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], itemLayoutType: ItemLayoutType, expandedPremiumGroups: Set<AnyHashable>) {
init(width: CGFloat, containerInsets: UIEdgeInsets, itemGroups: [ItemGroupDescription], itemLayoutType: ItemLayoutType) {
self.width = width
self.containerInsets = containerInsets
@ -392,7 +392,7 @@ public final class EmojiPagerContentComponent: Component {
let numRowsInGroup = (itemGroup.itemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
var groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * self.visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
if itemGroup.isPremium && expandedPremiumGroups.contains(itemGroup.groupId) {
if itemGroup.isPremium {
groupContentSize.height += self.premiumButtonInset + self.premiumButtonHeight
}
self.itemGroupLayouts.append(ItemGroupLayout(
@ -765,7 +765,6 @@ public final class EmojiPagerContentComponent: Component {
private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:]
private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:]
private var visibleGroupPremiumButtons: [AnyHashable: ComponentView<Empty>] = [:]
private var expandedPremiumGroups: Set<AnyHashable> = Set()
private var ignoreScrolling: Bool = false
private var keepTopPanelVisibleUntilScrollingInput: Bool = false
@ -1034,7 +1033,8 @@ public final class EmojiPagerContentComponent: Component {
let locationInScrollView = recognizer.location(in: self.scrollView)
outer: for (id, groupHeader) in self.visibleGroupHeaders {
if groupHeader.frame.insetBy(dx: -10.0, dy: -6.0).contains(locationInScrollView) {
for group in component.itemGroups {
let _ = id
/*for group in component.itemGroups {
if group.groupId == id {
if group.isPremium && !self.expandedPremiumGroups.contains(id) {
if self.expandedPremiumGroups.contains(id) {
@ -1059,7 +1059,7 @@ public final class EmojiPagerContentComponent: Component {
break outer
}
}
}
}*/
}
}
@ -1288,7 +1288,7 @@ public final class EmojiPagerContentComponent: Component {
}
groupBorderTransition.setFrame(layer: groupBorderLayer, frame: groupBorderFrame)
if self.expandedPremiumGroups.contains(itemGroup.groupId) {
if itemGroup.isPremium {
validGroupPremiumButtonIds.insert(itemGroup.groupId)
let groupPremiumButton: ComponentView<Empty>
@ -1562,7 +1562,7 @@ public final class EmojiPagerContentComponent: Component {
var itemTransition = transition
let itemLayout = ItemLayout(width: availableSize.width, containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top + 9.0, left: pagerEnvironment.containerInsets.left + 12.0, bottom: 9.0 + pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right + 12.0), itemGroups: itemGroups, itemLayoutType: component.itemLayoutType, expandedPremiumGroups: expandedPremiumGroups)
let itemLayout = ItemLayout(width: availableSize.width, containerInsets: UIEdgeInsets(top: pagerEnvironment.containerInsets.top + 9.0, left: pagerEnvironment.containerInsets.left + 12.0, bottom: 9.0 + pagerEnvironment.containerInsets.bottom, right: pagerEnvironment.containerInsets.right + 12.0), itemGroups: itemGroups, itemLayoutType: component.itemLayoutType)
if let previousItemLayout = self.itemLayout {
if previousItemLayout.width != itemLayout.width {
itemTransition = .immediate

View File

@ -1331,7 +1331,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
var immediatelyLayoutInputContextPanelAndAnimateAppearance = false
if let inputContextPanelNode = inputContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputContextPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction) {
if let inputContextPanelNode = inputContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputContextPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, chatPresentationContext: self.controllerInteraction.presentationContext) {
if inputContextPanelNode !== self.inputContextPanelNode {
dismissedInputContextPanelNode = self.inputContextPanelNode
self.inputContextPanelNode = inputContextPanelNode
@ -1345,7 +1345,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
var immediatelyLayoutOverlayContextPanelAndAnimateAppearance = false
if let overlayContextPanelNode = chatOverlayContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.overlayContextPanelNode, interfaceInteraction: self.interfaceInteraction) {
if let overlayContextPanelNode = chatOverlayContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.overlayContextPanelNode, interfaceInteraction: self.interfaceInteraction, chatPresentationContext: self.controllerInteraction.presentationContext) {
if overlayContextPanelNode !== self.overlayContextPanelNode {
dismissedOverlayContextPanelNode = self.overlayContextPanelNode
self.overlayContextPanelNode = overlayContextPanelNode

View File

@ -20,7 +20,7 @@ class ChatInputContextPanelNode: ASDisplayNode {
var theme: PresentationTheme
var fontSize: PresentationFontSize
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.context = context
self.theme = theme
self.fontSize = fontSize

View File

@ -25,7 +25,7 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult
}
}
func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatInputContextPanelNode? {
func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? {
guard let _ = chatPresentationInterfaceState.renderedPeer?.peer else {
return nil
}
@ -34,7 +34,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
if let currentPanel = currentPanel as? CommandMenuChatInputContextPanelNode {
return currentPanel
} else {
let panel = CommandMenuChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: renderedPeer.peerId)
let panel = CommandMenuChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: renderedPeer.peerId, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
return panel
}
@ -68,7 +68,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
if let currentPanel = currentPanel as? DisabledContextResultsChatInputContextPanelNode {
return currentPanel
} else {
let panel = DisabledContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = DisabledContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
return panel
}
@ -86,7 +86,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results: results.map({ $0.file }), query: query)
return currentPanel
} else {
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: chatPresentationInterfaceState.renderedPeer?.peerId)
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: chatPresentationInterfaceState.renderedPeer?.peerId, chatPresentationContext: chatPresentationContext)
panel.controllerInteraction = controllerInteraction
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results: results.map({ $0.file }), query: query)
@ -99,7 +99,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
@ -111,7 +111,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = EmojisChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = EmojisChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
@ -123,7 +123,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(peers)
return currentPanel
} else {
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input)
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(peers)
return panel
@ -137,7 +137,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(commands)
return currentPanel
} else {
let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(commands)
return panel
@ -153,7 +153,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
@ -163,7 +163,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize)
let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
@ -177,7 +177,7 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
return nil
}
func chatOverlayContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> ChatInputContextPanelNode? {
func chatOverlayContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? {
guard let searchQuerySuggestionResult = chatPresentationInterfaceState.searchQuerySuggestionResult, let _ = chatPresentationInterfaceState.renderedPeer?.peer else {
return nil
}
@ -189,7 +189,7 @@ func chatOverlayContextPanelForChatPresentationIntefaceState(_ chatPresentationI
currentPanel.updateResults(peers)
return currentPanel
} else {
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .search)
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .search, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(peers)
return panel

View File

@ -341,21 +341,61 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
)
}
}
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
return signal
|> map { keywords -> [(String, String)] in
var result: [(String, String)] = []
for keyword in keywords {
for emoticon in keyword.emoticons {
result.append((emoticon, keyword.keyword))
}
}
return result
}
|> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in return .emojis(result, range) }
}
|> castError(ChatContextQueryError.self)
|> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
hasPremium
)
|> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] 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 !alt.isEmpty, let keyword = allEmoticons[alt] {
result.append((alt, item.file, keyword))
}
default:
break
}
}
}
for keyword in keywords {
for emoticon in keyword.emoticons {
result.append((emoticon, nil, keyword.keyword))
}
}
return result
}
|> map { result -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
return { _ in return .emojis(result, range) }
}
|> castError(ChatContextQueryError.self)
}
}
}

View File

@ -802,7 +802,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
return UIView()
}
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12))
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12), pointSize: CGSize(width: 24.0, height: 24.0))
}
}
}

View File

@ -63,7 +63,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private var enqueuedTransitions: [(CommandChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
@ -74,7 +74,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -66,7 +66,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let disposable = MetaDisposable()
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId, chatPresentationContext: ChatPresentationContext) {
self.listView = ListView()
self.listView.clipsToBounds = false
self.listView.isOpaque = false
@ -77,7 +77,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -16,14 +16,14 @@ final class DisabledContextResultsChatInputContextPanelNode: ChatInputContextPan
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.containerNode = ASDisplayNode()
self.separatorNode = ASDisplayNode()
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -10,9 +10,13 @@ import MergeLists
import AccountContext
import Emoji
import ChatPresentationInterfaceState
import AnimationCache
import MultiAnimationRenderer
import TextFormat
private struct EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
let symbol: String
private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
case symbol(String)
case media(MediaId)
}
private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? {
@ -55,25 +59,30 @@ private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
let theme: PresentationTheme
let symbol: String
let text: String
let file: TelegramMediaFile?
var stableId: EmojisChatInputContextPanelEntryStableId {
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol)
if let file = self.file {
return .media(file.fileId)
} else {
return .symbol(self.symbol)
}
}
func withUpdatedTheme(_ theme: PresentationTheme) -> EmojisChatInputContextPanelEntry {
return EmojisChatInputContextPanelEntry(index: self.index, theme: theme, symbol: self.symbol, text: self.text)
return EmojisChatInputContextPanelEntry(index: self.index, theme: theme, symbol: self.symbol, text: self.text, file: self.file)
}
static func ==(lhs: EmojisChatInputContextPanelEntry, rhs: EmojisChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.symbol == rhs.symbol && lhs.text == rhs.text && lhs.theme === rhs.theme
return lhs.index == rhs.index && lhs.symbol == rhs.symbol && lhs.text == rhs.text && lhs.theme === rhs.theme && lhs.file?.fileId == rhs.file?.fileId
}
static func <(lhs: EmojisChatInputContextPanelEntry, rhs: EmojisChatInputContextPanelEntry) -> Bool {
return lhs.index < rhs.index
}
func item(account: Account, emojiSelected: @escaping (String) -> Void) -> ListViewItem {
return EmojisChatInputPanelItem(theme: self.theme, symbol: self.symbol, text: self.text, emojiSelected: emojiSelected)
func item(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, emojiSelected: @escaping (String, TelegramMediaFile?) -> Void) -> ListViewItem {
return EmojisChatInputPanelItem(context: context, theme: self.theme, symbol: self.symbol, text: self.text, file: self.file, animationCache: animationCache, animationRenderer: animationRenderer, emojiSelected: emojiSelected)
}
}
@ -83,12 +92,12 @@ private struct EmojisChatInputContextPanelTransition {
let updates: [ListViewUpdateItem]
}
private func preparedTransition(from fromEntries: [EmojisChatInputContextPanelEntry], to toEntries: [EmojisChatInputContextPanelEntry], account: Account, emojiSelected: @escaping (String) -> Void) -> EmojisChatInputContextPanelTransition {
private func preparedTransition(from fromEntries: [EmojisChatInputContextPanelEntry], to toEntries: [EmojisChatInputContextPanelEntry], context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, emojiSelected: @escaping (String, TelegramMediaFile?) -> Void) -> EmojisChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, emojiSelected: emojiSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, emojiSelected: emojiSelected), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, animationCache: animationCache, animationRenderer: animationRenderer, emojiSelected: emojiSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, animationCache: animationCache, animationRenderer: animationRenderer, emojiSelected: emojiSelected), directionHint: nil) }
return EmojisChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
}
@ -106,7 +115,13 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
private var presentationInterfaceState: ChatPresentationInterfaceState?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
private let animationCache: AnimationCache
private let animationRenderer: MultiAnimationRenderer
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.animationCache = chatPresentationContext.animationCache
self.animationRenderer = chatPresentationContext.animationRenderer
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
@ -134,7 +149,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.placement = .overTextInput
self.isOpaque = false
@ -147,12 +162,12 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
self.clippingNode.addSubnode(self.listView)
}
func updateResults(_ results: [(String, String)]) {
func updateResults(_ results: [(String, TelegramMediaFile?, String)]) {
var entries: [EmojisChatInputContextPanelEntry] = []
var index = 0
var stableIds = Set<EmojisChatInputContextPanelEntryStableId>()
for (symbol, text) in results {
let entry = EmojisChatInputContextPanelEntry(index: index, theme: self.theme, symbol: symbol.normalizedEmoji, text: text)
for (symbol, file, text) in results {
let entry = EmojisChatInputContextPanelEntry(index: index, theme: self.theme, symbol: symbol.normalizedEmoji, text: text, file: file)
if stableIds.contains(entry.stableId) {
continue
}
@ -165,33 +180,54 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode {
private func prepareTransition(from: [EmojisChatInputContextPanelEntry]? , to: [EmojisChatInputContextPanelEntry]) {
let firstTime = self.currentEntries == nil
let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, emojiSelected: { [weak self] text in
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
var hashtagQueryRange: NSRange?
inner: for (range, type, _) in textInputStateContextQueryRangeAndType(textInputState) {
if type == [.emojiSearch] {
var range = range
range.location -= 1
range.length += 1
hashtagQueryRange = range
break inner
let transition = preparedTransition(from: from ?? [], to: to, context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, emojiSelected: { [weak self] text, file in
guard let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction else {
return
}
var text = text
interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in
var hashtagQueryRange: NSRange?
inner: for (range, type, _) in textInputStateContextQueryRangeAndType(textInputState) {
if type == [.emojiSearch] {
var range = range
range.location -= 1
range.length += 1
hashtagQueryRange = range
break inner
}
}
if let range = hashtagQueryRange {
let inputText = NSMutableAttributedString(attributedString: textInputState.inputText)
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
if let file = file {
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, displayText, packReference):
text = displayText
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file)
break loop
default:
break
}
}
}
if let range = hashtagQueryRange {
let inputText = NSMutableAttributedString(attributedString: textInputState.inputText)
let replacementText = text
inputText.replaceCharacters(in: range, with: replacementText)
let selectionPosition = range.lowerBound + (replacementText as NSString).length
return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode)
var replacementText = NSAttributedString(string: text)
if let emojiAttribute = emojiAttribute {
replacementText = NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])
}
return (textInputState, inputMode)
inputText.replaceCharacters(in: range, with: replacementText)
let selectionPosition = range.lowerBound + (replacementText.string as NSString).length
return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode)
}
return (textInputState, inputMode)
}
})
self.currentEntries = to

View File

@ -6,19 +6,32 @@ import TelegramCore
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import AnimationCache
import MultiAnimationRenderer
import EmojiTextAttachmentView
import AccountContext
import TextFormat
final class EmojisChatInputPanelItem: ListViewItem {
fileprivate let context: AccountContext
fileprivate let theme: PresentationTheme
fileprivate let symbol: String
fileprivate let text: String
private let emojiSelected: (String) -> Void
fileprivate let file: TelegramMediaFile?
fileprivate let animationCache: AnimationCache
fileprivate let animationRenderer: MultiAnimationRenderer
private let emojiSelected: (String, TelegramMediaFile?) -> Void
let selectable: Bool = true
public init(theme: PresentationTheme, symbol: String, text: String, emojiSelected: @escaping (String) -> Void) {
public init(context: AccountContext, theme: PresentationTheme, symbol: String, text: String, file: TelegramMediaFile?, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, emojiSelected: @escaping (String, TelegramMediaFile?) -> Void) {
self.context = context
self.theme = theme
self.symbol = symbol
self.text = text
self.file = file
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.emojiSelected = emojiSelected
}
@ -70,7 +83,7 @@ final class EmojisChatInputPanelItem: ListViewItem {
}
func selected(listView: ListView) {
self.emojiSelected(self.symbol)
self.emojiSelected(self.symbol, self.file)
}
}
@ -79,6 +92,7 @@ private let textFont = Font.regular(32.0)
final class EmojisChatInputPanelItemNode: ListViewItemNode {
static let itemSize = CGSize(width: 45.0, height: 45.0)
private let symbolNode: TextNode
private var emojiView: EmojiTextAttachmentView?
init() {
self.symbolNode = TextNode()
@ -111,6 +125,45 @@ final class EmojisChatInputPanelItemNode: ListViewItemNode {
if let strongSelf = self {
let _ = symbolApply()
strongSelf.symbolNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.width - symbolLayout.size.width) / 2.0), y: 0.0), size: symbolLayout.size)
if let file = item.file {
strongSelf.symbolNode.isHidden = true
let emojiView: EmojiTextAttachmentView
if let current = strongSelf.emojiView {
emojiView = current
} else {
emojiView = EmojiTextAttachmentView(
context: item.context,
emoji: ChatTextInputTextCustomEmojiAttribute(
stickerPack: nil,
fileId: file.fileId.id,
file: file
),
file: file,
cache: item.animationCache,
renderer: item.animationRenderer,
placeholderColor: item.theme.list.mediaPlaceholderColor,
pointSize: CGSize(width: 40.0, height: 40.0)
)
emojiView.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
strongSelf.emojiView = emojiView
strongSelf.view.addSubview(emojiView)
let emojiSize = CGSize(width: 40.0, height: 40.0)
let emojiFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.width - emojiSize.width) / 2.0) + 1.0, y: floorToScreenPixels((EmojisChatInputPanelItemNode.itemSize.height - emojiSize.height) / 2.0)), size: emojiSize)
emojiView.center = emojiFrame.center
emojiView.bounds = CGRect(origin: CGPoint(), size: emojiFrame.size)
}
} else {
strongSelf.symbolNode.isHidden = false
if let emojiView = strongSelf.emojiView {
strongSelf.emojiView = nil
emojiView.removeFromSuperview()
}
}
}
})
}

View File

@ -69,7 +69,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
@ -80,7 +80,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -91,7 +91,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
private var enqueuedTransitions: [(HorizontalListContextResultsChatInputContextPanelTransition, Bool)] = []
private var hasValidLayout = false
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.strings = strings
self.separatorNode = ASDisplayNode()
@ -108,7 +108,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -117,7 +117,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private var stickerPreviewController: StickerPreviewController?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.strings = strings
self.backgroundNode = ASImageNode()
@ -144,7 +144,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
self.stickersInteraction = HorizontalStickersChatContextPanelInteraction()
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.placement = .overTextInput
self.isOpaque = false

View File

@ -492,7 +492,7 @@ final class InlineReactionSearchPanel: ChatInputContextPanelNode {
private var choosingStickerDisposable: Disposable?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId?) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId?, chatPresentationContext: ChatPresentationContext) {
self.containerNode = ASDisplayNode()
self.backgroundNode = ASDisplayNode()
@ -535,7 +535,7 @@ final class InlineReactionSearchPanel: ChatInputContextPanelNode {
self.stickersNode = InlineReactionSearchStickersNode(context: context, theme: theme, strings: strings, peerId: peerId)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.placement = .overPanels
self.isOpaque = false

View File

@ -67,7 +67,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
private var enqueuedTransitions: [(CommandChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, mode: MentionChatInputContextPanelMode) {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, mode: MentionChatInputContextPanelMode, chatPresentationContext: ChatPresentationContext) {
self.mode = mode
self.listView = ListView()
@ -80,7 +80,7 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -84,7 +84,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
private var stickerPreviewController: StickerPreviewController?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.strings = strings
self.listView = ListView()
@ -99,7 +99,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
self.stickersInteraction = StickersChatInputContextPanelInteraction()
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true

View File

@ -133,7 +133,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
private let loadMoreDisposable = MetaDisposable()
private var isLoadingMore: Bool = false
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize) {
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
@ -145,7 +145,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
return strings.VoiceOver_ScrollStatus(row, count).string
}
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize)
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true