Emoji status and reaction improvements

This commit is contained in:
Ali 2022-08-26 20:28:11 +03:00
parent 29933dd7d5
commit 3b636d5cb9
35 changed files with 921 additions and 326 deletions

View File

@ -29,8 +29,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let availableReactions: AvailableReactions? public let availableReactions: AvailableReactions?
public let defaultReaction: MessageReaction.Reaction? public let defaultReaction: MessageReaction.Reaction?
public let isPremium: Bool public let isPremium: Bool
public let forceInlineReactions: Bool
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool) { public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, forceInlineReactions: Bool = false) {
self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions self.isRecentActions = isRecentActions
@ -45,6 +46,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.availableReactions = availableReactions self.availableReactions = availableReactions
self.defaultReaction = defaultReaction self.defaultReaction = defaultReaction
self.isPremium = isPremium self.isPremium = isPremium
self.forceInlineReactions = forceInlineReactions
} }
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -87,6 +89,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.isPremium != rhs.isPremium { if lhs.isPremium != rhs.isPremium {
return false return false
} }
if lhs.forceInlineReactions != rhs.forceInlineReactions {
return false
}
return true return true
} }
} }

View File

@ -849,6 +849,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private func openStatusSetup(sourceView: UIView) { private func openStatusSetup(sourceView: UIView) {
self.present(EmojiStatusSelectionController( self.present(EmojiStatusSelectionController(
context: self.context, context: self.context,
mode: .statusSelection,
sourceView: sourceView, sourceView: sourceView,
emojiContent: EmojiPagerContentComponent.emojiInputData( emojiContent: EmojiPagerContentComponent.emojiInputData(
context: self.context, context: self.context,

View File

@ -118,7 +118,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
case .premium: case .premium:
statusContent = .premium(color: self.theme.list.itemAccentColor) statusContent = .premium(color: self.theme.list.itemAccentColor)
case let .emoji(emoji): case let .emoji(emoji):
statusContent = .emojiStatus(status: emoji, size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor) statusContent = .animation(content: .customEmoji(fileId: emoji.fileId), size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor)
} }
var titleCredibilityIconTransition: Transition var titleCredibilityIconTransition: Transition
@ -351,7 +351,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
case .premium: case .premium:
statusContent = .premium(color: self.theme.list.itemAccentColor) statusContent = .premium(color: self.theme.list.itemAccentColor)
case let .emoji(emoji): case let .emoji(emoji):
statusContent = .emojiStatus(status: emoji, size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor) statusContent = .animation(content: .customEmoji(fileId: emoji.fileId), size: CGSize(width: 22.0, height: 22.0), placeholderColor: self.theme.list.mediaPlaceholderColor)
} }
var titleCredibilityIconTransition = Transition(transition) var titleCredibilityIconTransition = Transition(transition)

View File

@ -1524,7 +1524,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = messages.last?.author { if let peer = messages.last?.author {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus { if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
currentCredibilityIconContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isScam { } else if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor) currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
@ -1545,7 +1545,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus { if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
currentCredibilityIconContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isScam { } else if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor) currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)

View File

@ -616,7 +616,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} else if peer.isFake { } else if peer.isFake {
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor) credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
} else if case let .user(user) = peer, let emojiStatus = user.emojiStatus { } else if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
credibilityIcon = .emojiStatus(status: emojiStatus, size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isVerified { } else if peer.isVerified {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {

View File

@ -614,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} else if item.peer.isFake { } else if item.peer.isFake {
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor) credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
} else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus { } else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus {
credibilityIcon = .emojiStatus(status: emojiStatus, size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if item.peer.isVerified { } else if item.peer.isVerified {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled { } else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {

View File

@ -295,8 +295,8 @@ class EmojiHeaderComponent: Component {
context: component.context, context: component.context,
animationCache: component.animationCache, animationCache: component.animationCache,
animationRenderer: component.animationRenderer, animationRenderer: component.animationRenderer,
content: .emojiStatus( content: .animation(
status: PeerEmojiStatus(fileId: component.fileId), content: .customEmoji(fileId: component.fileId),
size: CGSize(width: 100.0, height: 100.0), size: CGSize(width: 100.0, height: 100.0),
placeholderColor: component.placeholderColor placeholderColor: component.placeholderColor
), ),

View File

@ -103,6 +103,7 @@ swift_library(
"//submodules/PremiumUI:PremiumUI", "//submodules/PremiumUI:PremiumUI",
"//submodules/InviteLinksUI:InviteLinksUI", "//submodules/InviteLinksUI:InviteLinksUI",
"//submodules/HorizontalPeerItem:HorizontalPeerItem", "//submodules/HorizontalPeerItem:HorizontalPeerItem",
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,361 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import Postbox
import TelegramCore
import ItemListUI
import EmojiStatusComponent
import ComponentFlow
import AccountContext
public class ItemListReactionItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let title: String
let reaction: MessageReaction.Reaction
let availableReactions: AvailableReactions?
public let sectionId: ItemListSectionId
let style: ItemListStyle
let action: (() -> Void)?
public let tag: ItemListItemTag?
public init(context: AccountContext, presentationData: ItemListPresentationData, title: String, reaction: MessageReaction.Reaction, availableReactions: AvailableReactions?, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil) {
self.context = context
self.presentationData = presentationData
self.title = title
self.reaction = reaction
self.availableReactions = availableReactions
self.sectionId = sectionId
self.style = style
self.action = action
self.tag = tag
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ItemListReactionItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ItemListReactionItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
public var selectable: Bool = true
public func selected(listView: ListView){
listView.clearHighlightAnimated(true)
self.action?()
}
}
private let badgeFont = Font.regular(15.0)
public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
let titleNode: TextNode
let iconView: ComponentHostView<Empty>
private let activateArea: AccessibilityAreaNode
private var item: ItemListReactionItem?
private var fileDisposable: Disposable?
private var file: TelegramMediaFile?
override public var canBeSelected: Bool {
if let item = self.item, let _ = item.action {
return true
} else {
return false
}
}
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.iconView = ComponentHostView<Empty>()
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.view.addSubview(self.iconView)
self.addSubnode(self.activateArea)
}
deinit {
self.fileDisposable?.dispose()
}
public func asyncLayout() -> (_ item: ItemListReactionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item
return { item, params, neighbors in
var rightInset: CGFloat
rightInset = 34.0 + params.rightInset
let _ = rightInset
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset
var additionalTextRightInset: CGFloat = 0.0
additionalTextRightInset += 44.0
let titleColor: UIColor = item.presentationData.theme.list.itemPrimaryTextColor
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 11.0
let height: CGFloat
height = verticalInset * 2.0 + titleLayout.size.height
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsGroupedInsets(neighbors, params)
}
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
strongSelf.activateArea.accessibilityTraits = []
if currentItem?.presentationData.theme !== item.presentationData.theme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
}
let _ = titleApply()
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
strongSelf.backgroundNode.removeFromSupernode()
}
if strongSelf.topStripeNode.supernode != nil {
strongSelf.topStripeNode.removeFromSupernode()
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
var animationContent: EmojiStatusComponent.AnimationContent?
switch item.reaction {
case .builtin:
if let availableReactions = item.availableReactions {
for reaction in availableReactions.reactions {
if reaction.value == item.reaction {
animationContent = .file(file: reaction.selectAnimation)
break
}
}
}
case let .custom(fileId):
animationContent = .customEmoji(fileId: fileId)
}
if let animationContent = animationContent {
let iconBoundingSize = CGSize(width: 28.0, height: 28.0)
let iconOffsetX: CGFloat = 0.0
let iconSize = strongSelf.iconView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: item.context,
animationCache: item.context.animationCache,
animationRenderer: item.context.animationRenderer,
content: .animation(content: animationContent, size: iconBoundingSize, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor), action: nil, longTapAction: nil
)),
environment: {},
containerSize: iconBoundingSize
)
strongSelf.iconView.isUserInteractionEnabled = false
strongSelf.iconView.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - iconSize.width + iconOffsetX, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)
}
/*if let arrowImage = strongSelf.arrowNode.image {
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
}*/
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel))
}
})
}
}
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
var anchorNode: ASDisplayNode?
if self.bottomStripeNode.supernode != nil {
anchorNode = self.bottomStripeNode
} else if self.topStripeNode.supernode != nil {
anchorNode = self.topStripeNode
} else if self.backgroundNode.supernode != nil {
anchorNode = self.backgroundNode
}
if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
} else {
self.addSubnode(self.highlightedBackgroundNode)
}
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
if animated {
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
if let strongSelf = self {
if completed {
strongSelf.highlightedBackgroundNode.removeFromSupernode()
}
}
})
self.highlightedBackgroundNode.alpha = 0.0
} else {
self.highlightedBackgroundNode.removeFromSupernode()
}
}
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -12,19 +12,21 @@ import PresentationDataUtils
import AccountContext import AccountContext
import ReactionImageComponent import ReactionImageComponent
import WebPBinding import WebPBinding
import EmojiStatusSelectionComponent
import EntityKeyboard
private final class QuickReactionSetupControllerArguments { private final class QuickReactionSetupControllerArguments {
let context: AccountContext let context: AccountContext
let selectItem: (MessageReaction.Reaction) -> Void let openQuickReaction: () -> Void
let toggleReaction: () -> Void let toggleReaction: () -> Void
init( init(
context: AccountContext, context: AccountContext,
selectItem: @escaping (MessageReaction.Reaction) -> Void, openQuickReaction: @escaping () -> Void,
toggleReaction: @escaping () -> Void toggleReaction: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.selectItem = selectItem self.openQuickReaction = openQuickReaction
self.toggleReaction = toggleReaction self.toggleReaction = toggleReaction
} }
} }
@ -39,21 +41,21 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
case demoHeader case demoHeader
case demoMessage case demoMessage
case demoDescription case demoDescription
case itemsHeader case quickReaction
case item(MessageReaction.Reaction) case quickReactionDescription
} }
case demoHeader(String) case demoHeader(String)
case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?) case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?)
case demoDescription(String) case demoDescription(String)
case itemsHeader(String) case quickReaction(String, MessageReaction.Reaction, AvailableReactions)
case item(index: Int, value: MessageReaction.Reaction, image: UIImage?, imageIsAnimation: Bool, text: String, isSelected: Bool) case quickReactionDescription(String)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .demoHeader, .demoMessage, .demoDescription: case .demoHeader, .demoMessage, .demoDescription:
return QuickReactionSetupControllerSection.demo.rawValue return QuickReactionSetupControllerSection.demo.rawValue
case .itemsHeader, .item: case .quickReaction, .quickReactionDescription:
return QuickReactionSetupControllerSection.items.rawValue return QuickReactionSetupControllerSection.items.rawValue
} }
} }
@ -66,10 +68,10 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
return .demoMessage return .demoMessage
case .demoDescription: case .demoDescription:
return .demoDescription return .demoDescription
case .itemsHeader: case .quickReaction:
return .itemsHeader return .quickReaction
case let .item(_, value, _, _, _, _): case .quickReactionDescription:
return .item(value) return .quickReactionDescription
} }
} }
@ -81,10 +83,10 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
return 1 return 1
case .demoDescription: case .demoDescription:
return 2 return 2
case .itemsHeader: case .quickReaction:
return 3 return 3
case let .item(index, _, _, _, _, _): case .quickReactionDescription:
return 100 + index return 4
} }
} }
@ -108,14 +110,14 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .itemsHeader(text): case let .quickReaction(lhsText, lhsReaction, lhsAvailableReactions):
if case .itemsHeader(text) = rhs { if case let .quickReaction(rhsText, rhsReaction, rhsAvailableReactions) = rhs, lhsText == rhsText, lhsReaction == rhsReaction, lhsAvailableReactions == rhsAvailableReactions {
return true return true
} else { } else {
return false return false
} }
case let .item(index, value, file, imageIsAnimation, text, isEnabled): case let .quickReactionDescription(text):
if case .item(index, value, file, imageIsAnimation, text, isEnabled) = rhs { if case .quickReactionDescription(text) = rhs {
return true return true
} else { } else {
return false return false
@ -151,28 +153,12 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
) )
case let .demoDescription(text): case let .demoDescription(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .itemsHeader(text): case let .quickReaction(title, reaction, availableReactions):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListReactionItem(context: arguments.context, presentationData: presentationData, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: {
case let .item(_, value, image, imageIsAnimation, text, isSelected): arguments.openQuickReaction()
var imageFitSize = CGSize(width: 30.0, height: 30.0) })
if imageIsAnimation { case let .quickReactionDescription(text):
imageFitSize.width = floor(imageFitSize.width * 2.0) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
imageFitSize.height = floor(imageFitSize.height * 2.0)
}
return ItemListCheckboxItem(
presentationData: presentationData,
icon: image,
iconSize: image?.size.aspectFitted(imageFitSize),
title: text,
style: .right,
color: .accent,
checked: isSelected,
zeroSeparatorInsets: false,
sectionId: self.section,
action: {
arguments.selectItem(value)
}
)
} }
} }
} }
@ -184,7 +170,6 @@ private struct QuickReactionSetupControllerState: Equatable {
private func quickReactionSetupControllerEntries( private func quickReactionSetupControllerEntries(
presentationData: PresentationData, presentationData: PresentationData,
availableReactions: AvailableReactions?, availableReactions: AvailableReactions?,
images: [MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)],
reactionSettings: ReactionSettings, reactionSettings: ReactionSettings,
state: QuickReactionSetupControllerState, state: QuickReactionSetupControllerState,
isPremium: Bool isPremium: Bool
@ -204,27 +189,11 @@ private func quickReactionSetupControllerEntries(
)) ))
entries.append(.demoDescription(presentationData.strings.Settings_QuickReactionSetup_DemoInfo)) entries.append(.demoDescription(presentationData.strings.Settings_QuickReactionSetup_DemoInfo))
entries.append(.itemsHeader(presentationData.strings.Settings_QuickReactionSetup_ReactionListHeader)) //TODO:localize
var index = 0 entries.append(.quickReaction("Choose Your Quick Reaction", reactionSettings.quickReaction, availableReactions))
for availableReaction in availableReactions.reactions {
if !availableReaction.isEnabled {
continue
}
if !isPremium && availableReaction.isPremium { //TODO:localize
continue entries.append(.quickReactionDescription("You can set any emoji as your quick reaction."))
}
entries.append(.item(
index: index,
value: availableReaction.value,
image: images[availableReaction.value]?.image,
imageIsAnimation: images[availableReaction.value]?.isAnimation ?? false,
text: availableReaction.title,
isSelected: reactionSettings.quickReaction == availableReaction.value
))
index += 1
}
} }
return entries return entries
@ -243,17 +212,14 @@ public func quickReactionSetupController(
var dismissImpl: (() -> Void)? var dismissImpl: (() -> Void)?
let _ = dismissImpl let _ = dismissImpl
var openQuickReactionImpl: (() -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let arguments = QuickReactionSetupControllerArguments( let arguments = QuickReactionSetupControllerArguments(
context: context, context: context,
selectItem: { reaction in openQuickReaction: {
updateState { state in openQuickReactionImpl?()
var state = state
state.hasReaction = false
return state
}
let _ = context.engine.stickers.updateQuickReaction(reaction: reaction).start()
}, },
toggleReaction: { toggleReaction: {
updateState { state in updateState { state in
@ -275,80 +241,22 @@ public func quickReactionSetupController(
return reactionSettings return reactionSettings
} }
let images: Signal<[MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)], NoError> = context.engine.stickers.availableReactions()
|> mapToSignal { availableReactions -> Signal<[MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)], NoError> in
var signals: [Signal<(MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?), NoError>] = []
if let availableReactions = availableReactions {
for availableReaction in availableReactions.reactions {
if !availableReaction.isEnabled {
continue
}
if let centerAnimation = availableReaction.centerAnimation {
let signal: Signal<(MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?), NoError> = reactionStaticImage(context: context, animation: centerAnimation, pixelSize: CGSize(width: 72.0 * 2.0, height: 72.0 * 2.0), queue: sharedReactionStaticImage)
|> map { data -> (MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?) in
guard data.isComplete else {
return (availableReaction.value, nil)
}
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
return (availableReaction.value, nil)
}
guard let image = UIImage(data: dataValue) else {
return (availableReaction.value, nil)
}
return (availableReaction.value, (image, true))
}
signals.append(signal)
} else {
let signal: Signal<(MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?), NoError> = context.account.postbox.mediaBox.resourceData(availableReaction.staticIcon.resource)
|> map { data -> (MessageReaction.Reaction, (image: UIImage, isAnimation: Bool)?) in
guard data.complete else {
return (availableReaction.value, nil)
}
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
return (availableReaction.value, nil)
}
guard let image = WebP.convert(fromWebP: dataValue) else {
return (availableReaction.value, nil)
}
return (availableReaction.value, (image, false))
}
signals.append(signal)
}
}
}
return combineLatest(queue: .mainQueue(), signals)
|> map { values -> [MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)] in
var dict: [MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)] = [:]
for (key, image) in values {
if let image = image {
dict[key] = image
}
}
return dict
}
}
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(queue: .mainQueue(), let signal = combineLatest(queue: .mainQueue(),
presentationData, presentationData,
statePromise.get(), statePromise.get(),
context.engine.stickers.availableReactions(), context.engine.stickers.availableReactions(),
settings, settings,
images,
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, availableReactions, settings, images, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, availableReactions, settings, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
let isPremium = accountPeer?.isPremium ?? false let isPremium = accountPeer?.isPremium ?? false
let title: String = presentationData.strings.Settings_QuickReactionSetup_Title let title: String = presentationData.strings.Settings_QuickReactionSetup_Title
let entries = quickReactionSetupControllerEntries( let entries = quickReactionSetupControllerEntries(
presentationData: presentationData, presentationData: presentationData,
availableReactions: availableReactions, availableReactions: availableReactions,
images: images,
reactionSettings: settings, reactionSettings: settings,
state: state, state: state,
isPremium: isPremium isPremium: isPremium
@ -388,6 +296,74 @@ public func quickReactionSetupController(
} }
} }
openQuickReactionImpl = { [weak controller] in
let _ = (combineLatest(queue: .mainQueue(),
settings,
context.engine.stickers.availableReactions()
)
|> take(1)
|> deliverOnMainQueue).start(next: { settings, availableReactions in
var currentSelectedFileId: MediaId?
switch settings.quickReaction {
case .builtin:
if let availableReactions = availableReactions {
if let reaction = availableReactions.reactions.first(where: { $0.value == settings.quickReaction }) {
currentSelectedFileId = reaction.selectAnimation.fileId
break
}
}
case let .custom(fileId):
currentSelectedFileId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
}
var selectedItems = Set<MediaId>()
if let currentSelectedFileId = currentSelectedFileId {
selectedItems.insert(currentSelectedFileId)
}
guard let controller = controller else {
return
}
var sourceItemNode: ItemListReactionItemNode?
controller.forEachItemNode { itemNode in
if let itemNode = itemNode as? ItemListReactionItemNode {
sourceItemNode = itemNode
}
}
if let sourceItemNode = sourceItemNode {
controller.present(EmojiStatusSelectionController(
context: context,
mode: .quickReactionSelection(completion: {
updateState { state in
var state = state
state.hasReaction = false
return state
}
}),
sourceView: sourceItemNode.iconView,
emojiContent: EmojiPagerContentComponent.emojiInputData(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
isStandalone: false,
isStatusSelection: false,
isReactionSelection: true,
isQuickReactionSelection: true,
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: context.account.peerId,
selectedItems: selectedItems
),
destinationItemView: { [weak sourceItemNode] in
return sourceItemNode?.iconView
}
), in: .window(.root))
}
})
}
dismissImpl = { [weak controller] in dismissImpl = { [weak controller] in
guard let controller = controller else { guard let controller = controller else {
return return

View File

@ -147,7 +147,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
private func beginReactionAnimation() { private func beginReactionAnimation() {
if let item = self.item, let updatedReaction = item.reaction, let availableReactions = item.availableReactions, let messageNode = self.messageNode as? ChatMessageItemNodeProtocol { if let item = self.item, let updatedReaction = item.reaction, let availableReactions = item.availableReactions, let messageNode = self.messageNode as? ChatMessageItemNodeProtocol {
if let targetView = messageNode.targetReactionView(value: updatedReaction) { if let _ = messageNode.targetReactionView(value: updatedReaction) {
switch updatedReaction {
case .builtin:
for reaction in availableReactions.reactions { for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else { guard let centerAnimation = reaction.centerAnimation else {
continue continue
@ -157,6 +159,49 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
} }
if reaction.value == updatedReaction { if reaction.value == updatedReaction {
let reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
)
self.beginReactionAnimation(reactionItem: reactionItem)
break
}
}
case let .custom(fileId):
let _ = (item.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
guard let strongSelf = self else {
return
}
if let itemFile = files[fileId] {
let reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
appearAnimation: itemFile,
stillAnimation: itemFile,
listAnimation: itemFile,
largeListAnimation: itemFile,
applicationAnimation: nil,
largeApplicationAnimation: nil,
isCustom: true
)
strongSelf.beginReactionAnimation(reactionItem: reactionItem)
}
})
}
}
}
}
private func beginReactionAnimation(reactionItem: ReactionItem) {
if let item = self.item, let updatedReaction = item.reaction, let messageNode = self.messageNode as? ChatMessageItemNodeProtocol {
if let targetView = messageNode.targetReactionView(value: updatedReaction) {
if let standaloneReactionAnimation = self.standaloneReactionAnimation { if let standaloneReactionAnimation = self.standaloneReactionAnimation {
standaloneReactionAnimation.cancel() standaloneReactionAnimation.cancel()
standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in
@ -174,16 +219,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
supernode.addSubnode(standaloneReactionAnimation) supernode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = supernode.bounds standaloneReactionAnimation.frame = supernode.bounds
standaloneReactionAnimation.animateReactionSelection( standaloneReactionAnimation.animateReactionSelection(
context: item.context, theme: item.theme, animationCache: animationCache, reaction: ReactionItem( context: item.context, theme: item.theme, animationCache: animationCache, reaction: reactionItem,
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
listAnimation: centerAnimation,
largeListAnimation: reaction.activateAnimation,
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
),
avatarPeers: [], avatarPeers: [],
playHaptic: false, playHaptic: false,
isLarge: false, isLarge: false,
@ -194,10 +230,6 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
} }
) )
} }
break
}
}
} }
} }
} }

View File

@ -19,7 +19,7 @@ import WebPBinding
import ReactionImageComponent import ReactionImageComponent
private final class InstalledStickerPacksControllerArguments { private final class InstalledStickerPacksControllerArguments {
let account: Account let context: AccountContext
let openStickerPack: (StickerPackCollectionInfo) -> Void let openStickerPack: (StickerPackCollectionInfo) -> Void
let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void
@ -37,8 +37,8 @@ private final class InstalledStickerPacksControllerArguments {
let expandTrendingPacks: () -> Void let expandTrendingPacks: () -> Void
let addPack: (StickerPackCollectionInfo) -> Void let addPack: (StickerPackCollectionInfo) -> Void
init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openEmoji: @escaping () -> Void, openQuickReaction: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void, toggleAnimatedStickers: @escaping (Bool) -> Void, toggleSuggestAnimatedEmoji: @escaping (Bool) -> Void, togglePackSelected: @escaping (ItemCollectionId) -> Void, expandTrendingPacks: @escaping () -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) { init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openEmoji: @escaping () -> Void, openQuickReaction: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void, toggleAnimatedStickers: @escaping (Bool) -> Void, toggleSuggestAnimatedEmoji: @escaping (Bool) -> Void, togglePackSelected: @escaping (ItemCollectionId) -> Void, expandTrendingPacks: @escaping () -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) {
self.account = account self.context = context
self.openStickerPack = openStickerPack self.openStickerPack = openStickerPack
self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions
self.removePack = removePack self.removePack = removePack
@ -88,7 +88,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?) case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?)
case masks(PresentationTheme, String) case masks(PresentationTheme, String)
case emoji(PresentationTheme, String) case emoji(PresentationTheme, String)
case quickReaction(String, UIImage?) case quickReaction(String, MessageReaction.Reaction, AvailableReactions)
case animatedStickers(PresentationTheme, String, Bool) case animatedStickers(PresentationTheme, String, Bool)
case animatedStickersInfo(PresentationTheme, String) case animatedStickersInfo(PresentationTheme, String)
case suggestAnimatedEmoji(String, Bool) case suggestAnimatedEmoji(String, Bool)
@ -171,8 +171,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .quickReaction(lhsText, lhsImage): case let .quickReaction(lhsText, lhsReaction, lhsAvailableReactions):
if case let .quickReaction(rhsText, rhsImage) = rhs, lhsText == rhsText, lhsImage === rhsImage { if case let .quickReaction(rhsText, rhsReaction, rhsAvailableReactions) = rhs, lhsText == rhsText, lhsReaction == rhsReaction, lhsAvailableReactions == rhsAvailableReactions {
return true return true
} else { } else {
return false return false
@ -430,14 +430,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openEmoji() arguments.openEmoji()
}) })
case let .quickReaction(title, image): case let .quickReaction(title, reaction, availableReactions):
let labelStyle: ItemListDisclosureLabelStyle return ItemListReactionItem(context: arguments.context, presentationData: presentationData, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: {
if let image = image {
labelStyle = .image(image: image, size: image.size.aspectFitted(CGSize(width: 30.0, height: 30.0)))
} else {
labelStyle = .text
}
return ItemListDisclosureItem(presentationData: presentationData, title: title, label: "", labelStyle: labelStyle, sectionId: self.section, style: .blocks, action: {
arguments.openQuickReaction() arguments.openQuickReaction()
}) })
case let .archived(_, text, count, archived): case let .archived(_, text, count, archived):
@ -457,7 +451,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case let .trendingPacksTitle(_, text): case let .trendingPacksTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .trendingPack(_, _, _, info, topItem, count, animatedStickers, unread, installed): case let .trendingPack(_, _, _, info, topItem, count, animatedStickers, unread, installed):
return ItemListStickerPackItem(presentationData: presentationData, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { return ItemListStickerPackItem(presentationData: presentationData, account: arguments.context.account, packInfo: info, itemCount: count, topItem: topItem, unread: unread, control: .installation(installed: installed), editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info) arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { _, _ in }, setPackIdWithRevealedOptions: { _, _ in
}, addPack: { }, addPack: {
@ -472,7 +466,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case let .packsTitle(_, text): case let .packsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected): case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected):
return ItemListStickerPackItem(presentationData: presentationData, account: arguments.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { return ItemListStickerPackItem(presentationData: presentationData, account: arguments.context.account, packInfo: info, itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: {
arguments.openStickerPack(info) arguments.openStickerPack(info)
}, setPackIdWithRevealedOptions: { current, previous in }, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous) arguments.setPackIdWithRevealedOptions(current, previous)
@ -556,7 +550,7 @@ private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> It
private let maxTrendingPacksDisplayedLimit: Int32 = 3 private let maxTrendingPacksDisplayedLimit: Int32 = 3
private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, temporaryPackOrder: [ItemCollectionId]?, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?, stickerSettings: StickerSettings, quickReactionImage: UIImage?) -> [InstalledStickerPacksEntry] { private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, temporaryPackOrder: [ItemCollectionId]?, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?, stickerSettings: StickerSettings, quickReaction: MessageReaction.Reaction?, availableReactions: AvailableReactions?) -> [InstalledStickerPacksEntry] {
var entries: [InstalledStickerPacksEntry] = [] var entries: [InstalledStickerPacksEntry] = []
var installedPacks = Set<ItemCollectionId>() var installedPacks = Set<ItemCollectionId>()
@ -592,7 +586,9 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati
entries.append(.emoji(presentationData.theme, presentationData.strings.StickersList_EmojiItem)) entries.append(.emoji(presentationData.theme, presentationData.strings.StickersList_EmojiItem))
entries.append(.quickReaction(presentationData.strings.Settings_QuickReactionSetup_NavigationTitle, quickReactionImage)) if let quickReaction = quickReaction, let availableReactions = availableReactions {
entries.append(.quickReaction(presentationData.strings.Settings_QuickReactionSetup_NavigationTitle, quickReaction, availableReactions))
}
entries.append(.animatedStickers(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickers, stickerSettings.loopAnimatedStickers)) entries.append(.animatedStickers(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickers, stickerSettings.loopAnimatedStickers))
entries.append(.animatedStickersInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickersInfo)) entries.append(.animatedStickersInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickersInfo))
@ -733,7 +729,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)? var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)?
var navigationControllerImpl: (() -> NavigationController?)? var navigationControllerImpl: (() -> NavigationController?)?
let arguments = InstalledStickerPacksControllerArguments(account: context.account, openStickerPack: { info in let arguments = InstalledStickerPacksControllerArguments(context: context, openStickerPack: { info in
presentStickerPackController?(info) presentStickerPackController?(info)
}, setPackIdWithRevealedOptions: { packId, fromPackId in }, setPackIdWithRevealedOptions: { packId, fromPackId in
updateState { state in updateState { state in
@ -925,21 +921,14 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil) let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil)
let featured = Promise<[FeaturedStickerPackItem]>() let featured = Promise<[FeaturedStickerPackItem]>()
let quickReactionImage: Signal<UIImage?, NoError> let quickReaction: Signal<MessageReaction.Reaction?, NoError>
switch mode { switch mode {
case .general, .modal: case .general, .modal:
featured.set(context.account.viewTracker.featuredStickerPacks()) featured.set(context.account.viewTracker.featuredStickerPacks())
archivedPromise.set(.single(archivedPacks) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init))) archivedPromise.set(.single(archivedPacks) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init)))
quickReactionImage = combineLatest( quickReaction = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings])
context.engine.stickers.availableReactions(), |> map { preferencesView -> MessageReaction.Reaction? in
context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings])
)
|> map { availableReactions, preferencesView -> TelegramMediaFile? in
guard let availableReactions = availableReactions else {
return nil
}
let reactionSettings: ReactionSettings let reactionSettings: ReactionSettings
if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) {
reactionSettings = value reactionSettings = value
@ -947,45 +936,17 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
reactionSettings = .default reactionSettings = .default
} }
for reaction in availableReactions.reactions { return reactionSettings.quickReaction
if reaction.value == reactionSettings.quickReaction {
return reaction.staticIcon
}
}
return nil
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { file -> Signal<UIImage?, NoError> in
guard let file = file else {
return .single(nil)
}
return context.account.postbox.mediaBox.resourceData(file.resource)
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs.complete == rhs.complete
})
|> map { data -> UIImage? in
guard data.complete else {
return nil
}
guard let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
return nil
}
guard let image = WebP.convert(fromWebP: dataValue) else {
return nil
}
return image
}
}
case .masks: case .masks:
featured.set(.single([])) featured.set(.single([]))
archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .masks) |> map(Optional.init))) archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .masks) |> map(Optional.init)))
quickReactionImage = .single(nil) quickReaction = .single(nil)
case .emoji: case .emoji:
featured.set(.single([])) featured.set(.single([]))
archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .emoji) |> map(Optional.init))) archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .emoji) |> map(Optional.init)))
quickReactionImage = .single(nil) quickReaction = .single(nil)
} }
var previousPackCount: Int? var previousPackCount: Int?
@ -995,10 +956,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
temporaryPackOrder.get(), temporaryPackOrder.get(),
combineLatest(queue: .mainQueue(), featured.get(), archivedPromise.get()), combineLatest(queue: .mainQueue(), featured.get(), archivedPromise.get()),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]),
quickReactionImage quickReaction,
context.engine.stickers.availableReactions()
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, view, temporaryPackOrder, featuredAndArchived, sharedData, quickReactionImage -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, view, temporaryPackOrder, featuredAndArchived, sharedData, quickReaction, availableReactions -> (ItemListControllerState, (ItemListNodeState, Any)) in
var stickerSettings = StickerSettings.defaultSettings var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
stickerSettings = value stickerSettings = value
@ -1152,7 +1114,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, temporaryPackOrder: temporaryPackOrder, featured: featuredAndArchived.0, archived: featuredAndArchived.1, stickerSettings: stickerSettings, quickReactionImage: quickReactionImage), style: .blocks, ensureVisibleItemTag: focusOnItemTag, toolbarItem: toolbarItem, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10)) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, temporaryPackOrder: temporaryPackOrder, featured: featuredAndArchived.0, archived: featuredAndArchived.1, stickerSettings: stickerSettings, quickReaction: quickReaction, availableReactions: availableReactions), style: .blocks, ensureVisibleItemTag: focusOnItemTag, toolbarItem: toolbarItem, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10))
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }
|> afterDisposed { |> afterDisposed {

View File

@ -15,13 +15,27 @@ import TextFormat
public final class EmojiStatusComponent: Component { public final class EmojiStatusComponent: Component {
public typealias EnvironmentType = Empty public typealias EnvironmentType = Empty
public enum AnimationContent: Equatable {
case file(file: TelegramMediaFile)
case customEmoji(fileId: Int64)
var fileId: MediaId {
switch self {
case let .file(file):
return file.fileId
case let .customEmoji(fileId):
return MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
}
}
}
public enum Content: Equatable { public enum Content: Equatable {
case none case none
case premium(color: UIColor) case premium(color: UIColor)
case verified(fillColor: UIColor, foregroundColor: UIColor) case verified(fillColor: UIColor, foregroundColor: UIColor)
case fake(color: UIColor) case fake(color: UIColor)
case scam(color: UIColor) case scam(color: UIColor)
case emojiStatus(status: PeerEmojiStatus, size: CGSize, placeholderColor: UIColor) case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor)
} }
public let context: AccountContext public let context: AccountContext
@ -153,14 +167,14 @@ public final class EmojiStatusComponent: Component {
iconImage = nil iconImage = nil
case .scam: case .scam:
iconImage = nil iconImage = nil
case let .emojiStatus(emojiStatus, size, placeholderColor): case let .animation(animationContent, size, placeholderColor):
iconImage = nil iconImage = nil
emojiFileId = emojiStatus.fileId emojiFileId = animationContent.fileId.id
emojiPlaceholderColor = placeholderColor emojiPlaceholderColor = placeholderColor
emojiSize = size emojiSize = size
if case let .emojiStatus(previousEmojiStatus, _, _) = self.component?.content { if case let .animation(previousAnimationContent, _, _) = self.component?.content {
if previousEmojiStatus.fileId != emojiStatus.fileId { if previousAnimationContent.fileId != animationContent.fileId {
self.emojiFileDisposable?.dispose() self.emojiFileDisposable?.dispose()
self.emojiFileDisposable = nil self.emojiFileDisposable = nil
@ -180,11 +194,18 @@ public final class EmojiStatusComponent: Component {
} }
} }
} }
switch animationContent {
case let .file(file):
self.emojiFile = file
case .customEmoji:
break
}
} }
} else { } else {
iconImage = self.iconView?.image iconImage = self.iconView?.image
if case let .emojiStatus(status, size, placeholderColor) = component.content { if case let .animation(animationContent, size, placeholderColor) = component.content {
emojiFileId = status.fileId emojiFileId = animationContent.fileId.id
emojiPlaceholderColor = placeholderColor emojiPlaceholderColor = placeholderColor
emojiSize = size emojiSize = size
} }

View File

@ -548,7 +548,13 @@ public final class EmojiStatusSelectionController: ViewController {
let cloudOffset0: CGFloat = 30.0 let cloudOffset0: CGFloat = 30.0
let cloudSize0: CGFloat = 16.0 let cloudSize0: CGFloat = 16.0
let cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0)) var cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
var invertX = false
if cloudFrame0.maxX >= layout.size.width - layout.safeInsets.right - 32.0 {
cloudFrame0.origin.x = floor(sourceOrigin.x - cloudSize0 - cloudOffset0 + cloudSize0 / 2.0)
invertX = true
}
transition.setFrame(layer: self.cloudLayer0, frame: cloudFrame0) transition.setFrame(layer: self.cloudLayer0, frame: cloudFrame0)
if self.cloudShadowLayer0.bounds.size != cloudFrame0.size { if self.cloudShadowLayer0.bounds.size != cloudFrame0.size {
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame0.size), cornerRadius: 24.0).cgPath let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame0.size), cornerRadius: 24.0).cgPath
@ -559,7 +565,10 @@ public final class EmojiStatusSelectionController: ViewController {
let cloudOffset1 = CGPoint(x: -9.0, y: -14.0) let cloudOffset1 = CGPoint(x: -9.0, y: -14.0)
let cloudSize1: CGFloat = 8.0 let cloudSize1: CGFloat = 8.0
let cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1)) var cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
if invertX {
cloudFrame1.origin.x = floor(cloudFrame0.midX - cloudSize1 - cloudOffset1.x + cloudSize1 / 2.0)
}
transition.setFrame(layer: self.cloudLayer1, frame: cloudFrame1) transition.setFrame(layer: self.cloudLayer1, frame: cloudFrame1)
if self.cloudShadowLayer1.bounds.size != cloudFrame1.size { if self.cloudShadowLayer1.bounds.size != cloudFrame1.size {
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath
@ -622,8 +631,34 @@ public final class EmojiStatusSelectionController: ViewController {
return return
} }
switch controller.mode {
case .statusSelection:
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile) let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
case let .quickReactionSelection(completion):
if let item = item, let itemFile = item.itemFile {
var selectedReaction: MessageReaction.Reaction?
if let availableReactions = self.availableReactions {
for reaction in availableReactions.reactions {
if reaction.selectAnimation.fileId == itemFile.fileId {
selectedReaction = reaction.value
break
}
}
}
if selectedReaction == nil {
selectedReaction = .custom(itemFile.fileId.id)
}
if let selectedReaction = selectedReaction {
let _ = context.engine.stickers.updateQuickReaction(reaction: selectedReaction).start()
}
}
completion()
}
if let item = item, let destinationView = controller.destinationItemView() { if let item = item, let destinationView = controller.destinationItemView() {
self.animateOutToStatus(groupId: groupId, item: item, destinationView: destinationView) self.animateOutToStatus(groupId: groupId, item: item, destinationView: destinationView)
@ -633,9 +668,15 @@ public final class EmojiStatusSelectionController: ViewController {
} }
} }
public enum Mode {
case statusSelection
case quickReactionSelection(completion: () -> Void)
}
private let context: AccountContext private let context: AccountContext
private weak var sourceView: UIView? private weak var sourceView: UIView?
private let emojiContent: Signal<EmojiPagerContentComponent, NoError> private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
private let mode: Mode
private let destinationItemView: () -> UIView? private let destinationItemView: () -> UIView?
fileprivate let _ready = Promise<Bool>() fileprivate let _ready = Promise<Bool>()
@ -643,8 +684,9 @@ public final class EmojiStatusSelectionController: ViewController {
return self._ready return self._ready
} }
public init(context: AccountContext, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, destinationItemView: @escaping () -> UIView?) { public init(context: AccountContext, mode: Mode, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, destinationItemView: @escaping () -> UIView?) {
self.context = context self.context = context
self.mode = mode
self.sourceView = sourceView self.sourceView = sourceView
self.emojiContent = emojiContent self.emojiContent = emojiContent
self.destinationItemView = destinationItemView self.destinationItemView = destinationItemView

View File

@ -255,7 +255,7 @@ public final class EntityKeyboardAnimationData: Equatable {
} }
} }
public final class PassthroughLayer: CALayer { public class PassthroughLayer: CALayer {
public var mirrorLayer: CALayer? public var mirrorLayer: CALayer?
override init() { override init() {
@ -1778,6 +1778,7 @@ public final class EmojiPagerContentComponent: Component {
public let itemLayoutType: ItemLayoutType public let itemLayoutType: ItemLayoutType
public let warpContentsOnEdges: Bool public let warpContentsOnEdges: Bool
public let enableLongPress: Bool public let enableLongPress: Bool
public let selectedItems: Set<MediaId>
public init( public init(
id: AnyHashable, id: AnyHashable,
@ -1789,7 +1790,8 @@ public final class EmojiPagerContentComponent: Component {
itemGroups: [ItemGroup], itemGroups: [ItemGroup],
itemLayoutType: ItemLayoutType, itemLayoutType: ItemLayoutType,
warpContentsOnEdges: Bool, warpContentsOnEdges: Bool,
enableLongPress: Bool enableLongPress: Bool,
selectedItems: Set<MediaId>
) { ) {
self.id = id self.id = id
self.context = context self.context = context
@ -1801,6 +1803,7 @@ public final class EmojiPagerContentComponent: Component {
self.itemLayoutType = itemLayoutType self.itemLayoutType = itemLayoutType
self.warpContentsOnEdges = warpContentsOnEdges self.warpContentsOnEdges = warpContentsOnEdges
self.enableLongPress = enableLongPress self.enableLongPress = enableLongPress
self.selectedItems = selectedItems
} }
public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool { public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool {
@ -1837,6 +1840,9 @@ public final class EmojiPagerContentComponent: Component {
if lhs.enableLongPress != rhs.enableLongPress { if lhs.enableLongPress != rhs.enableLongPress {
return false return false
} }
if lhs.selectedItems != rhs.selectedItems {
return false
}
return true return true
} }
@ -2441,6 +2447,32 @@ public final class EmojiPagerContentComponent: Component {
} }
} }
private final class ItemSelectionLayer: PassthroughLayer {
let tintContainerLayer: SimpleLayer
override init() {
self.tintContainerLayer = SimpleLayer()
super.init()
self.mirrorLayer = self.tintContainerLayer
}
override func action(forKey event: String) -> CAAction? {
return nullAction
}
override init(layer: Any) {
self.tintContainerLayer = SimpleLayer()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private final class ContentScrollLayer: CALayer { private final class ContentScrollLayer: CALayer {
var mirrorLayer: CALayer? var mirrorLayer: CALayer?
@ -2546,6 +2578,7 @@ public final class EmojiPagerContentComponent: Component {
private let placeholdersContainerView: UIView private let placeholdersContainerView: UIView
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:] private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:]
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:] private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:] private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:]
private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:] private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:]
@ -2674,13 +2707,17 @@ public final class EmojiPagerContentComponent: Component {
public func animateIn(fromLocation: CGPoint) { public func animateIn(fromLocation: CGPoint) {
let scrollLocation = self.convert(fromLocation, to: self.scrollView) let scrollLocation = self.convert(fromLocation, to: self.scrollView)
for (_, itemLayer) in self.visibleItemLayers { for (key, itemLayer) in self.visibleItemLayers {
let distanceVector = CGPoint(x: scrollLocation.x - itemLayer.position.x, y: scrollLocation.y - itemLayer.position.y) let distanceVector = CGPoint(x: scrollLocation.x - itemLayer.position.x, y: scrollLocation.y - itemLayer.position.y)
let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y) let distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y)
let distanceNorm = min(1.0, max(0.0, distance / self.bounds.width)) let distanceNorm = min(1.0, max(0.0, distance / self.bounds.width))
let delay = 0.05 + (distanceNorm) * 0.3 let delay = 0.05 + (distanceNorm) * 0.3
itemLayer.animateScale(from: 0.01, to: 1.0, duration: 0.18, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) itemLayer.animateScale(from: 0.01, to: 1.0, duration: 0.18, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
if let itemSelectionLayer = self.visibleItemSelectionLayers[key] {
itemSelectionLayer.animateScale(from: 0.01, to: 1.0, duration: 0.18, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
}
} }
} }
@ -2689,7 +2726,7 @@ public final class EmojiPagerContentComponent: Component {
return return
} }
for (_, itemLayer) in self.visibleItemLayers { for (key, itemLayer) in self.visibleItemLayers {
guard case let .animation(animationData) = itemLayer.item.content else { guard case let .animation(animationData) = itemLayer.item.content else {
continue continue
} }
@ -2699,6 +2736,10 @@ public final class EmojiPagerContentComponent: Component {
if let sourceItem = sourceItems[file.fileId] { if let sourceItem = sourceItems[file.fileId] {
itemLayer.animatePosition(from: CGPoint(x: sourceItem.position.x - itemLayer.position.x, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) itemLayer.animatePosition(from: CGPoint(x: sourceItem.position.x - itemLayer.position.x, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
if let itemSelectionLayer = self.visibleItemSelectionLayers[key] {
itemSelectionLayer.animatePosition(from: CGPoint(x: sourceItem.position.x - itemLayer.position.x, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
component.animationRenderer.setFrameIndex(itemId: animationData.resource.resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder) component.animationRenderer.setFrameIndex(itemId: animationData.resource.resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder)
} else { } else {
let distance = itemLayer.position.y - itemLayout.frame(groupIndex: 0, itemIndex: 0).midY let distance = itemLayer.position.y - itemLayout.frame(groupIndex: 0, itemIndex: 0).midY
@ -2710,6 +2751,11 @@ public final class EmojiPagerContentComponent: Component {
itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, delay: delay) itemLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, delay: delay)
if let itemSelectionLayer = self.visibleItemSelectionLayers[key] {
itemSelectionLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemSelectionLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, delay: delay)
}
} }
} }
@ -2786,6 +2832,10 @@ public final class EmojiPagerContentComponent: Component {
for (id, layer) in self.visibleItemLayers { for (id, layer) in self.visibleItemLayers {
previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY))
} }
var previousVisibleItemSelectionLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:]
for (id, layer) in self.visibleItemSelectionLayers {
previousVisibleItemSelectionLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY))
}
var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:] var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:]
for (id, view) in self.visibleItemPlaceholderViews { for (id, view) in self.visibleItemPlaceholderViews {
previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY))
@ -3084,6 +3134,9 @@ public final class EmojiPagerContentComponent: Component {
for (_, layer) in self.visibleItemLayers { for (_, layer) in self.visibleItemLayers {
layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true)
} }
for (_, layer) in self.visibleItemSelectionLayers {
layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true)
}
for (id, layerAndFrame) in previousVisibleLayers { for (id, layerAndFrame) in previousVisibleLayers {
if self.visibleItemLayers[id] != nil { if self.visibleItemLayers[id] != nil {
continue continue
@ -3095,6 +3148,17 @@ public final class EmojiPagerContentComponent: Component {
layer?.removeFromSuperlayer() layer?.removeFromSuperlayer()
}) })
} }
for (id, layerAndFrame) in previousVisibleItemSelectionLayers {
if self.visibleItemSelectionLayers[id] != nil {
continue
}
let layer = layerAndFrame.0
layer.frame = layerAndFrame.1.offsetBy(dx: 0.0, dy: self.scrollView.bounds.minY)
self.scrollView.layer.addSublayer(layer)
layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -commonItemOffset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, additive: true, completion: { [weak layer] _ in
layer?.removeFromSuperlayer()
})
}
for (_, view) in self.visibleItemPlaceholderViews { for (_, view) in self.visibleItemPlaceholderViews {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) view.layer.animatePosition(from: CGPoint(x: 0.0, y: commonItemOffset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true)
@ -3794,10 +3858,6 @@ public final class EmojiPagerContentComponent: Component {
} else { } else {
pointSize = itemPlaybackSize pointSize = itemPlaybackSize
} }
/*if case let .animation(animationData) = item.content, animationData.isReaction {
pointSize.width += pointSize.width
pointSize.height += pointSize.height
}*/
let placeholderColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1) let placeholderColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1)
itemLayer = ItemLayer( itemLayer = ItemLayer(
@ -3858,12 +3918,13 @@ public final class EmojiPagerContentComponent: Component {
} }
} }
) )
//itemLayer.backgroundColor = UIColor.lightGray.cgColor
self.scrollView.layer.addSublayer(itemLayer) self.scrollView.layer.addSublayer(itemLayer)
self.visibleItemLayers[itemId] = itemLayer self.visibleItemLayers[itemId] = itemLayer
} }
var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index) var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index)
let baseItemFrame = itemFrame
itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0) itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0)
itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0) itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0)
@ -3889,33 +3950,6 @@ public final class EmojiPagerContentComponent: Component {
let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY) let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
itemTransition.setPosition(layer: itemLayer, position: itemPosition) itemTransition.setPosition(layer: itemLayer, position: itemPosition)
if itemLayout.curveNearBounds && !"".isEmpty {
let fractionLength: CGFloat = itemFrame.height
let rotationX: CGFloat = 1.0 - max(0.0, min(1.0, (effectiveVisibleBounds.maxY + 4.0 + itemFrame.height / 2.0 - itemPosition.y) / fractionLength))
if rotationX.isZero {
itemTransition.setTransform(layer: itemLayer, transform: CATransform3DIdentity)
} else {
var transform = CATransform3DIdentity
let centralOffset = itemFrame.midX - effectiveVisibleBounds.width / 2.0
transform = CATransform3DTranslate(transform, centralOffset * 2.0, 0.0, 0.0)
var projectionMatrix = CATransform3DIdentity
projectionMatrix.m34 = -1.0 / 500.0
transform = CATransform3DConcat(projectionMatrix, transform)
transform = CATransform3DTranslate(transform, -centralOffset * 2.0, 0.0, 0.0)
let rotationAngle = CGFloat.pi * 0.5 * rotationX
let rotationOffset = sin(rotationAngle)
let zOffset: CGFloat = 0.0
transform = CATransform3DTranslate(transform, 0.0, -rotationOffset * itemFrame.height * 0.5, -zOffset * itemFrame.height * 0.5)
transform = CATransform3DRotate(transform, rotationAngle, 1.0, 0.0, 0.0)
itemTransition.setTransform(layer: itemLayer, transform: transform)
}
}
var badge: ItemLayer.Badge? var badge: ItemLayer.Badge?
if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker { if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker {
badge = .premium badge = .premium
@ -3933,6 +3967,24 @@ public final class EmojiPagerContentComponent: Component {
} }
} }
if let itemFile = item.itemFile, component.selectedItems.contains(itemFile.fileId) {
let itemSelectionLayer: ItemSelectionLayer
if let current = self.visibleItemSelectionLayers[itemId] {
itemSelectionLayer = current
} else {
itemSelectionLayer = ItemSelectionLayer()
itemSelectionLayer.cornerRadius = 8.0
itemSelectionLayer.tintContainerLayer.cornerRadius = 8.0
self.scrollView.layer.insertSublayer(itemSelectionLayer, below: itemLayer)
self.mirrorContentScrollView.layer.addSublayer(itemSelectionLayer.tintContainerLayer)
self.visibleItemSelectionLayers[itemId] = itemSelectionLayer
}
itemSelectionLayer.backgroundColor = keyboardChildEnvironment.theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.cgColor
itemSelectionLayer.tintContainerLayer.backgroundColor = UIColor.white.cgColor
itemSelectionLayer.frame = baseItemFrame
}
if animateItemIn, !transition.animation.isImmediate, let contentAnimation = contentAnimation, case .groupExpanded(id: itemGroup.groupId) = contentAnimation.type, let placeholderView = self.visibleItemPlaceholderViews[itemId] { if animateItemIn, !transition.animation.isImmediate, let contentAnimation = contentAnimation, case .groupExpanded(id: itemGroup.groupId) = contentAnimation.type, let placeholderView = self.visibleItemPlaceholderViews[itemId] {
placeholderView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) placeholderView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
placeholderView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) placeholderView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
@ -3949,6 +4001,8 @@ public final class EmojiPagerContentComponent: Component {
if !validIds.contains(id) { if !validIds.contains(id) {
removedIds.append(id) removedIds.append(id)
let itemSelectionLayer = self.visibleItemSelectionLayers[id]
if !transition.animation.isImmediate { if !transition.animation.isImmediate {
if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id.groupId { if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id.groupId {
if let previousAbsolutePosition = previousAbsoluteItemPositions?[.item(id: id)] { if let previousAbsolutePosition = previousAbsoluteItemPositions?[.item(id: id)] {
@ -3961,30 +4015,93 @@ public final class EmojiPagerContentComponent: Component {
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
if let itemSelectionLayer = itemSelectionLayer {
itemSelectionLayer.opacity = 0.0
itemSelectionLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16)
itemSelectionLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemSelectionLayer] _ in
itemSelectionLayer?.removeFromSuperlayer()
})
let itemSelectionTintContainerLayer = itemSelectionLayer.tintContainerLayer
itemSelectionTintContainerLayer.opacity = 0.0
itemSelectionTintContainerLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16)
itemSelectionTintContainerLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemSelectionTintContainerLayer] _ in
itemSelectionTintContainerLayer?.removeFromSuperlayer()
})
}
} else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId { } else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId {
transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
if let itemSelectionLayer = itemSelectionLayer {
transition.setPosition(layer: itemSelectionLayer, position: position, completion: { [weak itemSelectionLayer] _ in
itemSelectionLayer?.removeFromSuperlayer()
})
}
} else { } else {
itemLayer.opacity = 0.0 itemLayer.opacity = 0.0
itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2) itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemLayer] _ in itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer() itemLayer?.removeFromSuperlayer()
}) })
if let itemSelectionLayer = itemSelectionLayer {
itemSelectionLayer.opacity = 0.0
itemSelectionLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
itemSelectionLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemSelectionLayer] _ in
itemSelectionLayer?.removeFromSuperlayer()
})
let itemSelectionTintContainerLayer = itemSelectionLayer.tintContainerLayer
itemSelectionTintContainerLayer.opacity = 0.0
itemSelectionTintContainerLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
itemSelectionTintContainerLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak itemSelectionTintContainerLayer] _ in
itemSelectionTintContainerLayer?.removeFromSuperlayer()
})
}
} }
} else { } else {
itemLayer.removeFromSuperlayer() itemLayer.removeFromSuperlayer()
if let itemSelectionLayer = itemSelectionLayer {
itemSelectionLayer.removeFromSuperlayer()
}
} }
} }
} }
for id in removedIds { for id in removedIds {
self.visibleItemLayers.removeValue(forKey: id) self.visibleItemLayers.removeValue(forKey: id)
self.visibleItemSelectionLayers.removeValue(forKey: id)
if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) { if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) {
view.removeFromSuperview() view.removeFromSuperview()
removedPlaceholerViews = true removedPlaceholerViews = true
} }
} }
var removedItemSelectionLayerIds: [ItemLayer.Key] = []
for (id, itemSelectionLayer) in self.visibleItemSelectionLayers {
var fileId: MediaId?
switch id.itemId {
case let .animation(id):
switch id {
case let .file(fileIdValue):
fileId = fileIdValue
default:
break
}
default:
break
}
if let fileId = fileId, component.selectedItems.contains(fileId) {
} else {
itemSelectionLayer.removeFromSuperlayer()
removedItemSelectionLayerIds.append(id)
}
}
for id in removedItemSelectionLayerIds {
self.visibleItemSelectionLayers.removeValue(forKey: id)
}
var removedGroupHeaderIds: [AnyHashable] = [] var removedGroupHeaderIds: [AnyHashable] = []
for (id, groupHeaderLayer) in self.visibleGroupHeaders { for (id, groupHeaderLayer) in self.visibleGroupHeaders {
@ -4563,7 +4680,7 @@ public final class EmojiPagerContentComponent: Component {
return hasPremium return hasPremium
} }
public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, topReactionItems: [AvailableReactions.Reaction], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?) -> Signal<EmojiPagerContentComponent, NoError> { public static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, isStatusSelection: Bool, isReactionSelection: Bool, isQuickReactionSelection: Bool = false, topReactionItems: [AvailableReactions.Reaction], areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?, selectedItems: Set<MediaId> = Set()) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
@ -4577,6 +4694,7 @@ public final class EmojiPagerContentComponent: Component {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
} else if isReactionSelection { } else if isReactionSelection {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions) orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions)
} }
@ -4612,6 +4730,7 @@ public final class EmojiPagerContentComponent: Component {
var recentEmoji: OrderedItemListView? var recentEmoji: OrderedItemListView?
var featuredStatusEmoji: OrderedItemListView? var featuredStatusEmoji: OrderedItemListView?
var recentStatusEmoji: OrderedItemListView? var recentStatusEmoji: OrderedItemListView?
var topReactions: OrderedItemListView?
var recentReactions: OrderedItemListView? var recentReactions: OrderedItemListView?
for orderedView in view.orderedItemListsViews { for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji { if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
@ -4622,6 +4741,8 @@ public final class EmojiPagerContentComponent: Component {
recentStatusEmoji = orderedView recentStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentReactions { } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentReactions {
recentReactions = orderedView recentReactions = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudTopReactions {
topReactions = orderedView
} }
} }
@ -4698,6 +4819,41 @@ public final class EmojiPagerContentComponent: Component {
} }
} else if isReactionSelection { } else if isReactionSelection {
var existingIds = Set<MessageReaction.Reaction>() var existingIds = Set<MessageReaction.Reaction>()
var topReactionItems = topReactionItems
if topReactionItems.isEmpty {
if let topReactions = topReactions {
for item in topReactions.items {
guard let topReaction = item.contents.get(RecentReactionItem.self) else {
continue
}
switch topReaction.content {
case let .builtin(value):
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
topReactionItems.append(reaction)
} else {
continue
}
case let .custom(file):
topReactionItems.append(AvailableReactions.Reaction(
isEnabled: true,
isPremium: false,
value: .custom(file.fileId.id),
title: "",
staticIcon: file,
appearAnimation: file,
selectAnimation: file,
activateAnimation: file,
effectAnimation: file,
aroundAnimation: file,
centerAnimation: file
))
}
}
}
}
for reactionItem in topReactionItems { for reactionItem in topReactionItems {
if existingIds.contains(reactionItem.value) { if existingIds.contains(reactionItem.value) {
continue continue
@ -4717,7 +4873,7 @@ public final class EmojiPagerContentComponent: Component {
if let groupIndex = itemGroupIndexById[groupId] { if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem) itemGroups[groupIndex].items.append(resultItem)
if itemGroups[groupIndex].items.count >= 16 { if itemGroups[groupIndex].items.count >= 8 * 2 {
break break
} }
} else { } else {
@ -4732,7 +4888,12 @@ public final class EmojiPagerContentComponent: Component {
hasRecent = true hasRecent = true
} }
let maxRecentLineCount = 4 let maxRecentLineCount: Int
#if DEBUG
maxRecentLineCount = 4
#else
maxRecentLineCount = 10
#endif
//TODO:localize //TODO:localize
let popularTitle = hasRecent ? "Recently Used" : "Popular" let popularTitle = hasRecent ? "Recently Used" : "Popular"
@ -4761,7 +4922,7 @@ public final class EmojiPagerContentComponent: Component {
itemGroups[groupIndex].items.append(resultItem) itemGroups[groupIndex].items.append(resultItem)
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent, headerItem: nil, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent && !isQuickReactionSelection, headerItem: nil, items: [resultItem]))
} }
} }
} }
@ -4815,7 +4976,7 @@ public final class EmojiPagerContentComponent: Component {
popularInsertIndex += 1 popularInsertIndex += 1
} else { } else {
itemGroupIndexById[groupId] = itemGroups.count itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent, headerItem: nil, items: [resultItem])) itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent && !isQuickReactionSelection, headerItem: nil, items: [resultItem]))
} }
} }
} }
@ -5048,7 +5209,8 @@ public final class EmojiPagerContentComponent: Component {
}, },
itemLayoutType: .compact, itemLayoutType: .compact,
warpContentsOnEdges: isReactionSelection || isStatusSelection, warpContentsOnEdges: isReactionSelection || isStatusSelection,
enableLongPress: isReactionSelection enableLongPress: isReactionSelection && !isQuickReactionSelection,
selectedItems: selectedItems
) )
} }
return emojiItems return emojiItems

View File

@ -986,9 +986,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)), strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)),
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction, messageNode: node as? ChatMessageItemView), contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction, messageNode: node as? ChatMessageItemView),
peerMessageAllowedReactions(context: strongSelf.context, message: topMessage), peerMessageAllowedReactions(context: strongSelf.context, message: topMessage),
peerMessageSelectedReactionFiles(context: strongSelf.context, message: topMessage),
topMessageReactions(context: strongSelf.context, message: topMessage), topMessageReactions(context: strongSelf.context, message: topMessage),
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager) ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
).start(next: { peer, actions, allowedReactions, topReactions, chatTextSelectionTips in ).start(next: { peer, actions, allowedReactions, selectedReactionFiles, topReactions, chatTextSelectionTips in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1107,7 +1108,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
topReactionItems: reactionItems, topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false, areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true, areCustomEmojiEnabled: true,
chatPeerId: strongSelf.chatLocation.peerId chatPeerId: strongSelf.chatLocation.peerId,
selectedItems: selectedReactionFiles
) )
} }
} }
@ -1714,7 +1716,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else { } else {
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts(itemNode: itemNode) strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts(itemNode: itemNode)
if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message, isPremium: strongSelf.presentationInterfaceState.isPremium) { if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message, isPremium: strongSelf.presentationInterfaceState.isPremium, forceInline: false) {
var hideRemovedReaction: Bool = false var hideRemovedReaction: Bool = false
if let reactions = mergedMessageReactions(attributes: message.attributes) { if let reactions = mergedMessageReactions(attributes: message.attributes) {
for reaction in reactions.reactions { for reaction in reactions.reactions {
@ -16973,6 +16975,29 @@ func peerMessageAllowedReactions(context: AccountContext, message: Message) -> S
} }
} }
func peerMessageSelectedReactionFiles(context: AccountContext, message: Message) -> Signal<Set<MediaId>, NoError> {
return context.engine.stickers.availableReactions()
|> take(1)
|> map { availableReactions -> Set<MediaId> in
var result = Set<MediaId>()
if let effectiveReactions = message.effectiveReactions {
for reaction in effectiveReactions {
switch reaction.value {
case .builtin:
if let availableReaction = availableReactions?.reactions.first(where: { $0.value == reaction.value }) {
result.insert(availableReaction.selectAnimation.fileId)
}
case let .custom(fileId):
result.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))
}
}
}
return result
}
}
final class ChatControllerNavigationData: CustomViewControllerNavigationData { final class ChatControllerNavigationData: CustomViewControllerNavigationData {
let peerId: PeerId let peerId: PeerId

View File

@ -499,7 +499,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
}, },
itemLayoutType: .detailed, itemLayoutType: .detailed,
warpContentsOnEdges: false, warpContentsOnEdges: false,
enableLongPress: false enableLongPress: false,
selectedItems: Set()
) )
} }

View File

@ -1061,7 +1061,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,
@ -1216,7 +1216,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
let reactions: ReactionsMessageAttribute let reactions: ReactionsMessageAttribute
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) { if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])
} else { } else {
reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])

View File

@ -678,7 +678,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: textStatusType, type: textStatusType,
layoutInput: .trailingContent(contentWidth: trailingContentWidth, reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), layoutInput: .trailingContent(contentWidth: trailingContentWidth, reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil),
constrainedSize: textConstrainedSize, constrainedSize: textConstrainedSize,
availableReactions: associatedData.availableReactions, availableReactions: associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -200,7 +200,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
let firstMessage = item.content.firstMessage let firstMessage = item.content.firstMessage
let reactionsAreInline = shouldDisplayInlineDateReactions(message: firstMessage, isPremium: item.associatedData.isPremium) let reactionsAreInline: Bool
reactionsAreInline = shouldDisplayInlineDateReactions(message: firstMessage, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions)
if reactionsAreInline { if reactionsAreInline {
needReactions = false needReactions = false
} }
@ -1681,7 +1682,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -219,7 +219,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil),
constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude), constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -1354,7 +1354,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
} }
func shouldDisplayInlineDateReactions(message: Message, isPremium: Bool) -> Bool { func shouldDisplayInlineDateReactions(message: Message, isPremium: Bool, forceInline: Bool) -> Bool {
if forceInline {
return true
}
if message.id.peerId.namespace == Namespaces.Peer.CloudUser || message.id.peerId.namespace == Namespaces.Peer.SecretChat { if message.id.peerId.namespace == Namespaces.Peer.CloudUser || message.id.peerId.namespace == Namespaces.Peer.SecretChat {
if let chatPeer = message.peers[message.id.peerId] as? TelegramUser, chatPeer.isPremium { if let chatPeer = message.peers[message.id.peerId] as? TelegramUser, chatPeer.isPremium {
return false return false

View File

@ -543,7 +543,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
} }
let reactions: ReactionsMessageAttribute let reactions: ReactionsMessageAttribute
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) { if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])
} else { } else {
reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])

View File

@ -747,7 +747,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: arguments.message, dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings) let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: arguments.message, dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings)
let displayReactionsInline = shouldDisplayInlineDateReactions(message: arguments.message, isPremium: arguments.associatedData.isPremium) let displayReactionsInline = shouldDisplayInlineDateReactions(message: arguments.message, isPremium: arguments.associatedData.isPremium, forceInline: arguments.associatedData.forceInlineReactions)
var reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings? var reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings?
if displayReactionsInline || arguments.displayReactions { if displayReactionsInline || arguments.displayReactions {

View File

@ -325,7 +325,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -512,7 +512,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
impressionCount: dateAndStatus.viewCount, impressionCount: dateAndStatus.viewCount,
dateText: dateAndStatus.dateText, dateText: dateAndStatus.dateText,
type: dateAndStatus.type, type: dateAndStatus.type,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: associatedData.availableReactions, availableReactions: associatedData.availableReactions,
reactions: dateAndStatus.dateReactions, reactions: dateAndStatus.dateReactions,

View File

@ -252,7 +252,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -1073,7 +1073,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil),
constrainedSize: textConstrainedSize, constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -120,7 +120,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium), preferAdditionalInset: false)), layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)),
constrainedSize: textConstrainedSize, constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,

View File

@ -531,7 +531,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
impressionCount: viewCount, impressionCount: viewCount,
dateText: dateText, dateText: dateText,
type: statusType, type: statusType,
layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil),
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions, availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions, reactions: dateReactionsAndPeers.reactions,
@ -683,7 +683,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
let reactions: ReactionsMessageAttribute let reactions: ReactionsMessageAttribute
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium) { if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])
} else { } else {
reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: []) reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])

View File

@ -363,7 +363,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} }
let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput
dateLayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium), preferAdditionalInset: false)) dateLayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false))
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context, context: item.context,

View File

@ -679,7 +679,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
case .scam: case .scam:
titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor) titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor)
case let .emojiStatus(emojiStatus): case let .emojiStatus(emojiStatus):
titleCredibilityContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor) titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor)
} }
let titleCredibilitySize = self.titleCredibilityIconView.update( let titleCredibilitySize = self.titleCredibilityIconView.update(

View File

@ -2354,8 +2354,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor) emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
case let .emojiStatus(emojiStatus): case let .emojiStatus(emojiStatus):
currentEmojiStatus = emojiStatus currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor) emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15)) emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15))
} }
let animateStatusIcon = !self.titleCredibilityIconView.bounds.isEmpty let animateStatusIcon = !self.titleCredibilityIconView.bounds.isEmpty

View File

@ -3097,6 +3097,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
strongSelf.controller?.present(EmojiStatusSelectionController( strongSelf.controller?.present(EmojiStatusSelectionController(
context: strongSelf.context, context: strongSelf.context,
mode: .statusSelection,
sourceView: sourceView, sourceView: sourceView,
emojiContent: EmojiPagerContentComponent.emojiInputData( emojiContent: EmojiPagerContentComponent.emojiInputData(
context: strongSelf.context, context: strongSelf.context,

View File

@ -1351,7 +1351,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
chatLocation = .peer(id: messages.first!.id.peerId) chatLocation = .peer(id: messages.first!.id.peerId)
} }
return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, defaultReaction: nil, isPremium: false), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil) return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: availableReactions, defaultReaction: nil, isPremium: false, forceInlineReactions: true), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil)
} }
public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {