diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index 8dcdf78175..ee86e2921e 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -114,6 +114,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { func update( theme: PresentationTheme, + forceDark: Bool, size: CGSize, cloudSourcePoint: CGFloat, isLeftAligned: Bool, @@ -128,7 +129,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { if self.theme !== theme { self.theme = theme - if theme.overallDarkAppearance { + if theme.overallDarkAppearance && !forceDark { if let vibrancyEffectView = self.vibrancyEffectView { self.vibrancyEffectView = nil vibrancyEffectView.removeFromSuperview() @@ -136,7 +137,11 @@ final class ReactionContextBackgroundNode: ASDisplayNode { } else { if self.vibrancyEffectView == nil { let style: UIBlurEffect.Style - style = .extraLight + if forceDark { + style = .dark + } else { + style = .extraLight + } let blurEffect = UIBlurEffect(style: style) let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect) let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 3ffced4a04..f4ad980981 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -120,6 +120,42 @@ private final class ExpandItemView: UIView { } } +private final class TitleLabelView: UIView { + let contentView = ComponentView() + let tintContentView = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, text: String, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + let contentSize = self.contentView.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: UIColor(white: 1.0, alpha: 0.2))), + environment: {}, + containerSize: size + ) + let _ = self.tintContentView.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: .white)), + environment: {}, + containerSize: size + ) + + if let contentView = self.contentView.view { + if contentView.superview == nil { + contentView.layer.rasterizationScale = UIScreenScale + self.addSubview(contentView) + } + transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentSize.width) / 2.0), y: 6.0), size: contentSize)) + } + } +} + public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private struct ItemLayout { var itemSize: CGFloat @@ -208,6 +244,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private var visibleItemMaskNodes: [Int: ASDisplayNode] = [:] private let expandItemView: ExpandItemView? + private let title: String? + private var titleLabelView: TitleLabelView? + private var reactionSelectionComponentHost: ComponentView? private var longPressRecognizer: UILongPressGestureRecognizer? @@ -234,13 +273,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { public var displayTail: Bool = true public var forceTailToRight: Bool = false + public var forceDark: Bool = false private var didAnimateIn: Bool = false public private(set) var isAnimatingOut: Bool = false public private(set) var isAnimatingOutToReaction: Bool = false + private var contentTopInset: CGFloat = 0.0 public var contentHeight: CGFloat { - return self.currentContentHeight + return self.contentTopInset + self.currentContentHeight } private var currentContentHeight: CGFloat = 46.0 @@ -323,7 +364,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } - public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { + public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], selectedItems: Set, title: String? = nil, getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateOverlayWantsToBeBelowKeyboard: @escaping (ContainedViewLayoutTransition) -> Void) { self.context = context self.presentationData = presentationData self.items = items @@ -415,6 +456,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.expandItemView = nil } + self.title = title + if self.title != nil { + let titleLabelView = TitleLabelView(frame: CGRect()) + self.titleLabelView = titleLabelView + self.contentContainer.view.addSubview(titleLabelView) + self.contentTopInset = 24.0 + } + super.init() self.addSubnode(self.backgroundNode) @@ -635,7 +684,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private func calculateBackgroundFrame(containerSize: CGSize, insets: UIEdgeInsets, anchorRect: CGRect, contentSize: CGSize, centerAligned: Bool) -> (backgroundFrame: CGRect, visualBackgroundFrame: CGRect, isLeftAligned: Bool, cloudSourcePoint: CGFloat) { var contentSize = contentSize contentSize.width = max(46.0, contentSize.width) - contentSize.height = self.currentContentHeight + contentSize.height = self.contentTopInset + self.currentContentHeight let sideInset: CGFloat if self.forceTailToRight { @@ -944,6 +993,22 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } + if let title = self.title, let titleLabelView = self.titleLabelView { + let baseTitleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.scrollNode.view.bounds.width, height: 20.0)) + + transition.updateFrame(view: titleLabelView, frame: baseTitleFrame) + titleLabelView.update(size: baseTitleFrame.size, text: title, theme: self.presentationData.theme, transition: transition) + transition.updateAlpha(layer: titleLabelView.layer, alpha: self.isExpanded ? 0.0 : 1.0) + if let titleView = titleLabelView.contentView.view, let tintContentView = titleLabelView.tintContentView.view { + if tintContentView.superview == nil { + tintContentView.layer.rasterizationScale = UIScreenScale + self.contentTintContainer.view.addSubview(tintContentView) + } + transition.updateFrame(view: tintContentView, frame: titleView.frame.offsetBy(dx: baseTitleFrame.minX, dy: baseTitleFrame.minY)) + transition.updateAlpha(layer: tintContentView.layer, alpha: self.isExpanded ? 0.0 : 1.0) + } + } + if let expandItemView = self.expandItemView { let expandItemSize: CGFloat let expandTintOffset: CGFloat @@ -954,7 +1019,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { expandItemSize = 30.0 expandTintOffset = 0.0 } - let baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0) + (self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance)) + let baseNextFrame = CGRect(origin: CGPoint(x: self.scrollNode.view.bounds.width - expandItemSize - 9.0, y: self.contentTopInset + containerHeight - contentHeight + floor((contentHeight - expandItemSize) / 2.0) + (self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0)), size: CGSize(width: expandItemSize, height: expandItemSize + self.extensionDistance)) transition.updateFrame(view: expandItemView, frame: baseNextFrame) transition.updateFrame(view: expandItemView.tintView, frame: baseNextFrame.offsetBy(dx: 0.0, dy: expandTintOffset)) @@ -964,10 +1029,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if let currentMaskFrame = currentMaskFrame { let transition = maskTransition ?? transition - transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: -1000.0 + currentMaskFrame.minX, y: 0.0, width: 1000.0, height: self.currentContentHeight + self.extensionDistance)) + transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: -1000.0 + currentMaskFrame.minX, y: 0.0, width: 1000.0, height: self.contentTopInset + self.currentContentHeight + self.extensionDistance)) transition.updateFrame(node: self.rightBackgroundMaskNode, frame: CGRect(x: currentMaskFrame.maxX, y: 0.0, width: 1000.0, height: self.currentContentHeight + self.extensionDistance)) } else { - transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: 0.0, y: 0.0, width: 1000.0, height: self.currentContentHeight + self.extensionDistance)) + transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: 0.0, y: 0.0, width: 1000.0, height: self.contentTopInset + self.currentContentHeight + self.extensionDistance)) self.rightBackgroundMaskNode.frame = CGRect(origin: .zero, size: .zero) } @@ -1055,7 +1120,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { visibleItemCount: itemCount ) - var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : 0.0), size: actualBackgroundFrame.size) + var scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.isExpanded ? (46.0 + 54.0 - 4.0) : self.contentTopInset), size: actualBackgroundFrame.size) scrollFrame.origin.y += floorToScreenPixels(self.extensionDistance / 2.0) transition.updateFrame(node: self.contentContainer, frame: visualBackgroundFrame, beginWithCurrentState: true) @@ -1195,6 +1260,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: self.backgroundNode, frame: visualBackgroundFrame, beginWithCurrentState: true) self.backgroundNode.update( theme: self.presentationData.theme, + forceDark: self.forceDark, size: visualBackgroundFrame.size, cloudSourcePoint: cloudSourcePoint - visualBackgroundFrame.minX, isLeftAligned: isLeftAligned, @@ -2210,6 +2276,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let point = recognizer.location(in: self.view) if let expandItemView = self.expandItemView, expandItemView.bounds.contains(self.view.convert(point, to: self.expandItemView)) { + self.animateFromExtensionDistance = self.contentTopInset * 2.0 + self.extensionDistance + self.contentTopInset = 0.0 self.currentContentHeight = 300.0 self.isExpanded = true self.longPressRecognizer?.isEnabled = false @@ -2247,9 +2315,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.longPressRecognizer?.isEnabled = false - self.animateFromExtensionDistance = self.extensionDistance + self.animateFromExtensionDistance = self.contentTopInset * 2.0 + self.extensionDistance self.extensionDistance = 0.0 self.visibleExtensionDistance = 0.0 + self.contentTopInset = 0.0 self.currentContentHeight = 300.0 self.isExpanded = true self.isExpandedUpdated(.animated(duration: 0.4, curve: .spring)) diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index 130c93133c..cf7766a76a 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -7,7 +7,7 @@ public final class AnimatedTextComponent: Component { public struct Item: Equatable { public enum Content: Equatable { case text(String) - case number(Int) + case number(Int, minDigits: Int) } public var id: AnyHashable @@ -86,11 +86,16 @@ public final class AnimatedTextComponent: Component { } else { itemText = text.map(String.init) } - case let .number(value): + case let .number(value, minDigits): + var valueText: String = "\(value)" + while valueText.count < minDigits { + valueText.insert("0", at: valueText.startIndex) + } + if item.isUnbreakable { - itemText = ["\(value)"] + itemText = [valueText] } else { - itemText = "\(value)".map(String.init) + itemText = valueText.map(String.init) } } var index = 0 diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index edd3d8c58a..c3787effe0 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -184,7 +184,7 @@ public final class ButtonTextContentComponent: Component { font: Font.semibold(15.0), color: component.badgeForeground, items: [ - AnimatedTextComponent.Item(id: AnyHashable(0), content: .number(component.badge)) + AnimatedTextComponent.Item(id: AnyHashable(0), content: .number(component.badge, minDigits: 0)) ] )) )), diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index a030a7852b..e71c784aa1 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1100,7 +1100,7 @@ final class MediaEditorScreenComponent: Component { theme: environment.theme, strings: environment.strings, style: .editor, - placeholder: environment.strings.Story_Editor_InputPlaceholderAddCaption, + placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption), maxLength: Int(component.context.userLimits.maxStoryCaptionLength), queryTypes: [.mention], alwaysDarkWhenHasText: false, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift index 5324035b2b..e26a52b0ae 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StoryPreviewComponent.swift @@ -250,7 +250,7 @@ final class StoryPreviewComponent: Component { theme: presentationData.theme, strings: presentationData.strings, style: .story, - placeholder: presentationData.strings.Story_InputPlaceholderReplyPrivately, + placeholder: .plain(presentationData.strings.Story_InputPlaceholderReplyPrivately), maxLength: nil, queryTypes: [], alwaysDarkWhenHasText: false, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index fd6453217f..0c8ebebe9e 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -33,6 +33,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/StickerPeekUI", "//submodules/Components/ReactionButtonListComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 2c36d7c134..cc5d1a2ad2 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -15,6 +15,7 @@ import ChatContextQuery import TextFormat import EmojiSuggestionsComponent import AudioToolbox +import AnimatedTextComponent public final class MessageInputPanelComponent: Component { public struct ContextQueryTypes: OptionSet { @@ -56,6 +57,26 @@ public final class MessageInputPanelComponent: Component { } } + public enum Placeholder: Equatable { + public enum CounterItemContent: Equatable { + case text(String) + case number(Int, minDigits: Int) + } + + public struct CounterItem: Equatable { + public var id: Int + public var content: CounterItemContent + + public init(id: Int, content: CounterItemContent) { + self.id = id + self.content = content + } + } + + case plain(String) + case counter([CounterItem]) + } + public final class ExternalState { public fileprivate(set) var isEditing: Bool = false public fileprivate(set) var hasText: Bool = false @@ -75,7 +96,7 @@ public final class MessageInputPanelComponent: Component { public let theme: PresentationTheme public let strings: PresentationStrings public let style: Style - public let placeholder: String + public let placeholder: Placeholder public let maxLength: Int? public let queryTypes: ContextQueryTypes public let alwaysDarkWhenHasText: Bool @@ -124,7 +145,7 @@ public final class MessageInputPanelComponent: Component { theme: PresentationTheme, strings: PresentationStrings, style: Style, - placeholder: String, + placeholder: Placeholder, maxLength: Int?, queryTypes: ContextQueryTypes, alwaysDarkWhenHasText: Bool, @@ -560,6 +581,8 @@ public final class MessageInputPanelComponent: Component { } func update(component: MessageInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let previousPlaceholder = self.component?.placeholder + var insets = UIEdgeInsets(top: 14.0, left: 9.0, bottom: 6.0, right: 41.0) if let _ = component.attachmentAction { @@ -642,23 +665,39 @@ public final class MessageInputPanelComponent: Component { ) let isEditing = self.textFieldExternalState.isEditing || component.forceIsEditing + var placeholderItems: [AnimatedTextComponent.Item] = [] + switch component.placeholder { + case let .plain(string): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(0 as Int), content: .text(string))) + case let .counter(items): + for item in items { + switch item.content { + case let .text(string): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .text(string))) + case let .number(value, minDigits): + placeholderItems.append(AnimatedTextComponent.Item(id: AnyHashable(item.id), content: .number(value, minDigits: minDigits))) + } + } + } + + let placeholderTransition: Transition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? Transition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate let placeholderSize = self.placeholder.update( - transition: .immediate, - component: AnyComponent(Text( - text: component.placeholder, + transition: placeholderTransition, + component: AnyComponent(AnimatedTextComponent( font: Font.regular(17.0), - color: UIColor(rgb: 0xffffff, alpha: 0.3) + color: UIColor(rgb: 0xffffff, alpha: 0.3), + items: placeholderItems )), environment: {}, containerSize: availableTextFieldSize ) let _ = self.vibrancyPlaceholder.update( - transition: .immediate, - component: AnyComponent(Text( - text: component.placeholder, + transition: placeholderTransition, + component: AnyComponent(AnimatedTextComponent( font: Font.regular(17.0), - color: .white + color: .white, + items: placeholderItems )), environment: {}, containerSize: availableTextFieldSize diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 547f2f5083..fb562bb5d3 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2379,7 +2379,7 @@ public final class StoryItemSetContainerComponent: Component { self.bottomContentGradientLayer.colors = colors self.bottomContentGradientLayer.type = .axial - self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.6) + self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3) } let wasPanning = self.component?.isPanning ?? false @@ -2438,11 +2438,23 @@ public final class StoryItemSetContainerComponent: Component { disabledPlaceholder = component.strings.Story_FooterReplyUnavailable } - let inputPlaceholder: String + let inputPlaceholder: MessageInputPanelComponent.Placeholder if let stealthModeTimeout = component.stealthModeTimeout { - inputPlaceholder = component.strings.Story_StealthModeActivePlaceholder("\(stringForDuration(stealthModeTimeout))").string + //TODO:localize + + let minutes = Int(stealthModeTimeout / 60) + let seconds = Int(stealthModeTimeout % 60) + + inputPlaceholder = .counter([ + MessageInputPanelComponent.Placeholder.CounterItem(id: 0, content: .text("Stealth Mode active – ")), + MessageInputPanelComponent.Placeholder.CounterItem(id: 1, content: .number(minutes, minDigits: 2)), + MessageInputPanelComponent.Placeholder.CounterItem(id: 2, content: .text(":")), + MessageInputPanelComponent.Placeholder.CounterItem(id: 3, content: .number(seconds, minDigits: 2)), + ]) + + //inputPlaceholder = component.strings.Story_StealthModeActivePlaceholder("\(stringForDuration(stealthModeTimeout))").string } else { - inputPlaceholder = component.strings.Story_InputPlaceholderReplyPrivately + inputPlaceholder = .plain(component.strings.Story_InputPlaceholderReplyPrivately) } var keyboardHeight = component.deviceMetrics.standardInputHeight(inLandscape: false) @@ -3861,6 +3873,7 @@ public final class StoryItemSetContainerComponent: Component { if let current = self.reactionContextNode { reactionContextNode = current } else { + //TODO:localize reactionContextNodeTransition = .immediate reactionContextNode = ReactionContextNode( context: component.context, @@ -3868,6 +3881,7 @@ public final class StoryItemSetContainerComponent: Component { presentationData: component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme), items: reactionItems.map(ReactionContextItem.reaction), selectedItems: component.slice.item.storyItem.myReaction.flatMap { Set([$0]) } ?? Set(), + title: self.displayLikeReactions ? nil : "Send reaction as a private message", getEmojiContent: { [weak self] animationCache, animationRenderer in guard let self, let component = self.component else { preconditionFailure() @@ -3915,6 +3929,7 @@ public final class StoryItemSetContainerComponent: Component { ) reactionContextNode.displayTail = self.displayLikeReactions reactionContextNode.forceTailToRight = self.displayLikeReactions + reactionContextNode.forceDark = true self.reactionContextNode = reactionContextNode reactionContextNode.reactionSelected = { [weak self] updateReaction, _ in