From 6035a52fd3f0b63863dc7354fa50a3d08e3ef902 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 21 Dec 2021 18:36:44 +0400 Subject: [PATCH 1/4] Various Improvements --- .../Sources/Node/ChatListItem.swift | 8 + submodules/SettingsUI/BUILD | 1 + .../LocalizationListControllerNode.swift | 27 +++- .../ChatPinnedMessageTitlePanelNode.swift | 44 +++++- .../Sources/ChatTextInputMenu.swift | 2 +- .../Sources/ChatTextInputPanelNode.swift | 143 +++++++++++++++++- .../Sources/ReplyAccessoryPanelNode.swift | 69 ++++++--- .../Sources/ChatTextInputAttributes.swift | 18 ++- 8 files changed, 286 insertions(+), 26 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index cef20e0fe2..5b0a72e542 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1786,11 +1786,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.inputActivitiesNode.alpha = 1.0 strongSelf.textNode.alpha = 0.0 strongSelf.authorNode.alpha = 0.0 + strongSelf.dustNode?.alpha = 0.0 if animated || animateContent { strongSelf.inputActivitiesNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) strongSelf.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) strongSelf.authorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) + strongSelf.dustNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) } } } else { @@ -1798,6 +1800,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.inputActivitiesNode.alpha = 0.0 strongSelf.textNode.alpha = 1.0 strongSelf.authorNode.alpha = 1.0 + strongSelf.dustNode?.alpha = 1.0 if animated || animateContent { strongSelf.inputActivitiesNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { value in if let strongSelf = self, value { @@ -1806,6 +1809,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) strongSelf.authorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + strongSelf.dustNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } else { strongSelf.inputActivitiesNode.removeFromSupernode() } @@ -2016,6 +2020,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { textFrame.origin.x = contentRect.origin.x transition.updateFrameAdditive(node: self.textNode, frame: textFrame) + if let dustNode = self.dustNode { + transition.updateFrameAdditive(node: dustNode, frame: textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)) + } + var mediaPreviewOffsetX = textFrame.origin.x + 1.0 let contentImageSpacing: CGFloat = 2.0 for (_, media, mediaSize) in self.currentMediaPreviewSpecs { diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 55a781e9c6..aa68c921f7 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -92,6 +92,7 @@ swift_library( "//submodules/DebugSettingsUI:DebugSettingsUI", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", "//submodules/WebPBinding:WebPBinding", + "//submodules/Translate:Translate", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift index 5ef5d5d003..c3a778db4b 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift @@ -15,6 +15,7 @@ import SearchBarNode import SearchUI import UndoUI import TelegramUIPreferences +import Translate private enum LanguageListSection: ItemListSectionId { case translate @@ -432,8 +433,20 @@ final class LocalizationListControllerNode: ViewControllerTracingNode { var existingIds = Set() var showTranslate = true + var ignoredLanguages: [String] = [] if let translationSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { showTranslate = translationSettings.showTranslate + if let languages = translationSettings.ignoredLanguages { + ignoredLanguages = languages + } else { + if let activeLanguageCode = activeLanguageCode, supportedTranslationLanguages.contains(activeLanguageCode) { + ignoredLanguages = [activeLanguageCode] + } + } + } else { + if let activeLanguageCode = activeLanguageCode, supportedTranslationLanguages.contains(activeLanguageCode) { + ignoredLanguages = [activeLanguageCode] + } } let localizationListState = (view.views[preferencesKey] as? PreferencesView)?.values[PreferencesKeys.localizationListState]?.get(LocalizationListState.self) @@ -444,8 +457,18 @@ final class LocalizationListControllerNode: ViewControllerTracingNode { entries.append(.translateTitle(text: presentationData.strings.Localization_TranslateMessages.uppercased())) entries.append(.translate(text: presentationData.strings.Localization_ShowTranslate, value: showTranslate)) if showTranslate { - entries.append(.doNotTranslate(text: presentationData.strings.Localization_DoNotTranslate, value: "")) - entries.append(.translateInfo(text: presentationData.strings.Localization_DoNotTranslateInfo)) + var value = "" + if ignoredLanguages.count > 1 { + value = ignoredLanguages.joined(separator: ", ") + } else if let code = ignoredLanguages.first { + let enLocale = Locale(identifier: "en") + if let title = enLocale.localizedString(forLanguageCode: code) { + value = title + } + } + + entries.append(.doNotTranslate(text: presentationData.strings.Localization_DoNotTranslate, value: value)) + entries.append(.translateInfo(text: ignoredLanguages.count > 1 ? presentationData.strings.Localization_DoNotTranslateManyInfo : presentationData.strings.Localization_DoNotTranslateInfo)) } else { entries.append(.translateInfo(text: presentationData.strings.Localization_ShowTranslateInfo)) } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index d4158f8c69..15cedf05cc 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -15,6 +15,8 @@ import AnimatedCountLabelNode import AnimatedNavigationStripeNode import ContextUI import RadialStatusNode +import InvisibleInkDustNode +import TextFormat private enum PinnedMessageAnimation { case slideToTop @@ -50,6 +52,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { private let lineNode: AnimatedNavigationStripeNode private let titleNode: AnimatedCountLabelNode private let textNode: TextNode + private var dustNode: InvisibleInkDustNode? private let imageNode: TransformImageNode private let imageNodeContainer: ASDisplayNode @@ -451,7 +454,25 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), titleStrings) - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: foldLineBreaks(descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId).0), font: Font.regular(15.0), textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) + let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId) + + let messageText: NSAttributedString + let textFont = Font.regular(15.0) + if isText { + let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in + if case .Spoiler = entity.type { + return true + } else { + return false + } + } + let textColor = theme.chat.inputPanel.primaryTextColor + messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + } else { + messageText = NSAttributedString(string: foldLineBreaks(textString), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) + } + + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) Queue.mainQueue().async { if let strongSelf = self { @@ -463,7 +484,26 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize())) strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size) - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) + + let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size) + strongSelf.textNode.frame = textFrame + + if !textLayout.spoilers.isEmpty { + let dustNode: InvisibleInkDustNode + if let current = strongSelf.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: nil) + dustNode.isUserInteractionEnabled = false + strongSelf.dustNode = dustNode + strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.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: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + } else if let dustNode = strongSelf.dustNode { + dustNode.removeFromSupernode() + strongSelf.dustNode = nil + } let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight)) animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame) diff --git a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift b/submodules/TelegramUI/Sources/ChatTextInputMenu.swift index 630f06b617..81fedc3d29 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMenu.swift @@ -27,11 +27,11 @@ final class ChatTextInputMenu { UIMenuController.shared.menuItems = [] case .format: UIMenuController.shared.menuItems = [ + UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:"))), UIMenuItem(title: self.stringBold, action: Selector(("formatAttributesBold:"))), UIMenuItem(title: self.stringItalic, action: Selector(("formatAttributesItalic:"))), UIMenuItem(title: self.stringMonospace, action: Selector(("formatAttributesMonospace:"))), UIMenuItem(title: self.stringLink, action: Selector(("formatAttributesLink:"))), - UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:"))), UIMenuItem(title: self.stringStrikethrough, action: Selector(("formatAttributesStrikethrough:"))), UIMenuItem(title: self.stringUnderline, action: Selector(("formatAttributesUnderline:"))) ] diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 1ef0d2ffde..6f28bbcbda 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -352,7 +352,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } + private var currentState: ChatTextInputState? func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, accessoryItems: [ChatTextInputAccessoryItem], animated: Bool) { + self.currentState = state + if state.inputText.length != 0 && self.textInputNode == nil { self.loadTextInputNode() } @@ -713,6 +716,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } textInputNode.frame = 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 - self.textInputViewInternalInsets.bottom)) + textInputNode.view.layoutIfNeeded() + self.updateSpoiler() } self.textInputBackgroundNode.isUserInteractionEnabled = false @@ -1793,10 +1798,143 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } private func updateSpoiler() { + guard let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { + return + } + + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor + + var rects: [CGRect] = [] + + if let attributedText = textInputNode.attributedText { + let beginning = textInputNode.textView.beginningOfDocument + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in + if let _ = attributes[ChatTextInputAttributes.spoiler] { + func addSpoiler(startIndex: Int, endIndex: Int) { + if let start = textInputNode.textView.position(from: beginning, offset: startIndex), let end = textInputNode.textView.position(from: start, offset: endIndex - startIndex), let textRange = textInputNode.textView.textRange(from: start, to: end) { + let textRects = textInputNode.textView.selectionRects(for: textRange) + for textRect in textRects { + rects.append(textRect.rect.insetBy(dx: 1.0, dy: 1.0).offsetBy(dx: 0.0, dy: 1.0)) + } + } + } + + var startIndex: Int? + var currentIndex: Int? + + let nsString = (attributedText.string as NSString) + nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in + if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil { + if let currentStartIndex = startIndex { + startIndex = nil + let endIndex = range.location + addSpoiler(startIndex: currentStartIndex, endIndex: endIndex) + } + } else if startIndex == nil { + startIndex = range.location + } + currentIndex = range.location + range.length + } + + if let currentStartIndex = startIndex, let currentIndex = currentIndex { + startIndex = nil + let endIndex = currentIndex + addSpoiler(startIndex: currentStartIndex, endIndex: endIndex) + } + } + }) + } + + if !rects.isEmpty { + let dustNode: InvisibleInkDustNode + if let current = self.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: nil) + dustNode.alpha = self.spoilersRevealed ? 0.0 : 1.0 + dustNode.isUserInteractionEnabled = false + textInputNode.textView.addSubview(dustNode.view) + self.dustNode = dustNode + } + dustNode.frame = CGRect(origin: CGPoint(), size: textInputNode.textView.contentSize) + dustNode.update(size: textInputNode.textView.contentSize, color: textColor, rects: rects) + } else if let dustNode = self.dustNode { + dustNode.removeFromSupernode() + self.dustNode = nil + } + } + + private func updateSpoilersRevealed() { guard let textInputNode = self.textInputNode else { return } - print(textInputNode.attributedText?.description ?? "") + + let selectionRange = textInputNode.textView.selectedRange + + var revealed = false + if let attributedText = textInputNode.attributedText { + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in + if let _ = attributes[ChatTextInputAttributes.spoiler] { + if let _ = selectionRange.intersection(range) { + revealed = true + } + } + }) + } + + guard self.spoilersRevealed != revealed else { + return + } + self.spoilersRevealed = revealed + + if revealed { + self.updateInternalSpoilersRevealed(true) + } else { + Queue.mainQueue().after(1.5, { + self.updateInternalSpoilersRevealed(false) + }) + } + } + + private func updateInternalSpoilersRevealed(_ revealed: Bool) { + guard self.spoilersRevealed == revealed, let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { + return + } + + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor + let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor + let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) + + refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed) + + if let state = self.currentState { + textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed) + } + + if textInputNode.textView.subviews.count > 1 { + let containerView = textInputNode.textView.subviews[1] + if let canvasView = containerView.subviews.first { + if let snapshotView = canvasView.snapshotView(afterScreenUpdates: false) { + textInputNode.view.insertSubview(snapshotView, at: 0) + canvasView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } + + if revealed { + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + if let dustNode = self.dustNode { + transition.updateAlpha(node: dustNode, alpha: 0.0) + } + } else { + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + if let dustNode = self.dustNode { + transition.updateAlpha(node: dustNode, alpha: 1.0) + } + } } private func updateCounterTextNode(transition: ContainedViewLayoutTransition) { @@ -2069,6 +2207,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) refreshChatTextInputTypingAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) + + self.updateSpoilersRevealed() } } @@ -2193,6 +2333,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.spoiler), inputMode) } + self.updateSpoilersRevealed() } @objc func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 57550bec1e..5018cf30ef 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -11,6 +11,8 @@ import AccountContext import LocalizedPeerData import PhotoResources import TelegramStringFormatting +import InvisibleInkDustNode +import TextFormat final class ReplyAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() @@ -23,6 +25,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { let iconNode: ASImageNode let titleNode: ImmediateTextNode let textNode: ImmediateTextNode + var dustNode: InvisibleInkDustNode? let imageNode: TransformImageNode private let actionArea: AccessibilityAreaNode @@ -96,6 +99,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { var authorName = "" var text = "" + var isText = true if let forwardInfo = message?.forwardInfo, forwardInfo.flags.contains(.isImported) { if let author = forwardInfo.author { authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) @@ -105,8 +109,34 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { } else if let author = message?.effectiveAuthor { authorName = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) } + + let isMedia: Bool if let message = message { - (text, _, _) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) + switch messageContentKind(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) { + case .text: + isMedia = false + default: + isMedia = true + } + (text, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) + } else { + isMedia = false + } + + let textFont = Font.regular(14.0) + let messageText: NSAttributedString + if isText, let message = message { + let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in + if case .Spoiler = entity.type { + return true + } else { + return false + } + } + let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor + messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + } else { + messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? strongSelf.theme.chat.inputPanel.secondaryTextColor : strongSelf.theme.chat.inputPanel.primaryTextColor) } var updatedMediaReference: AnyMediaReference? @@ -169,22 +199,10 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { updateImageSignal = .single({ _ in return nil }) } } - - let isMedia: Bool - if let message = message { - switch messageContentKind(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: context.account.peerId) { - case .text: - isMedia = false - default: - isMedia = true - } - } else { - isMedia = false - } - + strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string, font: Font.medium(14.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor) - strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: isMedia ? strongSelf.theme.chat.inputPanel.secondaryTextColor : strongSelf.theme.chat.inputPanel.primaryTextColor) - + strongSelf.textNode.attributedText = messageText + let headerString: String if let message = message, message.flags.contains(.Incoming), let author = message.author { headerString = "Reply to message. From: \(EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder))" @@ -295,8 +313,25 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { } let textSize = self.textNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height)) + let textFrame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset - self.textNode.insets.left, y: 25.0 - self.textNode.insets.top), size: textSize) if self.textNode.supernode == self { - self.textNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset - self.textNode.insets.left, y: 25.0 - self.textNode.insets.top), size: textSize) + self.textNode.frame = textFrame + } + + if let textLayout = self.textNode.cachedLayout, !textLayout.spoilers.isEmpty { + if self.dustNode == nil { + let dustNode = InvisibleInkDustNode(textNode: nil) + self.dustNode = dustNode + self.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.textNode) + + } + if let dustNode = self.dustNode { + dustNode.update(size: textFrame.size, color: self.theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) + } + } else if let dustNode = self.dustNode { + self.dustNode = nil + dustNode.removeFromSupernode() } } diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 597539aaf2..2c1bd90901 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -86,7 +86,11 @@ public func textAttributedStringForStateText(_ stateText: NSAttributedString, fo result.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) } else if key == ChatTextInputAttributes.spoiler { result.addAttribute(key, value: value, range: range) - result.addAttribute(NSAttributedString.Key.backgroundColor, value: textColor.withAlphaComponent(0.15), range: range) + if spoilersRevealed { + result.addAttribute(NSAttributedString.Key.backgroundColor, value: textColor.withAlphaComponent(0.15), range: range) + } else { + result.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range) + } } } @@ -472,7 +476,11 @@ public func refreshChatTextInputAttributes(_ textNode: ASEditableTextNode, theme textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) } else if key == ChatTextInputAttributes.spoiler { textNode.textView.textStorage.addAttribute(key, value: value, range: range) - textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: range) + if spoilersRevealed { + textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: range) + } else { + textNode.textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range) + } } } @@ -564,7 +572,11 @@ public func refreshGenericTextInputAttributes(_ textNode: ASEditableTextNode, th textNode.textView.textStorage.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range) } else if key == ChatTextInputAttributes.spoiler { textNode.textView.textStorage.addAttribute(key, value: value, range: range) - textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: UIColor.clear, range: range) + if spoilersRevealed { + textNode.textView.textStorage.addAttribute(NSAttributedString.Key.backgroundColor, value: theme.chat.inputPanel.primaryTextColor.withAlphaComponent(0.15), range: range) + } else { + textNode.textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.clear, range: range) + } } } From d60d2beaa4a2dc643d1b4d45869353b3fcd8bada Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 21 Dec 2021 18:48:21 +0400 Subject: [PATCH 2/4] Don't show Spoiler format option in secret chats --- submodules/TelegramUI/Sources/ChatController.swift | 2 +- .../TelegramUI/Sources/ChatTextInputMenu.swift | 12 +++++++++--- .../TelegramUI/Sources/ChatTextInputPanelNode.swift | 8 +++++++- .../Sources/PeerSelectionTextInputPanelNode.swift | 8 +++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 98ca92e802..da9ccc66f7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9798,7 +9798,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private func getCaptionPanelView() -> TGCaptionPanelView { let presentationData = self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) - var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) + var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil) var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)? var ensureFocusedImpl: (() -> Void)? diff --git a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift b/submodules/TelegramUI/Sources/ChatTextInputMenu.swift index 81fedc3d29..402145b38e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputMenu.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputMenu.swift @@ -17,6 +17,8 @@ final class ChatTextInputMenu { private var stringUnderline: String = "Underline" private var stringSpoiler: String = "Spoiler" + private let hasSpoilers: Bool + private(set) var state: ChatTextInputMenuState = .inactive { didSet { if self.state != oldValue { @@ -26,8 +28,7 @@ final class ChatTextInputMenu { case .general: UIMenuController.shared.menuItems = [] case .format: - UIMenuController.shared.menuItems = [ - UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:"))), + var menuItems: [UIMenuItem] = [ UIMenuItem(title: self.stringBold, action: Selector(("formatAttributesBold:"))), UIMenuItem(title: self.stringItalic, action: Selector(("formatAttributesItalic:"))), UIMenuItem(title: self.stringMonospace, action: Selector(("formatAttributesMonospace:"))), @@ -35,6 +36,10 @@ final class ChatTextInputMenu { UIMenuItem(title: self.stringStrikethrough, action: Selector(("formatAttributesStrikethrough:"))), UIMenuItem(title: self.stringUnderline, action: Selector(("formatAttributesUnderline:"))) ] + if self.hasSpoilers { + menuItems.insert(UIMenuItem(title: self.stringSpoiler, action: Selector(("formatAttributesSpoiler:"))), at: 0) + } + UIMenuController.shared.menuItems = menuItems } } @@ -43,7 +48,8 @@ final class ChatTextInputMenu { private var observer: NSObjectProtocol? - init() { + init(hasSpoilers: Bool = false) { + self.hasSpoilers = hasSpoilers self.observer = NotificationCenter.default.addObserver(forName: UIMenuController.didHideMenuNotification, object: nil, queue: nil, using: { [weak self] _ in self?.back() }) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 6f28bbcbda..38ca07e01b 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -296,7 +296,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { var isMediaDeleted: Bool = false - private let inputMenu = ChatTextInputMenu() + private let inputMenu: ChatTextInputMenu private var theme: PresentationTheme? private var strings: PresentationStrings? @@ -451,6 +451,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { init(presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) { self.presentationInterfaceState = presentationInterfaceState + var hasSpoilers = true + if presentationInterfaceState.chatLocation.peerId.namespace == Namespaces.Peer.SecretChat { + hasSpoilers = false + } + self.inputMenu = ChatTextInputMenu(hasSpoilers: hasSpoilers) + self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true diff --git a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift b/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift index f1a3b97b39..775e22681c 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionTextInputPanelNode.swift @@ -141,7 +141,7 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A private var presentationInterfaceState: ChatPresentationInterfaceState? private var initializedPlaceholder = false - private let inputMenu = ChatTextInputMenu() + private let inputMenu: ChatTextInputMenu private var theme: PresentationTheme? private var strings: PresentationStrings? @@ -241,6 +241,12 @@ class PeerSelectionTextInputPanelNode: ChatInputPanelNode, TGCaptionPanelView, A self.presentationInterfaceState = presentationInterfaceState self.isCaption = isCaption + var hasSpoilers = true + if presentationInterfaceState.chatLocation.peerId.namespace == Namespaces.Peer.SecretChat { + hasSpoilers = false + } + self.inputMenu = ChatTextInputMenu(hasSpoilers: hasSpoilers) + self.textInputContainerBackgroundNode = ASImageNode() self.textInputContainerBackgroundNode.isUserInteractionEnabled = false self.textInputContainerBackgroundNode.displaysAsynchronously = false From 52cfe331ac08eadc383c085d1854746b6eacdc52 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 21 Dec 2021 19:35:39 +0400 Subject: [PATCH 3/4] Various Improvements --- .../ChatInterfaceStateContextMenus.swift | 26 ++++++--- .../Sources/ChatTextInputPanelNode.swift | 54 +++++++++++-------- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 1f04a3f9c3..77ed83fa29 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -698,7 +698,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState resourceAvailable = false } - if (!messages[0].text.isEmpty || resourceAvailable || diceEmoji != nil) && !chatPresentationInterfaceState.copyProtectionEnabled { + var messageText: String = "" + for message in messages { + if !message.text.isEmpty { + if messageText.isEmpty { + messageText = message.text + } else { + messageText = "" + break + } + } + } + + if (!messageText.isEmpty || resourceAvailable || diceEmoji != nil) && !chatPresentationInterfaceState.copyProtectionEnabled { let message = messages[0] var isExpired = false for media in message.media { @@ -728,7 +740,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let restrictedText = restrictedText { storeMessageTextInPasteboard(restrictedText, entities: nil) } else { - storeMessageTextInPasteboard(message.text, entities: messageEntities) + storeMessageTextInPasteboard(messageText, entities: messageEntities) } Queue.mainQueue().after(0.2, { @@ -744,7 +756,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState |> deliverOnMainQueue).start(next: { data in if data.complete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = UIImage(data: imageData) { - if !message.text.isEmpty { + if !messageText.isEmpty { copyTextWithEntities() } else { UIPasteboard.general.image = image @@ -770,20 +782,20 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState f(.default) }))) - if canTranslateText(context: context, text: message.text, showTranslate: translationSettings.showTranslate, ignoredLanguages: translationSettings.ignoredLanguages) { + if canTranslateText(context: context, text: messageText, showTranslate: translationSettings.showTranslate, ignoredLanguages: translationSettings.ignoredLanguages) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: message.text), .translate) + controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: messageText), .translate) f(.default) }))) } - if isSpeakSelectionEnabled() && !message.text.isEmpty { + if isSpeakSelectionEnabled() && !messageText.isEmpty { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuSpeak, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: message.text), .speak) + controllerInteraction.performTextSelectionAction(0, NSAttributedString(string: messageText), .speak) f(.default) }))) } diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 38ca07e01b..266761ef42 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -352,10 +352,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - private var currentState: ChatTextInputState? func updateInputTextState(_ state: ChatTextInputState, keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, accessoryItems: [ChatTextInputAccessoryItem], animated: Bool) { - self.currentState = state - if state.inputText.length != 0 && self.textInputNode == nil { self.loadTextInputNode() } @@ -1870,7 +1867,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - private func updateSpoilersRevealed() { + private func updateSpoilersRevealed(animated: Bool = true) { guard let textInputNode = self.textInputNode else { return } @@ -1894,15 +1891,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.spoilersRevealed = revealed if revealed { - self.updateInternalSpoilersRevealed(true) + self.updateInternalSpoilersRevealed(true, animated: animated) } else { Queue.mainQueue().after(1.5, { - self.updateInternalSpoilersRevealed(false) + self.updateInternalSpoilersRevealed(false, animated: true) }) } } - private func updateInternalSpoilersRevealed(_ revealed: Bool) { + private func updateInternalSpoilersRevealed(_ revealed: Bool, animated: Bool) { guard self.spoilersRevealed == revealed, let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { return } @@ -1913,11 +1910,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { refreshChatTextInputAttributes(textInputNode, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed) - if let state = self.currentState { - textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed) - } - - if textInputNode.textView.subviews.count > 1 { + textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed) + + if textInputNode.textView.subviews.count > 1, animated { let containerView = textInputNode.textView.subviews[1] if let canvasView = containerView.subviews.first { if let snapshotView = canvasView.snapshotView(afterScreenUpdates: false) { @@ -1930,16 +1925,20 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } - if revealed { - let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) - if let dustNode = self.dustNode { - transition.updateAlpha(node: dustNode, alpha: 0.0) - } - } else { - let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) - if let dustNode = self.dustNode { - transition.updateAlpha(node: dustNode, alpha: 1.0) + if animated { + if revealed { + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + if let dustNode = self.dustNode { + transition.updateAlpha(node: dustNode, alpha: 0.0) + } + } else { + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) + if let dustNode = self.dustNode { + transition.updateAlpha(node: dustNode, alpha: 1.0) + } } + } else if let dustNode = self.dustNode { + dustNode.alpha = revealed ? 0.0 : 1.0 } } @@ -2336,10 +2335,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { @objc func formatAttributesSpoiler(_ sender: Any) { self.inputMenu.back() + + var animated = false + if let attributedText = self.textInputNode?.attributedText { + attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in + if let _ = attributes[ChatTextInputAttributes.spoiler] { + animated = true + } + }) + } + self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.spoiler), inputMode) } - self.updateSpoilersRevealed() + + self.updateSpoilersRevealed(animated: animated) } @objc func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { From 1a8b151446d3a088e7c5431f24c660210a22f968 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 21 Dec 2021 21:02:38 +0400 Subject: [PATCH 4/4] Various Improvements --- .../Sources/Node/ChatListItem.swift | 2 +- submodules/Display/Source/TextNode.swift | 51 +++++++++++++++---- .../ChatItemGalleryFooterContentNode.swift | 2 +- .../Sources/InvisibleInkDustNode.swift | 19 +++---- .../ChatInterfaceStateContextMenus.swift | 6 ++- .../Sources/ChatMessageNotificationItem.swift | 2 +- .../Sources/ChatMessageReplyInfoNode.swift | 2 +- .../ChatMessageTextBubbleContentNode.swift | 2 +- .../ChatPinnedMessageTitlePanelNode.swift | 2 +- .../Sources/ChatTextInputPanelNode.swift | 2 +- .../PeerInfoScreenLabeledValueItem.swift | 2 +- .../Sources/ReplyAccessoryPanelNode.swift | 2 +- 12 files changed, 62 insertions(+), 32 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 5b0a72e542..72502f1f39 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1757,7 +1757,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.dustNode = dustNode strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode) } - dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) + dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) } else if let dustNode = strongSelf.dustNode { diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 8871464fdf..5cff2c7bbd 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -44,14 +44,16 @@ private final class TextNodeLine { let isRTL: Bool let strikethroughs: [TextNodeStrikethrough] let spoilers: [TextNodeSpoiler] + let spoilerWords: [TextNodeSpoiler] - init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler]) { + init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler], spoilerWords: [TextNodeSpoiler]) { self.line = line self.frame = frame self.range = range self.isRTL = isRTL self.strikethroughs = strikethroughs self.spoilers = spoilers + self.spoilerWords = spoilerWords } } @@ -174,6 +176,7 @@ public final class TextNodeLayout: NSObject { fileprivate let displaySpoilers: Bool public let hasRTL: Bool public let spoilers: [(NSRange, CGRect)] + public let spoilerWords: [(NSRange, CGRect)] fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) { self.attributedString = attributedString @@ -199,14 +202,17 @@ public final class TextNodeLayout: NSObject { self.displaySpoilers = displaySpoilers var hasRTL = false var spoilers: [(NSRange, CGRect)] = [] + var spoilerWords: [(NSRange, CGRect)] = [] for line in lines { if line.isRTL { hasRTL = true } spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY)) }) + spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: line.frame.minX, dy: line.frame.minY)) }) } self.hasRTL = hasRTL self.spoilers = spoilers + self.spoilerWords = spoilerWords } public func areLinesEqual(to other: TextNodeLayout) -> Bool { @@ -952,6 +958,7 @@ public class TextNode: ASDisplayNode { while true { var strikethroughs: [TextNodeStrikethrough] = [] var spoilers: [TextNodeSpoiler] = [] + var spoilerWords: [TextNodeSpoiler] = [] var lineConstrainedWidth = constrainedSize.width var lineConstrainedWidthDelta: CGFloat = 0.0 @@ -973,7 +980,7 @@ public class TextNode: ASDisplayNode { let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth)) - func addSpoiler(line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + func addSpoiler(line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int) { var secondaryLeftOffset: CGFloat = 0.0 let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset) var leftOffset = floor(rawLeftOffset) @@ -988,7 +995,25 @@ public class TextNode: ASDisplayNode { rightOffset = ceil(secondaryRightOffset) } - spoilers.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent))) + spoilers.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset), height: ascent + descent))) + } + + func addSpoilerWord(line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) { + var secondaryLeftOffset: CGFloat = 0.0 + let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset) + var leftOffset = floor(rawLeftOffset) + if !rawLeftOffset.isEqual(to: secondaryLeftOffset) { + leftOffset = floor(secondaryLeftOffset) + } + + var secondaryRightOffset: CGFloat = 0.0 + let rawRightOffset = CTLineGetOffsetForStringIndex(line, endIndex, &secondaryRightOffset) + var rightOffset = ceil(rawRightOffset) + if !rawRightOffset.isEqual(to: secondaryRightOffset) { + rightOffset = ceil(secondaryRightOffset) + } + + spoilerWords.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent))) } var isLastLine = false @@ -1056,7 +1081,7 @@ public class TextNode: ASDisplayNode { if let currentStartIndex = startIndex { startIndex = nil let endIndex = range.location - addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) + addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) } } else if startIndex == nil { startIndex = range.location @@ -1067,8 +1092,10 @@ public class TextNode: ASDisplayNode { if let currentStartIndex = startIndex, let currentIndex = currentIndex { startIndex = nil let endIndex = currentIndex - addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex, rightInset: truncated ? 12.0 : 0.0) + addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex, rightInset: truncated ? 12.0 : 0.0) } + + addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length - 1) } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) @@ -1098,7 +1125,7 @@ public class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers)) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords)) break } else { if lineCharacterCount > 0 { @@ -1135,7 +1162,7 @@ public class TextNode: ASDisplayNode { if let currentStartIndex = startIndex { startIndex = nil let endIndex = range.location - addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) + addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) } } else if startIndex == nil { startIndex = range.location @@ -1146,8 +1173,10 @@ public class TextNode: ASDisplayNode { if let currentStartIndex = startIndex, let currentIndex = currentIndex { startIndex = nil let endIndex = currentIndex - addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) + addSpoilerWord(line: coreTextLine, ascent: ascent, descent: descent, startIndex: currentStartIndex, endIndex: endIndex) } + + addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length - 1) } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) @@ -1176,7 +1205,7 @@ public class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers)) + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords)) } else { if !lines.isEmpty { layoutSize.height += fontLineSpacing @@ -1293,7 +1322,7 @@ public class TextNode: ASDisplayNode { if layout.displaySpoilers && !line.spoilers.isEmpty { context.saveGState() var clipRects: [CGRect] = [] - for spoiler in line.spoilers { + for spoiler in line.spoilerWords { clipRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) } context.clip(to: clipRects) @@ -1328,7 +1357,7 @@ public class TextNode: ASDisplayNode { if layout.displaySpoilers { context.restoreGState() } else { - for spoiler in line.spoilers { + for spoiler in line.spoilerWords { clearRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) } } diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 070be199d5..df4179027e 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -737,7 +737,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } if let dustNode = self.dustNode { - dustNode.update(size: textFrame.size, color: .white, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) + dustNode.update(size: textFrame.size, color: .white, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) } } else { diff --git a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift index 82980fab56..db2f20a0b6 100644 --- a/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift +++ b/submodules/InvisibleInkDustNode/Sources/InvisibleInkDustNode.swift @@ -45,7 +45,7 @@ private let emitterMaskImage: UIImage = { }() public class InvisibleInkDustNode: ASDisplayNode { - private var currentParams: (size: CGSize, color: UIColor, rects: [CGRect])? + private var currentParams: (size: CGSize, color: UIColor, rects: [CGRect], wordRects: [CGRect])? private weak var textNode: TextNode? private let textMaskNode: ASDisplayNode @@ -162,7 +162,7 @@ public class InvisibleInkDustNode: ASDisplayNode { } @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { - guard let (size, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else { + guard let (size, _, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else { return } @@ -192,8 +192,6 @@ public class InvisibleInkDustNode: ASDisplayNode { self?.emitterNode.view.mask = nil }) self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - - self.isRevealedUpdated(true) } Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) { @@ -209,7 +207,6 @@ public class InvisibleInkDustNode: ASDisplayNode { let timeToRead = min(45.0, ceil(max(4.0, textLength * 0.04))) Queue.mainQueue().after(timeToRead * UIView.animationDurationFactor()) { self.isRevealed = false - self.isRevealedUpdated(false) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .linear) transition.updateAlpha(node: self, alpha: 1.0) @@ -218,12 +215,12 @@ public class InvisibleInkDustNode: ASDisplayNode { } private func updateEmitter() { - guard let (size, color, rects) = self.currentParams else { + guard let (size, color, _, wordRects) = self.currentParams else { return } self.emitter?.color = color.cgColor - self.emitterLayer?.setValue(rects, forKey: "emitterRects") + self.emitterLayer?.setValue(wordRects, forKey: "emitterRects") self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size) let radius = max(size.width, size.height) @@ -231,15 +228,15 @@ public class InvisibleInkDustNode: ASDisplayNode { self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff") var square: Float = 0.0 - for rect in rects { + for rect in wordRects { square += Float(rect.width * rect.height) } self.emitter?.birthRate = square * 0.4 } - public func update(size: CGSize, color: UIColor, rects: [CGRect]) { - self.currentParams = (size, color, rects) + public func update(size: CGSize, color: UIColor, rects: [CGRect], wordRects: [CGRect]) { + self.currentParams = (size, color, rects, wordRects) self.emitterNode.frame = CGRect(origin: CGPoint(), size: size) self.emitterMaskNode.frame = self.emitterNode.bounds @@ -252,7 +249,7 @@ public class InvisibleInkDustNode: ASDisplayNode { } public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - if let (_, _, rects) = self.currentParams { + if let (_, _, rects, _) = self.currentParams { for rect in rects { if rect.contains(point) { return true diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 77ed83fa29..ddb7994b10 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -82,7 +82,11 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo } } } else if let author = message.author, message.author?.id != message.id.peerId, author.id.namespace == Namespaces.Peer.CloudChannel && message.id.peerId.namespace == Namespaces.Peer.CloudChannel, !message.flags.contains(.Incoming) { - hasEditRights = true + if message.media.contains(where: { $0 is TelegramMediaInvoice }) { + hasEditRights = false + } else { + hasEditRights = true + } } else if message.author?.id == message.id.peerId, let peer = message.peers[message.id.peerId] { if let peer = peer as? TelegramChannel { switch peer.info { diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift index 6ea076b896..589d06b240 100644 --- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift @@ -410,7 +410,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.insertSubnode(dustNode, aboveSubnode: self.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: presentationData.theme.inAppNotification.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: presentationData.theme.inAppNotification.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) }) } else if let dustNode = self.dustNode { dustNode.removeFromSupernode() self.dustNode = nil diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index e4b5d79f78..504c9a139f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -254,7 +254,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { node.contentNode.insertSubnode(dustNode, aboveSubnode: 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: dustColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: dustColor, 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) }) } else if let dustNode = node.dustNode { dustNode.removeFromSupernode() node.dustNode = nil diff --git a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift index 45d9529641..0360664c40 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTextBubbleContentNode.swift @@ -405,7 +405,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode) } 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, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: messageTheme.secondaryTextColor, 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) }) } else if let spoilerTextNode = strongSelf.spoilerTextNode { strongSelf.spoilerTextNode = nil spoilerTextNode.removeFromSupernode() diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 15cedf05cc..e84f1a03aa 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -499,7 +499,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.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: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.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) }) } else if let dustNode = strongSelf.dustNode { dustNode.removeFromSupernode() strongSelf.dustNode = nil diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 266761ef42..d6900ef29b 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1860,7 +1860,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.dustNode = dustNode } dustNode.frame = CGRect(origin: CGPoint(), size: textInputNode.textView.contentSize) - dustNode.update(size: textInputNode.textView.contentSize, color: textColor, rects: rects) + dustNode.update(size: textInputNode.textView.contentSize, color: textColor, rects: rects, wordRects: rects) } else if let dustNode = self.dustNode { dustNode.removeFromSupernode() self.dustNode = nil diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index 5ab17d132e..67b0302480 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -279,7 +279,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { textColorValue = presentationData.theme.list.itemAccentColor } - self.expandNode.attributedText = NSAttributedString(string: presentationData.strings.PeerInfo_BioExpand.uppercased(), font: Font.medium(15.0), textColor: presentationData.theme.list.itemAccentColor) + self.expandNode.attributedText = NSAttributedString(string: presentationData.strings.PeerInfo_BioExpand, font: Font.medium(15.0), textColor: presentationData.theme.list.itemAccentColor) let expandSize = self.expandNode.updateLayout(CGSize(width: width, height: 100.0)) self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor) diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 5018cf30ef..89553036a2 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -326,7 +326,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { } if let dustNode = self.dustNode { - dustNode.update(size: textFrame.size, color: self.theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + dustNode.update(size: textFrame.size, color: self.theme.chat.inputPanel.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) }) dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) } } else if let dustNode = self.dustNode {