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 defaultReaction: MessageReaction.Reaction?
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.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
@ -45,6 +46,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.availableReactions = availableReactions
self.defaultReaction = defaultReaction
self.isPremium = isPremium
self.forceInlineReactions = forceInlineReactions
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -87,6 +89,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.isPremium != rhs.isPremium {
return false
}
if lhs.forceInlineReactions != rhs.forceInlineReactions {
return false
}
return true
}
}

View File

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

View File

@ -118,7 +118,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
case .premium:
statusContent = .premium(color: self.theme.list.itemAccentColor)
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
@ -351,7 +351,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
case .premium:
statusContent = .premium(color: self.theme.list.itemAccentColor)
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)

View File

@ -1524,7 +1524,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = messages.last?.author {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
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 {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
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 {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
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 {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)

View File

@ -616,7 +616,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} else if peer.isFake {
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
} 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 {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {

View File

@ -614,7 +614,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} else if item.peer.isFake {
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
} 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 {
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {

View File

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

View File

@ -103,6 +103,7 @@ swift_library(
"//submodules/PremiumUI:PremiumUI",
"//submodules/InviteLinksUI:InviteLinksUI",
"//submodules/HorizontalPeerItem:HorizontalPeerItem",
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
],
visibility = [
"//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 ReactionImageComponent
import WebPBinding
import EmojiStatusSelectionComponent
import EntityKeyboard
private final class QuickReactionSetupControllerArguments {
let context: AccountContext
let selectItem: (MessageReaction.Reaction) -> Void
let openQuickReaction: () -> Void
let toggleReaction: () -> Void
init(
context: AccountContext,
selectItem: @escaping (MessageReaction.Reaction) -> Void,
openQuickReaction: @escaping () -> Void,
toggleReaction: @escaping () -> Void
) {
self.context = context
self.selectItem = selectItem
self.openQuickReaction = openQuickReaction
self.toggleReaction = toggleReaction
}
}
@ -39,21 +41,21 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
case demoHeader
case demoMessage
case demoDescription
case itemsHeader
case item(MessageReaction.Reaction)
case quickReaction
case quickReactionDescription
}
case demoHeader(String)
case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?)
case demoDescription(String)
case itemsHeader(String)
case item(index: Int, value: MessageReaction.Reaction, image: UIImage?, imageIsAnimation: Bool, text: String, isSelected: Bool)
case quickReaction(String, MessageReaction.Reaction, AvailableReactions)
case quickReactionDescription(String)
var section: ItemListSectionId {
switch self {
case .demoHeader, .demoMessage, .demoDescription:
return QuickReactionSetupControllerSection.demo.rawValue
case .itemsHeader, .item:
case .quickReaction, .quickReactionDescription:
return QuickReactionSetupControllerSection.items.rawValue
}
}
@ -66,10 +68,10 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
return .demoMessage
case .demoDescription:
return .demoDescription
case .itemsHeader:
return .itemsHeader
case let .item(_, value, _, _, _, _):
return .item(value)
case .quickReaction:
return .quickReaction
case .quickReactionDescription:
return .quickReactionDescription
}
}
@ -81,10 +83,10 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
return 1
case .demoDescription:
return 2
case .itemsHeader:
case .quickReaction:
return 3
case let .item(index, _, _, _, _, _):
return 100 + index
case .quickReactionDescription:
return 4
}
}
@ -108,14 +110,14 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
} else {
return false
}
case let .itemsHeader(text):
if case .itemsHeader(text) = rhs {
case let .quickReaction(lhsText, lhsReaction, lhsAvailableReactions):
if case let .quickReaction(rhsText, rhsReaction, rhsAvailableReactions) = rhs, lhsText == rhsText, lhsReaction == rhsReaction, lhsAvailableReactions == rhsAvailableReactions {
return true
} else {
return false
}
case let .item(index, value, file, imageIsAnimation, text, isEnabled):
if case .item(index, value, file, imageIsAnimation, text, isEnabled) = rhs {
case let .quickReactionDescription(text):
if case .quickReactionDescription(text) = rhs {
return true
} else {
return false
@ -151,28 +153,12 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry {
)
case let .demoDescription(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .itemsHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .item(_, value, image, imageIsAnimation, text, isSelected):
var imageFitSize = CGSize(width: 30.0, height: 30.0)
if imageIsAnimation {
imageFitSize.width = floor(imageFitSize.width * 2.0)
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)
}
)
case let .quickReaction(title, reaction, availableReactions):
return ItemListReactionItem(context: arguments.context, presentationData: presentationData, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: {
arguments.openQuickReaction()
})
case let .quickReactionDescription(text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
@ -184,7 +170,6 @@ private struct QuickReactionSetupControllerState: Equatable {
private func quickReactionSetupControllerEntries(
presentationData: PresentationData,
availableReactions: AvailableReactions?,
images: [MessageReaction.Reaction: (image: UIImage, isAnimation: Bool)],
reactionSettings: ReactionSettings,
state: QuickReactionSetupControllerState,
isPremium: Bool
@ -204,27 +189,11 @@ private func quickReactionSetupControllerEntries(
))
entries.append(.demoDescription(presentationData.strings.Settings_QuickReactionSetup_DemoInfo))
entries.append(.itemsHeader(presentationData.strings.Settings_QuickReactionSetup_ReactionListHeader))
var index = 0
for availableReaction in availableReactions.reactions {
if !availableReaction.isEnabled {
continue
}
if !isPremium && availableReaction.isPremium {
continue
}
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
}
//TODO:localize
entries.append(.quickReaction("Choose Your Quick Reaction", reactionSettings.quickReaction, availableReactions))
//TODO:localize
entries.append(.quickReactionDescription("You can set any emoji as your quick reaction."))
}
return entries
@ -243,17 +212,14 @@ public func quickReactionSetupController(
var dismissImpl: (() -> Void)?
let _ = dismissImpl
var openQuickReactionImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
let arguments = QuickReactionSetupControllerArguments(
context: context,
selectItem: { reaction in
updateState { state in
var state = state
state.hasReaction = false
return state
}
let _ = context.engine.stickers.updateQuickReaction(reaction: reaction).start()
openQuickReaction: {
openQuickReactionImpl?()
},
toggleReaction: {
updateState { state in
@ -275,80 +241,22 @@ public func quickReactionSetupController(
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 signal = combineLatest(queue: .mainQueue(),
presentationData,
statePromise.get(),
context.engine.stickers.availableReactions(),
settings,
images,
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
|> 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 title: String = presentationData.strings.Settings_QuickReactionSetup_Title
let entries = quickReactionSetupControllerEntries(
presentationData: presentationData,
availableReactions: availableReactions,
images: images,
reactionSettings: settings,
state: state,
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
guard let controller = controller else {
return

View File

@ -147,56 +147,88 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
private func beginReactionAnimation() {
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) {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
if let standaloneReactionAnimation = self.standaloneReactionAnimation {
standaloneReactionAnimation.cancel()
standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in
standaloneReactionAnimation?.removeFromSupernode()
})
self.standaloneReactionAnimation = nil
if let _ = messageNode.targetReactionView(value: updatedReaction) {
switch updatedReaction {
case .builtin:
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if let supernode = self.supernode {
let standaloneReactionAnimation = StandaloneReactionAnimation()
self.standaloneReactionAnimation = standaloneReactionAnimation
let animationCache = item.context.animationCache
supernode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = supernode.bounds
standaloneReactionAnimation.animateReactionSelection(
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: [],
playHaptic: false,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
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
}
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 {
standaloneReactionAnimation.cancel()
standaloneReactionAnimation.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimation] _ in
standaloneReactionAnimation?.removeFromSupernode()
})
self.standaloneReactionAnimation = nil
}
if let supernode = self.supernode {
let standaloneReactionAnimation = StandaloneReactionAnimation()
self.standaloneReactionAnimation = standaloneReactionAnimation
let animationCache = item.context.animationCache
supernode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = supernode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: item.context, theme: item.theme, animationCache: animationCache, reaction: reactionItem,
avatarPeers: [],
playHaptic: false,
isLarge: false,
targetView: targetView,
addStandaloneReactionAnimation: nil,
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
}
}

View File

@ -19,7 +19,7 @@ import WebPBinding
import ReactionImageComponent
private final class InstalledStickerPacksControllerArguments {
let account: Account
let context: AccountContext
let openStickerPack: (StickerPackCollectionInfo) -> Void
let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void
@ -37,8 +37,8 @@ private final class InstalledStickerPacksControllerArguments {
let expandTrendingPacks: () -> 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) {
self.account = account
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.context = context
self.openStickerPack = openStickerPack
self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions
self.removePack = removePack
@ -88,7 +88,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?)
case masks(PresentationTheme, String)
case emoji(PresentationTheme, String)
case quickReaction(String, UIImage?)
case quickReaction(String, MessageReaction.Reaction, AvailableReactions)
case animatedStickers(PresentationTheme, String, Bool)
case animatedStickersInfo(PresentationTheme, String)
case suggestAnimatedEmoji(String, Bool)
@ -171,8 +171,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
} else {
return false
}
case let .quickReaction(lhsText, lhsImage):
if case let .quickReaction(rhsText, rhsImage) = rhs, lhsText == rhsText, lhsImage === rhsImage {
case let .quickReaction(lhsText, lhsReaction, lhsAvailableReactions):
if case let .quickReaction(rhsText, rhsReaction, rhsAvailableReactions) = rhs, lhsText == rhsText, lhsReaction == rhsReaction, lhsAvailableReactions == rhsAvailableReactions {
return true
} else {
return false
@ -430,14 +430,8 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openEmoji()
})
case let .quickReaction(title, image):
let labelStyle: ItemListDisclosureLabelStyle
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: {
case let .quickReaction(title, reaction, availableReactions):
return ItemListReactionItem(context: arguments.context, presentationData: presentationData, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: {
arguments.openQuickReaction()
})
case let .archived(_, text, count, archived):
@ -457,7 +451,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case let .trendingPacksTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
}, setPackIdWithRevealedOptions: { _, _ in
}, addPack: {
@ -472,7 +466,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry {
case let .packsTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
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)
}, setPackIdWithRevealedOptions: { current, previous in
arguments.setPackIdWithRevealedOptions(current, previous)
@ -556,7 +550,7 @@ private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> It
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 installedPacks = Set<ItemCollectionId>()
@ -592,7 +586,9 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati
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(.animatedStickersInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_AnimatedStickersInfo))
@ -733,7 +729,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
var presentStickerPackController: ((StickerPackCollectionInfo) -> Void)?
var navigationControllerImpl: (() -> NavigationController?)?
let arguments = InstalledStickerPacksControllerArguments(account: context.account, openStickerPack: { info in
let arguments = InstalledStickerPacksControllerArguments(context: context, openStickerPack: { info in
presentStickerPackController?(info)
}, setPackIdWithRevealedOptions: { packId, fromPackId in
updateState { state in
@ -925,21 +921,14 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil)
let featured = Promise<[FeaturedStickerPackItem]>()
let quickReactionImage: Signal<UIImage?, NoError>
let quickReaction: Signal<MessageReaction.Reaction?, NoError>
switch mode {
case .general, .modal:
featured.set(context.account.viewTracker.featuredStickerPacks())
archivedPromise.set(.single(archivedPacks) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init)))
quickReactionImage = combineLatest(
context.engine.stickers.availableReactions(),
context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings])
)
|> map { availableReactions, preferencesView -> TelegramMediaFile? in
guard let availableReactions = availableReactions else {
return nil
}
quickReaction = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings])
|> map { preferencesView -> MessageReaction.Reaction? in
let reactionSettings: ReactionSettings
if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) {
reactionSettings = value
@ -947,45 +936,17 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
reactionSettings = .default
}
for reaction in availableReactions.reactions {
if reaction.value == reactionSettings.quickReaction {
return reaction.staticIcon
}
}
return nil
return reactionSettings.quickReaction
}
|> 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:
featured.set(.single([]))
archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .masks) |> map(Optional.init)))
quickReactionImage = .single(nil)
quickReaction = .single(nil)
case .emoji:
featured.set(.single([]))
archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .emoji) |> map(Optional.init)))
quickReactionImage = .single(nil)
quickReaction = .single(nil)
}
var previousPackCount: Int?
@ -995,10 +956,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
temporaryPackOrder.get(),
combineLatest(queue: .mainQueue(), featured.get(), archivedPromise.get()),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]),
quickReactionImage
quickReaction,
context.engine.stickers.availableReactions()
)
|> 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
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
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 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))
}
|> afterDisposed {

View File

@ -15,13 +15,27 @@ import TextFormat
public final class EmojiStatusComponent: Component {
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 {
case none
case premium(color: UIColor)
case verified(fillColor: UIColor, foregroundColor: UIColor)
case fake(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
@ -153,14 +167,14 @@ public final class EmojiStatusComponent: Component {
iconImage = nil
case .scam:
iconImage = nil
case let .emojiStatus(emojiStatus, size, placeholderColor):
case let .animation(animationContent, size, placeholderColor):
iconImage = nil
emojiFileId = emojiStatus.fileId
emojiFileId = animationContent.fileId.id
emojiPlaceholderColor = placeholderColor
emojiSize = size
if case let .emojiStatus(previousEmojiStatus, _, _) = self.component?.content {
if previousEmojiStatus.fileId != emojiStatus.fileId {
if case let .animation(previousAnimationContent, _, _) = self.component?.content {
if previousAnimationContent.fileId != animationContent.fileId {
self.emojiFileDisposable?.dispose()
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 {
iconImage = self.iconView?.image
if case let .emojiStatus(status, size, placeholderColor) = component.content {
emojiFileId = status.fileId
if case let .animation(animationContent, size, placeholderColor) = component.content {
emojiFileId = animationContent.fileId.id
emojiPlaceholderColor = placeholderColor
emojiSize = size
}

View File

@ -548,7 +548,13 @@ public final class EmojiStatusSelectionController: ViewController {
let cloudOffset0: CGFloat = 30.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)
if self.cloudShadowLayer0.bounds.size != cloudFrame0.size {
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 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)
if self.cloudShadowLayer1.bounds.size != cloudFrame1.size {
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath
@ -621,9 +630,35 @@ public final class EmojiStatusSelectionController: ViewController {
guard let controller = self.controller else {
return
}
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|> deliverOnMainQueue).start()
switch controller.mode {
case .statusSelection:
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|> 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() {
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 weak var sourceView: UIView?
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
private let mode: Mode
private let destinationItemView: () -> UIView?
fileprivate let _ready = Promise<Bool>()
@ -643,8 +684,9 @@ public final class EmojiStatusSelectionController: ViewController {
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.mode = mode
self.sourceView = sourceView
self.emojiContent = emojiContent
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?
override init() {
@ -1778,6 +1778,7 @@ public final class EmojiPagerContentComponent: Component {
public let itemLayoutType: ItemLayoutType
public let warpContentsOnEdges: Bool
public let enableLongPress: Bool
public let selectedItems: Set<MediaId>
public init(
id: AnyHashable,
@ -1789,7 +1790,8 @@ public final class EmojiPagerContentComponent: Component {
itemGroups: [ItemGroup],
itemLayoutType: ItemLayoutType,
warpContentsOnEdges: Bool,
enableLongPress: Bool
enableLongPress: Bool,
selectedItems: Set<MediaId>
) {
self.id = id
self.context = context
@ -1801,6 +1803,7 @@ public final class EmojiPagerContentComponent: Component {
self.itemLayoutType = itemLayoutType
self.warpContentsOnEdges = warpContentsOnEdges
self.enableLongPress = enableLongPress
self.selectedItems = selectedItems
}
public static func ==(lhs: EmojiPagerContentComponent, rhs: EmojiPagerContentComponent) -> Bool {
@ -1837,6 +1840,9 @@ public final class EmojiPagerContentComponent: Component {
if lhs.enableLongPress != rhs.enableLongPress {
return false
}
if lhs.selectedItems != rhs.selectedItems {
return false
}
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 {
var mirrorLayer: CALayer?
@ -2546,6 +2578,7 @@ public final class EmojiPagerContentComponent: Component {
private let placeholdersContainerView: UIView
private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:]
private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:]
private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:]
private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:]
private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:]
@ -2674,13 +2707,17 @@ public final class EmojiPagerContentComponent: Component {
public func animateIn(fromLocation: CGPoint) {
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 distance = sqrt(distanceVector.x * distanceVector.x + distanceVector.y * distanceVector.y)
let distanceNorm = min(1.0, max(0.0, distance / self.bounds.width))
let delay = 0.05 + (distanceNorm) * 0.3
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
}
for (_, itemLayer) in self.visibleItemLayers {
for (key, itemLayer) in self.visibleItemLayers {
guard case let .animation(animationData) = itemLayer.item.content else {
continue
}
@ -2699,6 +2736,10 @@ public final class EmojiPagerContentComponent: Component {
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)
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)
} else {
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.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 {
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)] = [:]
for (id, view) in self.visibleItemPlaceholderViews {
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 {
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 {
if self.visibleItemLayers[id] != nil {
continue
@ -3095,6 +3148,17 @@ public final class EmojiPagerContentComponent: Component {
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 {
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 {
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)
itemLayer = ItemLayer(
@ -3858,12 +3918,13 @@ public final class EmojiPagerContentComponent: Component {
}
}
)
//itemLayer.backgroundColor = UIColor.lightGray.cgColor
self.scrollView.layer.addSublayer(itemLayer)
self.visibleItemLayers[itemId] = itemLayer
}
var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index)
let baseItemFrame = itemFrame
itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 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)
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?
if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker {
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] {
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)
@ -3949,6 +4001,8 @@ public final class EmojiPagerContentComponent: Component {
if !validIds.contains(id) {
removedIds.append(id)
let itemSelectionLayer = self.visibleItemSelectionLayers[id]
if !transition.animation.isImmediate {
if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id.groupId {
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?.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 {
transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in
itemLayer?.removeFromSuperlayer()
})
if let itemSelectionLayer = itemSelectionLayer {
transition.setPosition(layer: itemSelectionLayer, position: position, completion: { [weak itemSelectionLayer] _ in
itemSelectionLayer?.removeFromSuperlayer()
})
}
} else {
itemLayer.opacity = 0.0
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?.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 {
itemLayer.removeFromSuperlayer()
if let itemSelectionLayer = itemSelectionLayer {
itemSelectionLayer.removeFromSuperlayer()
}
}
}
}
for id in removedIds {
self.visibleItemLayers.removeValue(forKey: id)
self.visibleItemSelectionLayers.removeValue(forKey: id)
if let view = self.visibleItemPlaceholderViews.removeValue(forKey: id) {
view.removeFromSuperview()
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] = []
for (id, groupHeaderLayer) in self.visibleGroupHeaders {
@ -4563,7 +4680,7 @@ public final class EmojiPagerContentComponent: Component {
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 isPremiumDisabled = premiumConfiguration.isPremiumDisabled
@ -4577,6 +4694,7 @@ public final class EmojiPagerContentComponent: Component {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
} else if isReactionSelection {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions)
}
@ -4612,6 +4730,7 @@ public final class EmojiPagerContentComponent: Component {
var recentEmoji: OrderedItemListView?
var featuredStatusEmoji: OrderedItemListView?
var recentStatusEmoji: OrderedItemListView?
var topReactions: OrderedItemListView?
var recentReactions: OrderedItemListView?
for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
@ -4622,6 +4741,8 @@ public final class EmojiPagerContentComponent: Component {
recentStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentReactions {
recentReactions = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudTopReactions {
topReactions = orderedView
}
}
@ -4698,6 +4819,41 @@ public final class EmojiPagerContentComponent: Component {
}
} else if isReactionSelection {
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 {
if existingIds.contains(reactionItem.value) {
continue
@ -4717,7 +4873,7 @@ public final class EmojiPagerContentComponent: Component {
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
if itemGroups[groupIndex].items.count >= 16 {
if itemGroups[groupIndex].items.count >= 8 * 2 {
break
}
} else {
@ -4732,7 +4888,12 @@ public final class EmojiPagerContentComponent: Component {
hasRecent = true
}
let maxRecentLineCount = 4
let maxRecentLineCount: Int
#if DEBUG
maxRecentLineCount = 4
#else
maxRecentLineCount = 10
#endif
//TODO:localize
let popularTitle = hasRecent ? "Recently Used" : "Popular"
@ -4761,7 +4922,7 @@ public final class EmojiPagerContentComponent: Component {
itemGroups[groupIndex].items.append(resultItem)
} else {
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
} else {
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,
warpContentsOnEdges: isReactionSelection || isStatusSelection,
enableLongPress: isReactionSelection
enableLongPress: isReactionSelection && !isQuickReactionSelection,
selectedItems: selectedItems
)
}
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)),
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),
peerMessageSelectedReactionFiles(context: strongSelf.context, message: topMessage),
topMessageReactions(context: strongSelf.context, message: topMessage),
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 {
return
}
@ -1107,7 +1108,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,
chatPeerId: strongSelf.chatLocation.peerId
chatPeerId: strongSelf.chatLocation.peerId,
selectedItems: selectedReactionFiles
)
}
}
@ -1714,7 +1716,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
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
if let reactions = mergedMessageReactions(attributes: message.attributes) {
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 {
let peerId: PeerId

View File

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

View File

@ -1061,7 +1061,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,
@ -1216,7 +1216,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
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: [])
} else {
reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])

View File

@ -678,7 +678,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
impressionCount: viewCount,
dateText: dateText,
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,
availableReactions: associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -200,7 +200,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
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 {
needReactions = false
}
@ -1681,7 +1682,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -219,7 +219,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
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 let chatPeer = message.peers[message.id.peerId] as? TelegramUser, chatPeer.isPremium {
return false

View File

@ -543,7 +543,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
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: [])
} else {
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 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?
if displayReactionsInline || arguments.displayReactions {

View File

@ -325,7 +325,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -512,7 +512,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
impressionCount: dateAndStatus.viewCount,
dateText: dateAndStatus.dateText,
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),
availableReactions: associatedData.availableReactions,
reactions: dateAndStatus.dateReactions,

View File

@ -252,7 +252,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -1073,7 +1073,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount,
dateText: dateText,
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,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -120,7 +120,7 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
impressionCount: viewCount,
dateText: dateText,
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,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,

View File

@ -531,7 +531,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
impressionCount: viewCount,
dateText: dateText,
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),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,
@ -683,7 +683,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
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: [])
} else {
reactions = mergedMessageReactions(attributes: item.message.attributes) ?? ReactionsMessageAttribute(canViewList: false, reactions: [], recentPeers: [])

View File

@ -363,7 +363,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
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(
context: item.context,

View File

@ -679,7 +679,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
case .scam:
titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor)
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(

View File

@ -2354,8 +2354,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
case let .emojiStatus(emojiStatus):
currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, 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))
emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
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

View File

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

View File

@ -1351,7 +1351,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
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 {