Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mike Renoir 2023-10-24 19:39:15 +04:00
commit 40dbadccd4
31 changed files with 514 additions and 252 deletions

View File

@ -177,7 +177,9 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
private let messageClipNode: ASDisplayNode
private let messageBackgroundNode: ASImageNode
private let fromMessageTextScrollView: UIScrollView
private let fromMessageTextNode: ChatInputTextNode
private let toMessageTextScrollView: UIScrollView
private let toMessageTextNode: ChatInputTextNode
private let scrollNode: ASScrollNode
@ -224,11 +226,17 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.messageClipNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.messageBackgroundNode = ASImageNode()
self.messageBackgroundNode.isUserInteractionEnabled = true
self.fromMessageTextNode = ChatInputTextNode()
self.fromMessageTextNode = ChatInputTextNode(disableTiling: true)
self.fromMessageTextNode.textView.isScrollEnabled = false
self.fromMessageTextNode.isUserInteractionEnabled = false
self.toMessageTextNode = ChatInputTextNode()
self.toMessageTextNode.alpha = 0.0
self.fromMessageTextScrollView = UIScrollView()
self.fromMessageTextScrollView.isUserInteractionEnabled = false
self.toMessageTextNode = ChatInputTextNode(disableTiling: true)
self.toMessageTextNode.textView.isScrollEnabled = false
self.toMessageTextNode.isUserInteractionEnabled = false
self.toMessageTextScrollView = UIScrollView()
self.toMessageTextScrollView.alpha = 0.0
self.toMessageTextScrollView.isUserInteractionEnabled = false
self.scrollNode = ASScrollNode()
self.scrollNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
@ -262,6 +270,18 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
if let attributedText = textInputView.attributedText, !attributedText.string.isEmpty {
self.animateInputField = true
if let textInputView = self.textInputView as? ChatInputTextView {
self.fromMessageTextNode.textView.theme = textInputView.theme
let mainColor = presentationData.theme.chat.message.outgoing.accentControlColor
self.toMessageTextNode.textView.theme = ChatInputTextView.Theme(
quote: ChatInputTextView.Theme.Quote(
background: mainColor.withMultipliedAlpha(0.1),
foreground: mainColor,
isDashed: textInputView.theme?.quote.isDashed == true
)
)
}
self.fromMessageTextNode.attributedText = attributedText
if let toAttributedText = self.fromMessageTextNode.attributedText?.mutableCopy() as? NSMutableAttributedString {
@ -292,8 +312,10 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.addSubnode(self.sendButtonNode)
self.scrollNode.addSubnode(self.messageClipNode)
self.messageClipNode.addSubnode(self.messageBackgroundNode)
self.messageClipNode.addSubnode(self.fromMessageTextNode)
self.messageClipNode.addSubnode(self.toMessageTextNode)
self.messageClipNode.view.addSubview(self.fromMessageTextScrollView)
self.fromMessageTextScrollView.addSubview(self.fromMessageTextNode.view)
self.messageClipNode.view.addSubview(self.toMessageTextScrollView)
self.toMessageTextScrollView.addSubview(self.toMessageTextNode.view)
self.contentNodes.forEach(self.contentContainerNode.addSubnode)
@ -457,12 +479,12 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
self.sourceSendButton.isHidden = true
if self.animateInputField {
self.fromMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.toMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
self.fromMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.toMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false)
} else {
self.messageBackgroundNode.isHidden = true
self.fromMessageTextNode.isHidden = true
self.toMessageTextNode.isHidden = true
self.fromMessageTextScrollView.isHidden = true
self.toMessageTextScrollView.isHidden = true
}
let duration = 0.4
@ -502,8 +524,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
textXOffset = initialWidth - self.messageClipNode.bounds.width
}
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.toMessageTextNode.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), to: CGPoint(), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
let contentOffset = CGPoint(x: self.sendButtonFrame.midX - self.contentContainerNode.frame.midX, y: self.sendButtonFrame.midY - self.contentContainerNode.frame.midY)
@ -562,8 +584,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
if self.animateInputField {
if cancel {
self.fromMessageTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
self.toMessageTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
self.fromMessageTextScrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
self.toMessageTextScrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false)
self.messageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, delay: 0.15, removeOnCompletion: false, completion: { _ in
completedAlpha = true
intermediateCompletion()
@ -596,7 +618,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
initialWidth = ceil(layout.size.width - self.textFieldFrame.origin.x - self.sendButtonFrame.width - layout.safeInsets.left - layout.safeInsets.right + 21.0)
}
let toFrame = CGRect(origin: CGPoint(), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 1.0))
let toFrame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: initialWidth, height: self.textFieldFrame.height + 2.0))
let delta = (toFrame.height - self.messageClipNode.bounds.height) / 2.0
if cancel && self.animateInputField {
@ -610,7 +632,7 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
clipDelta -= self.contentContainerNode.frame.height + 16.0
}
self.messageClipNode.layer.animateBounds(from: self.messageClipNode.bounds, to: toFrame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.messageClipNode.layer.animateBounds(from: self.messageClipNode.bounds, to: toFrame.offsetBy(dx: 0.0, dy: 1.0), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completedBubble = true
intermediateCompletion()
})
@ -624,8 +646,8 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
if self.textInputView.numberOfLines == 1 && self.textInputView.isRTL {
textXOffset = initialWidth - self.messageClipNode.bounds.width
}
self.fromMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
self.toMessageTextNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
self.fromMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
self.toMessageTextScrollView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: textXOffset, y: delta * 2.0 + textYOffset), duration: duration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
} else {
completedBubble = true
}
@ -702,6 +724,9 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
messageFrame.size.width += 32.0
messageFrame.origin.x -= 13.0
messageFrame.origin.y = layout.size.height - messageFrame.origin.y - messageFrame.size.height - 1.0
let messageHeightAddition: CGFloat = max(0.0, 35.0 - messageFrame.size.height)
if inputHeight.isZero || layout.isNonExclusive {
messageFrame.origin.y += menuHeightWithInset
}
@ -732,26 +757,37 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
let clipFrame = messageFrame
transition.updateFrame(node: self.messageClipNode, frame: clipFrame)
let backgroundFrame = CGRect(origin: CGPoint(), size: messageFrame.size)
var backgroundFrame = CGRect(origin: CGPoint(), size: messageFrame.size)
backgroundFrame.origin.y -= messageHeightAddition * 0.5
backgroundFrame.size.height += messageHeightAddition
transition.updateFrame(node: self.messageBackgroundNode, frame: backgroundFrame)
var textFrame = self.textFieldFrame
textFrame.origin = CGPoint(x: 13.0, y: 6.0 - UIScreenPixel)
textFrame.size.height = self.textInputView.contentSize.height
if let textInputView = self.textInputView as? ChatInputTextView {
textFrame.origin.y -= 5.0
self.fromMessageTextNode.textView.defaultTextContainerInset = textInputView.defaultTextContainerInset
self.toMessageTextNode.textView.defaultTextContainerInset = textInputView.defaultTextContainerInset
}
/*if let textInputView = self.textInputView as? ChatInputTextView {
textFrame.size.width -= textInputView.defaultTextContainerInset.right
} else {
textFrame.size.width -= self.textInputView.textContainerInset.right
}
}*/
if self.textInputView.isRTL {
textFrame.origin.x -= messageOriginDelta
}
self.fromMessageTextNode.frame = textFrame
self.fromMessageTextScrollView.frame = textFrame
self.fromMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
self.fromMessageTextNode.updateLayout(size: textFrame.size)
self.toMessageTextNode.frame = textFrame
self.toMessageTextScrollView.frame = textFrame
self.toMessageTextNode.frame = CGRect(origin: CGPoint(), size: textFrame.size)
self.toMessageTextNode.updateLayout(size: textFrame.size)
}

View File

@ -458,7 +458,7 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
self.highlightBackgroundNode.clipsToBounds = true
self.highlightBackgroundNode.cornerRadius = 14.0
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: presentationData.theme.contextMenu.primaryColor.withAlphaComponent(0.15), knob: presentationData.theme.contextMenu.primaryColor, knobDiameter: 8.0), strings: presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { _ in
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: presentationData.theme.contextMenu.primaryColor.withAlphaComponent(0.15), knob: presentationData.theme.contextMenu.primaryColor, knobDiameter: 8.0, isDark: presentationData.theme.overallDarkAppearance), strings: presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { _ in
}, present: { _, _ in
}, rootNode: { [weak self] in
return self

View File

@ -2070,7 +2070,21 @@ public final class ControlledTransition {
if layer.bounds == bounds {
return
}
let fromValue = layer.presentation()?.bounds ?? layer.bounds
let fromValue: CGRect
if let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in
guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else {
return false
}
if animation.keyPath == "bounds" {
return true
} else {
return false
}
}) {
fromValue = layer.presentation()?.bounds ?? layer.bounds
} else {
fromValue = layer.bounds
}
layer.bounds = bounds
self.add(animation: ControlledTransitionProperty(
layer: layer,

View File

@ -17,22 +17,32 @@ private final class ContextMenuContainerMaskView: UIView {
public final class ContextMenuContainerNode: ASDisplayNode {
private var cachedMaskParams: CachedMaskParams?
private let maskView = ContextMenuContainerMaskView()
public let containerNode: ASDisplayNode
public var relativeArrowPosition: (CGFloat, Bool)?
private var effectView: UIVisualEffectView?
public init(blurred: Bool) {
public init(isBlurred: Bool, isDark: Bool) {
self.containerNode = ASDisplayNode()
super.init()
if blurred {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.view.addSubview(effectView)
if isBlurred {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: isDark ? .dark : .light))
self.containerNode.view.addSubview(effectView)
self.effectView = effectView
} else {
self.backgroundColor = UIColor(rgb: 0x8c8e8e)
self.containerNode.backgroundColor = isDark ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0xF8F8F6)
}
self.view.mask = self.maskView
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowRadius = 10.0
self.layer.shadowOpacity = 0.2
self.layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
self.containerNode.view.mask = self.maskView
self.addSubnode(self.containerNode)
}
override public func didLoad() {
@ -48,6 +58,8 @@ public final class ContextMenuContainerNode: ASDisplayNode {
}
public func updateLayout(transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: self.bounds)
self.effectView?.frame = self.bounds
let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true)
@ -87,6 +99,13 @@ public final class ContextMenuContainerNode: ASDisplayNode {
}
layer.path = path.cgPath
}
if case let .animated(duration, curve) = transition, let previousPath = self.layer.shadowPath {
self.layer.shadowPath = path.cgPath
self.layer.animate(from: previousPath, to: path.cgPath, keyPath: "shadowPath", timingFunction: curve.timingFunction, duration: duration)
} else {
self.layer.shadowPath = path.cgPath
}
}
}
}

View File

@ -24,13 +24,15 @@ public struct ContextMenuControllerArguments {
public var hasHapticFeedback: Bool
public var blurred: Bool
public var skipCoordnateConversion: Bool
public var isDark: Bool
public init(actions: [ContextMenuAction], catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool, skipCoordnateConversion: Bool) {
public init(actions: [ContextMenuAction], catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool, skipCoordnateConversion: Bool, isDark: Bool) {
self.actions = actions
self.catchTapsOutside = catchTapsOutside
self.hasHapticFeedback = hasHapticFeedback
self.blurred = blurred
self.skipCoordnateConversion = skipCoordnateConversion
self.isDark = isDark
}
}
@ -40,7 +42,7 @@ public func setContextMenuControllerProvider(_ f: @escaping (ContextMenuControll
contextMenuControllerProvider = f
}
public func makeContextMenuController(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false, skipCoordnateConversion: Bool = false) -> ContextMenuController {
public func makeContextMenuController(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false, blurred: Bool = false, isDark: Bool = true, skipCoordnateConversion: Bool = false) -> ContextMenuController {
guard let contextMenuControllerProvider = contextMenuControllerProvider else {
preconditionFailure()
}
@ -49,6 +51,7 @@ public func makeContextMenuController(actions: [ContextMenuAction], catchTapsOut
catchTapsOutside: catchTapsOutside,
hasHapticFeedback: hasHapticFeedback,
blurred: blurred,
skipCoordnateConversion: skipCoordnateConversion
skipCoordnateConversion: skipCoordnateConversion,
isDark: isDark
))
}

View File

@ -33,8 +33,8 @@ final class TooltipControllerNode: ASDisplayNode {
self.dismissByTapOutside = dismissByTapOutside
self.dismissByTapOutsideSource = dismissByTapOutsideSource
self.containerNode = ContextMenuContainerNode(blurred: false)
self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.containerNode = ContextMenuContainerNode(isBlurred: false, isDark: true)
self.containerNode.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.imageNode = ASImageNode()
self.imageNode.image = content.image
@ -57,9 +57,9 @@ final class TooltipControllerNode: ASDisplayNode {
super.init()
self.containerNode.addSubnode(self.imageNode)
self.containerNode.addSubnode(self.textNode)
self.contentNode.flatMap { self.containerNode.addSubnode($0) }
self.containerNode.containerNode.addSubnode(self.imageNode)
self.containerNode.containerNode.addSubnode(self.textNode)
self.contentNode.flatMap { self.containerNode.containerNode.addSubnode($0) }
self.addSubnode(self.containerNode)
}

View File

@ -426,7 +426,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
)
self.textNode.visibility = true
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: defaultDarkPresentationTheme.list.itemAccentColor.withMultipliedAlpha(0.5), knob: defaultDarkPresentationTheme.list.itemAccentColor), strings: presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: defaultDarkPresentationTheme.list.itemAccentColor.withMultipliedAlpha(0.5), knob: defaultDarkPresentationTheme.list.itemAccentColor, isDark: true), strings: presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in
guard let self else {
return
}
@ -553,6 +553,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
return false
} else if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
return false
} else if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode)] {
return false
} else if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return false
} else if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {

View File

@ -424,6 +424,8 @@ public class ReplaceBoostScreen: ViewController {
positionTransition.updatePosition(layer: self.footerView.layer, position: CGPoint(x: self.footerView.center.x, y: self.bounds.height + self.footerView.bounds.height / 2.0))
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) {

View File

@ -103,8 +103,8 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
self.containerNode = ASDisplayNode()
self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
self.tooltipContainerNode = ContextMenuContainerNode(blurred: false)
self.tooltipContainerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.tooltipContainerNode = ContextMenuContainerNode(isBlurred: false, isDark: true)
self.tooltipContainerNode.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
self.textNode = ImmediateTextNode()
self.textNode.isUserInteractionEnabled = false
@ -119,7 +119,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
self.addSubnode(self.containerNode)
self.tooltipContainerNode.addSubnode(self.textNode)
self.tooltipContainerNode.containerNode.addSubnode(self.textNode)
self.addSubnode(self.tooltipContainerNode)
}

View File

@ -61,7 +61,7 @@ public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, edit
0x0771ff,
0x9047ff,
0xa256bf,
]
].reversed()
} else {
bubbleColors = [accentColor.withMultiplied(hue: 0.966, saturation: 0.61, brightness: 0.98).rgb, accentColor.rgb]
}

View File

@ -46,6 +46,9 @@ public func stringForDistance(strings: PresentationStrings, distance: CLLocation
}
public func flagEmoji(countryCode: String) -> String {
if countryCode.uppercased() == "FT" {
return "🏴‍☠️"
}
let base : UInt32 = 127397
var flagString = ""
for v in countryCode.uppercased().unicodeScalars {

View File

@ -20,6 +20,8 @@
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)();
@property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)();
- (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling;
@end
#endif /* Lottie_h */

View File

@ -14,6 +14,22 @@
@implementation ChatInputTextViewImpl
- (instancetype _Nonnull)initWithFrame:(CGRect)frame textContainer:(NSTextContainer * _Nullable)textContainer disableTiling:(bool)disableTiling {
self = [super initWithFrame:frame textContainer:textContainer];
if (self != nil) {
if (disableTiling) {
SEL selector = NSSelectorFromString(@"_disableTiledViews");
if (selector && [self respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop
}
}
}
return self;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (_shouldRespondToAction) {
@ -59,8 +75,7 @@
}
}
- (void)paste:(id)sender
{
- (void)paste:(id)sender {
if (_shouldPaste == nil || _shouldPaste()) {
[super paste:sender];
}

View File

@ -120,11 +120,11 @@ open class ChatInputTextNode: ASDisplayNode, UITextViewDelegate {
}
}
override public init() {
public init(disableTiling: Bool = false) {
super.init()
self.setViewBlock({
return ChatInputTextView()
return ChatInputTextView(disableTiling: disableTiling)
})
self.textView.delegate = self
@ -319,6 +319,9 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
private let measurementTextStorage: NSTextStorage
private let measurementLayoutManager: NSLayoutManager
private var validLayoutSize: CGSize?
private var isUpdatingLayout: Bool = false
private var blockQuotes: [Int: QuoteBackgroundView] = [:]
public var defaultTextContainerInset: UIEdgeInsets = UIEdgeInsets() {
@ -346,7 +349,13 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
return super.textInputMode
}
public init() {
override public var bounds: CGRect {
didSet {
assert(true)
}
}
public init(disableTiling: Bool) {
self.customTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0))
self.customLayoutManager = NSLayoutManager()
self.customTextStorage = NSTextStorage()
@ -359,7 +368,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
self.measurementTextStorage.addLayoutManager(self.measurementLayoutManager)
self.measurementLayoutManager.addTextContainer(self.measurementTextContainer)
super.init(frame: CGRect(), textContainer: self.customTextContainer)
super.init(frame: CGRect(), textContainer: self.customTextContainer, disableTiling: disableTiling)
self.textContainerInset = UIEdgeInsets()
self.backgroundColor = nil
@ -407,6 +416,20 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
fatalError("init(coder:) has not been implemented")
}
override public func scrollRectToVisible(_ rect: CGRect, animated: Bool) {
var rect = rect
if rect.maxY > self.contentSize.height - 8.0 {
rect = CGRect(origin: CGPoint(x: rect.minX, y: self.contentSize.height - 1.0), size: CGSize(width: rect.width, height: 1.0))
}
var animated = animated
if self.isUpdatingLayout {
animated = false
}
super.scrollRectToVisible(rect, animated: animated)
}
@objc public func layoutManager(_ layoutManager: NSLayoutManager, paragraphSpacingBeforeGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
guard let textStorage = layoutManager.textStorage else {
return 0.0
@ -535,8 +558,19 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
}
}
override public func setNeedsLayout() {
super.setNeedsLayout()
}
override public func layoutSubviews() {
let isLayoutUpdated = self.validLayoutSize != self.bounds.size
self.validLayoutSize = self.bounds.size
self.isUpdatingLayout = isLayoutUpdated
super.layoutSubviews()
self.isUpdatingLayout = false
}
public func updateTextElements() {
@ -721,7 +755,6 @@ private final class QuoteBackgroundView: UIView {
if self.theme != theme {
self.theme = theme
self.backgroundColor = theme.background
self.iconView.tintColor = theme.foreground
}

View File

@ -65,7 +65,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
private var contentFile: ChatMessageInteractiveFileNode?
private var actionButton: ChatMessageAttachedContentButtonNode?
private var actionButtonSeparator: SimpleLayer?
public let statusNode: ChatMessageDateAndStatusNode
public var statusNode: ChatMessageDateAndStatusNode?
private var inlineMediaValue: Media?
@ -109,12 +109,10 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
override public init() {
self.transformContainer = ASDisplayNode()
self.statusNode = ChatMessageDateAndStatusNode()
super.init()
self.addSubnode(self.transformContainer)
self.addSubnode(self.statusNode)
}
deinit {
@ -148,7 +146,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
let makeContentMedia = ChatMessageInteractiveMediaNode.asyncLayout(self.contentMedia)
let makeContentFile = ChatMessageInteractiveFileNode.asyncLayout(self.contentFile)
let makeActionButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.actionButton)
let makeStatusLayout = self.statusNode.asyncLayout()
let makeStatusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode)
return { [weak self] presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in
let isPreview = presentationData.isPreview
@ -604,35 +602,44 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
let statusLayoutAndContinue = makeStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context,
presentationData: presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(
contentWidth: trailingContentWidth,
reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false)
),
constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude),
availableReactions: associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message),
animationCache: controllerInteraction.presentationContext.animationCache,
animationRenderer: controllerInteraction.presentationContext.animationRenderer
))
actualWidth = max(actualWidth, statusLayoutAndContinue.0)
var statusLayoutAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))?
if case let .linear(_, bottom) = position {
switch bottom {
case .None, .Neighbour(_, .footer, _):
let statusLayoutAndContinueValue = makeStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context,
presentationData: presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(
contentWidth: trailingContentWidth,
reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false)
),
constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude),
availableReactions: associatedData.availableReactions,
reactions: dateReactionsAndPeers.reactions,
reactionPeers: dateReactionsAndPeers.peers,
displayAllReactionPeers: message.id.peerId.namespace == Namespaces.Peer.CloudUser,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message),
animationCache: controllerInteraction.presentationContext.animationCache,
animationRenderer: controllerInteraction.presentationContext.animationRenderer
))
statusLayoutAndContinue = statusLayoutAndContinueValue
actualWidth = max(actualWidth, statusLayoutAndContinueValue.0)
default:
break
}
}
actualWidth += insets.left + insets.right
return (actualWidth, { resultingWidth in
let statusSizeAndApply = statusLayoutAndContinue.1(resultingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0)
let statusSizeAndApply = statusLayoutAndContinue?.1(resultingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0)
let contentMediaSizeAndApply: (CGSize, (ListViewItemUpdateAnimation, Bool) -> ChatMessageInteractiveMediaNode)?
if let contentMediaFinalizeLayout {
@ -816,7 +823,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
if case let .linear(_, bottom) = position {
if case let .linear(_, bottom) = position, let statusSizeAndApply {
switch bottom {
case .None, .Neighbour(_, .footer, _):
let bottomStatusContentHeight = statusBackgroundSpacing + statusSizeAndApply.0.height
@ -1099,7 +1106,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let item = contentDisplayOrder.first(where: { $0.item == .actionButton }), let (actionButtonSize, actionButtonApply) = actionButtonSizeAndApply {
var actionButtonFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: actionButtonSize)
if let _ = message.adAttribute {
if let _ = message.adAttribute, let statusSizeAndApply {
actionButtonFrame.origin.y += statusSizeAndApply.0.height
}
@ -1151,38 +1158,40 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
actionButtonSeparator.removeFromSuperlayer()
}
do {
statusSizeAndApply.1(animation)
var statusFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0)
if let statusSizeAndApply {
var statusFrame = CGRect(origin: CGPoint(x: actualSize.width - backgroundInsets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0)
if let _ = message.adAttribute, let (actionButtonSize, _) = actionButtonSizeAndApply {
statusFrame.origin.y -= actionButtonSize.height + statusBackgroundSpacing
}
animation.animator.updateFrame(layer: self.statusNode.layer, frame: statusFrame, completion: nil)
self.statusNode.reactionSelected = { [weak self] value in
guard let self, let message = self.message else {
return
let statusNode = statusSizeAndApply.1(self.statusNode == nil ? .None : animation)
if self.statusNode !== statusNode {
self.statusNode?.removeFromSupernode()
self.statusNode = statusNode
self.addSubnode(statusNode)
statusNode.reactionSelected = { [weak self] value in
guard let self, let message = self.message else {
return
}
controllerInteraction.updateMessageReaction(message, .reaction(value))
}
controllerInteraction.updateMessageReaction(message, .reaction(value))
}
self.statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
guard let self, let message = self.message else {
gesture?.cancel()
return
}
controllerInteraction.openMessageReactionContextMenu(message, sourceNode, gesture, value)
}
if case let .linear(_, bottom) = position {
switch bottom {
case .None, .Neighbour(_, .footer, _):
animation.animator.updateAlpha(layer: self.statusNode.layer, alpha: 1.0, completion: nil)
default:
animation.animator.updateAlpha(layer: self.statusNode.layer, alpha: 0.0, completion: nil)
statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
guard let self, let message = self.message else {
gesture?.cancel()
return
}
controllerInteraction.openMessageReactionContextMenu(message, sourceNode, gesture, value)
}
statusNode.frame = statusFrame
} else {
animation.animator.updateFrame(layer: statusNode.layer, frame: statusFrame, completion: nil)
}
} else if let statusNode = self.statusNode {
self.statusNode = nil
statusNode.removeFromSupernode()
}
})
})
@ -2239,8 +2248,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if !self.statusNode.isHidden {
if let result = self.statusNode.reactionView(value: value) {
if let statusNode = self.statusNode, !statusNode.isHidden {
if let result = statusNode.reactionView(value: value) {
return result
}
}

View File

@ -2832,7 +2832,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if case let .System(duration, _) = animation {
legacyTransition = .animated(duration: duration, curve: .spring)
if let subject = item.associatedData.subject, case .messageOptions = subject {
if let subject = item.associatedData.subject, case .messageOptions = subject, !"".isEmpty {
useDisplayLinkAnimations = true
}
}
@ -3343,6 +3343,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.contentContainersWrapperNode.view.mask = nil
}
var animateTextAndWebpagePositionSwap: Bool?
var bottomStatusNodeAnimationSourcePosition: CGPoint?
if removedContentNodeIndices?.count ?? 0 != 0 || addedContentNodes?.count ?? 0 != 0 || updatedContentNodeOrder {
var updatedContentNodes = strongSelf.contentNodes
@ -3415,6 +3418,26 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
assert(sortedContentNodes.count == updatedContentNodes.count)
if animation.isAnimated, let fromTextIndex = strongSelf.contentNodes.firstIndex(where: { $0 is ChatMessageTextBubbleContentNode }), let fromWebpageIndex = strongSelf.contentNodes.firstIndex(where: { $0 is ChatMessageWebpageBubbleContentNode }) {
if let toTextIndex = sortedContentNodes.firstIndex(where: { $0 is ChatMessageTextBubbleContentNode }), let toWebpageIndex = sortedContentNodes.firstIndex(where: { $0 is ChatMessageWebpageBubbleContentNode }) {
if fromTextIndex == toWebpageIndex && fromWebpageIndex == toTextIndex {
animateTextAndWebpagePositionSwap = fromTextIndex < toTextIndex
if let textNode = strongSelf.contentNodes[fromTextIndex] as? ChatMessageTextBubbleContentNode, let webpageNode = strongSelf.contentNodes[fromWebpageIndex] as? ChatMessageWebpageBubbleContentNode {
if fromTextIndex > toTextIndex {
if let statusNode = textNode.statusNode, let contentSuperview = textNode.view.superview, statusNode.view.isDescendant(of: contentSuperview) {
bottomStatusNodeAnimationSourcePosition = statusNode.view.convert(CGPoint(x: statusNode.bounds.width, y: statusNode.bounds.height), to: contentSuperview)
}
} else {
if let statusNode = webpageNode.contentNode.statusNode, let contentSuperview = webpageNode.view.superview, statusNode.view.isDescendant(of: contentSuperview) {
bottomStatusNodeAnimationSourcePosition = statusNode.view.convert(CGPoint(x: statusNode.bounds.width, y: statusNode.bounds.height), to: contentSuperview)
}
}
}
}
}
}
strongSelf.contentNodes = sortedContentNodes
}
@ -3463,7 +3486,52 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
})
strongSelf.setAnimationForKey("contentNode\(contentNodeIndex)Frame", animation: animation)
} else {
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
var useExpensiveSnapshot = false
if case .messageOptions = item.associatedData.subject {
useExpensiveSnapshot = true
}
if let animateTextAndWebpagePositionSwap, let contentNode = contentNode as? ChatMessageTextBubbleContentNode, let snapshotView = useExpensiveSnapshot ? contentNode.view.snapshotView(afterScreenUpdates: false) : contentNode.layer.snapshotContentTreeAsView() {
let clippingView = UIView()
clippingView.clipsToBounds = true
clippingView.frame = contentNode.frame
clippingView.addSubview(snapshotView)
snapshotView.frame = CGRect(origin: CGPoint(), size: contentNode.bounds.size)
contentNode.view.superview?.insertSubview(clippingView, belowSubview: contentNode.view)
animation.animator.updateAlpha(layer: clippingView.layer, alpha: 0.0, completion: { [weak clippingView] _ in
clippingView?.removeFromSuperview()
})
let positionOffset: CGFloat = animateTextAndWebpagePositionSwap ? -1.0 : 1.0
animation.animator.updatePosition(layer: snapshotView.layer, position: CGPoint(x: snapshotView.center.x, y: snapshotView.center.y + positionOffset * contentNode.frame.height), completion: nil)
contentNode.frame = contentNodeFrame
if let statusNode = contentNode.statusNode, let contentSuperview = contentNode.view.superview, statusNode.view.isDescendant(of: contentSuperview), let bottomStatusNodeAnimationSourcePosition {
let localSourcePosition = statusNode.view.convert(bottomStatusNodeAnimationSourcePosition, from: contentSuperview)
let offset = CGPoint(x: statusNode.bounds.width - localSourcePosition.x, y: statusNode.bounds.height - localSourcePosition.y)
animation.animator.animatePosition(layer: statusNode.layer, from: statusNode.layer.position.offsetBy(dx: -offset.x, dy: -offset.y), to: statusNode.layer.position, completion: nil)
}
contentNode.animateClippingTransition(offset: positionOffset * contentNodeFrame.height, animation: animation)
contentNode.alpha = 0.0
animation.animator.updateAlpha(layer: contentNode.layer, alpha: 1.0, completion: nil)
} else if animateTextAndWebpagePositionSwap != nil, let contentNode = contentNode as? ChatMessageWebpageBubbleContentNode {
if let statusNode = contentNode.contentNode.statusNode, let contentSuperview = contentNode.view.superview, statusNode.view.isDescendant(of: contentSuperview), let bottomStatusNodeAnimationSourcePosition {
let localSourcePosition = statusNode.view.convert(bottomStatusNodeAnimationSourcePosition, from: contentSuperview)
let offset = CGPoint(x: statusNode.bounds.width - localSourcePosition.x, y: statusNode.bounds.height - localSourcePosition.y)
animation.animator.animatePosition(layer: statusNode.layer, from: statusNode.layer.position.offsetBy(dx: -offset.x, dy: -offset.y), to: statusNode.layer.position, completion: nil)
}
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
} else {
animation.animator.updateFrame(layer: contentNode.layer, frame: contentNodeFrame, completion: nil)
}
}
} else if animateAlpha {
contentNode.frame = contentNodeFrame

View File

@ -144,8 +144,8 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if !self.contentNode.statusNode.isHidden {
return self.contentNode.statusNode.reactionView(value: value)
if let statusNode = self.contentNode.statusNode, !statusNode.isHidden {
return statusNode.reactionView(value: value)
}
return nil
}

View File

@ -258,7 +258,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode
let locale = localeWithStrings(item.presentationData.strings)
let countryNames = giveaway.countries.map { id in
if let countryName = locale.localizedString(forRegionCode: id) {
return "\(flagEmoji(countryCode: id))\(countryName)"
return "\(flagEmoji(countryCode: id))\u{feff}\(countryName)"
} else {
return id
}

View File

@ -1796,7 +1796,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
knobColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionKnobColor
}
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor), strings: item.presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor, isDark: item.presentationData.theme.theme.overallDarkAppearance), strings: item.presentationData.strings, textNode: self.textNode, updateIsActive: { [weak self] value in
self?.updateIsTextSelectionActive?(value)
}, present: { [weak self] c, a in
self?.arguments?.controllerInteraction.presentGlobalOverlayController(c, a)

View File

@ -140,8 +140,8 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if !self.contentNode.statusNode.isHidden {
return self.contentNode.statusNode.reactionView(value: value)
if let statusNode = self.contentNode.statusNode, !statusNode.isHidden {
return statusNode.reactionView(value: value)
}
return nil
}

View File

@ -54,12 +54,13 @@ private final class CachedChatMessageText {
}
public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
private let containerNode: ASDisplayNode
private let textNode: TextNodeWithEntities
private var spoilerTextNode: TextNodeWithEntities?
private var dustNode: InvisibleInkDustNode?
private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode
private let statusNode: ChatMessageDateAndStatusNode
public var statusNode: ChatMessageDateAndStatusNode?
private var linkHighlightingNode: LinkHighlightingNode?
private var shimmeringNode: ShimmeringLinkNode?
private var textSelectionNode: TextSelectionNode?
@ -99,40 +100,26 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
required public init() {
self.textNode = TextNodeWithEntities()
self.containerNode = ASDisplayNode()
self.statusNode = ChatMessageDateAndStatusNode()
self.textNode = TextNodeWithEntities()
self.textAccessibilityOverlayNode = TextAccessibilityOverlayNode()
super.init()
self.addSubnode(self.containerNode)
self.textNode.textNode.isUserInteractionEnabled = false
self.textNode.textNode.contentMode = .topLeft
self.textNode.textNode.contentsScale = UIScreenScale
self.textNode.textNode.displaysAsynchronously = true
self.addSubnode(self.textNode.textNode)
self.addSubnode(self.textAccessibilityOverlayNode)
self.containerNode.addSubnode(self.textNode.textNode)
self.containerNode.addSubnode(self.textAccessibilityOverlayNode)
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
self?.item?.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: false, external: false))
}
self.statusNode.reactionSelected = { [weak self] value in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
}
self.statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
guard let strongSelf = self, let item = strongSelf.item else {
gesture?.cancel()
return
}
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
}
}
required public init?(coder aDecoder: NSCoder) {
@ -147,7 +134,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
let spoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
let statusLayout = self.statusNode.asyncLayout()
let statusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode)
let currentCachedChatMessageText = self.cachedChatMessageText
@ -454,7 +441,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
spoilerTextLayoutAndApply = nil
}
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))?
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
@ -527,6 +514,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.cachedChatMessageText = updatedCachedChatMessageText
}
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
let cachedLayout = strongSelf.textNode.textNode.cachedLayout
if case .System = animation {
@ -538,7 +527,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
fadeNode.contents = textContents
fadeNode.frame = strongSelf.textNode.textNode.frame
fadeNode.isLayerBacked = true
strongSelf.addSubnode(fadeNode)
strongSelf.containerNode.addSubnode(fadeNode)
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
fadeNode?.removeFromSupernode()
})
@ -559,7 +548,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
spoilerTextNode.textNode.contentMode = .topLeft
spoilerTextNode.textNode.contentsScale = UIScreenScale
spoilerTextNode.textNode.displaysAsynchronously = false
strongSelf.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode)
strongSelf.containerNode.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textAccessibilityOverlayNode)
strongSelf.spoilerTextNode = spoilerTextNode
}
@ -572,7 +561,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
strongSelf.dustNode = dustNode
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
strongSelf.containerNode.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
}
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, textColor: messageTheme.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
@ -611,27 +600,48 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.updateIsTranslating(isTranslating)
if let statusSizeAndApply = statusSizeAndApply {
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode)
statusSizeAndApply.1(.None)
if let statusSizeAndApply {
let statusNode = statusSizeAndApply.1(strongSelf.statusNode == nil ? .None : animation)
let statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
if strongSelf.statusNode !== statusNode {
strongSelf.statusNode?.removeFromSupernode()
strongSelf.statusNode = statusNode
strongSelf.addSubnode(statusNode)
statusNode.reactionSelected = { [weak strongSelf] value in
guard let strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value))
}
statusNode.openReactionPreview = { [weak strongSelf] gesture, sourceNode, value in
guard let strongSelf, let item = strongSelf.item else {
gesture?.cancel()
return
}
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
}
statusNode.frame = statusFrame
} else {
statusSizeAndApply.1(animation)
animation.animator.updateFrame(layer: statusNode.layer, frame: statusFrame, completion: nil)
}
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
} else if let statusNode = strongSelf.statusNode {
strongSelf.statusNode = nil
statusNode.removeFromSupernode()
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.statusNode.pressed = {
guard let strongSelf = self else {
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported), let statusNode = strongSelf.statusNode {
statusNode.pressed = {
guard let strongSelf = self, let statusNode = strongSelf.statusNode else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.statusNode)
item.controllerInteraction.displayImportedMessageTooltip(statusNode)
}
} else {
strongSelf.statusNode.pressed = nil
strongSelf.statusNode?.pressed = nil
}
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject {
@ -688,17 +698,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.statusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
@ -785,7 +795,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
} else {
if let _ = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: nil) {
if let statusNode = self.statusNode, let _ = statusNode.hitTest(self.view.convert(point, to: statusNode.view), with: nil) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
return ChatMessageBubbleContentTapAction(content: .none)
@ -793,7 +803,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.statusNode.supernode != nil, let result = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: event) {
if let statusNode = self.statusNode, statusNode.supernode != nil, let result = statusNode.hitTest(self.view.convert(point, to: statusNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
@ -815,7 +825,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
shimmeringNode.updateLayout(self.textNode.textNode.frame.size)
shimmeringNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shimmeringNode = shimmeringNode
self.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode)
self.containerNode.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode)
}
} else if let shimmeringNode = self.shimmeringNode {
self.shimmeringNode = nil
@ -862,7 +872,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
self.containerNode.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
}
linkHighlightingNode.frame = self.textNode.textNode.frame
linkHighlightingNode.updateRects(rects)
@ -893,7 +903,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.textHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.textHighlightColor)
self.textHighlightingNodes.append(textHighlightNode)
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
}
textHighlightNode.frame = self.textNode.textNode.frame
textHighlightNode.updateRects(rects)
@ -927,7 +937,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor.withMultipliedAlpha(0.5) : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor.withMultipliedAlpha(0.5))
self.linkPreviewHighlightingNodes.append(textHighlightNode)
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
}
textHighlightNode.frame = self.textNode.textNode.frame
textHighlightNode.updateRects(rects)
@ -951,7 +961,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
linkProgressView = TextLoadingEffectView(frame: CGRect())
self.linkProgressView = linkProgressView
self.view.addSubview(linkProgressView)
self.containerNode.view.addSubview(linkProgressView)
}
linkProgressView.frame = self.textNode.textNode.frame
@ -1050,7 +1060,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
} else {
textHighlightNode = LinkHighlightingNode(color: color)
self.quoteHighlightingNode = textHighlightNode
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
self.containerNode.insertSubnode(textHighlightNode, belowSubnode: self.textNode.textNode)
}
textHighlightNode.frame = self.textNode.textNode.frame
textHighlightNode.updateRects(rects)
@ -1094,7 +1104,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
knobColor = item.presentationData.theme.theme.chat.message.outgoing.textSelectionKnobColor
}
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor), strings: item.presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { [weak self] value in
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: knobColor, isDark: item.presentationData.theme.theme.overallDarkAppearance), strings: item.presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { [weak self] value in
self?.updateIsTextSelectionActive?(value)
}, present: { [weak self] c, a in
guard let self, let item = self.item else {
@ -1157,8 +1167,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
textSelectionNode.enableShare = enableOtherActions
textSelectionNode.menuSkipCoordnateConversion = !enableOtherActions
self.textSelectionNode = textSelectionNode
self.addSubnode(textSelectionNode)
self.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode)
self.containerNode.addSubnode(textSelectionNode)
self.containerNode.insertSubnode(textSelectionNode.highlightAreaNode, belowSubnode: self.textNode.textNode)
textSelectionNode.frame = self.textNode.textNode.frame
textSelectionNode.highlightAreaNode.frame = self.textNode.textNode.frame
}
@ -1180,8 +1190,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if !self.statusNode.isHidden {
return self.statusNode.reactionView(value: value)
if let statusNode = self.statusNode, !statusNode.isHidden {
return statusNode.reactionView(value: value)
}
return nil
}
@ -1191,7 +1201,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
public func animateFrom(sourceView: UIView, scrollOffset: CGFloat, widthDifference: CGFloat, transition: CombinedTransition) {
self.view.addSubview(sourceView)
self.containerNode.view.addSubview(sourceView)
sourceView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak sourceView] _ in
sourceView?.removeFromSuperview()
@ -1206,8 +1216,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
transition.vertical.animatePositionAdditive(node: self.textNode.textNode, offset: offset)
transition.updatePosition(layer: sourceView.layer, position: CGPoint(x: sourceView.layer.position.x - offset.x, y: sourceView.layer.position.y - offset.y))
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
transition.horizontal.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: -widthDifference, y: 0.0))
if let statusNode = self.statusNode {
statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
transition.horizontal.animatePositionAdditive(node: statusNode, offset: CGPoint(x: -widthDifference, y: 0.0))
}
}
public func beginTextSelection(range: NSRange?, displayMenu: Bool = true) {
@ -1260,4 +1272,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
return (substring, entities)
}
public func animateClippingTransition(offset: CGFloat, animation: ListViewItemUpdateAnimation) {
self.containerNode.clipsToBounds = true
self.containerNode.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: self.containerNode.bounds.size)
self.containerNode.alpha = 0.0
animation.animator.updateAlpha(layer: self.containerNode.layer, alpha: 1.0, completion: nil)
animation.animator.updateBounds(layer: self.containerNode.layer, bounds: CGRect(origin: CGPoint(), size: self.containerNode.bounds.size), completion: { [weak self] completed in
guard let self, completed else {
return
}
self.containerNode.clipsToBounds = false
})
}
}

View File

@ -25,7 +25,7 @@ private let titleFont: UIFont = Font.semibold(15.0)
public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
private var webPage: TelegramMediaWebpage?
private var contentNode: ChatMessageAttachedContentNode
public private(set) var contentNode: ChatMessageAttachedContentNode
override public var visibility: ListViewItemNodeVisibility {
didSet {

View File

@ -607,14 +607,14 @@ public final class MessageInlineBlockBackgroundView: UIView {
}
let placements: [Placement] = [
Placement(CGPoint(x: 176.0, y: 13.0), 38.0),
Placement(CGPoint(x: 51.0, y: 45.0), 58.0),
Placement(CGPoint(x: 349.0, y: 36.0), 58.0),
Placement(CGPoint(x: 132.0, y: 64.0), 46.0),
Placement(CGPoint(x: 241.0, y: 64.0), 54.0),
Placement(CGPoint(x: 68.0, y: 121.0), 44.0),
Placement(CGPoint(x: 178.0, y: 122.0), 47.0),
Placement(CGPoint(x: 315.0, y: 122.0), 47.0),
Placement(CGPoint(x: 180.0, y: 13.0), 38.0),
Placement(CGPoint(x: 55.0, y: 47.0), 58.0),
Placement(CGPoint(x: 364.0, y: 26.0), 58.0),
Placement(CGPoint(x: 133.0, y: 74.0), 46.0),
Placement(CGPoint(x: 262.0, y: 67.0), 54.0),
Placement(CGPoint(x: 62.0, y: 125.0), 44.0),
Placement(CGPoint(x: 171.0, y: 135.0), 47.0),
Placement(CGPoint(x: 320.0, y: 124.0), 47.0),
]
for placement in placements {
@ -631,7 +631,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0)
patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
var alphaFraction = abs(placement.position.x) / 400.0
var alphaFraction = abs(placement.position.x) / 500.0
alphaFraction = min(1.0, max(0.0, alphaFraction))
patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction)

View File

@ -16,14 +16,14 @@ final private class ContextMenuActionButton: HighlightTrackingButton {
final class ContextMenuActionNode: ASDisplayNode {
private let textNode: ImmediateTextNode?
private var textSize: CGSize?
private let iconNode: ASImageNode?
private let iconView: UIImageView?
private let action: () -> Void
private let button: ContextMenuActionButton
private let actionArea: AccessibilityAreaNode
var dismiss: (() -> Void)?
init(action: ContextMenuAction, blurred: Bool) {
init(action: ContextMenuAction, blurred: Bool, isDark: Bool) {
self.actionArea = AccessibilityAreaNode()
self.actionArea.accessibilityTraits = .button
@ -34,30 +34,30 @@ final class ContextMenuActionNode: ASDisplayNode {
let textNode = ImmediateTextNode()
textNode.isUserInteractionEnabled = false
textNode.displaysAsynchronously = false
textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white)
textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: isDark ? .white : .black)
textNode.isAccessibilityElement = false
self.textNode = textNode
self.iconNode = nil
self.iconView = nil
case let .textWithIcon(title, icon):
let textNode = ImmediateTextNode()
textNode.isUserInteractionEnabled = false
textNode.displaysAsynchronously = false
textNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: UIColor.white)
textNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: isDark ? .white : .black)
textNode.isAccessibilityElement = false
let iconNode = ASImageNode()
iconNode.displaysAsynchronously = false
iconNode.image = icon
let iconView = UIImageView()
iconView.tintColor = isDark ? .white : .black
iconView.image = icon
self.textNode = textNode
self.iconNode = iconNode
self.iconView = iconView
case let .icon(image):
let iconNode = ASImageNode()
iconNode.displaysAsynchronously = false
iconNode.image = image
let iconView = UIImageView()
iconView.tintColor = isDark ? .white : .black
iconView.image = image
self.iconNode = iconNode
self.iconView = iconView
self.textNode = nil
}
self.action = action.action
@ -68,21 +68,25 @@ final class ContextMenuActionNode: ASDisplayNode {
super.init()
if !blurred {
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
self.backgroundColor = isDark ? UIColor(rgb: 0x2f2f2f) : nil
}
if let textNode = self.textNode {
self.addSubnode(textNode)
}
if let iconNode = self.iconNode {
self.addSubnode(iconNode)
if let iconView = self.iconView {
self.view.addSubview(iconView)
}
self.button.highligthedChanged = { [weak self] highlighted in
if blurred {
self?.backgroundColor = highlighted ? UIColor(rgb: 0xffffff, alpha: 0.5) : .clear
if isDark {
if blurred {
self?.backgroundColor = highlighted ? UIColor(rgb: 0xffffff, alpha: 0.5) : .clear
} else {
self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f)
}
} else {
self?.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f)
self?.backgroundColor = highlighted ? UIColor(rgb: 0xDCE3DC) : .clear
}
}
self.view.addSubview(self.button)
@ -116,7 +120,7 @@ final class ContextMenuActionNode: ASDisplayNode {
var totalWidth = 0.0
totalWidth += textSize.width
if let image = self.iconNode?.image {
if let image = self.iconView?.image {
if totalWidth > 0.0 {
totalWidth += 11.0
}
@ -127,7 +131,7 @@ final class ContextMenuActionNode: ASDisplayNode {
}
return CGSize(width: totalWidth, height: 54.0)
} else if let iconNode = self.iconNode, let image = iconNode.image {
} else if let iconView = self.iconView, let image = iconView.image {
return CGSize(width: image.size.width + 36.0, height: 54.0)
} else {
return CGSize(width: 36.0, height: 54.0)
@ -144,7 +148,7 @@ final class ContextMenuActionNode: ASDisplayNode {
if let textSize = self.textSize {
totalWidth += textSize.width
}
if let image = self.iconNode?.image {
if let image = self.iconView?.image {
if totalWidth > 0.0 {
totalWidth += 11.0
}
@ -154,9 +158,9 @@ final class ContextMenuActionNode: ASDisplayNode {
if let textNode = self.textNode, let textSize = self.textSize {
textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - totalWidth) / 2.0), y: floor((self.bounds.size.height - textSize.height) / 2.0)), size: textSize)
}
if let iconNode = self.iconNode, let image = iconNode.image {
if let iconView = self.iconView, let image = iconView.image {
let iconSize = image.size
iconNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - totalWidth) / 2.0) + totalWidth - iconSize.width, y: floorToScreenPixels((self.bounds.size.height - iconSize.height) / 2.0)), size: iconSize)
iconView.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - totalWidth) / 2.0) + totalWidth - iconSize.width, y: floorToScreenPixels((self.bounds.size.height - iconSize.height) / 2.0)), size: iconSize)
}
}
}

View File

@ -19,6 +19,7 @@ public final class ContextMenuControllerImpl: ViewController, KeyShortcutRespond
private let hasHapticFeedback: Bool
private let blurred: Bool
private let skipCoordnateConversion: Bool
private let isDark: Bool
private var layout: ContainerViewLayout?
@ -33,6 +34,7 @@ public final class ContextMenuControllerImpl: ViewController, KeyShortcutRespond
self.hasHapticFeedback = arguments.hasHapticFeedback
self.blurred = arguments.blurred
self.skipCoordnateConversion = arguments.skipCoordnateConversion
self.isDark = arguments.isDark
super.init(navigationBarPresentationData: nil)
@ -54,7 +56,7 @@ public final class ContextMenuControllerImpl: ViewController, KeyShortcutRespond
return false
}
return dismissOnTap(view, point)
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback, blurred: self.blurred)
}, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback, blurred: self.blurred, isDark: self.isDark)
self.displayNodeDidLoad()
}

View File

@ -5,15 +5,6 @@ import AppBundle
import AsyncDisplayKit
import Display
private func generateShadowImage() -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 1.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(white: 0.18, alpha: 1.0).cgColor)
context.setFillColor(UIColor(white: 0.18, alpha: 1.0).cgColor)
context.fill(CGRect(origin: CGPoint(x: -15.0, y: 0.0), size: CGSize(width: 30.0, height: 1.0)))
})
}
private final class ArrowNode: HighlightTrackingButtonNode {
private let isLeft: Bool
@ -21,7 +12,7 @@ private final class ArrowNode: HighlightTrackingButtonNode {
private let separatorLayer: SimpleLayer
var action: (() -> Void)?
init(isLeft: Bool) {
init(isLeft: Bool, isDark: Bool) {
self.isLeft = isLeft
self.iconView = UIImageView()
@ -39,12 +30,15 @@ private final class ArrowNode: HighlightTrackingButtonNode {
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
self.backgroundColor = UIColor(rgb: 0x2f2f2f)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
self.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0x2f2f2f)
if isDark {
self.backgroundColor = highlighted ? UIColor(rgb: 0x8c8e8e) : nil
} else {
self.backgroundColor = highlighted ? UIColor(rgb: 0xDCE3DC) : nil
}
}
}
@ -70,6 +64,8 @@ private final class ArrowNode: HighlightTrackingButtonNode {
}
final class ContextMenuNode: ASDisplayNode {
private let isDark: Bool
private let actions: [ContextMenuAction]
private let dismiss: () -> Void
private let dismissOnTap: (UIView, CGPoint) -> Bool
@ -96,22 +92,24 @@ final class ContextMenuNode: ASDisplayNode {
private let feedback: HapticFeedback?
init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool = false) {
init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool = false, isDark: Bool = true) {
self.isDark = isDark
self.actions = actions
self.dismiss = dismiss
self.dismissOnTap = dismissOnTap
self.catchTapsOutside = catchTapsOutside
self.containerNode = ContextMenuContainerNode(blurred: blurred)
self.containerNode = ContextMenuContainerNode(isBlurred: blurred, isDark: isDark)
self.contentNode = ASDisplayNode()
self.contentNode.clipsToBounds = true
self.actionNodes = actions.map { action in
return ContextMenuActionNode(action: action, blurred: blurred)
return ContextMenuActionNode(action: action, blurred: blurred, isDark: isDark)
}
self.pageLeftNode = ArrowNode(isLeft: true)
self.pageRightNode = ArrowNode(isLeft: false)
self.pageLeftNode = ArrowNode(isLeft: true, isDark: isDark)
self.pageRightNode = ArrowNode(isLeft: false, isDark: isDark)
if hasHapticFeedback {
self.feedback = HapticFeedback()
@ -122,7 +120,7 @@ final class ContextMenuNode: ASDisplayNode {
super.init()
self.containerNode.addSubnode(self.contentNode)
self.containerNode.containerNode.addSubnode(self.contentNode)
self.addSubnode(self.containerNode)
let dismissNode = {
@ -133,8 +131,8 @@ final class ContextMenuNode: ASDisplayNode {
self.contentNode.addSubnode(actionNode)
}
self.containerNode.addSubnode(self.pageLeftNode)
self.containerNode.addSubnode(self.pageRightNode)
self.containerNode.containerNode.addSubnode(self.pageLeftNode)
self.containerNode.containerNode.addSubnode(self.pageRightNode)
let navigatePage: (Bool) -> Void = { [weak self] isLeft in
guard let self else {
@ -173,12 +171,12 @@ final class ContextMenuNode: ASDisplayNode {
var offsetX: CGFloat
}
let separatorColor = UIColor(rgb: 0x8c8e8e)
let separatorColor = self.isDark ? UIColor(rgb: 0x8c8e8e) : UIColor(rgb: 0xDCE3DC)
let height: CGFloat = 54.0
let pageLeftSize = self.pageLeftNode.update(color: .white, separatorColor: separatorColor, height: height)
let pageRightSize = self.pageRightNode.update(color: .white, separatorColor: separatorColor, height: height)
let pageLeftSize = self.pageLeftNode.update(color: self.isDark ? .white : .black, separatorColor: separatorColor, height: height)
let pageRightSize = self.pageRightNode.update(color: self.isDark ? .white : .black, separatorColor: separatorColor, height: height)
let maxPageWidth = layout.size.width - 20.0 - pageLeftSize.width - pageRightSize.width
var absoluteActionOffsetX: CGFloat = 0.0

View File

@ -729,7 +729,7 @@ final class StoryContentCaptionComponent: Component {
self.textSelectionKnobContainer.addSubview(textSelectionKnobSurface)
}
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: component.theme.list.itemAccentColor), strings: component.strings, textNode: textNode, updateIsActive: { [weak self] value in
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: selectionColor, knob: component.theme.list.itemAccentColor, isDark: true), strings: component.strings, textNode: textNode, updateIsActive: { [weak self] value in
guard let self else {
return
}

View File

@ -2019,26 +2019,46 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let self else {
return
}
if params.quote != nil {
if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(fromId), let toPeer = message.peers[id.peerId] {
switch toPeer {
case let channel as TelegramChannel:
if channel.username == nil && channel.usernames.isEmpty {
switch channel.participationStatus {
case .kicked, .left:
self.controllerInteraction?.attemptedNavigationToPrivateQuote(toPeer)
return
case .member:
break
}
}
default:
break
}
let continueNavigation: () -> Void = { [weak self] in
guard let self else {
return
}
self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId)
}
self.navigateToMessage(from: fromId, to: .id(id, params), forceInCurrentChat: fromId.peerId == id.peerId)
let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: id.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] toPeer in
guard let self else {
return
}
if params.quote != nil {
if let toPeer {
switch toPeer {
case let .channel(channel):
if channel.username == nil && channel.usernames.isEmpty {
switch channel.participationStatus {
case .kicked, .left:
self.controllerInteraction?.attemptedNavigationToPrivateQuote(toPeer._asPeer())
return
case .member:
break
}
}
default:
break
}
} else {
self.controllerInteraction?.attemptedNavigationToPrivateQuote(nil)
return
}
}
continueNavigation()
})
}, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: false)
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in

View File

@ -2284,7 +2284,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
textInputNode.textContainerInset = textInputViewRealInsets
let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom))
let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size
transition.updateFrame(node: textInputNode, frame: textFieldFrame)
//transition.updateFrame(node: textInputNode, frame: textFieldFrame)
textInputNode.frame = textFieldFrame
textInputNode.updateLayout(size: textFieldFrame.size)
self.updateInputField(textInputFrame: textFieldFrame, transition: Transition(transition))
if shouldUpdateLayout {

View File

@ -955,6 +955,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.updateCancelButton()
}
deinit {
self.disposable.dispose()
}
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
self.enqueuedTransitions.append(transition)

View File

@ -61,11 +61,13 @@ public final class TextSelectionTheme {
public let selection: UIColor
public let knob: UIColor
public let knobDiameter: CGFloat
public let isDark: Bool
public init(selection: UIColor, knob: UIColor, knobDiameter: CGFloat = 12.0) {
public init(selection: UIColor, knob: UIColor, knobDiameter: CGFloat = 12.0, isDark: Bool) {
self.selection = selection
self.knob = knob
self.knobDiameter = knobDiameter
self.isDark = isDark
}
}
@ -755,7 +757,7 @@ public final class TextSelectionNode: ASDisplayNode {
self.contextMenu?.dismiss()
let contextMenu = makeContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false, skipCoordnateConversion: self.menuSkipCoordnateConversion)
let contextMenu = makeContextMenuController(actions: actions, catchTapsOutside: false, hasHapticFeedback: false, isDark: self.theme.isDark, skipCoordnateConversion: self.menuSkipCoordnateConversion)
contextMenu.dismissOnTap = { [weak self] view, point in
guard let self else {
return true