mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Emoji status and reaction improvements
This commit is contained in:
parent
29933dd7d5
commit
3b636d5cb9
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -103,6 +103,7 @@ swift_library(
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/InviteLinksUI:InviteLinksUI",
|
||||
"//submodules/HorizontalPeerItem:HorizontalPeerItem",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -499,7 +499,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
},
|
||||
itemLayoutType: .detailed,
|
||||
warpContentsOnEdges: false,
|
||||
enableLongPress: false
|
||||
enableLongPress: false,
|
||||
selectedItems: Set()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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: [])
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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: [])
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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: [])
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user