mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Reaction improvements
This commit is contained in:
@@ -19,6 +19,11 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
case freeform
|
||||
}
|
||||
|
||||
enum DisplayAlignment {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private let container: ReactionButtonsLayoutContainer
|
||||
var reactionSelected: ((String) -> Void)?
|
||||
|
||||
@@ -33,22 +38,32 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
presentationData: ChatPresentationData,
|
||||
availableReactions: AvailableReactions?,
|
||||
reactions: ReactionsMessageAttribute,
|
||||
alignment: DisplayAlignment,
|
||||
constrainedWidth: CGFloat,
|
||||
type: DisplayType
|
||||
) -> (proposedWidth: CGFloat, continueLayout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> Void)) {
|
||||
let reactionColors: ReactionButtonComponent.Colors
|
||||
switch type {
|
||||
case .incoming, .freeform:
|
||||
case .incoming:
|
||||
reactionColors = ReactionButtonComponent.Colors(
|
||||
background: presentationData.theme.theme.chat.message.incoming.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||
foreground: presentationData.theme.theme.chat.message.incoming.accentTextColor.argb,
|
||||
stroke: presentationData.theme.theme.chat.message.incoming.accentTextColor.argb
|
||||
deselectedBackground: presentationData.theme.theme.chat.message.incoming.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||
selectedBackground: presentationData.theme.theme.chat.message.incoming.accentControlColor.withMultipliedAlpha(1.0).argb,
|
||||
deselectedForeground: presentationData.theme.theme.chat.message.incoming.accentTextColor.argb,
|
||||
selectedForeground: presentationData.theme.theme.chat.message.incoming.bubble.withWallpaper.fill.last!.argb
|
||||
)
|
||||
case .outgoing:
|
||||
reactionColors = ReactionButtonComponent.Colors(
|
||||
background: presentationData.theme.theme.chat.message.outgoing.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||
foreground: presentationData.theme.theme.chat.message.outgoing.accentTextColor.argb,
|
||||
stroke: presentationData.theme.theme.chat.message.outgoing.accentTextColor.argb
|
||||
deselectedBackground: presentationData.theme.theme.chat.message.outgoing.accentControlColor.withMultipliedAlpha(0.1).argb,
|
||||
selectedBackground: presentationData.theme.theme.chat.message.outgoing.accentControlColor.withMultipliedAlpha(1.0).argb,
|
||||
deselectedForeground: presentationData.theme.theme.chat.message.outgoing.accentTextColor.argb,
|
||||
selectedForeground: presentationData.theme.theme.chat.message.outgoing.bubble.withWallpaper.fill.last!.argb
|
||||
)
|
||||
case .freeform:
|
||||
reactionColors = ReactionButtonComponent.Colors(
|
||||
deselectedBackground: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper).argb,
|
||||
selectedBackground: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper).argb,
|
||||
deselectedForeground: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.incoming.actionButtonsTextColor, wallpaper: presentationData.theme.wallpaper).argb,
|
||||
selectedForeground: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.incoming.actionButtonsTextColor, wallpaper: presentationData.theme.wallpaper).argb
|
||||
)
|
||||
}
|
||||
|
||||
@@ -115,29 +130,53 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
let bottomInset: CGFloat = 2.0
|
||||
|
||||
return (proposedWidth: reactionButtonsSize.width, continueLayout: { [weak self] boundingWidth in
|
||||
return (size: CGSize(width: boundingWidth, height: topInset + reactionButtonsSize.height + bottomInset), apply: { animation in
|
||||
let size = CGSize(width: boundingWidth, height: topInset + reactionButtonsSize.height + bottomInset)
|
||||
return (size: size, apply: { animation in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var reactionButtonPosition = CGPoint(x: 0.0, y: topInset)
|
||||
var reactionButtonPosition: CGPoint
|
||||
switch alignment {
|
||||
case .left:
|
||||
reactionButtonPosition = CGPoint(x: -1.0, y: topInset)
|
||||
case .right:
|
||||
reactionButtonPosition = CGPoint(x: size.width + 1.0, y: topInset)
|
||||
}
|
||||
for item in reactionButtons.items {
|
||||
if reactionButtonPosition.x + item.size.width > boundingWidth {
|
||||
reactionButtonPosition.x = 0.0
|
||||
reactionButtonPosition.y += item.size.height + 6.0
|
||||
switch alignment {
|
||||
case .left:
|
||||
if reactionButtonPosition.x + item.size.width > boundingWidth {
|
||||
reactionButtonPosition.x = 0.0
|
||||
reactionButtonPosition.y += item.size.height + 6.0
|
||||
}
|
||||
case .right:
|
||||
if reactionButtonPosition.x - item.size.width < -1.0 {
|
||||
reactionButtonPosition.x = size.width + 1.0
|
||||
reactionButtonPosition.y += item.size.height + 6.0
|
||||
}
|
||||
}
|
||||
|
||||
let itemFrame: CGRect
|
||||
switch alignment {
|
||||
case .left:
|
||||
itemFrame = CGRect(origin: reactionButtonPosition, size: item.size)
|
||||
reactionButtonPosition.x += item.size.width + 6.0
|
||||
case .right:
|
||||
itemFrame = CGRect(origin: CGPoint(x: reactionButtonPosition.x - item.size.width, y: reactionButtonPosition.y), size: item.size)
|
||||
reactionButtonPosition.x -= item.size.width + 6.0
|
||||
}
|
||||
|
||||
if item.view.superview == nil {
|
||||
strongSelf.view.addSubview(item.view)
|
||||
item.view.frame = CGRect(origin: reactionButtonPosition, size: item.size)
|
||||
if animation.isAnimated {
|
||||
item.view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
item.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
item.view.frame = itemFrame
|
||||
} else {
|
||||
animation.animator.updateFrame(layer: item.view.layer, frame: CGRect(origin: reactionButtonPosition, size: item.size), completion: nil)
|
||||
animation.animator.updateFrame(layer: item.view.layer, frame: itemFrame, completion: nil)
|
||||
}
|
||||
reactionButtonPosition.x += item.size.width + 6.0
|
||||
}
|
||||
|
||||
for view in reactionButtons.removedViews {
|
||||
@@ -163,9 +202,15 @@ final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
func animateIn(animation: ListViewItemUpdateAnimation) {
|
||||
for (_, button) in self.container.buttons {
|
||||
button.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
animation.animator.animateScale(layer: button.layer, from: 0.01, to: 1.0, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(animation: ListViewItemUpdateAnimation) {
|
||||
for (_, button) in self.container.buttons {
|
||||
animation.animator.updateScale(layer: button.layer, scale: 0.01, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +229,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.updateMessageReaction(item.message, value)
|
||||
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +247,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
let topOffset: CGFloat
|
||||
if case let .linear(top, _) = preparePosition, case .Neighbour(_, .media, _) = top {
|
||||
//displaySeparator = false
|
||||
topOffset = 2.0
|
||||
topOffset = 4.0
|
||||
} else {
|
||||
//displaySeparator = true
|
||||
topOffset = 0.0
|
||||
@@ -213,7 +258,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
let buttonsUpdate = buttonsNode.prepareUpdate(
|
||||
context: item.context,
|
||||
presentationData: item.presentationData,
|
||||
availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, constrainedWidth: constrainedSize.width, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing)
|
||||
availableReactions: item.associatedData.availableReactions, reactions: reactionsAttribute, alignment: .left, constrainedWidth: constrainedSize.width, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing)
|
||||
|
||||
return (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + buttonsUpdate.proposedWidth, { boundingWidth in
|
||||
var boundingSize = CGSize()
|
||||
@@ -250,7 +295,7 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.buttonsNode.animateOut()
|
||||
self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false)))
|
||||
}
|
||||
|
||||
override func animateInsertionIntoBubble(_ duration: Double) {
|
||||
@@ -263,18 +308,18 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.buttonsNode.animateOut()
|
||||
self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false)))
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil) != nil {
|
||||
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil), result !== self.buttonsNode.view {
|
||||
return .ignore
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event) {
|
||||
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
@@ -284,3 +329,96 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode
|
||||
return self.buttonsNode.reactionTargetView(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
final class Arguments {
|
||||
let context: AccountContext
|
||||
let presentationData: ChatPresentationData
|
||||
let availableReactions: AvailableReactions?
|
||||
let reactions: ReactionsMessageAttribute
|
||||
let isIncoming: Bool
|
||||
let constrainedWidth: CGFloat
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
presentationData: ChatPresentationData,
|
||||
availableReactions: AvailableReactions?,
|
||||
reactions: ReactionsMessageAttribute,
|
||||
isIncoming: Bool,
|
||||
constrainedWidth: CGFloat
|
||||
) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.availableReactions = availableReactions
|
||||
self.reactions = reactions
|
||||
self.isIncoming = isIncoming
|
||||
self.constrainedWidth = constrainedWidth
|
||||
}
|
||||
}
|
||||
|
||||
private let buttonsNode: MessageReactionButtonsNode
|
||||
|
||||
var reactionSelected: ((String) -> Void)?
|
||||
|
||||
override init() {
|
||||
self.buttonsNode = MessageReactionButtonsNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.buttonsNode)
|
||||
self.buttonsNode.reactionSelected = { [weak self] value in
|
||||
self?.reactionSelected?(value)
|
||||
}
|
||||
}
|
||||
|
||||
class func asyncLayout(_ maybeNode: ChatMessageReactionButtonsNode?) -> (_ arguments: ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)) {
|
||||
return { arguments in
|
||||
let node = maybeNode ?? ChatMessageReactionButtonsNode()
|
||||
|
||||
let buttonsUpdate = node.buttonsNode.prepareUpdate(
|
||||
context: arguments.context,
|
||||
presentationData: arguments.presentationData,
|
||||
availableReactions: arguments.availableReactions,
|
||||
reactions: arguments.reactions,
|
||||
alignment: arguments.isIncoming ? .left : .right,
|
||||
constrainedWidth: arguments.constrainedWidth,
|
||||
type: .freeform
|
||||
)
|
||||
|
||||
return (buttonsUpdate.proposedWidth, { constrainedWidth in
|
||||
let buttonsResult = buttonsUpdate.continueLayout(constrainedWidth)
|
||||
|
||||
return (CGSize(width: constrainedWidth, height: buttonsResult.size.height), { animation in
|
||||
node.buttonsNode.frame = CGRect(origin: CGPoint(), size: buttonsResult.size)
|
||||
buttonsResult.apply(animation)
|
||||
|
||||
return node
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn(animation: ListViewItemUpdateAnimation) {
|
||||
self.buttonsNode.animateIn(animation: animation)
|
||||
self.buttonsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
func animateOut(animation: ListViewItemUpdateAnimation, completion: @escaping () -> Void) {
|
||||
self.buttonsNode.animateOut(animation: animation)
|
||||
animation.animator.updateAlpha(layer: self.buttonsNode.layer, alpha: 0.0, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
animation.animator.updateFrame(layer: self.buttonsNode.layer, frame: self.buttonsNode.layer.frame.offsetBy(dx: 0.0, dy: -self.buttonsNode.layer.bounds.height / 2.0), completion: nil)
|
||||
}
|
||||
|
||||
func reactionTargetView(value: String) -> UIView? {
|
||||
return self.buttonsNode.reactionTargetView(value: value)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view {
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user