mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stars reactions
This commit is contained in:
parent
b5549d74b0
commit
d820ad65e1
@ -2671,6 +2671,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
var selfTargetBounds = targetView.bounds
|
var selfTargetBounds = targetView.bounds
|
||||||
if case .builtin = itemNode.item.reaction.rawValue {
|
if case .builtin = itemNode.item.reaction.rawValue {
|
||||||
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
|
} else if case .stars = itemNode.item.reaction.rawValue {
|
||||||
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
||||||
@ -3775,6 +3777,308 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 1.0) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func animateOutToReaction(context: AccountContext, theme: PresentationTheme, item: ReactionItem, value: MessageReaction.Reaction, sourceView: UIView, targetView: UIView, hideNode: Bool, forceSwitchToInlineImmediately: Bool = false, animateTargetContainer: UIView?, addStandaloneReactionAnimation: ((StandaloneReactionAnimation) -> Void)?, onHit: (() -> Void)?, completion: @escaping () -> Void) {
|
||||||
|
let didTriggerExpandedReaction = !"".isEmpty
|
||||||
|
|
||||||
|
let itemNode = ReactionNode(context: context, theme: theme, item: item, icon: .none, animationCache: context.animationCache, animationRenderer: context.animationRenderer, loopIdle: false, isLocked: false, useDirectRendering: true)
|
||||||
|
if let contents = sourceView.layer.contents {
|
||||||
|
itemNode.setCustomContents(contents: contents)
|
||||||
|
}
|
||||||
|
self.addSubnode(itemNode)
|
||||||
|
itemNode.frame = sourceView.convert(sourceView.bounds, to: self.view)
|
||||||
|
itemNode.updateLayout(size: itemNode.frame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: .immediate)
|
||||||
|
sourceView.layer.isHidden = true
|
||||||
|
|
||||||
|
let switchToInlineImmediately: Bool
|
||||||
|
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker || itemNode.item.listAnimation.isStaticEmoji {
|
||||||
|
switch itemNode.item.reaction.rawValue {
|
||||||
|
case .builtin:
|
||||||
|
switchToInlineImmediately = forceSwitchToInlineImmediately
|
||||||
|
case .custom:
|
||||||
|
switchToInlineImmediately = !didTriggerExpandedReaction
|
||||||
|
case .stars:
|
||||||
|
switchToInlineImmediately = forceSwitchToInlineImmediately
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switchToInlineImmediately = !didTriggerExpandedReaction
|
||||||
|
}
|
||||||
|
|
||||||
|
if hideNode {
|
||||||
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
|
animateTargetContainer.isHidden = true
|
||||||
|
targetView.isHidden = true
|
||||||
|
} else {
|
||||||
|
targetView.alpha = 0.0
|
||||||
|
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itemNode.isExtracted = true
|
||||||
|
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
|
||||||
|
|
||||||
|
var selfTargetBounds = targetView.bounds
|
||||||
|
if case .builtin = itemNode.item.reaction.rawValue {
|
||||||
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
|
} else if case .stars = itemNode.item.reaction.rawValue {
|
||||||
|
selfTargetBounds = selfTargetBounds.insetBy(dx: -selfTargetBounds.width * 0.5, dy: -selfTargetBounds.height * 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
let selfTargetRect = self.view.convert(selfTargetBounds, from: targetView)
|
||||||
|
|
||||||
|
var expandedSize: CGSize = selfTargetRect.size
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isStaticEmoji {
|
||||||
|
expandedSize = CGSize(width: 80.0, height: 80.0)
|
||||||
|
} else {
|
||||||
|
expandedSize = CGSize(width: 120.0, height: 120.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
|
||||||
|
|
||||||
|
var effectFrame: CGRect
|
||||||
|
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
let expandFactor: CGFloat = 0.5
|
||||||
|
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
|
||||||
|
} else {
|
||||||
|
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||||
|
if itemNode.item.isCustom {
|
||||||
|
effectFrame = effectFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||||
|
|
||||||
|
self.addSubnode(itemNode)
|
||||||
|
itemNode.position = expandedFrame.center
|
||||||
|
transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size))
|
||||||
|
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, largeExpanded: didTriggerExpandedReaction, isPreviewing: false, transition: transition)
|
||||||
|
|
||||||
|
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
|
||||||
|
var genericAnimationView: AnimationView?
|
||||||
|
|
||||||
|
var additionalAnimation: TelegramMediaFile?
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
additionalAnimation = itemNode.item.largeApplicationAnimation
|
||||||
|
} else {
|
||||||
|
additionalAnimation = itemNode.item.applicationAnimation
|
||||||
|
}
|
||||||
|
|
||||||
|
if let additionalAnimation = additionalAnimation {
|
||||||
|
let additionalAnimationNodeValue = DefaultAnimatedStickerNodeImpl()
|
||||||
|
additionalAnimationNode = additionalAnimationNodeValue
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
if incomingMessage {
|
||||||
|
additionalAnimationNodeValue.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id)))
|
||||||
|
additionalAnimationNodeValue.frame = effectFrame
|
||||||
|
additionalAnimationNodeValue.updateLayout(size: effectFrame.size)
|
||||||
|
self.addSubnode(additionalAnimationNodeValue)
|
||||||
|
} else if itemNode.item.isCustom {
|
||||||
|
additionalAnimationNode = nil
|
||||||
|
|
||||||
|
var effectData: Data?
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
if let url = getAppBundle().url(forResource: "generic_reaction_effect", withExtension: "json") {
|
||||||
|
effectData = try? Data(contentsOf: url)
|
||||||
|
}
|
||||||
|
} else if let genericReactionEffect = self.genericReactionEffect, let data = try? Data(contentsOf: URL(fileURLWithPath: genericReactionEffect)) {
|
||||||
|
effectData = TGGUnzipData(data, 5 * 1024 * 1024) ?? data
|
||||||
|
} else {
|
||||||
|
if let url = getAppBundle().url(forResource: "generic_reaction_small_effect", withExtension: "json") {
|
||||||
|
effectData = try? Data(contentsOf: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let effectData = effectData, let composition = try? Animation.from(data: effectData) {
|
||||||
|
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
|
view.animationSpeed = 1.0
|
||||||
|
view.backgroundColor = nil
|
||||||
|
view.isOpaque = false
|
||||||
|
|
||||||
|
if incomingMessage {
|
||||||
|
view.layer.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
genericAnimationView = view
|
||||||
|
|
||||||
|
let animationCache = itemNode.context.animationCache
|
||||||
|
let animationRenderer = itemNode.context.animationRenderer
|
||||||
|
|
||||||
|
for i in 1 ... 32 {
|
||||||
|
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||||
|
for animationLayer in allLayers {
|
||||||
|
let baseItemLayer = InlineStickerItemLayer(
|
||||||
|
context: itemNode.context,
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
||||||
|
file: itemNode.item.listAnimation,
|
||||||
|
cache: animationCache,
|
||||||
|
renderer: animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||||
|
pointSize: CGSize(width: didTriggerExpandedReaction ? 64.0 : 32.0, height: didTriggerExpandedReaction ? 64.0 : 32.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if let sublayers = animationLayer.sublayers {
|
||||||
|
for sublayer in sublayers {
|
||||||
|
sublayer.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseItemLayer.isVisibleForAnimations = true
|
||||||
|
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||||
|
animationLayer.addSublayer(baseItemLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||||
|
} else {
|
||||||
|
view.frame = effectFrame.insetBy(dx: -20.0, dy: -20.0)
|
||||||
|
}
|
||||||
|
self.view.addSubview(view)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
additionalAnimationNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainAnimationCompleted = false
|
||||||
|
var additionalAnimationCompleted = false
|
||||||
|
let intermediateCompletion: () -> Void = {
|
||||||
|
if mainAnimationCompleted && additionalAnimationCompleted {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let additionalAnimationNode = additionalAnimationNode {
|
||||||
|
additionalAnimationNode.completed = { _ in
|
||||||
|
additionalAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
}
|
||||||
|
} else if let genericAnimationView = genericAnimationView {
|
||||||
|
genericAnimationView.play(completion: { _ in
|
||||||
|
additionalAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
additionalAnimationCompleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0), completion: { [weak itemNode, weak targetView, weak animateTargetContainer] _ in
|
||||||
|
let afterCompletion: () -> Void = {
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let itemNode = itemNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
|
animateTargetContainer.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if let targetView = targetView {
|
||||||
|
targetView.isHidden = false
|
||||||
|
targetView.alpha = 1.0
|
||||||
|
targetView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
}
|
||||||
|
|
||||||
|
HapticFeedback().tap()
|
||||||
|
onHit?()
|
||||||
|
|
||||||
|
if let targetView = targetView as? ReactionIconView {
|
||||||
|
if switchToInlineImmediately {
|
||||||
|
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
||||||
|
itemNode.isHidden = true
|
||||||
|
} else {
|
||||||
|
targetView.updateIsAnimationHidden(isAnimationHidden: true, transition: .immediate)
|
||||||
|
targetView.addSubnode(itemNode)
|
||||||
|
itemNode.frame = selfTargetBounds
|
||||||
|
}
|
||||||
|
} else if let targetView = targetView as? UIImageView {
|
||||||
|
itemNode.isHidden = true
|
||||||
|
targetView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
|
||||||
|
targetView.layer.animateScale(from: 0.2, to: 1.0, duration: 0.12)
|
||||||
|
}
|
||||||
|
|
||||||
|
if switchToInlineImmediately {
|
||||||
|
mainAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if switchToInlineImmediately {
|
||||||
|
afterCompletion()
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: afterCompletion)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15 * UIView.animationDurationFactor(), execute: {
|
||||||
|
additionalAnimationNode?.visibility = true
|
||||||
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
|
animateTargetContainer.isHidden = false
|
||||||
|
animateTargetContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
animateTargetContainer.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if !switchToInlineImmediately {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + min(5.0, 2.0 * UIView.animationDurationFactor()), execute: {
|
||||||
|
if didTriggerExpandedReaction {
|
||||||
|
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
|
||||||
|
if let strongSelf = self, didTriggerExpandedReaction, let addStandaloneReactionAnimation = addStandaloneReactionAnimation {
|
||||||
|
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.genericReactionEffect)
|
||||||
|
|
||||||
|
addStandaloneReactionAnimation(standaloneReactionAnimation)
|
||||||
|
|
||||||
|
standaloneReactionAnimation.animateReactionSelection(
|
||||||
|
context: context,
|
||||||
|
theme: theme,
|
||||||
|
animationCache: context.animationCache,
|
||||||
|
reaction: itemNode.item,
|
||||||
|
avatarPeers: [],
|
||||||
|
playHaptic: false,
|
||||||
|
isLarge: false,
|
||||||
|
targetView: targetView,
|
||||||
|
addStandaloneReactionAnimation: nil,
|
||||||
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
|
if let _ = standaloneReactionAnimation?.supernode {
|
||||||
|
standaloneReactionAnimation?.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if hideNode {
|
||||||
|
targetView.alpha = 1.0
|
||||||
|
targetView.isHidden = false
|
||||||
|
if let targetView = targetView as? ReactionIconView {
|
||||||
|
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
||||||
|
if let _ = itemNode.supernode {
|
||||||
|
itemNode.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
itemNode.view.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
|
||||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y)
|
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y)
|
||||||
transition.animateOffsetAdditive(node: self, offset: -offset.y)
|
transition.animateOffsetAdditive(node: self, offset: -offset.y)
|
||||||
|
@ -6,9 +6,6 @@ extension ReactionsMessageAttribute {
|
|||||||
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
|
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
|
||||||
switch reactions {
|
switch reactions {
|
||||||
case let .messageReactions(flags, results, recentReactions, topReactors):
|
case let .messageReactions(flags, results, recentReactions, topReactors):
|
||||||
//TODO:release
|
|
||||||
let _ = topReactors
|
|
||||||
|
|
||||||
let min = (flags & (1 << 0)) != 0
|
let min = (flags & (1 << 0)) != 0
|
||||||
let canViewList = (flags & (1 << 2)) != 0
|
let canViewList = (flags & (1 << 2)) != 0
|
||||||
let isTags = (flags & (1 << 3)) != 0
|
let isTags = (flags & (1 << 3)) != 0
|
||||||
@ -57,7 +54,23 @@ extension ReactionsMessageAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ReactionsMessageAttribute(canViewList: canViewList, isTags: isTags, reactions: reactions, recentPeers: parsedRecentReactions)
|
|
||||||
|
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
|
||||||
|
if let topReactors {
|
||||||
|
for item in topReactors {
|
||||||
|
switch item {
|
||||||
|
case let .messageReactor(flags, peerId, count):
|
||||||
|
topPeers.append(ReactionsMessageAttribute.TopPeer(
|
||||||
|
peerId: peerId.peerId,
|
||||||
|
count: count,
|
||||||
|
isTop: (flags & (1 << 0)) != 0,
|
||||||
|
isMy: (flags & (1 << 1)) != 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReactionsMessageAttribute(canViewList: canViewList, isTags: isTags, reactions: reactions, recentPeers: parsedRecentReactions, topPeers: topPeers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,7 +192,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool)
|
|||||||
recentPeers = updatedRecentPeers
|
recentPeers = updatedRecentPeers
|
||||||
|
|
||||||
if !reactions.isEmpty {
|
if !reactions.isEmpty {
|
||||||
result = ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: recentPeers)
|
result = ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: recentPeers, topPeers: current?.topPeers ?? [])
|
||||||
} else {
|
} else {
|
||||||
result = nil
|
result = nil
|
||||||
}
|
}
|
||||||
@ -198,9 +211,9 @@ public func mergedMessageReactions(attributes: [MessageAttribute], isTags: Bool)
|
|||||||
reactions.remove(at: index)
|
reactions.remove(at: index)
|
||||||
}
|
}
|
||||||
reactions.insert(MessageReaction(value: .stars, count: updatedCount, chosenOrder: -1), at: 0)
|
reactions.insert(MessageReaction(value: .stars, count: updatedCount, chosenOrder: -1), at: 0)
|
||||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: result.recentPeers)
|
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: reactions, recentPeers: result.recentPeers, topPeers: result.topPeers)
|
||||||
} else {
|
} else {
|
||||||
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: [MessageReaction(value: .stars, count: pendingStars.count, chosenOrder: -1)], recentPeers: [])
|
return ReactionsMessageAttribute(canViewList: current?.canViewList ?? false, isTags: current?.isTags ?? isTags, reactions: [MessageReaction(value: .stars, count: pendingStars.count, chosenOrder: -1)], recentPeers: [], topPeers: [])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return result
|
return result
|
||||||
@ -211,8 +224,6 @@ extension ReactionsMessageAttribute {
|
|||||||
convenience init(apiReactions: Api.MessageReactions) {
|
convenience init(apiReactions: Api.MessageReactions) {
|
||||||
switch apiReactions {
|
switch apiReactions {
|
||||||
case let .messageReactions(flags, results, recentReactions, topReactors):
|
case let .messageReactions(flags, results, recentReactions, topReactors):
|
||||||
//TODO:release
|
|
||||||
let _ = topReactors
|
|
||||||
let canViewList = (flags & (1 << 2)) != 0
|
let canViewList = (flags & (1 << 2)) != 0
|
||||||
let isTags = (flags & (1 << 3)) != 0
|
let isTags = (flags & (1 << 3)) != 0
|
||||||
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
|
let parsedRecentReactions: [ReactionsMessageAttribute.RecentPeer]
|
||||||
@ -234,6 +245,21 @@ extension ReactionsMessageAttribute {
|
|||||||
parsedRecentReactions = []
|
parsedRecentReactions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topPeers: [ReactionsMessageAttribute.TopPeer] = []
|
||||||
|
if let topReactors {
|
||||||
|
for item in topReactors {
|
||||||
|
switch item {
|
||||||
|
case let .messageReactor(flags, peerId, count):
|
||||||
|
topPeers.append(ReactionsMessageAttribute.TopPeer(
|
||||||
|
peerId: peerId.peerId,
|
||||||
|
count: count,
|
||||||
|
isTop: (flags & (1 << 0)) != 0,
|
||||||
|
isMy: (flags & (1 << 1)) != 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
canViewList: canViewList,
|
canViewList: canViewList,
|
||||||
isTags: isTags,
|
isTags: isTags,
|
||||||
@ -247,7 +273,8 @@ extension ReactionsMessageAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
recentPeers: parsedRecentReactions
|
recentPeers: parsedRecentReactions,
|
||||||
|
topPeers: topPeers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,10 +328,39 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct TopPeer: Equatable, PostboxCoding {
|
||||||
|
public var peerId: PeerId
|
||||||
|
public var count: Int32
|
||||||
|
public var isTop: Bool
|
||||||
|
public var isMy: Bool
|
||||||
|
|
||||||
|
public init(peerId: PeerId, count: Int32, isTop: Bool, isMy: Bool) {
|
||||||
|
self.peerId = peerId
|
||||||
|
self.count = count
|
||||||
|
self.isMy = isMy
|
||||||
|
self.isTop = isTop
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0))
|
||||||
|
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
|
||||||
|
self.isTop = decoder.decodeBoolForKey("t", orElse: false)
|
||||||
|
self.isMy = decoder.decodeBoolForKey("m", orElse: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeInt64(self.peerId.toInt64(), forKey: "p")
|
||||||
|
encoder.encodeInt32(self.count, forKey: "c")
|
||||||
|
encoder.encodeBool(self.isTop, forKey: "t")
|
||||||
|
encoder.encodeBool(self.isMy, forKey: "m")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let canViewList: Bool
|
public let canViewList: Bool
|
||||||
public let isTags: Bool
|
public let isTags: Bool
|
||||||
public let reactions: [MessageReaction]
|
public let reactions: [MessageReaction]
|
||||||
public let recentPeers: [RecentPeer]
|
public let recentPeers: [RecentPeer]
|
||||||
|
public let topPeers: [TopPeer]
|
||||||
|
|
||||||
public var associatedPeerIds: [PeerId] {
|
public var associatedPeerIds: [PeerId] {
|
||||||
return self.recentPeers.map(\.peerId)
|
return self.recentPeers.map(\.peerId)
|
||||||
@ -357,11 +386,12 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(canViewList: Bool, isTags: Bool, reactions: [MessageReaction], recentPeers: [RecentPeer]) {
|
public init(canViewList: Bool, isTags: Bool, reactions: [MessageReaction], recentPeers: [RecentPeer], topPeers: [TopPeer]) {
|
||||||
self.canViewList = canViewList
|
self.canViewList = canViewList
|
||||||
self.isTags = isTags
|
self.isTags = isTags
|
||||||
self.reactions = reactions
|
self.reactions = reactions
|
||||||
self.recentPeers = recentPeers
|
self.recentPeers = recentPeers
|
||||||
|
self.topPeers = topPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(decoder: PostboxDecoder) {
|
required public init(decoder: PostboxDecoder) {
|
||||||
@ -369,6 +399,7 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
self.isTags = decoder.decodeBoolForKey("tg", orElse: false)
|
self.isTags = decoder.decodeBoolForKey("tg", orElse: false)
|
||||||
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
|
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
|
||||||
self.recentPeers = decoder.decodeObjectArrayWithDecoderForKey("rp")
|
self.recentPeers = decoder.decodeObjectArrayWithDecoderForKey("rp")
|
||||||
|
self.topPeers = decoder.decodeObjectArrayWithDecoderForKey("tp")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
@ -376,6 +407,7 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
encoder.encodeBool(self.isTags, forKey: "tg")
|
encoder.encodeBool(self.isTags, forKey: "tg")
|
||||||
encoder.encodeObjectArray(self.reactions, forKey: "r")
|
encoder.encodeObjectArray(self.reactions, forKey: "r")
|
||||||
encoder.encodeObjectArray(self.recentPeers, forKey: "rp")
|
encoder.encodeObjectArray(self.recentPeers, forKey: "rp")
|
||||||
|
encoder.encodeObjectArray(self.topPeers, forKey: "tp")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ReactionsMessageAttribute, rhs: ReactionsMessageAttribute) -> Bool {
|
public static func ==(lhs: ReactionsMessageAttribute, rhs: ReactionsMessageAttribute) -> Bool {
|
||||||
@ -391,6 +423,9 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
if lhs.recentPeers != rhs.recentPeers {
|
if lhs.recentPeers != rhs.recentPeers {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.topPeers != rhs.topPeers {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +447,8 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute {
|
|||||||
var recentPeer = recentPeer
|
var recentPeer = recentPeer
|
||||||
recentPeer.isUnseen = false
|
recentPeer.isUnseen = false
|
||||||
return recentPeer
|
return recentPeer
|
||||||
}
|
},
|
||||||
|
topPeers: self.topPeers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1275,9 +1275,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
let reactions: ReactionsMessageAttribute
|
let reactions: ReactionsMessageAttribute
|
||||||
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
||||||
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
} else {
|
} else {
|
||||||
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
}
|
}
|
||||||
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
||||||
if !reactions.reactions.isEmpty {
|
if !reactions.reactions.isEmpty {
|
||||||
|
@ -2039,9 +2039,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
let bubbleReactions: ReactionsMessageAttribute
|
let bubbleReactions: ReactionsMessageAttribute
|
||||||
if needReactions {
|
if needReactions {
|
||||||
bubbleReactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
bubbleReactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
} else {
|
} else {
|
||||||
bubbleReactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
bubbleReactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
}
|
}
|
||||||
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
|
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
|
||||||
bottomNodeMergeStatus = .Both
|
bottomNodeMergeStatus = .Both
|
||||||
|
@ -595,9 +595,9 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
|||||||
|
|
||||||
let reactions: ReactionsMessageAttribute
|
let reactions: ReactionsMessageAttribute
|
||||||
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
||||||
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
} else {
|
} else {
|
||||||
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
||||||
|
@ -539,7 +539,7 @@ public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||||
let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
let reactionsAttribute = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
let buttonsUpdate = buttonsNode.prepareUpdate(
|
let buttonsUpdate = buttonsNode.prepareUpdate(
|
||||||
context: item.context,
|
context: item.context,
|
||||||
presentationData: item.presentationData,
|
presentationData: item.presentationData,
|
||||||
|
@ -839,9 +839,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
let reactions: ReactionsMessageAttribute
|
let reactions: ReactionsMessageAttribute
|
||||||
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) {
|
||||||
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
} else {
|
} else {
|
||||||
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [])
|
reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
||||||
|
@ -174,7 +174,7 @@ private final class BadgeComponent: Component {
|
|||||||
private let badgeShapeLayer = SimpleShapeLayer()
|
private let badgeShapeLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
private let badgeForeground: SimpleLayer
|
private let badgeForeground: SimpleLayer
|
||||||
private let badgeIcon: UIImageView
|
let badgeIcon: UIImageView
|
||||||
private let badgeLabel: BadgeLabelView
|
private let badgeLabel: BadgeLabelView
|
||||||
private let badgeLabelMaskView = UIImageView()
|
private let badgeLabelMaskView = UIImageView()
|
||||||
|
|
||||||
@ -480,17 +480,20 @@ private final class PeerComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
|
let count: Int
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
peer: EnginePeer
|
peer: EnginePeer,
|
||||||
|
count: Int
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.count = count
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool {
|
static func ==(lhs: PeerComponent, rhs: PeerComponent) -> Bool {
|
||||||
@ -506,6 +509,9 @@ private final class PeerComponent: Component {
|
|||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.count != rhs.count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +552,7 @@ private final class PeerComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(PeerBadgeComponent(
|
component: AnyComponent(PeerBadgeComponent(
|
||||||
theme: component.theme,
|
theme: component.theme,
|
||||||
title: "800"
|
title: "\(component.count)"
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||||
@ -590,21 +596,163 @@ private final class PeerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class SliderBackgroundComponent: Component {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let value: CGFloat
|
||||||
|
let topCutoff: CGFloat?
|
||||||
|
|
||||||
|
init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
value: CGFloat,
|
||||||
|
topCutoff: CGFloat?
|
||||||
|
) {
|
||||||
|
self.theme = theme
|
||||||
|
self.value = value
|
||||||
|
self.topCutoff = topCutoff
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: SliderBackgroundComponent, rhs: SliderBackgroundComponent) -> Bool {
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.value != rhs.value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.topCutoff != rhs.topCutoff {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView {
|
||||||
|
private let sliderBackground = UIView()
|
||||||
|
private let sliderForeground = UIView()
|
||||||
|
private let sliderStars = SliderStarsView()
|
||||||
|
|
||||||
|
private let topForegroundLine = SimpleLayer()
|
||||||
|
private let topBackgroundLine = SimpleLayer()
|
||||||
|
private let topForegroundText = ComponentView<Empty>()
|
||||||
|
private let topBackgroundText = ComponentView<Empty>()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.sliderBackground.clipsToBounds = true
|
||||||
|
|
||||||
|
self.sliderForeground.clipsToBounds = true
|
||||||
|
self.sliderForeground.addSubview(self.sliderStars)
|
||||||
|
|
||||||
|
self.addSubview(self.sliderBackground)
|
||||||
|
self.addSubview(self.sliderForeground)
|
||||||
|
|
||||||
|
self.sliderBackground.layer.addSublayer(self.topBackgroundLine)
|
||||||
|
self.sliderForeground.layer.addSublayer(self.topForegroundLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: SliderBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.sliderBackground.backgroundColor = UIColor(rgb: 0xEEEEEF)
|
||||||
|
self.sliderForeground.backgroundColor = UIColor(rgb: 0xFFB10D)
|
||||||
|
self.topForegroundLine.backgroundColor = component.theme.list.plainBackgroundColor.cgColor
|
||||||
|
self.topBackgroundLine.backgroundColor = UIColor(white: 0.0, alpha: 0.1).cgColor
|
||||||
|
|
||||||
|
transition.setFrame(view: self.sliderBackground, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
|
||||||
|
let sliderMinWidth = availableSize.height
|
||||||
|
let sliderAreaWidth: CGFloat = availableSize.width - sliderMinWidth
|
||||||
|
let sliderForegroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * component.value), height: availableSize.height))
|
||||||
|
transition.setFrame(view: self.sliderForeground, frame: sliderForegroundFrame)
|
||||||
|
|
||||||
|
self.sliderBackground.layer.cornerRadius = availableSize.height * 0.5
|
||||||
|
self.sliderForeground.layer.cornerRadius = availableSize.height * 0.5
|
||||||
|
|
||||||
|
self.sliderStars.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
|
self.sliderStars.update(size: availableSize, value: component.value)
|
||||||
|
|
||||||
|
self.sliderForeground.isHidden = sliderForegroundFrame.width <= sliderMinWidth
|
||||||
|
|
||||||
|
let topCutoff = component.topCutoff ?? 0.0
|
||||||
|
|
||||||
|
let topLineFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(sliderAreaWidth * topCutoff), y: 0.0), size: CGSize(width: 1.0, height: availableSize.height))
|
||||||
|
transition.setFrame(layer: self.topForegroundLine, frame: topLineFrame)
|
||||||
|
transition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame)
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let topTextSize = self.topForegroundText.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 1.0, alpha: 0.4)))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
)
|
||||||
|
let _ = self.topBackgroundText.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "TOP", font: Font.medium(17.0), textColor: UIColor(white: 0.0, alpha: 0.1)))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||||
|
)
|
||||||
|
let topTextFrame = CGRect(origin: CGPoint(x: topLineFrame.maxX + 6.0, y: floor((availableSize.height - topTextSize.height) * 0.5)), size: topTextSize)
|
||||||
|
if let topForegroundTextView = self.topForegroundText.view, let topBackgroundTextView = self.topBackgroundText.view {
|
||||||
|
if topForegroundTextView.superview == nil {
|
||||||
|
topBackgroundTextView.layer.anchorPoint = CGPoint()
|
||||||
|
self.sliderBackground.addSubview(topBackgroundTextView)
|
||||||
|
|
||||||
|
topForegroundTextView.layer.anchorPoint = CGPoint()
|
||||||
|
self.sliderForeground.addSubview(topForegroundTextView)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setPosition(view: topForegroundTextView, position: topTextFrame.origin)
|
||||||
|
transition.setPosition(view: topBackgroundTextView, position: topTextFrame.origin)
|
||||||
|
|
||||||
|
topForegroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||||
|
topBackgroundTextView.bounds = CGRect(origin: CGPoint(), size: topTextFrame.size)
|
||||||
|
|
||||||
|
topForegroundTextView.isHidden = component.topCutoff == nil || topTextFrame.maxX >= availableSize.width - 4.0
|
||||||
|
topBackgroundTextView.isHidden = component.topCutoff == nil || topTextFrame.maxX >= availableSize.width - 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if component.topCutoff == nil {
|
||||||
|
self.topForegroundLine.isHidden = true
|
||||||
|
self.topBackgroundLine.isHidden = true
|
||||||
|
} else {
|
||||||
|
self.topForegroundLine.isHidden = false
|
||||||
|
self.topBackgroundLine.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class ChatSendStarsScreenComponent: Component {
|
private final class ChatSendStarsScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
let balance: Int64?
|
let balance: Int64?
|
||||||
let topPeers: [EnginePeer]
|
let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||||
let completion: (Int64) -> Void
|
let completion: (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
balance: Int64?,
|
balance: Int64?,
|
||||||
topPeers: [EnginePeer],
|
topPeers: [ChatSendStarsScreen.TopPeer],
|
||||||
completion: @escaping (Int64) -> Void
|
completion: @escaping (Int64, ChatSendStarsScreen.TransitionOut) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
@ -664,10 +812,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
private let descriptionText = ComponentView<Empty>()
|
private let descriptionText = ComponentView<Empty>()
|
||||||
|
|
||||||
private let badgeStars = BadgeStarsView()
|
private let badgeStars = BadgeStarsView()
|
||||||
|
private let sliderBackground = ComponentView<Empty>()
|
||||||
private let slider = ComponentView<Empty>()
|
private let slider = ComponentView<Empty>()
|
||||||
private let sliderBackground = UIView()
|
|
||||||
private let sliderForeground = UIView()
|
|
||||||
private let sliderStars = SliderStarsView()
|
|
||||||
private let badge = ComponentView<Empty>()
|
private let badge = ComponentView<Empty>()
|
||||||
|
|
||||||
private var topPeersLeftSeparator: SimpleLayer?
|
private var topPeersLeftSeparator: SimpleLayer?
|
||||||
@ -695,6 +841,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
private var cachedStarImage: (UIImage, PresentationTheme)?
|
private var cachedStarImage: (UIImage, PresentationTheme)?
|
||||||
private var cachedCloseImage: UIImage?
|
private var cachedCloseImage: UIImage?
|
||||||
|
|
||||||
|
private var isPastTopCutoff: Bool?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.bottomOverscrollLimit = 200.0
|
self.bottomOverscrollLimit = 200.0
|
||||||
|
|
||||||
@ -740,9 +888,6 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
|
|
||||||
self.scrollView.addSubview(self.scrollContentView)
|
self.scrollView.addSubview(self.scrollContentView)
|
||||||
|
|
||||||
self.sliderForeground.clipsToBounds = true
|
|
||||||
self.sliderForeground.addSubview(self.sliderStars)
|
|
||||||
|
|
||||||
self.addSubview(self.navigationBarContainer)
|
self.addSubview(self.navigationBarContainer)
|
||||||
|
|
||||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||||
@ -955,34 +1100,44 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
containerSize: CGSize(width: availableSize.width - sliderInset * 2.0, height: 30.0)
|
containerSize: CGSize(width: availableSize.width - sliderInset * 2.0, height: 30.0)
|
||||||
)
|
)
|
||||||
let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize)
|
let sliderFrame = CGRect(origin: CGPoint(x: sliderInset, y: contentHeight + 127.0), size: sliderSize)
|
||||||
if let sliderView = self.slider.view {
|
let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0))
|
||||||
|
|
||||||
|
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
||||||
|
|
||||||
|
var topCutoffFraction: CGFloat?
|
||||||
|
if let maxCount = component.topPeers.max(by: { $0.count < $1.count })?.count {
|
||||||
|
let topCutoffFractionValue = CGFloat(maxCount) / CGFloat(1000 - 1)
|
||||||
|
topCutoffFraction = topCutoffFractionValue
|
||||||
|
|
||||||
|
let isPastCutoff = progressFraction >= topCutoffFractionValue
|
||||||
|
if let isPastTopCutoff = self.isPastTopCutoff, isPastTopCutoff != isPastCutoff {
|
||||||
|
HapticFeedback().tap()
|
||||||
|
}
|
||||||
|
self.isPastTopCutoff = isPastCutoff
|
||||||
|
} else {
|
||||||
|
self.isPastTopCutoff = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.sliderBackground.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(SliderBackgroundComponent(
|
||||||
|
theme: environment.theme,
|
||||||
|
value: progressFraction,
|
||||||
|
topCutoff: topCutoffFraction
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: sliderBackgroundFrame.size
|
||||||
|
)
|
||||||
|
|
||||||
|
if let sliderView = self.slider.view, let sliderBackgroundView = self.sliderBackground.view {
|
||||||
if sliderView.superview == nil {
|
if sliderView.superview == nil {
|
||||||
self.scrollContentView.addSubview(self.badgeStars)
|
self.scrollContentView.addSubview(self.badgeStars)
|
||||||
self.scrollContentView.addSubview(self.sliderBackground)
|
self.scrollContentView.addSubview(sliderBackgroundView)
|
||||||
self.scrollContentView.addSubview(self.sliderForeground)
|
|
||||||
self.scrollContentView.addSubview(sliderView)
|
self.scrollContentView.addSubview(sliderView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: sliderView, frame: sliderFrame)
|
transition.setFrame(view: sliderView, frame: sliderFrame)
|
||||||
|
|
||||||
self.sliderBackground.backgroundColor = UIColor(rgb: 0xEEEEEF)
|
transition.setFrame(view: sliderBackgroundView, frame: sliderBackgroundFrame)
|
||||||
self.sliderForeground.backgroundColor = UIColor(rgb: 0xFFB10D)
|
|
||||||
|
|
||||||
let sliderBackgroundFrame = CGRect(origin: CGPoint(x: sliderFrame.minX - 8.0, y: sliderFrame.minY + 7.0), size: CGSize(width: sliderFrame.width + 16.0, height: sliderFrame.height - 14.0))
|
|
||||||
transition.setFrame(view: self.sliderBackground, frame: sliderBackgroundFrame)
|
|
||||||
|
|
||||||
let progressFraction: CGFloat = CGFloat(self.amount) / CGFloat(1000 - 1)
|
|
||||||
let sliderMinWidth = sliderBackgroundFrame.height
|
|
||||||
let sliderAreaWidth: CGFloat = sliderBackgroundFrame.width - sliderMinWidth
|
|
||||||
let sliderForegroundFrame = CGRect(origin: CGPoint(x: sliderBackgroundFrame.minX, y: sliderBackgroundFrame.minY), size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * progressFraction), height: sliderBackgroundFrame.height))
|
|
||||||
transition.setFrame(view: self.sliderForeground, frame: sliderForegroundFrame)
|
|
||||||
|
|
||||||
self.sliderBackground.layer.cornerRadius = sliderBackgroundFrame.height * 0.5
|
|
||||||
self.sliderForeground.layer.cornerRadius = sliderBackgroundFrame.height * 0.5
|
|
||||||
|
|
||||||
self.sliderStars.frame = CGRect(origin: .zero, size: sliderBackgroundFrame.size)
|
|
||||||
self.sliderStars.update(size: sliderBackgroundFrame.size, value: progressFraction)
|
|
||||||
|
|
||||||
self.sliderForeground.isHidden = sliderForegroundFrame.width <= sliderMinWidth
|
|
||||||
|
|
||||||
var effectiveInertiaDirection = self.inertiaDirection
|
var effectiveInertiaDirection = self.inertiaDirection
|
||||||
if progressFraction <= 0.03 || progressFraction >= 0.97 {
|
if progressFraction <= 0.03 || progressFraction >= 0.97 {
|
||||||
@ -999,6 +1154,11 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let sliderMinWidth = sliderBackgroundFrame.height
|
||||||
|
let sliderAreaWidth: CGFloat = sliderBackgroundFrame.width - sliderMinWidth
|
||||||
|
let sliderForegroundFrame = CGRect(origin: sliderBackgroundFrame.origin, size: CGSize(width: sliderMinWidth + floorToScreenPixels(sliderAreaWidth * progressFraction), height: sliderBackgroundFrame.height))
|
||||||
|
|
||||||
var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize)
|
var badgeFrame = CGRect(origin: CGPoint(x: sliderForegroundFrame.minX + sliderForegroundFrame.width - floorToScreenPixels(sliderMinWidth * 0.5), y: sliderForegroundFrame.minY - 8.0), size: badgeSize)
|
||||||
if let badgeView = self.badge.view as? BadgeComponent.View {
|
if let badgeView = self.badge.view as? BadgeComponent.View {
|
||||||
if badgeView.superview == nil {
|
if badgeView.superview == nil {
|
||||||
@ -1215,14 +1375,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
var validIds: [EnginePeer.Id] = []
|
var validIds: [EnginePeer.Id] = []
|
||||||
var items: [(itemView: ComponentView<Empty>, size: CGSize)] = []
|
var items: [(itemView: ComponentView<Empty>, size: CGSize)] = []
|
||||||
for topPeer in component.topPeers {
|
for topPeer in component.topPeers {
|
||||||
validIds.append(topPeer.id)
|
validIds.append(topPeer.peer.id)
|
||||||
|
|
||||||
let itemView: ComponentView<Empty>
|
let itemView: ComponentView<Empty>
|
||||||
if let current = self.topPeerItems[topPeer.id] {
|
if let current = self.topPeerItems[topPeer.peer.id] {
|
||||||
itemView = current
|
itemView = current
|
||||||
} else {
|
} else {
|
||||||
itemView = ComponentView()
|
itemView = ComponentView()
|
||||||
self.topPeerItems[topPeer.id] = itemView
|
self.topPeerItems[topPeer.peer.id] = itemView
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemSize = itemView.update(
|
let itemSize = itemView.update(
|
||||||
@ -1231,7 +1391,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
peer: topPeer
|
peer: topPeer.peer,
|
||||||
|
count: topPeer.count
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||||
@ -1306,7 +1467,15 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.completion(self.amount)
|
guard let badgeView = self.badge.view as? BadgeComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
component.completion(
|
||||||
|
self.amount,
|
||||||
|
ChatSendStarsScreen.TransitionOut(
|
||||||
|
sourceView: badgeView.badgeIcon
|
||||||
|
)
|
||||||
|
)
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
@ -1399,14 +1568,14 @@ private final class ChatSendStarsScreenComponent: Component {
|
|||||||
|
|
||||||
public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
||||||
public final class InitialData {
|
public final class InitialData {
|
||||||
let peer: EnginePeer
|
fileprivate let peer: EnginePeer
|
||||||
let balance: Int64?
|
fileprivate let balance: Int64?
|
||||||
let topPeers: [EnginePeer]
|
fileprivate let topPeers: [ChatSendStarsScreen.TopPeer]
|
||||||
|
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
balance: Int64?,
|
balance: Int64?,
|
||||||
topPeers: [EnginePeer]
|
topPeers: [ChatSendStarsScreen.TopPeer]
|
||||||
) {
|
) {
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.balance = balance
|
self.balance = balance
|
||||||
@ -1414,13 +1583,41 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate final class TopPeer: Equatable {
|
||||||
|
let peer: EnginePeer
|
||||||
|
let count: Int
|
||||||
|
|
||||||
|
init(peer: EnginePeer, count: Int) {
|
||||||
|
self.peer = peer
|
||||||
|
self.count = count
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: TopPeer, rhs: TopPeer) -> Bool {
|
||||||
|
if lhs.peer != rhs.peer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.count != rhs.count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class TransitionOut {
|
||||||
|
public let sourceView: UIView
|
||||||
|
|
||||||
|
init(sourceView: UIView) {
|
||||||
|
self.sourceView = sourceView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
private var presenceDisposable: Disposable?
|
private var presenceDisposable: Disposable?
|
||||||
|
|
||||||
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64) -> Void) {
|
public init(context: AccountContext, initialData: InitialData, completion: @escaping (Int64, TransitionOut) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
super.init(context: context, component: ChatSendStarsScreenComponent(
|
super.init(context: context, component: ChatSendStarsScreenComponent(
|
||||||
@ -1454,7 +1651,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func initialData(context: AccountContext, peerId: EnginePeer.Id) -> Signal<InitialData?, NoError> {
|
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, topPeers: [ReactionsMessageAttribute.TopPeer]) -> Signal<InitialData?, NoError> {
|
||||||
let balance: Signal<Int64?, NoError>
|
let balance: Signal<Int64?, NoError>
|
||||||
if let starsContext = context.starsContext {
|
if let starsContext = context.starsContext {
|
||||||
balance = starsContext.state
|
balance = starsContext.state
|
||||||
@ -1467,19 +1664,33 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
|
context.engine.data.get(
|
||||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
|
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
|
||||||
|
EngineDataMap(topPeers.map(\.peerId).map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
|
),
|
||||||
balance
|
balance
|
||||||
)
|
)
|
||||||
|> map { peer, accountPeer, balance -> InitialData? in
|
|> map { peerAndTopPeerMap, balance -> InitialData? in
|
||||||
guard let peer, let accountPeer else {
|
let (peer, topPeerMap) = peerAndTopPeerMap
|
||||||
|
guard let peer else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return InitialData(
|
return InitialData(
|
||||||
peer: peer,
|
peer: peer,
|
||||||
balance: balance,
|
balance: balance,
|
||||||
topPeers: [accountPeer, peer]
|
topPeers: topPeers.compactMap { topPeer -> ChatSendStarsScreen.TopPeer? in
|
||||||
|
guard let topPeerValue = topPeerMap[topPeer.peerId] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let topPeerValue else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ChatSendStarsScreen.TopPeer(
|
||||||
|
peer: topPeerValue,
|
||||||
|
count: Int(topPeer.count)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10922,7 +10922,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pane.canReorder() {
|
if pane.canReorder() {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Reorder", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.BotPreviews_MenuReorder, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak pane] _, a in
|
}, action: { [weak pane] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
@ -10937,7 +10937,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Select", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
if ignoreNextActions {
|
if ignoreNextActions {
|
||||||
|
@ -314,7 +314,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
|||||||
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: false, isUnseen: false, isMy: true, peerId: accountPeer.id, timestamp: nil))
|
recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: false, isUnseen: false, isMy: true, peerId: accountPeer.id, timestamp: nil))
|
||||||
peers[accountPeer.id] = accountPeer
|
peers[accountPeer.id] = accountPeer
|
||||||
}
|
}
|
||||||
attributes.append(ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [MessageReaction(value: reaction, count: 1, chosenOrder: 0)], recentPeers: recentPeers))
|
attributes.append(ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [MessageReaction(value: reaction, count: 1, chosenOrder: 0)], recentPeers: recentPeers, topPeers: []))
|
||||||
}
|
}
|
||||||
|
|
||||||
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[userPeerId], text: messageText, attributes: attributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: item.availableReactions, accountPeer: item.accountPeer, isCentered: true, isPreview: true, isStandalone: false)
|
let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[userPeerId], text: messageText, attributes: attributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: item.availableReactions, accountPeer: item.accountPeer, isCentered: true, isPreview: true, isStandalone: false)
|
||||||
|
@ -313,10 +313,16 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.link == nil {
|
if self.link == nil {
|
||||||
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
|
var previousTimestamp = CACurrentMediaTime()
|
||||||
|
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timestamp = CACurrentMediaTime()
|
||||||
|
let deltaTime = max(0.0, min(10.0 / 60.0, timestamp - previousTimestamp))
|
||||||
|
previousTimestamp = timestamp
|
||||||
|
|
||||||
for shockwave in self.shockwaves {
|
for shockwave in self.shockwaves {
|
||||||
shockwave.timeValue += deltaTime * (1.0 / CGFloat(UIView.animationDurationFactor()))
|
shockwave.timeValue += deltaTime * (1.0 / CGFloat(UIView.animationDurationFactor()))
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ import SavedTagNameAlertController
|
|||||||
import PremiumUI
|
import PremiumUI
|
||||||
import ChatSendStarsScreen
|
import ChatSendStarsScreen
|
||||||
import ChatMessageItemCommon
|
import ChatMessageItemCommon
|
||||||
|
import ChatMessageItemView
|
||||||
|
import ReactionSelectionNode
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
func presentTagPremiumPaywall() {
|
func presentTagPremiumPaywall() {
|
||||||
@ -161,19 +163,123 @@ extension ChatControllerImpl {
|
|||||||
self.window?.presentInGlobalOverlay(controller)
|
self.window?.presentInGlobalOverlay(controller)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if case .stars = value {
|
if case .stars = value, let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false) {
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
cancelParentGestures(view: sourceView)
|
cancelParentGestures(view: sourceView)
|
||||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId)
|
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, topPeers: reactionsAttribute.topPeers)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
||||||
guard let self, let initialData else {
|
guard let self, let initialData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount in
|
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, transitionOut in
|
||||||
guard let self, amount > 0 else {
|
guard let self, amount > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceItemNode: ChatMessageItemView?
|
||||||
|
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ChatMessageItemView {
|
||||||
|
if itemNode.item?.message.id == message.id {
|
||||||
|
sourceItemNode = itemNode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||||
|
var reactionItem: ReactionItem?
|
||||||
|
|
||||||
|
for reaction in availableReactions.reactions {
|
||||||
|
guard let centerAnimation = reaction.centerAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if reaction.value == .stars {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let reactionItem {
|
||||||
|
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||||
|
|
||||||
|
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||||
|
|
||||||
|
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||||
|
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||||
|
standaloneReactionAnimation.animateOutToReaction(
|
||||||
|
context: self.context,
|
||||||
|
theme: self.presentationData.theme,
|
||||||
|
item: reactionItem,
|
||||||
|
value: .stars,
|
||||||
|
sourceView: transitionOut.sourceView,
|
||||||
|
targetView: targetView,
|
||||||
|
hideNode: false,
|
||||||
|
forceSwitchToInlineImmediately: false,
|
||||||
|
animateTargetContainer: nil,
|
||||||
|
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||||
|
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||||
|
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||||
|
},
|
||||||
|
onHit: { [weak self, weak itemNode] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||||
|
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
|
standaloneReactionAnimation?.removeFromSupernode()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/*standaloneReactionAnimation.animateReactionSelection(
|
||||||
|
context: strongSelf.context,
|
||||||
|
theme: strongSelf.presentationData.theme,
|
||||||
|
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
|
||||||
|
reaction: reactionItem,
|
||||||
|
avatarPeers: [],
|
||||||
|
playHaptic: false,
|
||||||
|
isLarge: false,
|
||||||
|
targetView: targetView,
|
||||||
|
addStandaloneReactionAnimation: { standaloneReactionAnimation in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||||
|
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
|
||||||
|
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||||
|
},
|
||||||
|
onHit: { [weak itemNode] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) {
|
||||||
|
strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
completion: { [weak standaloneReactionAnimation] in
|
||||||
|
standaloneReactionAnimation?.removeFromSupernode()
|
||||||
|
}
|
||||||
|
)*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount))
|
||||||
|
|
||||||
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user