mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Present reaction UI in a different way
This commit is contained in:
parent
f7fab3f1af
commit
965d2628aa
@ -77,7 +77,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.animationNode.automaticallyLoadFirstFrame = loadFirstFrame
|
self.animationNode.automaticallyLoadFirstFrame = loadFirstFrame
|
||||||
self.animationNode.playToCompletionOnStop = true
|
self.animationNode.playToCompletionOnStop = true
|
||||||
|
|
||||||
var intrinsicSize = CGSize(width: maximizedReactionSize + 18.0, height: maximizedReactionSize + 18.0)
|
var intrinsicSize = CGSize(width: maximizedReactionSize + 14.0, height: maximizedReactionSize + 14.0)
|
||||||
|
|
||||||
self.imageNode = ASImageNode()
|
self.imageNode = ASImageNode()
|
||||||
switch reaction {
|
switch reaction {
|
||||||
@ -195,9 +195,8 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
private var shadowBlur: CGFloat = 8.0
|
private var shadowBlur: CGFloat = 8.0
|
||||||
private var minimizedReactionSize: CGFloat = 30.0
|
private var minimizedReactionSize: CGFloat = 28.0
|
||||||
private var maximizedReactionSize: CGFloat = 60.0
|
private var smallCircleSize: CGFloat = 14.0
|
||||||
private var smallCircleSize: CGFloat = 8.0
|
|
||||||
|
|
||||||
private var isRightAligned: Bool = false
|
private var isRightAligned: Bool = false
|
||||||
|
|
||||||
@ -246,44 +245,17 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
isRightAligned = true
|
isRightAligned = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInitial && self.reactionNodes.isEmpty {
|
let reactionSideInset: CGFloat = 10.0
|
||||||
let availableContentWidth = constrainedSize.width //max(100.0, initialAnchorX)
|
var reactionSpacing: CGFloat = 6.0
|
||||||
var minimizedReactionSize = (availableContentWidth - self.maximizedReactionSize) / (CGFloat(self.reactions.count - 1) + CGFloat(self.reactions.count + 1) * 0.2)
|
let minReactionSpacing: CGFloat = 2.0
|
||||||
minimizedReactionSize = max(16.0, floor(minimizedReactionSize))
|
let minimizedReactionSize = self.minimizedReactionSize
|
||||||
minimizedReactionSize = min(30.0, minimizedReactionSize)
|
let contentWidth: CGFloat = CGFloat(self.reactions.count) * (minimizedReactionSize) + CGFloat(self.reactions.count - 1) * reactionSpacing + reactionSideInset * 2.0
|
||||||
|
let spaceForMaximizedReaction = CGFloat(self.reactions.count - 1) * reactionSpacing - CGFloat(self.reactions.count - 1) * minReactionSpacing
|
||||||
self.minimizedReactionSize = minimizedReactionSize
|
let maximizedReactionSize: CGFloat = minimizedReactionSize + spaceForMaximizedReaction
|
||||||
self.shadowBlur = floor(minimizedReactionSize * 0.26)
|
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
|
||||||
self.smallCircleSize = 8.0
|
|
||||||
|
|
||||||
let backgroundHeight = floor(minimizedReactionSize * 1.4)
|
|
||||||
|
|
||||||
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
|
||||||
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
|
||||||
for i in 0 ..< self.bubbleNodes.count {
|
|
||||||
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
|
||||||
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
|
|
||||||
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: self.maximizedReactionSize, loadFirstFrame: true)
|
|
||||||
}
|
|
||||||
self.reactionNodes.forEach(self.addSubnode(_:))
|
|
||||||
}
|
|
||||||
|
|
||||||
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.4)
|
|
||||||
|
|
||||||
let reactionSpacing: CGFloat = floor(self.minimizedReactionSize * 0.2)
|
|
||||||
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
|
|
||||||
|
|
||||||
let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
|
|
||||||
|
|
||||||
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
|
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
|
||||||
if isRightAligned {
|
backgroundFrame = backgroundFrame.offsetBy(dx: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
|
||||||
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
|
|
||||||
} else {
|
|
||||||
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
|
|
||||||
}
|
|
||||||
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
|
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
|
||||||
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
|
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
|
||||||
|
|
||||||
@ -295,14 +267,44 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
if let reaction = self.reactions.last, case .reply = reaction {
|
if let reaction = self.reactions.last, case .reply = reaction {
|
||||||
maximizedIndex = self.reactions.count - 1
|
maximizedIndex = self.reactions.count - 1
|
||||||
}
|
}
|
||||||
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).contains(touchPoint) {
|
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: 0.0, dy: 10.0).contains(touchPoint) {
|
||||||
maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
|
maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
|
||||||
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
|
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
|
||||||
}
|
}
|
||||||
if maximizedIndex == -1 {
|
|
||||||
|
let interReactionSpacing: CGFloat
|
||||||
|
if maximizedIndex != -1 {
|
||||||
|
interReactionSpacing = minReactionSpacing
|
||||||
|
} else {
|
||||||
|
interReactionSpacing = reactionSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
if isInitial && self.reactionNodes.isEmpty {
|
||||||
|
let availableContentWidth = constrainedSize.width //max(100.0, initialAnchorX)
|
||||||
|
|
||||||
|
self.shadowBlur = floor(minimizedReactionSize * 0.26)
|
||||||
|
self.smallCircleSize = 14.0
|
||||||
|
|
||||||
|
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
||||||
|
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
|
||||||
|
for i in 0 ..< self.bubbleNodes.count {
|
||||||
|
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
||||||
|
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
|
||||||
|
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: maximizedReactionSize - 12.0, loadFirstFrame: true)
|
||||||
|
}
|
||||||
|
self.reactionNodes.forEach(self.addSubnode(_:))
|
||||||
|
}
|
||||||
|
|
||||||
|
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
/*if maximizedIndex == -1 {
|
||||||
backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize
|
backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize
|
||||||
backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize
|
backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize
|
||||||
}
|
}*/
|
||||||
|
|
||||||
self.isRightAligned = isRightAligned
|
self.isRightAligned = isRightAligned
|
||||||
|
|
||||||
@ -315,8 +317,8 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame)
|
backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame)
|
||||||
|
|
||||||
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing
|
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSideInset
|
||||||
if offsetFromStart > backgroundFrame.maxX - shadowBlur || offsetFromStart < backgroundFrame.minX {
|
if maximizedIndex != -1 {
|
||||||
self.hasSelectedNode = false
|
self.hasSelectedNode = false
|
||||||
} else {
|
} else {
|
||||||
self.hasSelectedNode = true
|
self.hasSelectedNode = true
|
||||||
@ -353,14 +355,14 @@ final class ReactionSelectionNode: ASDisplayNode {
|
|||||||
|
|
||||||
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
|
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
|
||||||
if isMaximized {
|
if isMaximized {
|
||||||
reactionFrame.origin.x -= 9.0
|
reactionFrame.origin.x -= 7.0
|
||||||
reactionFrame.size.width += 18.0
|
reactionFrame.size.width += 14.0
|
||||||
}
|
}
|
||||||
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 18.0), transition: transition, displayText: isMaximized)
|
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 14.0), transition: transition, displayText: isMaximized)
|
||||||
|
|
||||||
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
|
||||||
|
|
||||||
reactionX += reactionSize + reactionSpacing
|
reactionX += reactionSize + interReactionSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
|
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
|
||||||
|
@ -16,6 +16,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
public var availableReactions: (() -> [ReactionGestureItem])?
|
public var availableReactions: (() -> [ReactionGestureItem])?
|
||||||
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
|
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
|
||||||
public var getAnchorPoint: (() -> CGPoint?)?
|
public var getAnchorPoint: (() -> CGPoint?)?
|
||||||
|
public var shouldElevateAnchorPoint: (() -> Bool)?
|
||||||
public var began: (() -> Void)?
|
public var began: (() -> Void)?
|
||||||
public var updateOffset: ((CGFloat, Bool) -> Void)?
|
public var updateOffset: ((CGFloat, Bool) -> Void)?
|
||||||
public var completed: ((ReactionGestureItem?) -> Void)?
|
public var completed: ((ReactionGestureItem?) -> Void)?
|
||||||
@ -99,7 +100,9 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
self.f()
|
self.f()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let activationTimer = Timer(timeInterval: 0.1, target: TimerTarget { [weak self] in
|
let elevate = self.shouldElevateAnchorPoint?() ?? false
|
||||||
|
|
||||||
|
let activationTimer = Timer(timeInterval: elevate ? 0.1 : 0.01, target: TimerTarget { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -108,7 +111,9 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
|
|||||||
let location = strongSelf.currentLocation
|
let location = strongSelf.currentLocation
|
||||||
if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?(), let localAnchorPoint = strongSelf.getAnchorPoint?() {
|
if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?(), let localAnchorPoint = strongSelf.getAnchorPoint?() {
|
||||||
strongSelf.currentContainer = reactionContainer
|
strongSelf.currentContainer = reactionContainer
|
||||||
let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view)
|
//let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view)
|
||||||
|
let elevate = strongSelf.shouldElevateAnchorPoint?() ?? false
|
||||||
|
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil).offsetBy(dx: 0.0, dy: elevate ? -44.0 : 22.0)
|
||||||
let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil)
|
let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil)
|
||||||
strongSelf.currentAnchorPoint = reactionContainerLocation
|
strongSelf.currentAnchorPoint = reactionContainerLocation
|
||||||
strongSelf.currentAnchorStartPoint = location
|
strongSelf.currentAnchorStartPoint = location
|
||||||
|
@ -449,6 +449,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
}
|
}
|
||||||
return CGPoint(x: strongSelf.backgroundNode.frame.maxX, y: strongSelf.backgroundNode.frame.minY)
|
return CGPoint(x: strongSelf.backgroundNode.frame.maxX, y: strongSelf.backgroundNode.frame.minY)
|
||||||
}
|
}
|
||||||
|
reactionRecognizer.shouldElevateAnchorPoint = { [weak self] in
|
||||||
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return item.controllerInteraction.canSetupReply(item.message)
|
||||||
|
}
|
||||||
reactionRecognizer.began = { [weak self] in
|
reactionRecognizer.began = { [weak self] in
|
||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
return
|
return
|
||||||
@ -493,6 +499,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
|
|||||||
guard let strongSelf = self, let item = strongSelf.item else {
|
guard let strongSelf = self, let item = strongSelf.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !item.controllerInteraction.canSetupReply(item.message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if strongSelf.swipeToReplyFeedback == nil {
|
if strongSelf.swipeToReplyFeedback == nil {
|
||||||
strongSelf.swipeToReplyFeedback = HapticFeedback()
|
strongSelf.swipeToReplyFeedback = HapticFeedback()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user