From 9505dcf9f459dd34d00d2daab7a0297461b9023c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 21 Dec 2021 22:15:05 +0400 Subject: [PATCH 1/2] Context menu improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 34 +++ ...tControllerExtractedPresentationNode.swift | 11 +- .../ReactionContextBackgroundNode.swift | 195 ++++++++++++++++++ .../Sources/ReactionContextNode.swift | 183 ++-------------- .../ChatInterfaceStateContextMenus.swift | 14 +- 5 files changed, 270 insertions(+), 167 deletions(-) create mode 100644 submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4b63357a04..08524f9e6c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -217,6 +217,40 @@ "PUSH_CHAT_MESSAGE_GAME_SCORE" = "%1$@ scored %4$@ in game %3$@ in the group %2$@"; "PUSH_CHAT_MESSAGE_VIDEOS" = "%1$@ sent %3$@ videos to the group %2$@"; +"PUSH_REACT_TEXT" = "%1$@|%2$@ to your %3$@"; +"PUSH_REACT_NOTEXT" = "%1$@|%2$@ to your message"; +"PUSH_REACT_PHOTO" = "%1$@|%2$@ to your photo"; +"PUSH_REACT_VIDEO" = "%1$@|%2$@ to your video"; +"PUSH_REACT_ROUND" = "%1$@|%2$@ to your video message"; +"PUSH_REACT_DOC" = "%1$@|%2$@ to your file"; +"PUSH_REACT_STICKER" = "%1$@|%2$@ to your %3$@sticker"; +"PUSH_REACT_AUDIO" = "%1$@|%2$@ to your voice message"; +"PUSH_REACT_CONTACT" = "%1$@|%2$@ to your contact %3$@"; +"PUSH_REACT_GEO" = "%1$@|%2$@ to your map"; +"PUSH_REACT_GEOLIVE" = "%1$@|%2$@ to your live location"; +"PUSH_REACT_POLL" = "%1$@|%2$@ to your poll %3$@"; +"PUSH_REACT_QUIZ" = "%1$@|%2$@ to your quiz %3$@"; +"PUSH_REACT_GAME" = "%1$@|%2$@ to your game"; +"PUSH_REACT_INVOICE" = "%1$@|%2$@ to your invoice"; +"PUSH_REACT_GIF" = "%1$@|%2$@ to your GIF"; + +"PUSH_CHAT_REACT_TEXT" = "%2$@|%1$@ %3$@ to your %4$@"; +"PUSH_CHAT_REACT_NOTEXT" = "%2$@|%1$@ %3$@ to your message"; +"PUSH_CHAT_REACT_PHOTO" = "%2$@|%1$@ %3$@ to your photo"; +"PUSH_CHAT_REACT_VIDEO" = "%2$@|%1$@ %3$@ to your video"; +"PUSH_CHAT_REACT_ROUND" = "%2$@|%1$@ %3$@ to your video message"; +"PUSH_CHAT_REACT_DOC" = "%2$@|%1$@ %3$@ to your file"; +"PUSH_CHAT_REACT_STICKER" = "%2$@|%1$@ %3$@ to your %4$@sticker"; +"PUSH_CHAT_REACT_AUDIO" = "%2$@|%1$@ %3$@ to your voice message"; +"PUSH_CHAT_REACT_CONTACT" = "%2$@|%1$@ %3$@ to your contact %4$@"; +"PUSH_CHAT_REACT_GEO" = "%2$@|%1$@ %3$@ to your map"; +"PUSH_CHAT_REACT_GEOLIVE" = "%2$@|%1$@ %3$@ to your live location"; +"PUSH_CHAT_REACT_POLL" = "%2$@|%1$@ %3$@ to your poll %4$@"; +"PUSH_CHAT_REACT_QUIZ" = "%2$@|%1$@ %3$@ to your quiz %4$@"; +"PUSH_CHAT_REACT_GAME" = "%2$@|%1$@ %3$@ to your game"; +"PUSH_CHAT_REACT_INVOICE" = "%2$@|%1$@ %3$@ to your invoice"; +"PUSH_CHAT_REACT_GIF" = "%2$@|%1$@ %3$@ to your GIF"; + "PUSH_REMINDER_TITLE" = "🗓 Reminder"; "PUSH_SENDER_YOU" = "📅 You"; diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 54686eafdf..b6ccc1f53f 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -8,7 +8,7 @@ import TelegramCore import SwiftSignalKit import ReactionSelectionNode -final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode { +final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode, UIScrollViewDelegate { private final class ContentNode: ASDisplayNode { let offsetContainerNode: ASDisplayNode let containingNode: ContextExtractedContentContainingNode @@ -126,6 +126,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo self.scrollNode.addSubnode(self.contentRectDebugNode) #endif*/ + self.scrollNode.view.delegate = self + self.dismissTapNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dismissTapGesture(_:)))) } @@ -162,6 +164,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if let reactionContextNode = self.reactionContextNode { + let isIntersectingContent = scrollView.contentOffset.y >= 10.0 + reactionContextNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: .animated(duration: 0.25, curve: .easeInOut)) + } + } + func highlightGestureMoved(location: CGPoint) { self.actionsStackNode.highlightGestureMoved(location: self.view.convert(location, to: self.actionsStackNode.view)) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift new file mode 100644 index 0000000000..6fe7b5158f --- /dev/null +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -0,0 +1,195 @@ +import Foundation +import AsyncDisplayKit +import Display +import TelegramPresentationData +import AccountContext + +private func generateBackgroundImage(foreground: UIColor, diameter: CGFloat, sideInset: CGFloat) -> UIImage? { + return generateImage(CGSize(width: diameter + sideInset * 2.0, height: diameter + sideInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(foreground.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: sideInset, y: sideInset), size: CGSize(width: diameter, height: diameter))) + })?.stretchableImage(withLeftCapWidth: Int(sideInset + diameter / 2.0), topCapHeight: Int(sideInset + diameter / 2.0)) +} + +private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, sideInset: CGFloat) -> UIImage? { + return generateImage(CGSize(width: diameter + sideInset * 2.0, height: diameter + sideInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(foreground.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: sideInset, y: sideInset), size: CGSize(width: diameter, height: diameter))) + })?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + sideInset / 2.0), topCapHeight: Int(diameter / 2.0 + sideInset / 2.0)) +} + +private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { + return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(shadow.cgColor) + context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) + })?.stretchableImage(withLeftCapWidth: Int(shadowBlur + diameter / 2.0), topCapHeight: Int(shadowBlur + diameter / 2.0)) +} + + +final class ReactionContextBackgroundNode: ASDisplayNode { + private let largeCircleSize: CGFloat + private let smallCircleSize: CGFloat + + private let backgroundNode: NavigationBackgroundNode + + private let maskLayer: SimpleLayer + private let backgroundLayer: SimpleLayer + private let backgroundShadowLayer: SimpleLayer + private let largeCircleLayer: SimpleLayer + private let largeCircleShadowLayer: SimpleLayer + private let smallCircleLayer: SimpleLayer + private let smallCircleShadowLayer: SimpleLayer + + private var theme: PresentationTheme? + + init(largeCircleSize: CGFloat, smallCircleSize: CGFloat) { + self.largeCircleSize = largeCircleSize + self.smallCircleSize = smallCircleSize + + self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) + + self.maskLayer = SimpleLayer() + self.backgroundLayer = SimpleLayer() + self.backgroundShadowLayer = SimpleLayer() + self.largeCircleLayer = SimpleLayer() + self.largeCircleShadowLayer = SimpleLayer() + self.smallCircleLayer = SimpleLayer() + self.smallCircleShadowLayer = SimpleLayer() + + self.backgroundLayer.backgroundColor = UIColor.black.cgColor + self.backgroundLayer.masksToBounds = true + self.backgroundLayer.cornerRadius = 52.0 / 2.0 + + self.largeCircleLayer.backgroundColor = UIColor.black.cgColor + self.largeCircleLayer.masksToBounds = true + self.largeCircleLayer.cornerRadius = largeCircleSize / 2.0 + + self.smallCircleLayer.backgroundColor = UIColor.black.cgColor + self.smallCircleLayer.masksToBounds = true + self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0 + + if #available(iOS 13.0, *) { + self.backgroundLayer.cornerCurve = .circular + self.largeCircleLayer.cornerCurve = .circular + self.smallCircleLayer.cornerCurve = .circular + } + + super.init() + + self.layer.addSublayer(self.backgroundShadowLayer) + self.layer.addSublayer(self.smallCircleShadowLayer) + self.layer.addSublayer(self.largeCircleShadowLayer) + + self.backgroundShadowLayer.opacity = 0.0 + self.largeCircleShadowLayer.opacity = 0.0 + self.smallCircleShadowLayer.opacity = 0.0 + + self.addSubnode(self.backgroundNode) + + self.maskLayer.addSublayer(self.smallCircleLayer) + self.maskLayer.addSublayer(self.largeCircleLayer) + self.maskLayer.addSublayer(self.backgroundLayer) + + self.backgroundNode.layer.mask = self.maskLayer + } + + func updateIsIntersectingContent(isIntersectingContent: Bool, transition: ContainedViewLayoutTransition) { + let shadowAlpha: CGFloat = isIntersectingContent ? 1.0 : 0.0 + transition.updateAlpha(layer: self.backgroundShadowLayer, alpha: shadowAlpha) + transition.updateAlpha(layer: self.smallCircleShadowLayer, alpha: shadowAlpha) + transition.updateAlpha(layer: self.largeCircleShadowLayer, alpha: shadowAlpha) + } + + func update( + theme: PresentationTheme, + size: CGSize, + cloudSourcePoint: CGFloat, + isLeftAligned: Bool, + transition: ContainedViewLayoutTransition + ) { + let shadowInset: CGFloat = 15.0 + + if self.theme !== theme { + self.theme = theme + + self.backgroundNode.updateColor(color: theme.contextMenu.backgroundColor, transition: .immediate) + + let shadowColor = UIColor(white: 0.0, alpha: 0.4) + + if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: 52.0, shadowBlur: shadowInset) { + ASDisplayNodeSetResizableContents(self.backgroundShadowLayer, image) + } + if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: self.largeCircleSize, shadowBlur: shadowInset) { + ASDisplayNodeSetResizableContents(self.largeCircleShadowLayer, image) + } + if let image = generateBubbleShadowImage(shadow: shadowColor, diameter: self.smallCircleSize, shadowBlur: shadowInset) { + ASDisplayNodeSetResizableContents(self.smallCircleShadowLayer, image) + } + } + + let backgroundFrame = CGRect(origin: CGPoint(), size: size) + + let largeCircleFrame: CGRect + let smallCircleFrame: CGRect + if isLeftAligned { + largeCircleFrame = CGRect(origin: CGPoint(x: cloudSourcePoint - floor(largeCircleSize / 2.0), y: size.height - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) + smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) + } else { + largeCircleFrame = CGRect(origin: CGPoint(x: cloudSourcePoint - floor(largeCircleSize / 2.0), y: size.height - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) + smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) + } + + let contentBounds = backgroundFrame.insetBy(dx: -10.0, dy: -10.0).union(largeCircleFrame).union(smallCircleFrame) + + transition.updateFrame(layer: self.backgroundLayer, frame: backgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)) + transition.updateFrame(layer: self.largeCircleLayer, frame: largeCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)) + transition.updateFrame(layer: self.smallCircleLayer, frame: smallCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)) + + transition.updateFrame(layer: self.backgroundShadowLayer, frame: backgroundFrame.insetBy(dx: -shadowInset, dy: -shadowInset)) + transition.updateFrame(layer: self.largeCircleShadowLayer, frame: largeCircleFrame.insetBy(dx: -shadowInset, dy: -shadowInset)) + transition.updateFrame(layer: self.smallCircleShadowLayer, frame: smallCircleFrame.insetBy(dx: -shadowInset, dy: -shadowInset)) + + transition.updateFrame(node: self.backgroundNode, frame: contentBounds) + self.backgroundNode.update(size: contentBounds.size, transition: transition) + } + + func animateIn() { + let smallCircleDuration: Double = 0.5 + let largeCircleDuration: Double = 0.5 + let largeCircleDelay: Double = 0.08 + let mainCircleDuration: Double = 0.5 + let mainCircleDelay: Double = 0.1 + + self.smallCircleLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration) + + self.largeCircleLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: largeCircleDelay) + self.largeCircleLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay) + + self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: mainCircleDelay) + self.backgroundLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) + } + + func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) { + let springDuration: Double = 0.42 + let springDamping: CGFloat = 104.0 + let springDelay: Double = 0.22 + + self.backgroundLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) + self.backgroundLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) + } + + func animateOut() { + self.backgroundLayer.animateAlpha(from: CGFloat(self.backgroundLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) + self.largeCircleLayer.animateAlpha(from: CGFloat(self.largeCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) + self.smallCircleLayer.animateAlpha(from: CGFloat(self.smallCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) + } +} diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 1dadfc99ca..12f421c12a 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -37,68 +37,11 @@ public final class ReactionContextItem { private let largeCircleSize: CGFloat = 16.0 private let smallCircleSize: CGFloat = 8.0 -private func generateBackgroundImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { - return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(foreground.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - })?.stretchableImage(withLeftCapWidth: Int(shadowBlur + diameter / 2.0), topCapHeight: Int(shadowBlur + diameter / 2.0)) -} - -private func generateBackgroundShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { - return generateImage(CGSize(width: diameter * 2.0 + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(shadow.cgColor) - context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor) - - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur + diameter, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.fill(CGRect(origin: CGPoint(x: shadowBlur + diameter / 2.0, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) - - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur + diameter, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.fill(CGRect(origin: CGPoint(x: shadowBlur + diameter / 2.0, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - })?.stretchableImage(withLeftCapWidth: Int(diameter + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0)) -} - -private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { - return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(foreground.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - })?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0)) -} - -private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { - return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(shadow.cgColor) - context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) - })?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0 + shadowBlur / 2.0), topCapHeight: Int(diameter / 2.0 + shadowBlur / 2.0)) -} - public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private let theme: PresentationTheme private let items: [ReactionContextItem] - private let backgroundNode: ASImageNode - private let backgroundShadowNode: ASImageNode - private let backgroundContainerNode: ASDisplayNode - - private let largeCircleNode: ASImageNode - private let largeCircleShadowNode: ASImageNode - - private let smallCircleNode: ASImageNode - private let smallCircleShadowNode: ASImageNode + private let backgroundNode: ReactionContextBackgroundNode private let contentContainer: ASDisplayNode private let contentContainerMask: UIImageView @@ -122,44 +65,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.theme = theme self.items = items - let shadowBlur: CGFloat = 5.0 - - self.backgroundNode = ASImageNode() - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.displaysAsynchronously = false - - self.backgroundShadowNode = ASImageNode() - self.backgroundShadowNode.displayWithoutProcessing = true - self.backgroundShadowNode.displaysAsynchronously = false - - self.backgroundContainerNode = ASDisplayNode() - self.backgroundContainerNode.allowsGroupOpacity = true - - self.largeCircleNode = ASImageNode() - self.largeCircleNode.displayWithoutProcessing = true - self.largeCircleNode.displaysAsynchronously = false - - self.largeCircleShadowNode = ASImageNode() - self.largeCircleShadowNode.displayWithoutProcessing = true - self.largeCircleShadowNode.displaysAsynchronously = false - - self.smallCircleNode = ASImageNode() - self.smallCircleNode.displayWithoutProcessing = true - self.smallCircleNode.displaysAsynchronously = false - - self.smallCircleShadowNode = ASImageNode() - self.smallCircleShadowNode.displayWithoutProcessing = true - self.smallCircleShadowNode.displaysAsynchronously = false - - self.backgroundNode.image = generateBackgroundImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: 52.0, shadowBlur: shadowBlur) - - self.backgroundShadowNode.image = generateBackgroundShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: 52.0, shadowBlur: shadowBlur) - - self.largeCircleNode.image = generateBubbleImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: largeCircleSize, shadowBlur: shadowBlur) - self.smallCircleNode.image = generateBubbleImage(foreground: theme.contextMenu.backgroundColor.withAlphaComponent(1.0), diameter: smallCircleSize, shadowBlur: shadowBlur) - - self.largeCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: largeCircleSize, shadowBlur: shadowBlur) - self.smallCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: smallCircleSize, shadowBlur: shadowBlur) + self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize) self.scrollNode = ASScrollNode() self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true @@ -200,18 +106,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { context.fill(CGRect(origin: CGPoint(x: maskGradientWidth, y: 0.0), size: CGSize(width: 1.0, height: size.height))) })?.stretchableImage(withLeftCapWidth: Int(maskGradientWidth), topCapHeight: 0) self.contentContainer.view.mask = self.contentContainerMask - //self.contentContainer.view.addSubview(self.contentContainerMask) super.init() - self.addSubnode(self.smallCircleShadowNode) - self.addSubnode(self.largeCircleShadowNode) - self.addSubnode(self.backgroundShadowNode) - - self.backgroundContainerNode.addSubnode(self.smallCircleNode) - self.backgroundContainerNode.addSubnode(self.largeCircleNode) - self.backgroundContainerNode.addSubnode(self.backgroundNode) - self.addSubnode(self.backgroundContainerNode) + self.addSubnode(self.backgroundNode) self.scrollNode.view.delegate = self @@ -233,6 +131,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: transition, animateInFromAnchorRect: nil, animateOutToAnchorRect: nil) } + public func updateIsIntersectingContent(isIntersectingContent: Bool, transition: ContainedViewLayoutTransition) { + self.backgroundNode.updateIsIntersectingContent(isIntersectingContent: isIntersectingContent, transition: transition) + } + private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize) -> (backgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) { var contentSize = contentSize contentSize.width = max(52.0, contentSize.width) @@ -308,7 +210,6 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let sideInset: CGFloat = 11.0 let itemSpacing: CGFloat = 9.0 let itemSize: CGFloat = 40.0 - let shadowBlur: CGFloat = 5.0 let verticalInset: CGFloat = 13.0 let rowHeight: CGFloat = 30.0 @@ -350,34 +251,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } self.updateScrolling(transition: transition) - - let isInOverflow = backgroundFrame.maxY > anchorRect.minY - let backgroundAlpha: CGFloat = isInOverflow ? 1.0 : 0.8 - let shadowAlpha: CGFloat = isInOverflow ? 1.0 : 0.0 - transition.updateAlpha(node: self.backgroundContainerNode, alpha: backgroundAlpha) - transition.updateAlpha(node: self.backgroundShadowNode, alpha: shadowAlpha) - transition.updateAlpha(node: self.largeCircleShadowNode, alpha: shadowAlpha) - transition.updateAlpha(node: self.smallCircleShadowNode, alpha: shadowAlpha) - transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) - - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) - transition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) - - let largeCircleFrame: CGRect - let smallCircleFrame: CGRect - if isLeftAligned { - largeCircleFrame = CGRect(origin: CGPoint(x: cloudSourcePoint - floor(largeCircleSize / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) - smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) - } else { - largeCircleFrame = CGRect(origin: CGPoint(x: cloudSourcePoint - floor(largeCircleSize / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) - smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) - } - - transition.updateFrame(node: self.largeCircleNode, frame: largeCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) - transition.updateFrame(node: self.largeCircleShadowNode, frame: largeCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) - transition.updateFrame(node: self.smallCircleNode, frame: smallCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) - transition.updateFrame(node: self.smallCircleShadowNode, frame: smallCircleFrame.insetBy(dx: -shadowBlur, dy: -shadowBlur)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + self.backgroundNode.update( + theme: self.theme, + size: backgroundFrame.size, + cloudSourcePoint: cloudSourcePoint - backgroundFrame.minX, + isLeftAligned: isLeftAligned, + transition: transition + ) if let animateInFromAnchorRect = animateInFromAnchorRect { let springDuration: Double = 0.42 @@ -386,14 +268,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let sourceBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateInFromAnchorRect, contentSize: CGSize(width: backgroundFrame.height, height: contentHeight)).0 - self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - backgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) - self.backgroundNode.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size).insetBy(dx: -shadowBlur, dy: -shadowBlur)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -shadowBlur, dy: -shadowBlur)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) + self.backgroundNode.animateInFromAnchorRect(size: backgroundFrame.size, sourceBackgroundFrame: sourceBackgroundFrame.offsetBy(dx: -backgroundFrame.minX, dy: -backgroundFrame.minY)) self.contentContainer.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - backgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) self.contentContainer.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: backgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) - - //self.contentContainerMask.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - backgroundFrame.midX, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) - //self.contentContainerMask.layer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: backgroundFrame.size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) } else if let animateOutToAnchorRect = animateOutToAnchorRect { let targetBackgroundFrame = self.calculateBackgroundFrame(containerSize: size, insets: backgroundInsets, anchorRect: animateOutToAnchorRect, contentSize: CGSize(width: visibleContentWidth, height: contentHeight)).0 @@ -410,22 +288,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: sourceAnchorRect, animateOutToAnchorRect: nil) } - let smallCircleDuration: Double = 0.5 - let largeCircleDuration: Double = 0.5 - let largeCircleDelay: Double = 0.08 let mainCircleDuration: Double = 0.5 let mainCircleDelay: Double = 0.1 - self.smallCircleNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration) - self.smallCircleShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: smallCircleDuration) - - self.largeCircleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: largeCircleDelay) - self.largeCircleNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay) - self.largeCircleShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay) - - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: mainCircleDelay) - self.backgroundNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) - self.backgroundShadowNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) + self.backgroundNode.animateIn() for i in 0 ..< self.itemNodes.count { let itemNode = self.itemNodes[i] @@ -433,22 +299,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: itemDelay) itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: itemDelay, initialVelocity: 0.0) } - - /*if let itemNode = self.itemNodes.first { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: mainCircleDelay) - itemNode.didAppear() - itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay, completion: { _ in - }) - }*/ } public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) { - self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.backgroundNode.animateOut() + for itemNode in self.itemNodes { if itemNode.isExtracted { continue diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 1f04a3f9c3..ad4a8f608f 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -146,7 +146,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo return false } -private func canViewReadStats(message: Message, isMessageRead: Bool, appConfig: AppConfiguration) -> Bool { +private func canViewReadStats(message: Message, cachedData: CachedPeerData?, isMessageRead: Bool, appConfig: AppConfiguration) -> Bool { guard let peer = message.peers[message.id.peerId] else { return false } @@ -195,6 +195,16 @@ private func canViewReadStats(message: Message, isMessageRead: Bool, appConfig: case let channel as TelegramChannel: if case .broadcast = channel.info { return false + } else if let cachedData = cachedData as? CachedChannelData { + if let memberCount = cachedData.participantsSummary.memberCount { + if Int(memberCount) > maxParticipantCount { + return false + } + } else { + return false + } + } else { + return false } case let group as TelegramGroup: if group.participantCount > maxParticipantCount { @@ -1191,7 +1201,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - let canViewStats = canViewReadStats(message: message, isMessageRead: isMessageRead, appConfig: appConfig) + let canViewStats = canViewReadStats(message: message, cachedData: cachedData, isMessageRead: isMessageRead, appConfig: appConfig) var reactionCount = 0 for reaction in mergedMessageReactionsAndPeers(message: message).reactions { reactionCount += Int(reaction.count) From 64c94d1fbadd1152681c98427d4c5d48939880d0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 21 Dec 2021 22:22:17 +0400 Subject: [PATCH 2/2] Fix sticker reaction layout --- submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index c16ce72c85..b34d37ca52 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -986,7 +986,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { let reactionButtonsNode = reactionButtonsSizeAndApply.1(animation) - var reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY - 10.0), size: reactionButtonsSizeAndApply.0) + var reactionButtonsFrame = CGRect(origin: CGPoint(x: imageFrame.minX, y: imageFrame.maxY - 4.0), size: reactionButtonsSizeAndApply.0) if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { reactionButtonsFrame.origin.y += 4.0 + actionButtonsSizeAndApply.0.height }