mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
08b76943c2
@ -1205,7 +1205,7 @@ swift_library(
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI",
|
||||
"//submodules/TelegramUI",
|
||||
"//submodules/TelegramUI/Components/ShareExtensionContext"
|
||||
],
|
||||
)
|
||||
|
@ -2,6 +2,9 @@ import UIKit
|
||||
import TelegramUI
|
||||
import BuildConfig
|
||||
import ShareExtensionContext
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
@objc(ShareRootController)
|
||||
class ShareRootController: UIViewController {
|
||||
@ -46,7 +49,24 @@ class ShareRootController: UIViewController {
|
||||
|
||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||
|
||||
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild), getExtensionContext: { [weak self] in
|
||||
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appBuildType: buildConfig.isAppStoreBuild ? .public : .internal, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil), useBetaFeatures: !buildConfig.isAppStoreBuild, makeTempContext: { accountManager, appLockContext, applicationBindings, InitialPresentationDataAndSettings, networkArguments in
|
||||
return makeTempContext(
|
||||
sharedContainerPath: appGroupUrl.path,
|
||||
rootPath: rootPath,
|
||||
appGroupPath: appGroupUrl.path,
|
||||
accountManager: accountManager,
|
||||
appLockContext: appLockContext,
|
||||
encryptionParameters: ValueBoxEncryptionParameters(
|
||||
forceEncryptionIfNoSet: false,
|
||||
key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.0)!,
|
||||
salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.1)!
|
||||
),
|
||||
applicationBindings: applicationBindings,
|
||||
initialPresentationDataAndSettings: InitialPresentationDataAndSettings,
|
||||
networkArguments: networkArguments,
|
||||
buildConfig: buildConfig
|
||||
)
|
||||
}), getExtensionContext: { [weak self] in
|
||||
return self?.extensionContext
|
||||
})
|
||||
}
|
||||
|
@ -10149,6 +10149,51 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Premium.BoostByGiftDescription" = "Boost your channel by gifting your subscribers Telegram Premium. [Get boosts >]()";
|
||||
|
||||
"ChatContextMenu.QuoteSelectionTip" = "Hold on a word, then move cursor to select more| text to quote.";
|
||||
|
||||
"Chat.ReplyPanel.ReplyToQuoteBy" = "Reply to Quote by %@";
|
||||
"Chat.ReplyPanel.ReplyTo" = "Reply to %@";
|
||||
"Chat.ReplyPanel.AccessibilityReplyToMessageFrom" = "Reply to message. From: %@";
|
||||
"Chat.ReplyPanel.AccessibilityReplyToYourMessage" = "Reply to your message.";
|
||||
"Chat.ReplyPanel.AccessibilityReplyToMessage" = "Reply to message.";
|
||||
"Chat.ReplyPanel.HintReplyOptions" = "Tap here for reply options";
|
||||
"Chat.ReplyPanel.HintReplyOptionsShort" = "Tap here for options";
|
||||
|
||||
"Chat.ToastQuoteChatUnavailbalePrivateChannel" = "This quote is from a private channel";
|
||||
"Chat.ToastQuoteChatUnavailbalePrivateGroup" = "This quote is from a private group";
|
||||
"Chat.ToastQuoteChatUnavailbalePrivateChat" = "This quote is from a private chat";
|
||||
|
||||
"Chat.TitleQuoteSelection" = "Reply to Quote";
|
||||
"Chat.TitleReply" = "Reply to Message";
|
||||
"Chat.TitleLinkOptions" = "Link Preview Settings";
|
||||
"Chat.SubtitleQuoteSelectionTip" = "You can select a specific part to quote";
|
||||
"Chat.SubtitleLinkListTip" = "Tap on a link to generate its preview";
|
||||
|
||||
"Chat.ToastQuoteNotFound" = "Quote not found";
|
||||
|
||||
"TextFormat.Quote" = "Quote";
|
||||
|
||||
"TextSelection.SelectAll" = "Select All";
|
||||
|
||||
"Conversation.MessageOptionsApplyChanges" = "Apply Changes";
|
||||
"Conversation.ForwardOptionsCancel" = "Do Not Forward";
|
||||
"Conversation.MessageOptionsTabForward" = "Forward";
|
||||
"Conversation.MessageOptionsQuoteSelectedPart" = "Quote Selected Part";
|
||||
"Conversation.MessageOptionsQuoteSelect" = "Select Specific Quote";
|
||||
"Conversation.MessageOptionsQuoteRemove" = "Remove Quote";
|
||||
"Conversation.MessageOptionsReplyInAnotherChat" = "Reply in Another Chat";
|
||||
"Conversation.MessageOptionsReplyCancel" = "Do Not Reply";
|
||||
"Conversation.MessageOptionsTabReply" = "Reply";
|
||||
"Conversation.MoveReplyToAnotherChatTitle" = "Reply in...";
|
||||
"Conversation.MessageOptionsLinkMoveUp" = "Move Up";
|
||||
"Conversation.MessageOptionsLinkMoveDown" = "Move Down";
|
||||
"Conversation.MessageOptionsShrinkImage" = "Shrink Photo";
|
||||
"Conversation.MessageOptionsEnlargeImage" = "Enlarge Photo";
|
||||
"Conversation.MessageOptionsShrinkVideo" = "Shrink Video";
|
||||
"Conversation.MessageOptionsEnlargeVideo" = "Enlarge Video";
|
||||
"Conversation.LinkOptionsCancel" = "Do Not Preview";
|
||||
"Conversation.MessageOptionsTabLink" = "Link";
|
||||
|
||||
"Stats.Boosts.ShowMoreBoosts_1" = "Show %@ More Boost";
|
||||
"Stats.Boosts.ShowMoreBoosts_any" = "Show %@ More Boosts";
|
||||
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/ConfettiEffect:ConfettiEffect",
|
||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/ActivityIndicator",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -15,6 +15,7 @@ import MimeTypes
|
||||
import ConfettiEffect
|
||||
import TelegramUniversalVideoContent
|
||||
import SolidRoundedButtonNode
|
||||
import ActivityIndicator
|
||||
|
||||
private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? {
|
||||
if useTotalFileAllocatedSize {
|
||||
@ -886,3 +887,48 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatImportTempController: ViewController {
|
||||
override public var _presentedInModal: Bool {
|
||||
get {
|
||||
return true
|
||||
} set(value) {
|
||||
}
|
||||
}
|
||||
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
public init(presentationData: PresentationData) {
|
||||
let presentationData = presentationData
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(presentationData.theme.list.itemAccentColor, 22.0, 1.0, false))
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData))
|
||||
|
||||
self.title = presentationData.strings.ChatImport_Title
|
||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
//self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}
|
||||
|
||||
override public func displayNodeDidLoad() {
|
||||
super.displayNodeDidLoad()
|
||||
|
||||
self.displayNode.addSubnode(self.activityIndicator)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
let navigationHeight = self.navigationLayout(layout: layout).navigationFrame.maxY
|
||||
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: navigationHeight + floor((layout.size.height - navigationHeight - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
@ -304,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)
|
||||
|
||||
@ -469,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
|
||||
@ -514,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)
|
||||
|
||||
@ -574,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()
|
||||
@ -608,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 {
|
||||
@ -622,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()
|
||||
})
|
||||
@ -636,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
|
||||
}
|
||||
@ -772,10 +782,12 @@ final class ChatSendMessageActionSheetControllerNode: ViewControllerTracingNode,
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -404,8 +404,7 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
}
|
||||
icon = UIImage(bundleImageName: "Chat/Context Menu/Tip")
|
||||
case .quoteSelection:
|
||||
//TODO:localize
|
||||
var rawText = "Hold on a word, then move cursor to select more| text to quote."
|
||||
var rawText = presentationData.strings.ChatContextMenu_QuoteSelectionTip
|
||||
if let range = rawText.range(of: "|") {
|
||||
rawText.removeSubrange(range)
|
||||
self.text = rawText
|
||||
@ -458,7 +457,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
|
||||
|
@ -755,9 +755,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case .controller:
|
||||
case let .controller(source):
|
||||
if let contentNode = controllerContentNode {
|
||||
var defaultContentSize = CGSize(width: layout.size.width - 12.0 * 2.0, height: layout.size.height - 12.0 * 2.0 - contentTopInset - layout.safeInsets.bottom)
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
defaultContentSize.width = min(defaultContentSize.width, 400.0)
|
||||
}
|
||||
defaultContentSize.height = min(defaultContentSize.height, 460.0)
|
||||
|
||||
let contentSize: CGSize
|
||||
@ -770,7 +773,30 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
isContentResizeableVertically = true
|
||||
}
|
||||
|
||||
contentRect = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) * 0.5), y: floor((layout.size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
if let transitionInfo = source.transitionInfo(), let (sourceView, sourceRect) = transitionInfo.sourceNode() {
|
||||
let sourcePoint = sourceView.convert(sourceRect.center, to: self.view)
|
||||
|
||||
contentRect = CGRect(origin: CGPoint(x: sourcePoint.x - floor(contentSize.width * 0.5), y: sourcePoint.y - floor(contentSize.height * 0.5)), size: contentSize)
|
||||
if contentRect.origin.x < 0.0 {
|
||||
contentRect.origin.x = 0.0
|
||||
}
|
||||
if contentRect.origin.y < 0.0 {
|
||||
contentRect.origin.y = 0.0
|
||||
}
|
||||
if contentRect.origin.x + contentRect.width > layout.size.width {
|
||||
contentRect.origin.x = layout.size.width - contentRect.width
|
||||
}
|
||||
if contentRect.origin.y + contentRect.height > layout.size.height {
|
||||
contentRect.origin.y = layout.size.height - contentRect.height
|
||||
}
|
||||
} else {
|
||||
contentRect = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) * 0.5), y: floor((layout.size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
}
|
||||
} else {
|
||||
contentRect = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) * 0.5), y: floor((layout.size.height - contentSize.height) * 0.5)), size: contentSize)
|
||||
}
|
||||
|
||||
contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))
|
||||
} else {
|
||||
return
|
||||
@ -815,9 +841,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
let actionsStackPresentation: ContextControllerActionsStackNode.Presentation
|
||||
switch self.source {
|
||||
case .location, .reference:
|
||||
case .location, .reference, .controller:
|
||||
actionsStackPresentation = .inline
|
||||
case .extracted, .controller:
|
||||
case .extracted:
|
||||
actionsStackPresentation = .modal
|
||||
}
|
||||
|
||||
@ -841,7 +867,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
contentHeight = min(contentHeight, contentRect.height)
|
||||
contentHeight = max(contentHeight, 200.0)
|
||||
|
||||
contentRect = CGRect(origin: CGPoint(x: 12.0, y: floor((layout.size.height - contentHeight) * 0.5)), size: CGSize(width: layout.size.width - 12.0 * 2.0, height: contentHeight))
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
} else {
|
||||
contentRect = CGRect(origin: CGPoint(x: contentRect.minX, y: floor(contentRect.midY - contentHeight * 0.5)), size: CGSize(width: contentRect.width, height: contentHeight))
|
||||
}
|
||||
}
|
||||
|
||||
var isAnimatingOut = false
|
||||
|
@ -570,12 +570,21 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
transition: .immediate
|
||||
)
|
||||
case .controller:
|
||||
self.backgroundNode.updateColor(
|
||||
color: presentationData.theme.contextMenu.dimColor,
|
||||
enableBlur: true,
|
||||
forceKeepBlur: true,
|
||||
transition: .immediate
|
||||
)
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
self.backgroundNode.updateColor(
|
||||
color: UIColor(white: 0.0, alpha: 0.4),
|
||||
enableBlur: false,
|
||||
forceKeepBlur: false,
|
||||
transition: .immediate
|
||||
)
|
||||
} else {
|
||||
self.backgroundNode.updateColor(
|
||||
color: presentationData.theme.contextMenu.dimColor,
|
||||
enableBlur: true,
|
||||
forceKeepBlur: true,
|
||||
transition: .immediate
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
))
|
||||
}
|
||||
|
@ -1353,13 +1353,23 @@ open class TextNode: ASDisplayNode {
|
||||
var lineAscent: CGFloat = 0.0
|
||||
var lineDescent: CGFloat = 0.0
|
||||
let lineWidth = CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, nil)
|
||||
|
||||
var isRTL = false
|
||||
let glyphRuns = CTLineGetGlyphRuns(line) as NSArray
|
||||
if glyphRuns.count != 0 {
|
||||
let run = glyphRuns[0] as! CTRun
|
||||
if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) {
|
||||
isRTL = true
|
||||
}
|
||||
}
|
||||
|
||||
calculatedSegment.lines.append(TextNodeLine(
|
||||
line: line,
|
||||
frame: CGRect(origin: CGPoint(x: additionalOffsetX, y: 0.0), size: CGSize(width: lineWidth + additionalSegmentRightInset, height: lineAscent + lineDescent)),
|
||||
ascent: lineAscent,
|
||||
descent: lineDescent,
|
||||
range: NSRange(location: currentLineStartIndex, length: lineCharacterCount),
|
||||
isRTL: false,
|
||||
isRTL: isRTL && !segment.isBlockQuote,
|
||||
strikethroughs: [],
|
||||
spoilers: [],
|
||||
spoilerWords: [],
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1815593308] = { return Api.DocumentAttribute.parse_documentAttributeImageSize($0) }
|
||||
dict[1662637586] = { return Api.DocumentAttribute.parse_documentAttributeSticker($0) }
|
||||
dict[-745541182] = { return Api.DocumentAttribute.parse_documentAttributeVideo($0) }
|
||||
dict[-620277848] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||
dict[1070397423] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
|
||||
dict[-1764723459] = { return Api.EmailVerification.parse_emailVerificationApple($0) }
|
||||
dict[-1842457175] = { return Api.EmailVerification.parse_emailVerificationCode($0) }
|
||||
|
@ -1,13 +1,13 @@
|
||||
public extension Api {
|
||||
indirect enum DraftMessage: TypeConstructorDescription {
|
||||
case draftMessage(flags: Int32, replyTo: Api.MessageReplyHeader?, message: String, entities: [Api.MessageEntity]?, media: Api.MessageMedia?, date: Int32)
|
||||
case draftMessage(flags: Int32, replyTo: Api.InputReplyTo?, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?, date: Int32)
|
||||
case draftMessageEmpty(flags: Int32, date: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .draftMessage(let flags, let replyTo, let message, let entities, let media, let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(-620277848)
|
||||
buffer.appendInt32(1070397423)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)}
|
||||
@ -42,9 +42,9 @@ public extension Api {
|
||||
public static func parse_draftMessage(_ reader: BufferReader) -> DraftMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.MessageReplyHeader?
|
||||
var _2: Api.InputReplyTo?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.InputReplyTo
|
||||
} }
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
@ -52,9 +52,9 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
|
||||
} }
|
||||
var _5: Api.MessageMedia?
|
||||
var _5: Api.InputMedia?
|
||||
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.MessageMedia
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.InputMedia
|
||||
} }
|
||||
var _6: Int32?
|
||||
_6 = reader.readInt32()
|
||||
|
@ -709,7 +709,7 @@ extension StoreMessage {
|
||||
if mediaValue is TelegramMediaWebpage {
|
||||
let leadingPreview = (flags & (1 << 27)) != 0
|
||||
|
||||
if let webpageAttributes {
|
||||
if let webpageAttributes = webpageAttributes {
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: leadingPreview, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded, isSafe: webpageAttributes.isSafe))
|
||||
}
|
||||
}
|
||||
|
@ -1153,7 +1153,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
medias.append(mediaValue)
|
||||
|
||||
if mediaValue is TelegramMediaWebpage {
|
||||
if let webpageAttributes {
|
||||
if let webpageAttributes = webpageAttributes {
|
||||
attributes.append(WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: webpageAttributes.forceLargeMedia, isManuallyAdded: webpageAttributes.isManuallyAdded, isSafe: webpageAttributes.isSafe))
|
||||
}
|
||||
}
|
||||
@ -1538,27 +1538,43 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
var replySubject: EngineMessageReplySubject?
|
||||
if let replyToMsgHeader = replyToMsgHeader {
|
||||
switch replyToMsgHeader {
|
||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities):
|
||||
let _ = replyHeader
|
||||
let _ = replyMedia
|
||||
let _ = replyToTopId
|
||||
case let .inputReplyToMessage(_, replyToMsgId, topMsgId, replyToPeerId, quoteText, quoteEntities):
|
||||
let _ = topMsgId
|
||||
|
||||
var quote: EngineMessageReplyQuote?
|
||||
if let quoteText = quoteText {
|
||||
quote = EngineMessageReplyQuote(
|
||||
text: quoteText,
|
||||
entities: messageTextEntitiesFromApiEntities(quoteEntities ?? []),
|
||||
media: textMediaAndExpirationTimerFromApiMedia(replyMedia, accountPeerId).media
|
||||
media: nil
|
||||
)
|
||||
}
|
||||
|
||||
if let replyToMsgId = replyToMsgId {
|
||||
replySubject = EngineMessageReplySubject(
|
||||
messageId: MessageId(peerId: replyToPeerId?.peerId ?? peer.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId),
|
||||
quote: quote
|
||||
)
|
||||
var parsedReplyToPeerId: PeerId?
|
||||
switch replyToPeerId {
|
||||
case let .inputPeerChannel(channelId, _):
|
||||
parsedReplyToPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
|
||||
case let .inputPeerChannelFromMessage(_, _, channelId):
|
||||
parsedReplyToPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
|
||||
case let .inputPeerChat(chatId):
|
||||
parsedReplyToPeerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))
|
||||
case .inputPeerEmpty:
|
||||
break
|
||||
case .inputPeerSelf:
|
||||
parsedReplyToPeerId = accountPeerId
|
||||
case let .inputPeerUser(userId, _):
|
||||
parsedReplyToPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
case let .inputPeerUserFromMessage(_, _, userId):
|
||||
parsedReplyToPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
case .messageReplyStoryHeader:
|
||||
|
||||
replySubject = EngineMessageReplySubject(
|
||||
messageId: MessageId(peerId: parsedReplyToPeerId ?? peer.peerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId),
|
||||
quote: quote
|
||||
)
|
||||
case .inputReplyToStory:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ private final class AccountPresenceManagerImpl {
|
||||
self.shouldKeepOnlinePresenceDisposable = (shouldKeepOnlinePresence
|
||||
|> distinctUntilChanged
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] value in
|
||||
guard let self else {
|
||||
guard let `self` = self else {
|
||||
return
|
||||
}
|
||||
if self.wasOnline != value {
|
||||
|
@ -5,22 +5,30 @@ public class OutgoingChatContextResultMessageAttribute: MessageAttribute {
|
||||
public let queryId: Int64
|
||||
public let id: String
|
||||
public let hideVia: Bool
|
||||
public let webpageUrl: String?
|
||||
|
||||
public init(queryId: Int64, id: String, hideVia: Bool) {
|
||||
public init(queryId: Int64, id: String, hideVia: Bool, webpageUrl: String?) {
|
||||
self.queryId = queryId
|
||||
self.id = id
|
||||
self.hideVia = hideVia
|
||||
self.webpageUrl = webpageUrl
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.queryId = decoder.decodeInt64ForKey("q", orElse: 0)
|
||||
self.id = decoder.decodeStringForKey("i", orElse: "")
|
||||
self.hideVia = decoder.decodeBoolForKey("v", orElse: false)
|
||||
self.webpageUrl = decoder.decodeOptionalStringForKey("wurl")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.queryId, forKey: "q")
|
||||
encoder.encodeString(self.id, forKey: "i")
|
||||
encoder.encodeBool(self.hideVia, forKey: "v")
|
||||
if let webpageUrl = self.webpageUrl {
|
||||
encoder.encodeString(webpageUrl, forKey: "wurl")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "wurl")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,13 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId:
|
||||
replyToMessageId = EngineMessageReplySubject(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: MessageId.Id(clamping: threadId)), quote: nil)
|
||||
}
|
||||
|
||||
var webpageUrl: String?
|
||||
if case let .webpage(_, _, url, _, _) = result.message {
|
||||
webpageUrl = url
|
||||
}
|
||||
|
||||
var attributes: [MessageAttribute] = []
|
||||
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia))
|
||||
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: result.queryId, id: result.id, hideVia: hideVia, webpageUrl: webpageUrl))
|
||||
if !hideVia {
|
||||
attributes.append(InlineBotMessageAttribute(peerId: botId, title: nil))
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, m
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> PremiumGiveawayInfo? in
|
||||
if let result {
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .giveawayInfo(flags, startDate, joinedTooEarlyDate, adminDisallowedChatId, disallowedCountry):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
|
@ -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 */
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
@ -355,7 +355,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, NSLayoutManagerDele
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
public init(disableTiling: Bool) {
|
||||
self.customTextContainer = ChatInputTextContainer(size: CGSize(width: 100.0, height: 100000.0))
|
||||
self.customLayoutManager = NSLayoutManager()
|
||||
self.customTextStorage = NSTextStorage()
|
||||
@ -368,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
|
||||
@ -558,6 +558,10 @@ 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
|
||||
@ -751,7 +755,6 @@ private final class QuoteBackgroundView: UIView {
|
||||
if self.theme != theme {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundColor = theme.background
|
||||
self.iconView.tintColor = theme.foreground
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -608,35 +606,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 {
|
||||
@ -820,7 +827,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
|
||||
@ -1103,7 +1110,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
|
||||
}
|
||||
|
||||
@ -1155,38 +1162,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()
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -2243,8 +2252,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
|
||||
}
|
||||
}
|
||||
|
@ -2841,7 +2841,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
|
||||
}
|
||||
}
|
||||
@ -3352,6 +3352,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
|
||||
|
||||
@ -3424,6 +3427,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
|
||||
}
|
||||
|
||||
@ -3472,7 +3495,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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -323,19 +323,19 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: titleColor)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
textString = NSAttributedString(string: replyForward.quote?.text ?? "Message")
|
||||
textString = NSAttributedString(string: replyForward.quote?.text ?? arguments.presentationData.strings.VoiceOver_ChatList_Message)
|
||||
if let media = replyForward.quote?.media {
|
||||
if let text = replyForward.quote?.text, !text.isEmpty {
|
||||
isMedia = false
|
||||
} else {
|
||||
if let contentKind = mediaContentKind(EngineMedia(media), message: nil, strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId) {
|
||||
let (string, _) = stringForMediaKind(contentKind, strings: arguments.strings)
|
||||
textString = string
|
||||
} else {
|
||||
textString = NSAttributedString(string: "Message")
|
||||
textString = NSAttributedString(string: arguments.presentationData.strings.VoiceOver_ChatList_Message)
|
||||
}
|
||||
isMedia = true
|
||||
}
|
||||
isMedia = true
|
||||
} else {
|
||||
isMedia = false
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -674,14 +674,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 {
|
||||
@ -698,7 +698,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)
|
||||
|
||||
|
@ -242,26 +242,33 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
let icon: UIImage?
|
||||
icon = UIImage(bundleImageName: "Chat/Input/Accessory Panels/PanelTextChannelIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
//TODO:localize
|
||||
if let _ = strongSelf.quote {
|
||||
if let icon {
|
||||
let string = "Reply to Quote by "
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))]
|
||||
titleText.append(.icon(icon))
|
||||
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)))
|
||||
if let icon {
|
||||
let rawString: PresentationStrings.FormattedString
|
||||
if strongSelf.quote != nil {
|
||||
rawString = strongSelf.strings.Chat_ReplyPanel_ReplyToQuoteBy(peer.debugDisplayTitle)
|
||||
} else {
|
||||
rawString = strongSelf.strings.Chat_ReplyPanel_ReplyTo(peer.debugDisplayTitle)
|
||||
}
|
||||
} else {
|
||||
if let icon {
|
||||
let string = "Reply to "
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))]
|
||||
if let nameRange = rawString.ranges.first {
|
||||
titleText = []
|
||||
|
||||
let rawNsString = rawString.string as NSString
|
||||
if nameRange.range.lowerBound != 0 {
|
||||
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: 0, length: nameRange.range.lowerBound)), font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
titleText.append(.icon(icon))
|
||||
titleText.append(.text(NSAttributedString(string: peer.debugDisplayTitle, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)))
|
||||
|
||||
if nameRange.range.upperBound != rawNsString.length {
|
||||
titleText.append(.text(NSAttributedString(string: rawNsString.substring(with: NSRange(location: nameRange.range.upperBound, length: rawNsString.length - nameRange.range.upperBound)), font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
} else {
|
||||
titleText.append(.text(NSAttributedString(string: rawString.string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let _ = strongSelf.quote {
|
||||
//TODO:localize
|
||||
let string = "Reply to Quote by \(authorName)"
|
||||
let string = strongSelf.strings.Chat_ReplyPanel_ReplyToQuoteBy(authorName).string
|
||||
titleText = [.text(NSAttributedString(string: string, font: Font.medium(15.0), textColor: strongSelf.theme.chat.inputPanel.panelControlAccentColor))]
|
||||
} else {
|
||||
let string = strongSelf.strings.Conversation_ReplyMessagePanelTitle(authorName).string
|
||||
@ -297,11 +304,11 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
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))"
|
||||
headerString = strongSelf.strings.Chat_ReplyPanel_AccessibilityReplyToMessageFrom(EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string
|
||||
} else if let message = message, !message.flags.contains(.Incoming) {
|
||||
headerString = "Reply to your message"
|
||||
headerString = strongSelf.strings.Chat_ReplyPanel_AccessibilityReplyToYourMessage
|
||||
} else {
|
||||
headerString = "Reply to message"
|
||||
headerString = strongSelf.strings.Chat_ReplyPanel_AccessibilityReplyToMessage
|
||||
}
|
||||
strongSelf.actionArea.accessibilityLabel = "\(headerString).\n\(text)"
|
||||
|
||||
@ -326,11 +333,10 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
Queue.mainQueue().after(3.0) {
|
||||
if let snapshotView = strongSelf.textNode.view.snapshotContentTree() {
|
||||
let text: String
|
||||
//TODO:localize
|
||||
if let (size, _, _) = strongSelf.validLayout, size.width > 320.0 {
|
||||
text = "Tap here for reply options"
|
||||
text = strongSelf.strings.Chat_ReplyPanel_HintReplyOptions
|
||||
} else {
|
||||
text = "Tap here for forwarding options"
|
||||
text = strongSelf.strings.Chat_ReplyPanel_HintReplyOptionsShort
|
||||
}
|
||||
strongSelf.textIsOptions = true
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
@ -2708,8 +2708,6 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
let inputPlaceholder: MessageInputPanelComponent.Placeholder
|
||||
if let stealthModeTimeout = component.stealthModeTimeout {
|
||||
//TODO:localize
|
||||
|
||||
let minutes = Int(stealthModeTimeout / 60)
|
||||
let seconds = Int(stealthModeTimeout % 60)
|
||||
|
||||
|
@ -229,28 +229,11 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Apply Changes", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_MessageOptionsApplyChanges, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
/*items.append(.action(ContextMenuActionItem(text: messagesCount == 1 ? presentationData.strings.Conversation_ForwardOptions_SendMessage : presentationData.strings.Conversation_ForwardOptions_SendMessages, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController, weak chatController] c, f in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
if let selectedMessageIds = (chatController as? ChatControllerImpl)?.selectedMessageIds {
|
||||
var forwardMessageIds = selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? []
|
||||
forwardMessageIds = forwardMessageIds.filter { selectedMessageIds.contains($0) }
|
||||
selfController.updateChatPresentationInterfaceState(interactive: false, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds) }) })
|
||||
}
|
||||
|
||||
selfController.controllerInteraction?.sendCurrentMessage(false)
|
||||
|
||||
f(.default)
|
||||
})))*/
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Do Not Forward", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptionsCancel, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
@ -273,10 +256,9 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
return (ContextController.Source(
|
||||
id: AnyHashable(OptionsId.forward),
|
||||
title: "Forward",
|
||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabForward,
|
||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
||||
items: items |> map { ContextController.Items(id: AnyHashable("forward"), content: .list($0)) }
|
||||
), dismissedForCancel)
|
||||
@ -304,8 +286,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if replySubject.quote != nil {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Quote Selected Part", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsQuoteSelectedPart, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteSelected"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak selfController, weak chatController] _, f in
|
||||
guard let selfController, let chatController else {
|
||||
@ -345,8 +326,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
f(.default)
|
||||
})))
|
||||
} else if let message = messages.first, !message.text.isEmpty {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Select Specific Quote", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Quote"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController, weak chatController] c, _ in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsQuoteSelect, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Quote"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController, weak chatController] c, _ in
|
||||
guard let selfController, let chatController else {
|
||||
return
|
||||
}
|
||||
@ -371,8 +351,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
})))
|
||||
subItems.append(.separator)
|
||||
|
||||
//TODO:localize
|
||||
subItems.append(.action(ContextMenuActionItem(text: "Quote Selected Part", icon: { theme in
|
||||
subItems.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsQuoteSelectedPart, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteSelected"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak selfController, weak contentNode] _, f in
|
||||
guard let selfController, let contentNode else {
|
||||
@ -438,8 +417,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
}
|
||||
|
||||
if canReplyInAnotherChat {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Reply in Another Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsReplyInAnotherChat, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
@ -455,13 +433,13 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Apply Changes", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsApplyChanges, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
if replySubject.quote != nil {
|
||||
items.append(.action(ContextMenuActionItem(text: "Remove Quote", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteRemove"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsQuoteRemove, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/QuoteRemove"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
@ -472,7 +450,7 @@ private func generateChatReplyOptionItems(selfController: ChatControllerImpl, ch
|
||||
selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) })
|
||||
})))
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: "Do Not Reply", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsReplyCancel, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController] c, f in
|
||||
f(.default)
|
||||
|
||||
guard let selfController else {
|
||||
@ -539,10 +517,9 @@ private func chatReplyOptions(selfController: ChatControllerImpl, sourceNode: AS
|
||||
selfController.controllerInteraction?.performTextSelectionAction(message, canCopy, text, action)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
return ContextController.Source(
|
||||
id: AnyHashable(OptionsId.reply),
|
||||
title: "Reply",
|
||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabReply,
|
||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
||||
items: items
|
||||
)
|
||||
@ -564,7 +541,7 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj
|
||||
updatedPresentationData: selfController.updatedPresentationData,
|
||||
filter: filter,
|
||||
hasFilters: true,
|
||||
title: "Reply in...", //TODO:localize
|
||||
title: selfController.presentationData.strings.Conversation_MoveReplyToAnotherChatTitle,
|
||||
attemptSelection: { peer, _ in
|
||||
attemptSelectionImpl?(peer)
|
||||
},
|
||||
@ -759,8 +736,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
do {
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: linkOptions.linkBelowText ? "Move Up" : "Move Down", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: linkOptions.linkBelowText ? selfController.presentationData.strings.Conversation_MessageOptionsLinkMoveUp : selfController.presentationData.strings.Conversation_MessageOptionsLinkMoveDown, icon: { theme in
|
||||
return nil
|
||||
}, iconAnimation: ContextMenuActionItem.IconAnimation(
|
||||
name: linkOptions.linkBelowText ? "message_preview_sort_above" : "message_preview_sort_below"
|
||||
@ -784,15 +760,14 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
}
|
||||
|
||||
if case let .Loaded(content) = linkOptions.webpage.content, let isMediaLargeByDefault = content.isMediaLargeByDefault, isMediaLargeByDefault {
|
||||
//TODO:localize
|
||||
let shrinkTitle: String
|
||||
let enlargeTitle: String
|
||||
if let file = content.file, file.isVideo {
|
||||
shrinkTitle = "Shrink Video"
|
||||
enlargeTitle = "Enlarge Video"
|
||||
shrinkTitle = selfController.presentationData.strings.Conversation_MessageOptionsShrinkVideo
|
||||
enlargeTitle = selfController.presentationData.strings.Conversation_MessageOptionsEnlargeVideo
|
||||
} else {
|
||||
shrinkTitle = "Shrink Photo"
|
||||
enlargeTitle = "Enlarge Photo"
|
||||
shrinkTitle = selfController.presentationData.strings.Conversation_MessageOptionsShrinkImage
|
||||
enlargeTitle = selfController.presentationData.strings.Conversation_MessageOptionsEnlargeImage
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: linkOptions.largeMedia ? shrinkTitle : enlargeTitle, icon: { _ in
|
||||
@ -830,13 +805,11 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
items.append(.separator)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Apply Changes", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_MessageOptionsApplyChanges, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Do Not Preview", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController, weak chatController] c, f in
|
||||
items.append(.action(ContextMenuActionItem(text: selfController.presentationData.strings.Conversation_LinkOptionsCancel, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak selfController, weak chatController] c, f in
|
||||
guard let selfController else {
|
||||
return
|
||||
}
|
||||
@ -921,10 +894,9 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
return ContextController.Source(
|
||||
id: AnyHashable(OptionsId.link),
|
||||
title: "Link",
|
||||
title: selfController.presentationData.strings.Conversation_MessageOptionsTabLink,
|
||||
source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, passthroughTouches: true)),
|
||||
items: items
|
||||
)
|
||||
|
@ -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
|
||||
@ -4929,18 +4949,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
let text: String
|
||||
if let peer = peer as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
text = "This quote is from a private channel"
|
||||
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChannel
|
||||
} else {
|
||||
text = "This quote is from a private group"
|
||||
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateGroup
|
||||
}
|
||||
} else if peer is TelegramGroup {
|
||||
text = "This quote is from a private group"
|
||||
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateGroup
|
||||
} else {
|
||||
text = "This quote is from a private chat"
|
||||
text = self.presentationData.strings.Chat_ToastQuoteChatUnavailbalePrivateChat
|
||||
}
|
||||
self.controllerInteraction?.displayUndo(.info(title: nil, text: text, timeout: nil, customUndoText: nil))
|
||||
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
|
||||
@ -5273,15 +5292,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if !selectionState.canQuote {
|
||||
return nil
|
||||
}
|
||||
//TODO:localize
|
||||
return "You can select a specific part to quote"
|
||||
return presentationData.strings.Chat_SubtitleQuoteSelectionTip
|
||||
}
|
||||
case let .link(link):
|
||||
subtitleTextSignal = link.options
|
||||
|> map { options -> String? in
|
||||
if options.hasAlternativeLinks {
|
||||
//TODO:localize
|
||||
return "Tap on a link to generate its preview"
|
||||
return presentationData.strings.Chat_SubtitleLinkListTip
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -5333,12 +5350,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
if case let .messageOptions(_, _, info) = presentationInterfaceState.subject {
|
||||
if case .reply = info {
|
||||
//TODO:localize
|
||||
let titleContent: ChatTitleContent
|
||||
if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote {
|
||||
titleContent = .custom("Reply to Quote", subtitleText, false)
|
||||
titleContent = .custom(presentationInterfaceState.strings.Chat_TitleQuoteSelection, subtitleText, false)
|
||||
} else {
|
||||
titleContent = .custom("Reply to Message", subtitleText, false)
|
||||
titleContent = .custom(presentationInterfaceState.strings.Chat_TitleReply, subtitleText, false)
|
||||
}
|
||||
if strongSelf.chatTitleView?.titleContent != titleContent {
|
||||
if strongSelf.chatTitleView?.titleContent != nil {
|
||||
@ -5347,8 +5363,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.chatTitleView?.titleContent = titleContent
|
||||
}
|
||||
} else if case .link = info {
|
||||
//TODO:localize
|
||||
strongSelf.chatTitleView?.titleContent = .custom("Link Preview Settings", subtitleText, false)
|
||||
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitleLinkOptions, subtitleText, false)
|
||||
} else if displayedCount == 1 {
|
||||
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false)
|
||||
} else {
|
||||
@ -8012,8 +8027,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if message.text.contains(quote) {
|
||||
hasQuote = true
|
||||
} else {
|
||||
//TODO:localize
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: "Quote not found", timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Chat_ToastQuoteNotFound, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3401,7 +3401,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if !found {
|
||||
//TODO:localize
|
||||
let authorName: String = (replyMessage.author.flatMap(EnginePeer.init))?.compactDisplayTitle ?? ""
|
||||
let errorTextData = self.chatPresentationInterfaceState.strings.Chat_ErrorQuoteOutdatedText(authorName)
|
||||
let errorText = errorTextData.string
|
||||
|
@ -3710,8 +3710,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
|
||||
var children: [UIAction] = []
|
||||
|
||||
//TODO:localize
|
||||
children.append(UIAction(title: "Quote", image: nil) { [weak self] (action) in
|
||||
children.append(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in
|
||||
if let strongSelf = self {
|
||||
strongSelf.formatAttributesQuote(strongSelf)
|
||||
}
|
||||
|
56
submodules/TelegramUI/Sources/MakeTempAccountContext.swift
Normal file
56
submodules/TelegramUI/Sources/MakeTempAccountContext.swift
Normal file
@ -0,0 +1,56 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import AccountContext
|
||||
import BuildConfig
|
||||
import TelegramPresentationData
|
||||
|
||||
private var sharedTempContext: SharedAccountContextImpl?
|
||||
|
||||
public func makeTempContext(
|
||||
sharedContainerPath: String,
|
||||
rootPath: String,
|
||||
appGroupPath: String,
|
||||
accountManager: AccountManager<TelegramAccountManagerTypes>,
|
||||
appLockContext: AppLockContext,
|
||||
encryptionParameters: ValueBoxEncryptionParameters,
|
||||
applicationBindings: TelegramApplicationBindings,
|
||||
initialPresentationDataAndSettings: InitialPresentationDataAndSettings,
|
||||
networkArguments: NetworkInitializationArguments,
|
||||
buildConfig: BuildConfig
|
||||
) -> Signal<AccountContext, NoError> {
|
||||
let sharedContext = sharedTempContext ?? SharedAccountContextImpl(
|
||||
mainWindow: nil,
|
||||
sharedContainerPath: sharedContainerPath,
|
||||
basePath: rootPath,
|
||||
encryptionParameters: encryptionParameters,
|
||||
accountManager: accountManager,
|
||||
appLockContext: appLockContext,
|
||||
applicationBindings: applicationBindings,
|
||||
initialPresentationDataAndSettings: initialPresentationDataAndSettings,
|
||||
networkArguments: networkArguments,
|
||||
hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1,
|
||||
rootPath: rootPath,
|
||||
legacyBasePath: appGroupPath,
|
||||
apsNotificationToken: .single(nil),
|
||||
voipNotificationToken: .single(nil),
|
||||
firebaseSecretStream: .never(),
|
||||
setNotificationCall: { _ in
|
||||
},
|
||||
navigateToChat: { _, _, _ in
|
||||
}, displayUpgradeProgress: { _ in
|
||||
},
|
||||
appDelegate: nil
|
||||
)
|
||||
sharedTempContext = sharedContext
|
||||
|
||||
return sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> mapToSignal { accounts -> Signal<AccountContext, NoError> in
|
||||
guard let context = accounts.primary else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(context)
|
||||
}
|
||||
}
|
@ -160,7 +160,7 @@ final class NetworkBroadcastPartSource: BroadcastPartSource {
|
||||
switch result.status {
|
||||
case let .data(dataValue):
|
||||
#if DEBUG
|
||||
if let debugDumpDirectory {
|
||||
if let debugDumpDirectory = debugDumpDirectory {
|
||||
let tempFilePath = debugDumpDirectory.path + "/\(timestampMilliseconds).mp4"
|
||||
let _ = try? dataValue.subdata(in: 32 ..< dataValue.count).write(to: URL(fileURLWithPath: tempFilePath))
|
||||
print("Dump stream part: \(tempFilePath)")
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -738,8 +740,7 @@ public final class TextSelectionNode: ASDisplayNode {
|
||||
|
||||
let realFullRange = NSRange(location: 0, length: attributedString.length)
|
||||
if range != realFullRange {
|
||||
//TODO:localize
|
||||
actions.append(ContextMenuAction(content: .text(title: "Select All", accessibilityLabel: "Select All"), action: { [weak self] in
|
||||
actions.append(ContextMenuAction(content: .text(title: self.strings.TextSelection_SelectAll, accessibilityLabel: self.strings.TextSelection_SelectAll), action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -755,7 +756,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
|
||||
|
Loading…
x
Reference in New Issue
Block a user