diff --git a/Telegram/BUILD b/Telegram/BUILD index bd8e5cbd95..d460a2b6d5 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1205,7 +1205,7 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/TelegramUI:TelegramUI", + "//submodules/TelegramUI", "//submodules/TelegramUI/Components/ShareExtensionContext" ], ) diff --git a/Telegram/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift index fe182a9759..0808a033f4 100644 --- a/Telegram/Share/ShareRootController.swift +++ b/Telegram/Share/ShareRootController.swift @@ -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 }) } diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 29455f71cc..2dbf0e08e9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/ChatImportUI/BUILD b/submodules/ChatImportUI/BUILD index 0fe6a7232f..2ee541d7da 100644 --- a/submodules/ChatImportUI/BUILD +++ b/submodules/ChatImportUI/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/ConfettiEffect:ConfettiEffect", "//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/ActivityIndicator", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 863aa2aee5..0442a760a1 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -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)) + } +} + diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift index bff3646adc..d28ff3534d 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetControllerNode.swift @@ -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) } diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 3b1f27a6cc..59f89de751 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -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 diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index b86814d61b..dd554d8402 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -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 diff --git a/submodules/ContextUI/Sources/ContextSourceContainer.swift b/submodules/ContextUI/Sources/ContextSourceContainer.swift index 3eb6dffbf8..a12b0cd501 100644 --- a/submodules/ContextUI/Sources/ContextSourceContainer.swift +++ b/submodules/ContextUI/Sources/ContextSourceContainer.swift @@ -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 + ) + } } } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 8922a6dd6e..6dedb8bc91 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -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, diff --git a/submodules/Display/Source/ContextMenuContainerNode.swift b/submodules/Display/Source/ContextMenuContainerNode.swift index 85472178db..3eab3808b4 100644 --- a/submodules/Display/Source/ContextMenuContainerNode.swift +++ b/submodules/Display/Source/ContextMenuContainerNode.swift @@ -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 + } } } } diff --git a/submodules/Display/Source/ContextMenuController.swift b/submodules/Display/Source/ContextMenuController.swift index a4ec1ace2f..f48e5b69e4 100644 --- a/submodules/Display/Source/ContextMenuController.swift +++ b/submodules/Display/Source/ContextMenuController.swift @@ -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 )) } diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index e74ec20299..945c0d271d 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -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: [], diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift index c21deca4ff..8740ca13d0 100644 --- a/submodules/Display/Source/TooltipControllerNode.swift +++ b/submodules/Display/Source/TooltipControllerNode.swift @@ -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) } diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 95d13978c4..1049e30430 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -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 } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 36c96b3cc1..22ab048ed3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -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) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index b1797c5ec8..cbb6492fe0 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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) } diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index 8918bec605..b54c445aa2 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -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() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index e32283b2f7..aa8b7fb981 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -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)) } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 1e4c226452..f195353990 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -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 } } diff --git a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift index 445008c1b9..8c8a4fa31d 100644 --- a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift +++ b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingChatContextResultMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingChatContextResultMessageAttribute.swift index b6117cd959..aa9d797551 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingChatContextResultMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingChatContextResultMessageAttribute.swift @@ -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") + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index 5abaa52dbb..02f176275a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -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)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift index 82d096254f..1476796827 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/GiftCodes.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h index f32b117179..fc04921265 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/PublicHeaders/ChatInputTextViewImpl/ChatInputTextViewImpl.h @@ -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 */ diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m index e3b7594264..a92c35160a 100755 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/ChatInputTextViewImpl/Sources/ChatInputTextViewImpl.m @@ -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]; } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift index c1768ea438..c3d6faa980 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index fbc8d24ba6..940cdfe505 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index f626758f2e..0ff3374a9c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index b763bd5e92..1d9a30a9f1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 9e6febc9d9..25bbee8956 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift index d2d3f5bb71..59a16518f5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 033a6f3b67..e76d3563f6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index f7d38e91a4..d8abb87555 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -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 + }) + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 14f33a7653..27f5d03d33 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index 3192d5cf4a..b8f2132b43 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift index 101a027a0a..28ebc31ffb 100644 --- a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuActionNode.swift b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuActionNode.swift index b735547c2e..4d803b4a96 100644 --- a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuActionNode.swift +++ b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuActionNode.swift @@ -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) } } } diff --git a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuController.swift b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuController.swift index 6179b75a03..70ffcd512f 100644 --- a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuController.swift +++ b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuController.swift @@ -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() } diff --git a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift index b2d18f418d..be2e5f3e76 100644 --- a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift +++ b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift index 3840453de9..7066c6aff7 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift @@ -130,8 +130,9 @@ public struct ShareRootControllerInitializationData { public let appVersion: String public let bundleData: Data? public let useBetaFeatures: Bool + public let makeTempContext: (AccountManager, AppLockContext, TelegramApplicationBindings, InitialPresentationDataAndSettings, NetworkInitializationArguments) -> Signal - public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?, useBetaFeatures: Bool) { + public init(appBundleId: String, appBuildType: TelegramAppBuildType, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?, useBetaFeatures: Bool, makeTempContext: @escaping (AccountManager, AppLockContext, TelegramApplicationBindings, InitialPresentationDataAndSettings, NetworkInitializationArguments) -> Signal) { self.appBundleId = appBundleId self.appBuildType = appBuildType self.appGroupPath = appGroupPath @@ -142,6 +143,7 @@ public struct ShareRootControllerInitializationData { self.appVersion = appVersion self.bundleData = bundleData self.useBetaFeatures = useBetaFeatures + self.makeTempContext = makeTempContext } } @@ -349,6 +351,21 @@ public class ShareRootControllerImpl { let environment = ShareControllerEnvironmentExtension(presentationData: presentationData) let initializationData = self.initializationData + let networkArguments = NetworkInitializationArguments( + apiId: initializationData.apiId, + apiHash: initializationData.apiHash, + languagesCategory: initializationData.languagesCategory, + appVersion: initializationData.appVersion, + voipMaxLayer: 0, + voipVersions: [], + appData: .single(nil), + autolockDeadine: .single(nil), + encryptionProvider: OpenSSLEncryptionProvider(), + deviceModelName: nil, + useBetaFeatures: initializationData.useBetaFeatures, + isICloudEnabled: false + ) + let accountData: Signal<(ShareControllerEnvironment, ShareControllerAccountContext, [ShareControllerSwitchableAccount]), NoError> = accountManager.accountRecords() |> take(1) |> mapToSignal { view -> Signal<(ShareControllerEnvironment, ShareControllerAccountContext, [ShareControllerSwitchableAccount]), NoError> in @@ -364,21 +381,6 @@ public class ShareRootControllerImpl { continue } - let networkArguments = NetworkInitializationArguments( - apiId: initializationData.apiId, - apiHash: initializationData.apiHash, - languagesCategory: initializationData.languagesCategory, - appVersion: initializationData.appVersion, - voipMaxLayer: 0, - voipVersions: [], - appData: .single(nil), - autolockDeadine: .single(nil), - encryptionProvider: OpenSSLEncryptionProvider(), - deviceModelName: nil, - useBetaFeatures: initializationData.useBetaFeatures, - isICloudEnabled: false - ) - signals.append(standaloneStateManager( accountManager: accountManager, networkArguments: networkArguments, @@ -580,12 +582,9 @@ public class ShareRootControllerImpl { strongSelf.currentShareController = shareController strongSelf.mainWindow?.present(shareController, on: .root) } - - //TODO - //context.account.resetStateManagement() } - if !"".isEmpty {/* if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, inputItems.count == 1, let item = inputItems[0] as? NSExtensionItem, let attachments = item.attachments { + if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, inputItems.count == 1, let item = inputItems[0] as? NSExtensionItem, let attachments = item.attachments { for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) { attachment.loadItem(forTypeIdentifier: kUTTypeFileURL as String, completionHandler: { result, error in @@ -601,28 +600,12 @@ public class ShareRootControllerImpl { let fileExtension = (fileName as NSString).pathExtension var archivePathValue: String? + let _ = archivePathValue var otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)] = [] var mainFile: TempBoxFile? let appConfiguration = context.appConfiguration - /* - history_import_filters: { - "zip": { - "main_file_patterns": [ - "_chat\\.txt", - "KakaoTalkChats\\.txt", - "Talk_.*?\\.txt" - ] - }, - "txt": { - "patterns": [ - "^\\[LINE\\]" - ] - } - } - */ - if fileExtension.lowercased() == "zip" { let archivePath = url.path archivePathValue = archivePath @@ -748,489 +731,37 @@ public class ShareRootControllerImpl { } } - if let mainFile = mainFile, let mainFileHeader = extractTextFileHeader(path :mainFile.path) { - final class TempController: ViewController { - override public var _presentedInModal: Bool { - get { - return true - } set(value) { - } - } - - private let activityIndicator: ActivityIndicator - - init(environment: ShareControllerEnvironment) { - let presentationData = environment.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 func displayNodeDidLoad() { - super.displayNodeDidLoad() - - self.displayNode.addSubnode(self.activityIndicator) - } - - override 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)) - } - } + if let mainFile = mainFile, let mainFileHeader = extractTextFileHeader(path: mainFile.path) { + let _ = mainFileHeader let presentationData = environment.presentationData let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme)) strongSelf.navigationController = navigationController - navigationController.viewControllers = [TempController(environment: environment)] + navigationController.viewControllers = [ChatImportTempController(presentationData: environment.presentationData)] strongSelf.mainWindow?.present(navigationController, on: .root) - let _ = (TelegramEngine.HistoryImport(postbox: context.stateManager.postbox, network: context.stateManager.network).getInfo(header: mainFileHeader) - |> deliverOnMainQueue).start(next: { parseInfo in - switch parseInfo { - case let .group(groupTitle): - var attemptSelectionImpl: ((EnginePeer) -> Void)? - var createNewGroupImpl: (() -> Void)? - let controller = PeerSelectionControllerImpl(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in - attemptSelectionImpl?(peer) - }, createNewGroup: { - createNewGroupImpl?() - }, pretendPresentedInModal: true, selectForumThreads: false)) - - controller.customDismiss = { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - } - - controller.peerSelected = { peer, _ in - attemptSelectionImpl?(peer) - } - - controller.navigationPresentation = .default - - let beginWithPeer: (PeerId) -> Void = { peerId in - navigationController.view.endEditing(true) - navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) - } - - attemptSelectionImpl = { peer in - var errorText: String? - if case let .channel(channel) = peer { - if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else if case let .legacyGroup(group) = peer { - switch group.role { - case .creator: - break - default: - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric - } - - if let errorText = errorText { - let presentationData = environment.presentationData - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - } else { - controller.inProgress = true - let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) - |> deliverOnMainQueue).start(next: { result in - controller.inProgress = false - - let presentationData = environment.presentationData - - var errorText: String? - if case let .channel(channel) = peer { - if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else if case let .legacyGroup(group) = peer { - switch group.role { - case .creator: - break - default: - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else if case .user = peer { - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric - } - - if let errorText = errorText { - let presentationData = environment.presentationData - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - } else { - let presentationData = environment.presentationData - let text: String - switch result { - case .allowed: - if let groupTitle = groupTitle { - text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithTitle(groupTitle, peer.debugDisplayTitle).string - } else { - text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithoutTitle(peer.debugDisplayTitle).string - } - case let .alert(textValue): - text = textValue - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { - beginWithPeer(peer.id) - })], parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - } - }, error: { error in - controller.inProgress = false - - let presentationData = environment.presentationData - let errorText: String - switch error { - case .generic: - errorText = presentationData.strings.Login_UnknownError - case .chatAdminRequired: - errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin - case .invalidChatType: - errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType - case .userBlocked: - errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked - case .limitExceeded: - errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded - case .notMutualContact: - errorText = presentationData.strings.ChatImport_UserErrorNotMutual - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - }) - } - } - - createNewGroupImpl = { - let presentationData = environment.presentationData - let resolvedGroupTitle: String - if let groupTitle = groupTitle { - resolvedGroupTitle = groupTitle - } else { - resolvedGroupTitle = "Group" - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_CreateGroupAlertTitle, text: presentationData.strings.ChatImport_CreateGroupAlertText(resolvedGroupTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_CreateGroupAlertImportAction, action: { - var signal: Signal = _internal_createSupergroup(postbox: context.stateManager.postbox, network: context.stateManager.network, stateManager: context.stateManager, title: resolvedGroupTitle, description: nil, username: nil, isForum: false, isForHistoryImport: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - if let strongSelf = self { - strongSelf.mainWindow?.present(controller, on: .root) - } - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - signal = signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let _ = (signal - |> deliverOnMainQueue).start(next: { peerId in - if let peerId = peerId { - beginWithPeer(peerId) - } else { - } - }) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - })], actionLayout: .vertical, parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - } - - navigationController.viewControllers = [controller] - case let .privateChat(title): - let presentationData = environment.presentationData - - var attemptSelectionImpl: ((EnginePeer) -> Void)? - let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages, .excludeSecretChats], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in - attemptSelectionImpl?(peer) - }, pretendPresentedInModal: true, selectForumThreads: true)) - - controller.customDismiss = { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - } - - controller.peerSelected = { peer, _ in - attemptSelectionImpl?(peer) - } - - controller.navigationPresentation = .default - - let beginWithPeer: (PeerId) -> Void = { peerId in - navigationController.view.endEditing(true) - navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) - } - - attemptSelectionImpl = { [weak controller] peer in - controller?.inProgress = true - let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) - |> deliverOnMainQueue).start(next: { result in - controller?.inProgress = false - - let presentationData = environment.presentationData - let text: String - switch result { - case .allowed: - if let title = title { - text = presentationData.strings.ChatImport_SelectionConfirmationUserWithTitle(title, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string - } else { - text = presentationData.strings.ChatImport_SelectionConfirmationUserWithoutTitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string - } - case let .alert(textValue): - text = textValue - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { - beginWithPeer(peer.id) - })], parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - }, error: { error in - controller?.inProgress = false - - let presentationData = environment.presentationData - let errorText: String - switch error { - case .generic: - errorText = presentationData.strings.Login_UnknownError - case .chatAdminRequired: - errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin - case .invalidChatType: - errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType - case .userBlocked: - errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked - case .limitExceeded: - errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded - case .notMutualContact: - errorText = presentationData.strings.ChatImport_UserErrorNotMutual - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - }) - } - - navigationController.viewControllers = [controller] - case let .unknown(peerTitle): - var attemptSelectionImpl: ((EnginePeer) -> Void)? - var createNewGroupImpl: (() -> Void)? - let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.excludeDisabled, .doNotSearchMessages], hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in - attemptSelectionImpl?(peer) - }, createNewGroup: { - createNewGroupImpl?() - }, pretendPresentedInModal: true, selectForumThreads: true)) - - controller.customDismiss = { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - } - - controller.peerSelected = { peer, _ in - attemptSelectionImpl?(peer) - } - - controller.navigationPresentation = .default - - let beginWithPeer: (EnginePeer.Id) -> Void = { peerId in - navigationController.view.endEditing(true) - navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { - //inForeground.set(false) - self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) - }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) - } - - attemptSelectionImpl = { [weak controller] peer in - controller?.inProgress = true - let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) - |> deliverOnMainQueue).start(next: { result in - controller?.inProgress = false - - let presentationData = environment.presentationData - - var errorText: String? - if case let .channel(channel) = peer { - if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else if case let .legacyGroup(group) = peer { - switch group.role { - case .creator: - break - default: - errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin - } - } else if case .user = peer { - } else { - errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric - } - - if let errorText = errorText { - let presentationData = environment.presentationData - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - } else { - let presentationData = environment.presentationData - if case .user = peer { - let text: String - switch result { - case .allowed: - if let title = peerTitle { - text = presentationData.strings.ChatImport_SelectionConfirmationUserWithTitle(title, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string - } else { - text = presentationData.strings.ChatImport_SelectionConfirmationUserWithoutTitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string - } - case let .alert(textValue): - text = textValue - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { - beginWithPeer(peer.id) - })], parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - } else { - let text: String - switch result { - case .allowed: - if let groupTitle = peerTitle { - text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithTitle(groupTitle, peer.debugDisplayTitle).string - } else { - text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithoutTitle(peer.debugDisplayTitle).string - } - case let .alert(textValue): - text = textValue - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { - beginWithPeer(peer.id) - })], parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - } - } - }, error: { error in - controller?.inProgress = false - - let presentationData = environment.presentationData - let errorText: String - switch error { - case .generic: - errorText = presentationData.strings.Login_UnknownError - case .chatAdminRequired: - errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin - case .invalidChatType: - errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType - case .userBlocked: - errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked - case .limitExceeded: - errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded - case .notMutualContact: - errorText = presentationData.strings.ChatImport_UserErrorNotMutual - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - })]) - strongSelf.mainWindow?.present(controller, on: .root) - }) - } - - createNewGroupImpl = { - let presentationData = environment.presentationData - let resolvedGroupTitle: String - if let groupTitle = peerTitle { - resolvedGroupTitle = groupTitle - } else { - resolvedGroupTitle = "Group" - } - let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_CreateGroupAlertTitle, text: presentationData.strings.ChatImport_CreateGroupAlertText(resolvedGroupTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_CreateGroupAlertImportAction, action: { - var signal: Signal = _internal_createSupergroup(postbox: context.stateManager.postbox, network: context.stateManager.network, stateManager: context.stateManager, title: resolvedGroupTitle, description: nil, username: nil, isForum: false, isForHistoryImport: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - if let strongSelf = self { - strongSelf.mainWindow?.present(controller, on: .root) - } - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - signal = signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let _ = (signal - |> deliverOnMainQueue).start(next: { peerId in - if let peerId = peerId { - beginWithPeer(peerId) - } else { - } - }) - }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - })], actionLayout: .vertical, parseMarkdown: true) - strongSelf.mainWindow?.present(controller, on: .root) - } - - navigationController.viewControllers = [controller] - } - }, error: { _ in + if let mainWindow = strongSelf.mainWindow { + attemptChatImport( + context: context, + getExtensionContext: strongSelf.getExtensionContext, + accountManager: accountManager, + appLockContext: appLockContext, + applicationBindings: applicationBindings, + initialPresentationDataAndSettings: initialPresentationDataAndSettings!, + networkInitializationArguments: networkArguments, + presentationData: environment.presentationData, + makeTempContext: initializationData.makeTempContext, + mainWindow: mainWindow, + navigationController: navigationController, + archivePathValue: archivePathValue, + mainFileHeader: mainFileHeader, + mainFile: mainFile, + otherEntries: otherEntries, + beginShare: beginShare + ) + } else { beginShare() - }) + } } else { beginShare() return @@ -1240,7 +771,7 @@ public class ShareRootControllerImpl { return } } - beginShare()*/ + beginShare() } else { beginShare() } @@ -1299,3 +830,447 @@ public class ShareRootControllerImpl { } } } + +private func attemptChatImport( + context: ShareControllerAccountContext, + getExtensionContext: @escaping () -> NSExtensionContext?, + accountManager: AccountManager, + appLockContext: AppLockContext, + applicationBindings: TelegramApplicationBindings, + initialPresentationDataAndSettings: InitialPresentationDataAndSettings, + networkInitializationArguments: NetworkInitializationArguments, + presentationData: PresentationData, + makeTempContext: @escaping (AccountManager, AppLockContext, TelegramApplicationBindings, InitialPresentationDataAndSettings, NetworkInitializationArguments) -> Signal, + mainWindow: Window1, + navigationController: NavigationController, + archivePathValue: String?, + mainFileHeader: String, + mainFile: TempBoxFile, + otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)], + beginShare: @escaping () -> Void +) { + let _ = (makeTempContext( + accountManager, + appLockContext, + applicationBindings, + initialPresentationDataAndSettings, + networkInitializationArguments + ) + |> deliverOnMainQueue).start(next: { context in + context.account.resetStateManagement() + context.account.shouldBeServiceTaskMaster.set(.single(.now)) + + let _ = (TelegramEngine.HistoryImport(postbox: context.account.stateManager.postbox, network: context.account.stateManager.network).getInfo(header: mainFileHeader) + |> deliverOnMainQueue).start(next: { [weak mainWindow] parseInfo in + switch parseInfo { + case let .group(groupTitle): + var attemptSelectionImpl: ((EnginePeer) -> Void)? + var createNewGroupImpl: (() -> Void)? + + let controller = PeerSelectionControllerImpl(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in + attemptSelectionImpl?(peer) + }, createNewGroup: { + createNewGroupImpl?() + }, pretendPresentedInModal: true, selectForumThreads: false)) + + controller.customDismiss = { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + } + + controller.peerSelected = { peer, _ in + attemptSelectionImpl?(peer) + } + + controller.navigationPresentation = .default + + let beginWithPeer: (PeerId) -> Void = { peerId in + navigationController.view.endEditing(true) + navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) + } + + attemptSelectionImpl = { peer in + var errorText: String? + if case let .channel(channel) = peer { + if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else if case let .legacyGroup(group) = peer { + switch group.role { + case .creator: + break + default: + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric + } + + if let errorText = errorText { + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + } else { + controller.inProgress = true + let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) + |> deliverOnMainQueue).start(next: { result in + controller.inProgress = false + + var errorText: String? + if case let .channel(channel) = peer { + if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else if case let .legacyGroup(group) = peer { + switch group.role { + case .creator: + break + default: + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else if case .user = peer { + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric + } + + if let errorText = errorText { + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + } else { + let text: String + switch result { + case .allowed: + if let groupTitle = groupTitle { + text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithTitle(groupTitle, peer.debugDisplayTitle).string + } else { + text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithoutTitle(peer.debugDisplayTitle).string + } + case let .alert(textValue): + text = textValue + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { + beginWithPeer(peer.id) + })], parseMarkdown: true) + mainWindow?.present(controller, on: .root) + } + }, error: { error in + controller.inProgress = false + + let errorText: String + switch error { + case .generic: + errorText = presentationData.strings.Login_UnknownError + case .chatAdminRequired: + errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin + case .invalidChatType: + errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType + case .userBlocked: + errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked + case .limitExceeded: + errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded + case .notMutualContact: + errorText = presentationData.strings.ChatImport_UserErrorNotMutual + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + }) + } + } + + createNewGroupImpl = { + let resolvedGroupTitle: String + if let groupTitle = groupTitle { + resolvedGroupTitle = groupTitle + } else { + resolvedGroupTitle = "Group" + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_CreateGroupAlertTitle, text: presentationData.strings.ChatImport_CreateGroupAlertText(resolvedGroupTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_CreateGroupAlertImportAction, action: { + var signal: Signal = _internal_createSupergroup(postbox: context.account.stateManager.postbox, network: context.account.stateManager.network, stateManager: context.account.stateManager, title: resolvedGroupTitle, description: nil, username: nil, isForum: false, isForHistoryImport: true) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + mainWindow?.present(controller, on: .root) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + let _ = (signal + |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + beginWithPeer(peerId) + } else { + } + }) + }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + })], actionLayout: .vertical, parseMarkdown: true) + mainWindow?.present(controller, on: .root) + } + + navigationController.viewControllers = [controller] + case let .privateChat(title): + var attemptSelectionImpl: ((EnginePeer) -> Void)? + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages, .excludeSecretChats], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in + attemptSelectionImpl?(peer) + }, pretendPresentedInModal: true, selectForumThreads: true)) + + controller.customDismiss = { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + } + + controller.peerSelected = { peer, _ in + attemptSelectionImpl?(peer) + } + + controller.navigationPresentation = .default + + let beginWithPeer: (PeerId) -> Void = { peerId in + navigationController.view.endEditing(true) + navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) + } + + attemptSelectionImpl = { [weak controller] peer in + controller?.inProgress = true + let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) + |> deliverOnMainQueue).start(next: { result in + controller?.inProgress = false + + let text: String + switch result { + case .allowed: + if let title = title { + text = presentationData.strings.ChatImport_SelectionConfirmationUserWithTitle(title, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + } else { + text = presentationData.strings.ChatImport_SelectionConfirmationUserWithoutTitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + } + case let .alert(textValue): + text = textValue + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { + beginWithPeer(peer.id) + })], parseMarkdown: true) + mainWindow?.present(controller, on: .root) + }, error: { error in + controller?.inProgress = false + + let errorText: String + switch error { + case .generic: + errorText = presentationData.strings.Login_UnknownError + case .chatAdminRequired: + errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin + case .invalidChatType: + errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType + case .userBlocked: + errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked + case .limitExceeded: + errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded + case .notMutualContact: + errorText = presentationData.strings.ChatImport_UserErrorNotMutual + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + }) + } + + navigationController.viewControllers = [controller] + case let .unknown(peerTitle): + var attemptSelectionImpl: ((EnginePeer) -> Void)? + var createNewGroupImpl: (() -> Void)? + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.excludeDisabled, .doNotSearchMessages], hasContactSelector: true, hasGlobalSearch: false, title: presentationData.strings.ChatImport_Title, attemptSelection: { peer, _ in + attemptSelectionImpl?(peer) + }, createNewGroup: { + createNewGroupImpl?() + }, pretendPresentedInModal: true, selectForumThreads: true)) + + controller.customDismiss = { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + } + + controller.peerSelected = { peer, _ in + attemptSelectionImpl?(peer) + } + + controller.navigationPresentation = .default + + let beginWithPeer: (EnginePeer.Id) -> Void = { peerId in + navigationController.view.endEditing(true) + navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: { + //inForeground.set(false) + getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + }, peerId: peerId, archivePath: archivePathValue, mainEntry: mainFile, otherEntries: otherEntries)) + } + + attemptSelectionImpl = { [weak controller] peer in + controller?.inProgress = true + let _ = (context.engine.historyImport.checkPeerImport(peerId: peer.id) + |> deliverOnMainQueue).start(next: { result in + controller?.inProgress = false + + var errorText: String? + if case let .channel(channel) = peer { + if channel.hasPermission(.changeInfo), (channel.flags.contains(.isCreator) || channel.adminRights != nil) { + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else if case let .legacyGroup(group) = peer { + switch group.role { + case .creator: + break + default: + errorText = presentationData.strings.ChatImport_SelectionErrorNotAdmin + } + } else if case .user = peer { + } else { + errorText = presentationData.strings.ChatImport_SelectionErrorGroupGeneric + } + + if let errorText = errorText { + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + } else { + if case .user = peer { + let text: String + switch result { + case .allowed: + if let title = peerTitle { + text = presentationData.strings.ChatImport_SelectionConfirmationUserWithTitle(title, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + } else { + text = presentationData.strings.ChatImport_SelectionConfirmationUserWithoutTitle(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + } + case let .alert(textValue): + text = textValue + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { + beginWithPeer(peer.id) + })], parseMarkdown: true) + mainWindow?.present(controller, on: .root) + } else { + let text: String + switch result { + case .allowed: + if let groupTitle = peerTitle { + text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithTitle(groupTitle, peer.debugDisplayTitle).string + } else { + text = presentationData.strings.ChatImport_SelectionConfirmationGroupWithoutTitle(peer.debugDisplayTitle).string + } + case let .alert(textValue): + text = textValue + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_SelectionConfirmationAlertTitle, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_SelectionConfirmationAlertImportAction, action: { + beginWithPeer(peer.id) + })], parseMarkdown: true) + mainWindow?.present(controller, on: .root) + } + } + }, error: { error in + controller?.inProgress = false + + let errorText: String + switch error { + case .generic: + errorText = presentationData.strings.Login_UnknownError + case .chatAdminRequired: + errorText = presentationData.strings.ChatImportActivity_ErrorNotAdmin + case .invalidChatType: + errorText = presentationData.strings.ChatImportActivity_ErrorInvalidChatType + case .userBlocked: + errorText = presentationData.strings.ChatImportActivity_ErrorUserBlocked + case .limitExceeded: + errorText = presentationData.strings.ChatImportActivity_ErrorLimitExceeded + case .notMutualContact: + errorText = presentationData.strings.ChatImport_UserErrorNotMutual + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { + })]) + mainWindow?.present(controller, on: .root) + }) + } + + createNewGroupImpl = { + let resolvedGroupTitle: String + if let groupTitle = peerTitle { + resolvedGroupTitle = groupTitle + } else { + resolvedGroupTitle = "Group" + } + let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatImport_CreateGroupAlertTitle, text: presentationData.strings.ChatImport_CreateGroupAlertText(resolvedGroupTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatImport_CreateGroupAlertImportAction, action: { + var signal: Signal = _internal_createSupergroup(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, title: resolvedGroupTitle, description: nil, username: nil, isForum: false, isForHistoryImport: true) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + mainWindow?.present(controller, on: .root) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + let _ = (signal + |> deliverOnMainQueue).start(next: { peerId in + if let peerId = peerId { + beginWithPeer(peerId) + } else { + } + }) + }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + })], actionLayout: .vertical, parseMarkdown: true) + mainWindow?.present(controller, on: .root) + } + + navigationController.viewControllers = [controller] + } + }, error: { _ in + beginShare() + }) + }) +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index ce8c2cabf3..6f7cc5ec62 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 53df5ab689..99fe6f7958 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 2a034732cb..cc72586e4a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -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 ) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2540545773..fd6832cc9f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 5f43c18512..417c5e2f8e 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 5c5abd156f..f073a2adf6 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/MakeTempAccountContext.swift b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift new file mode 100644 index 0000000000..0fc439dd79 --- /dev/null +++ b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift @@ -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, + appLockContext: AppLockContext, + encryptionParameters: ValueBoxEncryptionParameters, + applicationBindings: TelegramApplicationBindings, + initialPresentationDataAndSettings: InitialPresentationDataAndSettings, + networkArguments: NetworkInitializationArguments, + buildConfig: BuildConfig +) -> Signal { + 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 in + guard let context = accounts.primary else { + return .complete() + } + return .single(context) + } +} diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 07fc2330dc..3569cf7708 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -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)") diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index cf19faa927..fd6cf39c1a 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -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