Channel reaction improvements

This commit is contained in:
Isaac 2023-11-25 23:07:15 +04:00
parent afdc11b171
commit 48a2a72a6a
11 changed files with 311 additions and 187 deletions

View File

@ -2246,6 +2246,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
public var reactionItems: [ReactionContextItem]
public var selectedReactionItems: Set<MessageReaction.Reaction>
public var animationCache: AnimationCache?
public var alwaysAllowPremiumReactions: Bool
public var getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?
public var disablePositionLock: Bool
public var tip: Tip?
@ -2259,6 +2260,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
reactionItems: [ReactionContextItem] = [],
selectedReactionItems: Set<MessageReaction.Reaction> = Set(),
animationCache: AnimationCache? = nil,
alwaysAllowPremiumReactions: Bool = false,
getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)? = nil,
disablePositionLock: Bool = false,
tip: Tip? = nil,
@ -2271,6 +2273,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
self.animationCache = animationCache
self.reactionItems = reactionItems
self.selectedReactionItems = selectedReactionItems
self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions
self.getEmojiContent = getEmojiContent
self.disablePositionLock = disablePositionLock
self.tip = tip
@ -2284,6 +2287,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
self.context = nil
self.reactionItems = []
self.selectedReactionItems = Set()
self.alwaysAllowPremiumReactions = false
self.getEmojiContent = nil
self.disablePositionLock = false
self.tip = nil

View File

@ -46,7 +46,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
var id: AnyHashable? { get }
var tip: ContextController.Tip? { get }
var tipSignal: Signal<ContextController.Tip?, NoError>? { get }
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? { get }
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? { get }
var dismissed: (() -> Void)? { get }
}
@ -906,7 +906,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
let id: AnyHashable?
let items: [ContextMenuItem]
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let tip: ContextController.Tip?
let tipSignal: Signal<ContextController.Tip?, NoError>?
let dismissed: (() -> Void)?
@ -914,7 +914,7 @@ final class ContextControllerActionsListStackItem: ContextControllerActionsStack
init(
id: AnyHashable?,
items: [ContextMenuItem],
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
tip: ContextController.Tip?,
tipSignal: Signal<ContextController.Tip?, NoError>?,
dismissed: (() -> Void)?
@ -1004,7 +1004,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
let id: AnyHashable?
private let content: ContextControllerItemsContent
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let tip: ContextController.Tip?
let tipSignal: Signal<ContextController.Tip?, NoError>?
let dismissed: (() -> Void)?
@ -1012,7 +1012,7 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
init(
id: AnyHashable?,
content: ContextControllerItemsContent,
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
tip: ContextController.Tip?,
tipSignal: Signal<ContextController.Tip?, NoError>?,
dismissed: (() -> Void)?
@ -1041,9 +1041,9 @@ final class ContextControllerActionsCustomStackItem: ContextControllerActionsSta
}
func makeContextControllerActionsStackItem(items: ContextController.Items) -> [ContextControllerActionsStackItem] {
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
var reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
if let context = items.context, let animationCache = items.animationCache, !items.reactionItems.isEmpty {
reactionItems = (context, items.reactionItems, items.selectedReactionItems, animationCache, items.getEmojiContent)
reactionItems = (context, items.reactionItems, items.selectedReactionItems, animationCache, alwaysAllowPremiumReactions: items.alwaysAllowPremiumReactions, items.getEmojiContent)
}
switch items.content {
case let .list(listItems):
@ -1167,7 +1167,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
var tip: ContextController.Tip?
let tipSignal: Signal<ContextController.Tip?, NoError>?
var tipNode: InnerTextSelectionTipContainerNode?
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?
let itemDismissed: (() -> Void)?
var storedScrollingState: CGFloat?
let positionLock: CGFloat?
@ -1182,7 +1182,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
item: ContextControllerActionsStackItem,
tip: ContextController.Tip?,
tipSignal: Signal<ContextController.Tip?, NoError>?,
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)?,
itemDismissed: (() -> Void)?,
positionLock: CGFloat?
) {
@ -1333,7 +1333,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
private var selectionPanGesture: UIPanGestureRecognizer?
var topReactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? {
var topReactionItems: (context: AccountContext, reactionItems: [ReactionContextItem], selectedReactionItems: Set<MessageReaction.Reaction>, animationCache: AnimationCache, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?)? {
return self.itemContainers.last?.reactionItems
}

View File

@ -639,6 +639,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
presentationData: presentationData,
items: reactionItems.reactionItems,
selectedItems: reactionItems.selectedReactionItems,
alwaysAllowPremiumReactions: reactionItems.alwaysAllowPremiumReactions,
getEmojiContent: reactionItems.getEmojiContent,
isExpandedUpdated: { [weak self] transition in
guard let strongSelf = self else {
@ -916,7 +917,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), anchorRect: reactionAnchorRect, isCoveredByInput: isCoveredByInput, isAnimatingOut: isAnimatingOut, transition: reactionContextNodeTransition)
if reactionContextNode.alwaysAllowPremiumReactions {
self.proposedReactionsPositionLock = contentRect.minY - 18.0 - reactionContextNode.contentHeight
} else {
self.proposedReactionsPositionLock = contentRect.minY - 18.0 - reactionContextNode.contentHeight - (46.0 + 54.0 - 4.0)
}
} else {
self.proposedReactionsPositionLock = nil
}

View File

@ -136,6 +136,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView {
items: reactionItems.map(ReactionContextItem.reaction),
selectedItems: Set(),
title: nil,
alwaysAllowPremiumReactions: false,
getEmojiContent: { [weak self] animationCache, animationRenderer in
guard let self else {
preconditionFailure()
@ -150,7 +151,7 @@ public class DrawingReactionEntityView: DrawingStickerEntityView {
animationCache: animationCache,
animationRenderer: animationRenderer,
isStandalone: false,
subject: .reaction,
subject: .reaction(onlyTop: false),
hasTrending: false,
topReactionItems: mappedReactionItems,
areUnicodeEmojiEnabled: false,

View File

@ -1192,10 +1192,10 @@ private final class LimitSheetContent: CombinedComponent {
case .nameColors:
titleText = strings.ChannelBoost_EnableColors
string = strings.ChannelBoost_EnableColorsLevelText("\(premiumConfiguration.minChannelNameColorLevel)").string
case .channelReactions:
case let .channelReactions(reactionCount):
//TODO:localize
titleText = "Custom Reactions"
string = "Your channel needs \(valueString) to add custom emoji as reactions.\n\nAsk your **Premium** subscribers to boost your channel with this link:"
string = "Your channel needs to reach **Level \(reactionCount)** to add **\(reactionCount)** custom emoji as reactions.\n\nAsk your **Premium** subscribers to boost your channel with this link:"
}
} else {
let storiesString = strings.ChannelBoost_StoriesPerDay(level)
@ -1777,10 +1777,10 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
case storiesWeekly
case storiesMonthly
public enum BoostSubject {
public enum BoostSubject: Equatable {
case stories
case nameColors
case channelReactions
case channelReactions(reactionCount: Int)
}
case storiesChannelBoost(peer: EnginePeer, boostSubject: BoostSubject, isCurrent: Bool, level: Int32, currentLevelBoosts: Int32, nextLevelBoosts: Int32?, link: String?, myBoostCount: Int32, canBoostAgain: Bool)

View File

@ -296,6 +296,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var extensionDistance: CGFloat = 0.0
public private(set) var visibleExtensionDistance: CGFloat = 0.0
private var emojiContentHeight: CGFloat = 300.0
private var didInitializeEmojiContentHeight: Bool = false
private var emojiContentLayout: EmojiPagerContentComponent.CustomLayout?
private var emojiContent: EmojiPagerContentComponent?
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
@ -322,6 +324,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
private var availableReactions: AvailableReactions?
private var availableReactionsDisposable: Disposable?
public let alwaysAllowPremiumReactions: Bool
private var hasPremium: Bool?
private var hasPremiumDisposable: Disposable?
@ -368,7 +371,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
}
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set<MessageReaction.Reaction>, title: String? = nil, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) {
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set<MessageReaction.Reaction>, title: String? = nil, alwaysAllowPremiumReactions: Bool, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) {
self.context = context
self.presentationData = presentationData
self.items = items
@ -468,6 +471,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.contentTopInset = 24.0
}
self.alwaysAllowPremiumReactions = alwaysAllowPremiumReactions
super.init()
self.addSubnode(self.backgroundNode)
@ -486,6 +491,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.availableReactions = availableReactions
})
if alwaysAllowPremiumReactions {
self.hasPremium = true
} else {
self.hasPremiumDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self else {
@ -493,6 +501,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
strongSelf.hasPremium = peer?.isPremium ?? false
})
}
if let getEmojiContent = getEmojiContent {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
@ -566,6 +575,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
emojiTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
}
var hideTopPanel = false
if strongSelf.isReactionSearchActive {
hideTopPanel = true
} else if strongSelf.alwaysAllowPremiumReactions {
hideTopPanel = true
}
let _ = reactionSelectionComponentHost.update(
transition: emojiTransition,
component: AnyComponent(EmojiStatusSelectionComponent(
@ -575,7 +591,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
emojiContent: emojiContent,
backgroundColor: .clear,
separatorColor: strongSelf.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5),
hideTopPanel: strongSelf.isReactionSearchActive,
hideTopPanel: hideTopPanel,
hideTopPanelUpdated: { hideTopPanel, transition in
guard let strongSelf = self else {
return
@ -585,7 +601,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
)),
environment: {},
containerSize: CGSize(width: componentView.bounds.width, height: 300.0)
containerSize: CGSize(width: componentView.bounds.width, height: strongSelf.emojiContentHeight)
)
}
})
@ -1023,7 +1039,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
expandItemSize = 30.0
expandTintOffset = 0.0
}
let baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: self.contentTopInset + containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0) + (self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance))
var baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: self.contentTopInset + containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance))
if self.isExpanded {
if self.alwaysAllowPremiumReactions {
} else {
baseNextFrame.origin.y += 46.0 + 54.0 - 4.0
}
}
transition.updateFrame(view: expandItemView, frame: baseNextFrame)
transition.updateFrame(view: expandItemView.tintView, frame: baseNextFrame.offsetBy(dx: 0.0, dy: expandTintOffset))
@ -1124,7 +1146,16 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
visibleItemCount: itemCount
)
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : self.contentTopInset), size: actualBackgroundFrame.size)
var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: actualBackgroundFrame.size)
if self.isExpanded {
if self.alwaysAllowPremiumReactions {
scrollFrame.origin.y += 0.0
} else {
scrollFrame.origin.y += 46.0 + 54.0 - 4.0
}
} else {
scrollFrame.origin.y += self.contentTopInset
}
scrollFrame.origin.y += floorToScreenPixels(self.extensionDistance / 2.0)
transition.updatePosition(node: self.contentContainer, position: visualBackgroundFrame.center, beginWithCurrentState: true)
@ -1142,6 +1173,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.updateScrolling(transition: transition)
self.emojiContentLayout = EmojiPagerContentComponent.CustomLayout(
topPanelAlwaysHidden: self.alwaysAllowPremiumReactions,
itemsPerRow: itemCount,
itemSize: itemSize,
sideInset: sideInset,
@ -1168,6 +1200,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
componentTransition = Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(contentAnimation)
}
var hideTopPanel = false
if self.isReactionSearchActive {
hideTopPanel = true
} else if self.alwaysAllowPremiumReactions {
hideTopPanel = true
}
let _ = reactionSelectionComponentHost.update(
transition: componentTransition,
component: AnyComponent(EmojiStatusSelectionComponent(
@ -1177,7 +1216,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
emojiContent: emojiContent,
backgroundColor: .clear,
separatorColor: self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5),
hideTopPanel: self.isReactionSearchActive,
hideTopPanel: hideTopPanel,
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
guard let strongSelf = self else {
return
@ -1187,7 +1226,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
)),
environment: {},
containerSize: CGSize(width: actualBackgroundFrame.width, height: 300.0)
containerSize: CGSize(width: actualBackgroundFrame.width, height: self.emojiContentHeight)
)
if let componentView = reactionSelectionComponentHost.view {
var animateIn = false
@ -1229,7 +1268,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if let mirrorContentClippingView = emojiView.mirrorContentClippingView {
mirrorContentClippingView.clipsToBounds = false
Transition(transition).animateBoundsOrigin(view: mirrorContentClippingView, from: CGPoint(x: 0.0, y: 46.0 + 54.0 - 4.0), to: CGPoint(), additive: true, completion: { [weak mirrorContentClippingView] _ in
var animationOffsetY: CGFloat = 0.0
if self.alwaysAllowPremiumReactions {
animationOffsetY += -4.0
} else {
animationOffsetY += 46.0 + 54.0 - 4.0
}
Transition(transition).animateBoundsOrigin(view: mirrorContentClippingView, from: CGPoint(x: 0.0, y: animationOffsetY), to: CGPoint(), additive: true, completion: { [weak mirrorContentClippingView] _ in
mirrorContentClippingView?.clipsToBounds = true
})
}
@ -1260,7 +1307,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
componentTransition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
if animateIn {
transition.animatePositionAdditive(layer: componentView.layer, offset: CGPoint(x: 0.0, y: -(46.0 + 54.0 - 4.0) + floorToScreenPixels(self.animateFromExtensionDistance / 2.0)))
var animationOffsetY: CGFloat = 0.0
if self.alwaysAllowPremiumReactions {
animationOffsetY += 4.0
} else {
animationOffsetY += 46.0 + 54.0 - 4.0
}
transition.animatePositionAdditive(layer: componentView.layer, offset: CGPoint(x: 0.0, y: -animationOffsetY + floorToScreenPixels(self.animateFromExtensionDistance / 2.0)))
}
}
}
@ -1317,6 +1371,17 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
return
}
if !self.didInitializeEmojiContentHeight {
self.didInitializeEmojiContentHeight = true
if emojiContent.contentItemGroups.count == 1 {
let itemCount = emojiContent.contentItemGroups[0].items.count
let numRows = (itemCount + (emojiContentLayout.itemsPerRow - 1)) / emojiContentLayout.itemsPerRow
let proposedHeight: CGFloat = CGFloat(numRows) * emojiContentLayout.itemSize + CGFloat(numRows - 1) * emojiContentLayout.itemSpacing + emojiContentLayout.itemSpacing * 2.0 + 5.0
self.emojiContentHeight = min(300.0, proposedHeight)
}
}
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer, isLongPress in
guard let strongSelf = self, let availableReactions = strongSelf.availableReactions, let itemFile = item.itemFile else {
@ -2347,7 +2412,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if let expandItemView = self.expandItemView, expandItemView.bounds.contains(self.view.convert(point, to: self.expandItemView)) {
self.animateFromExtensionDistance = self.contentTopInset * 2.0 + self.extensionDistance
self.contentTopInset = 0.0
self.currentContentHeight = 300.0
self.currentContentHeight = self.emojiContentHeight
self.isExpanded = true
self.longPressRecognizer?.isEnabled = false
self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring))
@ -2388,7 +2453,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.extensionDistance = 0.0
self.visibleExtensionDistance = 0.0
self.contentTopInset = 0.0
self.currentContentHeight = 300.0
self.currentContentHeight = self.emojiContentHeight
self.isExpanded = true
self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring))
}

View File

@ -2298,17 +2298,20 @@ public final class EmojiPagerContentComponent: Component {
}
public struct CustomLayout: Equatable {
public var topPanelAlwaysHidden: Bool
public var itemsPerRow: Int
public var itemSize: CGFloat
public var sideInset: CGFloat
public var itemSpacing: CGFloat
public init(
topPanelAlwaysHidden: Bool,
itemsPerRow: Int,
itemSize: CGFloat,
sideInset: CGFloat,
itemSpacing: CGFloat
) {
self.topPanelAlwaysHidden = topPanelAlwaysHidden
self.itemsPerRow = itemsPerRow
self.itemSize = itemSize
self.sideInset = sideInset
@ -3052,7 +3055,8 @@ public final class EmojiPagerContentComponent: Component {
var verticalGroupOrigin: CGFloat = self.itemInsets.top
self.itemGroupLayouts = []
for itemGroup in itemGroups {
for i in 0 ..< itemGroups.count {
let itemGroup = itemGroups[i]
var itemsPerRow = self.itemsPerRow
var nativeItemSize = self.nativeItemSize
var visibleItemSize = self.visibleItemSize
@ -3145,7 +3149,10 @@ public final class EmojiPagerContentComponent: Component {
collapsedItemIndex: collapsedItemIndex,
collapsedItemText: collapsedItemText
))
verticalGroupOrigin += groupContentSize.height + groupSpacing
verticalGroupOrigin += groupContentSize.height
if i != itemGroups.count - 1 {
verticalGroupOrigin += groupSpacing
}
}
verticalGroupOrigin += itemInsets.bottom
self.contentSize = CGSize(width: width, height: verticalGroupOrigin)
@ -6791,6 +6798,9 @@ public final class EmojiPagerContentComponent: Component {
self.scrollView.contentSize = effectiveContentSize
}
var scrollIndicatorInsets = pagerEnvironment.containerInsets
if let inputInteraction = component.inputInteractionHolder.inputInteraction, let customLayout = inputInteraction.customLayout, customLayout.topPanelAlwaysHidden {
scrollIndicatorInsets.top += 20.0
}
if self.warpView != nil {
scrollIndicatorInsets.bottom += 20.0
}
@ -7175,10 +7185,10 @@ public final class EmojiPagerContentComponent: Component {
return hasPremium
}
public enum Subject {
public enum Subject: Equatable {
case generic
case status
case reaction
case reaction(onlyTop: Bool)
case emoji
case topicIcon
case quickReaction
@ -7216,7 +7226,12 @@ public final class EmojiPagerContentComponent: Component {
var orderedItemListCollectionIds: [Int32] = []
switch subject {
case .backgroundIcon, .reactionList:
break
default:
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.LocalRecentEmoji)
}
var iconStatusEmoji: Signal<[TelegramMediaFile], NoError> = .single([])
@ -7234,7 +7249,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
|> take(1)
} else if [.reaction, .quickReaction].contains(subject) {
} else if [.reaction(onlyTop: false), .quickReaction].contains(subject) {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions)
} else if case .topicIcon = subject {
@ -7257,14 +7272,14 @@ public final class EmojiPagerContentComponent: Component {
}
let availableReactions: Signal<AvailableReactions?, NoError>
if [.reaction, .quickReaction, .reactionList].contains(subject) {
if [.reaction(onlyTop: false), .quickReaction, .reactionList].contains(subject) {
availableReactions = context.engine.stickers.availableReactions()
} else {
availableReactions = .single(nil)
}
let searchCategories: Signal<EmojiSearchCategories?, NoError>
if [.emoji, .reaction, .reactionList].contains(subject) {
if [.emoji, .reaction(onlyTop: false), .reactionList].contains(subject) {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .emoji)
} else if case .status = subject {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .status)
@ -7694,7 +7709,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
}
} else if [.reaction, .quickReaction].contains(subject) {
} else if [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction].contains(subject) {
var existingIds = Set<MessageReaction.Reaction>()
var topReactionItems = topReactionItems
@ -7720,7 +7735,9 @@ public final class EmojiPagerContentComponent: Component {
}
let maxTopLineCount: Int
if hasPremium {
if case .reaction(onlyTop: true) = subject {
maxTopLineCount = 1000
} else if hasPremium {
maxTopLineCount = 2
} else {
maxTopLineCount = 6
@ -7733,7 +7750,9 @@ public final class EmojiPagerContentComponent: Component {
existingIds.insert(reactionItem.reaction)
let icon: EmojiPagerContentComponent.Item.Icon
if !hasPremium, case .custom = reactionItem.reaction {
if case .reaction(onlyTop: true) = subject {
icon = .none
} else if !hasPremium, case .custom = reactionItem.reaction {
icon = .locked
} else {
icon = .none
@ -7768,6 +7787,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
if case .reaction(onlyTop: false) = subject {
var hasRecent = false
if let recentReactions = recentReactions, !recentReactions.items.isEmpty {
hasRecent = true
@ -7909,6 +7929,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
}
}
} else if [.profilePhoto, .groupPhoto].contains(subject) {
var existingIds = Set<MediaId>()
@ -8042,7 +8063,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
let hasRecentEmoji = ![.reaction, .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon].contains(subject)
let hasRecentEmoji = ![.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon, .reactionList].contains(subject)
if let recentEmoji = recentEmoji, hasRecentEmoji {
for item in recentEmoji.items {
@ -8108,7 +8129,7 @@ public final class EmojiPagerContentComponent: Component {
}
var icon: EmojiPagerContentComponent.Item.Icon = .none
if [.reaction, .quickReaction].contains(subject), !hasPremium {
if [.reaction(onlyTop: false), .quickReaction].contains(subject), !hasPremium {
icon = .locked
}
@ -8285,7 +8306,7 @@ public final class EmojiPagerContentComponent: Component {
var displaySearchWithPlaceholder: String?
let searchInitiallyHidden = true
if hasSearch {
if [.reaction, .quickReaction].contains(subject) {
if [.reaction(onlyTop: false), .quickReaction].contains(subject) {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder
} else if case .status = subject {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder
@ -8340,8 +8361,8 @@ public final class EmojiPagerContentComponent: Component {
)
}
let warpContentsOnEdges = [.reaction, .quickReaction, .status, .profilePhoto, .groupPhoto, .backgroundIcon].contains(subject)
let enableLongPress = [.reaction, .status].contains(subject)
let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .backgroundIcon].contains(subject)
let enableLongPress = [.reaction(onlyTop: true), .reaction(onlyTop: false), .status].contains(subject)
return EmojiPagerContentComponent(
id: "emoji",

View File

@ -225,7 +225,7 @@ public final class EmojiSelectionComponent: Component {
component: AnyComponent(EntityKeyboardComponent(
theme: component.theme,
strings: component.strings,
isContentInFocus: false,
isContentInFocus: true,
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: component.sideInset, bottom: component.bottomInset + 16.0, right: component.sideInset),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
emojiContent: component.emojiContent.withCustomTintColor(component.theme.list.itemPrimaryTextColor),

View File

@ -274,7 +274,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
})
if let boostStatus = self.boostStatus, !customReactions.isEmpty, customReactions.count > boostStatus.level {
self.displayPremiumScreen()
self.displayPremiumScreen(reactionCount: customReactions.count)
return
}
@ -306,7 +306,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
if !standalone {
switch error {
case .boostRequired:
self.displayPremiumScreen()
self.displayPremiumScreen(reactionCount: customReactions.count)
case .generic:
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
@ -330,7 +330,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
}
}
private func displayPremiumScreen() {
private func displayPremiumScreen(reactionCount: Int) {
guard let component = self.component else {
return
}
@ -344,7 +344,7 @@ final class PeerAllowedReactionsScreenComponent: Component {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
let link = status.url
let controller = PremiumLimitScreen(context: component.context, subject: .storiesChannelBoost(peer: peer, boostSubject: .channelReactions, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { [weak self] in
let controller = PremiumLimitScreen(context: component.context, subject: .storiesChannelBoost(peer: peer, boostSubject: .channelReactions(reactionCount: reactionCount), isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { [weak self] in
guard let self, let component = self.component else {
return true
}

View File

@ -4271,6 +4271,7 @@ public final class StoryItemSetContainerComponent: Component {
items: reactionItems.map(ReactionContextItem.reaction),
selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(),
title: self.displayLikeReactions ? nil : component.strings.Story_SendReactionAsMessage,
alwaysAllowPremiumReactions: false,
getEmojiContent: { [weak self] animationCache, animationRenderer in
guard let self, let component = self.component else {
preconditionFailure()
@ -4285,7 +4286,7 @@ public final class StoryItemSetContainerComponent: Component {
animationCache: animationCache,
animationRenderer: animationRenderer,
isStandalone: false,
subject: .reaction,
subject: .reaction(onlyTop: false),
hasTrending: false,
topReactionItems: mappedReactionItems,
areUnicodeEmojiEnabled: false,

View File

@ -112,6 +112,10 @@ extension ChatControllerImpl {
actions.reactionItems = topReactions.map(ReactionContextItem.reaction)
actions.selectedReactionItems = selectedReactions.reactions
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = channel.info {
actions.alwaysAllowPremiumReactions = true
}
if !actions.reactionItems.isEmpty {
let reactionItems: [EmojiComponentReactionItem] = actions.reactionItems.compactMap { item -> EmojiComponentReactionItem? in
switch item {
@ -150,7 +154,7 @@ extension ChatControllerImpl {
animationCache: animationCache,
animationRenderer: animationRenderer,
isStandalone: false,
subject: .reaction,
subject: .reaction(onlyTop: false),
hasTrending: false,
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
@ -159,6 +163,26 @@ extension ChatControllerImpl {
selectedItems: selectedReactions.files
)
}
} else if reactionItems.count > 10 {
actions.getEmojiContent = { [weak self] animationCache, animationRenderer in
guard let self else {
preconditionFailure()
}
return EmojiPagerContentComponent.emojiInputData(
context: self.context,
animationCache: animationCache,
animationRenderer: animationRenderer,
isStandalone: false,
subject: .reaction(onlyTop: true),
hasTrending: false,
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: false,
chatPeerId: self.chatLocation.peerId,
selectedItems: selectedReactions.files
)
}
}
}
}
@ -332,11 +356,14 @@ extension ChatControllerImpl {
}*/
if removedReaction == nil, case .custom = chosenReaction {
if let peer = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info {
} else {
if !self.presentationInterfaceState.isPremium {
controller?.premiumReactionsSelected?()
return
}
}
}
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {