diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 10d4f962b7..6ec760dd99 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -195,7 +195,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) } }, openUrl: { [weak self] url in - openUserGeneratedUrl(context: context, peerId: nil, url: url, concealed: false, present: { c in + let _ = openUserGeneratedUrl(context: context, peerId: nil, url: url, concealed: false, present: { c in present(c, nil) }, openResolved: { [weak self] resolved in context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peerId, navigation in diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 320d7dc448..b8827a10e0 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -789,7 +789,7 @@ public extension ContainedViewLayoutTransition { } } - func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + func updateAlpha(layer: CALayer, alpha: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if layer.opacity.isEqual(to: Float(alpha)) { if let completion = completion { completion(true) @@ -804,7 +804,12 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - let previousAlpha = layer.opacity + let previousAlpha: Float + if beginWithCurrentState, let presentation = layer.presentation() { + previousAlpha = presentation.opacity + } else { + previousAlpha = layer.opacity + } layer.opacity = Float(alpha) layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/PresentationDataUtils/Sources/OpenUrl.swift index 8ccc7d91f8..0d1cfdcf34 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/PresentationDataUtils/Sources/OpenUrl.swift @@ -6,33 +6,53 @@ import AccountContext import OverlayStatusController import UrlWhitelist -public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void) { +public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise? = nil) -> Disposable { var concealed = concealed let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let openImpl: () -> Void = { + let openImpl: () -> Disposable = { let disposable = MetaDisposable() var cancelImpl: (() -> Void)? - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - present(controller) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() + let progressSignal: Signal + + if let progress { + progressSignal = Signal { subscriber in + progress.set(.single(true)) + return ActionDisposable { + progress.set(.single(false)) } } + |> runOn(Queue.mainQueue()) + |> delay(0.05, queue: Queue.mainQueue()) + } else { + progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + present(controller) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.1, queue: Queue.mainQueue()) } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() cancelImpl = { disposable.dispose() } - disposable.set((context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) + + var resolveSignal: Signal + resolveSignal = context.sharedContext.resolveUrl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) + #if DEBUG + resolveSignal = resolveSignal |> delay(2.0, queue: .mainQueue()) + #endif + + disposable.set((resolveSignal |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() @@ -42,6 +62,10 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: progressDisposable.dispose() openResolved(result) })) + + return ActionDisposable { + cancelImpl?() + } } let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) @@ -55,10 +79,12 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: } var displayUrl = rawDisplayUrl displayUrl = displayUrl.replacingOccurrences(of: "\u{202e}", with: "") + let disposable = MetaDisposable() present(textAlertController(context: context, title: nil, text: presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { - openImpl() + disposable.set(openImpl()) })])) + return disposable } else { - openImpl() + return openImpl() } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD index b1d57b8d4d..4204788d7a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/TelegramUI/Components/WallpaperPreviewMedia", "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentButtonNode", "//submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 0694ec7e86..b6bf260464 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -28,6 +28,7 @@ import ChatMessageInteractiveFileNode import ChatMessageInteractiveMediaNode import WallpaperPreviewMedia import ChatMessageAttachedContentButtonNode +import MessageInlineBlockBackgroundView public enum ChatMessageAttachedContentActionIcon { case instant @@ -52,8 +53,7 @@ public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { } public final class ChatMessageAttachedContentNode: ASDisplayNode { - private var backgroundView: UIImageView? - private var lineDashView: UIImageView? + private var backgroundView: MessageInlineBlockBackgroundView? private let transformContainer: ASDisplayNode private var title: TextNodeWithEntities? @@ -84,6 +84,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { public var activateAction: (() -> Void)? public var requestUpdateLayout: (() -> Void)? + private var currentProgressDisposable: Disposable? + public var defaultContentAction: () -> ChatMessageBubbleContentTapAction = { return ChatMessageBubbleContentTapAction(content: .none) } public var visibility: ListViewItemNodeVisibility = .none { @@ -125,6 +127,20 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) + public func makeProgress() -> Promise { + let progress = Promise() + self.currentProgressDisposable?.dispose() + self.currentProgressDisposable = (progress.get() + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] hasProgress in + guard let self else { + return + } + self.backgroundView?.displayProgress = hasProgress + }) + return progress + } + public func asyncLayout() -> AsyncLayout { let makeTitleLayout = TextNodeWithEntities.asyncLayout(self.title) let makeSubtitleLayout = TextNodeWithEntities.asyncLayout(self.subtitle) @@ -167,31 +183,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if !incoming { mainColor = messageTheme.accentTextColor if let _ = author?.nameColor?.dashColors.1 { - secondaryColor = messageTheme.accentTextColor + secondaryColor = .clear } } else { var authorNameColor: UIColor? -// if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), author?.id.namespace == Namespaces.Peer.CloudUser { - authorNameColor = author?.nameColor?.color - secondaryColor = author?.nameColor?.dashColors.1 - -// if let rawAuthorNameColor = authorNameColor { -// var dimColors = false -// switch presentationData.theme.theme.name { -// case .builtin(.nightAccent), .builtin(.night): -// dimColors = true -// default: -// break -// } -// if dimColors { -// var hue: CGFloat = 0.0 -// var saturation: CGFloat = 0.0 -// var brightness: CGFloat = 0.0 -// rawAuthorNameColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil) -// authorNameColor = UIColor(hue: hue, saturation: saturation * 0.7, brightness: min(1.0, brightness * 1.2), alpha: 1.0) -// } -// } -// } + authorNameColor = author?.nameColor?.color + secondaryColor = author?.nameColor?.dashColors.1 if let authorNameColor { mainColor = authorNameColor @@ -768,39 +765,18 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if displayLine { let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: actualSize.width - backgroundInsets.left - backgroundInsets.right, height: actualSize.height - backgroundInsets.top - backgroundInsets.bottom)) - let backgroundView: UIImageView + let backgroundView: MessageInlineBlockBackgroundView if let current = self.backgroundView { backgroundView = current animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil) } else { - backgroundView = UIImageView() - backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(presentationData.theme.theme, dashedOutgoing: !incoming && secondaryColor != nil) + backgroundView = MessageInlineBlockBackgroundView() self.backgroundView = backgroundView backgroundView.frame = backgroundFrame self.transformContainer.view.insertSubview(backgroundView, at: 0) } - backgroundView.tintColor = mainColor - - if let secondaryColor { - let lineDashView: UIImageView - if let current = self.lineDashView { - lineDashView = current - } else { - lineDashView = UIImageView(image: PresentationResourcesChat.chatReplyLineDashTemplateImage(presentationData.theme.theme, incoming: incoming)) - lineDashView.clipsToBounds = true - self.lineDashView = lineDashView - self.transformContainer.view.insertSubview(lineDashView, aboveSubview: backgroundView) - } - lineDashView.tintColor = secondaryColor - lineDashView.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 12.0, height: backgroundFrame.height)) - lineDashView.layer.cornerRadius = 6.0 - } else { - if let lineDashView = self.lineDashView { - self.lineDashView = nil - lineDashView.removeFromSuperview() - } - } + backgroundView.update(size: backgroundFrame.size, primaryColor: mainColor, secondaryColor: secondaryColor, pattern: nil, animation: animation) } else { if let backgroundView = self.backgroundView { self.backgroundView = nil diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index d0e9c41ebf..4e64472871 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -3858,8 +3858,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let item = self.item { for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { - return .action({ - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text)) + return .action({ [weak self] in + guard let self else { + return + } + var progress: Promise? + if let replyInfoNode = self.replyInfoNode { + progress = replyInfoNode.makeProgress() + } + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.quote?.text, progress: progress)) }) } else if let attribute = attribute as? ReplyStoryAttribute { return .action({ diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index d7db733c7e..65a301126f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -130,6 +130,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { private var previousMediaReference: AnyMediaReference? private var expiredStoryIconView: UIImageView? + private var currentProgressDisposable: Disposable? + override public init() { self.backgroundView = MessageInlineBlockBackgroundView(frame: CGRect()) @@ -144,6 +146,24 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { self.addSubnode(self.contentNode) } + deinit { + self.currentProgressDisposable?.dispose() + } + + public func makeProgress() -> Promise { + let progress = Promise() + self.currentProgressDisposable?.dispose() + self.currentProgressDisposable = (progress.get() + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] hasProgress in + guard let self else { + return + } + self.backgroundView.displayProgress = hasProgress + }) + return progress + } + public static func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode) { let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode) let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 0dd3756dc8..43f59e8141 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -100,7 +100,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if item.message.text.contains(webpage.url) { isConcealed = false } - item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: webpage.url, concealed: isConcealed)) + item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: webpage.url, concealed: isConcealed, progress: strongSelf.contentNode.makeProgress())) } } } @@ -119,7 +119,12 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if item.message.text.contains(content.url) { isConcealed = false } - return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: content.url, concealed: isConcealed, allowInlineWebpageResolution: true))) + return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: content.url, concealed: isConcealed, allowInlineWebpageResolution: true)), activate: { [weak self] in + guard let self else { + return nil + } + return self.contentNode.makeProgress() + }) } } diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index 58b116527d..f4a91de421 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -28,7 +28,7 @@ private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloa context.restoreGState() } -private func generateTemplateImage(isMonochrome: Bool) -> UIImage { +private func generateBackgroundTemplateImage(addStripe: Bool) -> UIImage { return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -38,34 +38,56 @@ private func generateTemplateImage(isMonochrome: Bool) -> UIImage { context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.withAlphaComponent(isMonochrome ? 0.2 : 1.0).cgColor) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height))) + if addStripe { + context.setFillColor(UIColor.white.withMultipliedAlpha(0.2).cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height))) + } })!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate) } -private let plainTemplateImage: UIImage = { - return generateTemplateImage(isMonochrome: false) -}() - -private let monochromePatternTemplateImage: UIImage = { - return generateTemplateImage(isMonochrome: true) -}() - -private func generateDashBackgroundTemplateImage() -> UIImage { +private func generateProgressTemplateImage() -> UIImage { return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: radius).cgPath) + addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size), radius: radius) context.clip() - context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor) + context.setFillColor(UIColor.white.withMultipliedAlpha(0.4).cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.withAlphaComponent(0.2).cgColor) + context.setFillColor(UIColor.white.withMultipliedAlpha(0.7).cgColor) context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height))) + + context.resetClip() + + let borderWidth: CGFloat = 1.5 + addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size).insetBy(dx: borderWidth * 0.5, dy: borderWidth * 0.5), radius: radius) + context.setStrokeColor(UIColor.white.withAlphaComponent(0.7).cgColor) + context.strokePath() + })!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate) } +private let backgroundSolidTemplateImage: UIImage = { + return generateBackgroundTemplateImage(addStripe: true) +}() + +private let backgroundDashTemplateImage: UIImage = { + return generateBackgroundTemplateImage(addStripe: false) +}() + +private func generateDashBackgroundTemplateImage() -> UIImage { + return generateImage(CGSize(width: lineWidth, height: radius * 2.0 + 8.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height)), cornerRadius: radius).cgPath) + context.clip() + + context.setFillColor(UIColor.white.withAlphaComponent(1.0).cgColor) + context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height))) + })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate) +} + private let dashBackgroundTemplateImage: UIImage = { return generateDashBackgroundTemplateImage() }() @@ -102,6 +124,35 @@ private let dashMonochromeTemplateImage: UIImage = { return generateDashTemplateImage(isMonochrome: true) }() +private func generateGradient(gradientWidth: CGFloat, baseAlpha: CGFloat) -> UIImage { + return generateImage(CGSize(width: gradientWidth, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let foregroundColor = UIColor(white: 1.0, alpha: min(1.0, baseAlpha * 4.0)) + + if let shadowImage = UIImage(named: "Stories/PanelGradient") { + UIGraphicsPushContext(context) + + for i in 0 ..< 2 { + let shadowFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (size.width * 0.5), y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)) + + context.saveGState() + context.translateBy(x: shadowFrame.midX, y: shadowFrame.midY) + context.rotate(by: CGFloat(i == 0 ? 1.0 : -1.0) * CGFloat.pi * 0.5) + let adjustedRect = CGRect(origin: CGPoint(x: -shadowFrame.height * 0.5, y: -shadowFrame.width * 0.5), size: CGSize(width: shadowFrame.height, height: shadowFrame.width)) + + context.clip(to: adjustedRect, mask: shadowImage.cgImage!) + context.setFillColor(foregroundColor.cgColor) + context.fill(adjustedRect) + + context.restoreGState() + } + + UIGraphicsPopContext() + } + })!.withRenderingMode(.alwaysTemplate) +} + private final class PatternContentsTarget: MultiAnimationRenderTarget { private let imageUpdated: () -> Void @@ -121,6 +172,165 @@ private final class PatternContentsTarget: MultiAnimationRenderTarget { } } +private final class LineView: UIView { + private let backgroundView: UIImageView + private var dashBackgroundView: UIImageView? + + private var params: Params? + private var isAnimating: Bool = false + + private struct Params: Equatable { + var size: CGSize + var primaryColor: UIColor + var secondaryColor: UIColor? + var displayProgress: Bool + + init(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, displayProgress: Bool) { + self.size = size + self.primaryColor = primaryColor + self.secondaryColor = secondaryColor + self.displayProgress = displayProgress + } + } + + override init(frame: CGRect) { + self.backgroundView = UIImageView() + self.backgroundView.image = dashBackgroundTemplateImage + + super.init(frame: frame) + + self.layer.cornerRadius = radius + if #available(iOS 13.0, *) { + self.layer.cornerCurve = .circular + } + + self.addSubview(self.backgroundView) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func updateAnimations() { + guard let params = self.params else { + return + } + + if params.displayProgress { + if let dashBackgroundView = self.dashBackgroundView { + if dashBackgroundView.layer.animation(forKey: "progress") == nil { + let animation = dashBackgroundView.layer.makeAnimation(from: 18.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = 1.0 + self.isAnimating = true + animation.completion = { [weak self] _ in + guard let self else { + return + } + self.isAnimating = false + self.updateAnimations() + } + dashBackgroundView.layer.add(animation, forKey: "progress") + } + } else { + let phaseDuration: Double = 0.8 + if self.backgroundView.layer.animation(forKey: "progress") == nil { + let animation = self.backgroundView.layer.makeAnimation(from: 0.0 as NSNumber, to: -params.size.height as NSNumber, keyPath: "position.y", timingFunction: kCAMediaTimingFunctionSpring, duration: phaseDuration * 0.5, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: false, additive: true) + animation.repeatCount = 1.0 + self.isAnimating = true + animation.completion = { [weak self] _ in + guard let self else { + return + } + + let animation = self.backgroundView.layer.makeAnimation(from: params.size.height as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", timingFunction: kCAMediaTimingFunctionSpring, duration: phaseDuration * 0.5, delay: self.params?.displayProgress == true ? 0.1 : 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = 1.0 + self.isAnimating = true + animation.completion = { [weak self] _ in + guard let self else { + return + } + self.isAnimating = false + self.updateAnimations() + } + self.backgroundView.layer.add(animation, forKey: "progress") + } + self.backgroundView.layer.add(animation, forKey: "progress") + } + } + } + + if self.isAnimating && self.dashBackgroundView == nil { + self.backgroundView.backgroundColor = params.primaryColor + self.backgroundView.layer.masksToBounds = true + self.backgroundView.layer.cornerRadius = radius * 0.5 + } else { + self.backgroundView.backgroundColor = nil + self.backgroundView.layer.masksToBounds = false + } + + self.layer.masksToBounds = params.secondaryColor != nil || self.isAnimating + } + + func update(size: CGSize, primaryColor: UIColor, secondaryColor: UIColor?, displayProgress: Bool, animation: ListViewItemUpdateAnimation) { + let params = Params( + size: size, + primaryColor: primaryColor, + secondaryColor: secondaryColor, + displayProgress: displayProgress + ) + if self.params == params { + return + } + let previousParams = self.params + self.params = params + + let _ = previousParams + + + self.backgroundView.tintColor = primaryColor + + if let secondaryColor { + let dashBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -18.0), size: CGSize(width: radius * 2.0, height: size.height + 18.0)) + + let dashBackgroundView: UIImageView + if let current = self.dashBackgroundView { + dashBackgroundView = current + + animation.animator.updateFrame(layer: dashBackgroundView.layer, frame: dashBackgroundFrame, completion: nil) + } else { + dashBackgroundView = UIImageView() + self.dashBackgroundView = dashBackgroundView + self.addSubview(dashBackgroundView) + + dashBackgroundView.frame = dashBackgroundFrame + } + + if secondaryColor.alpha == 0.0 { + self.backgroundView.alpha = 0.2 + dashBackgroundView.image = dashMonochromeTemplateImage + dashBackgroundView.tintColor = primaryColor + } else { + self.backgroundView.alpha = 1.0 + dashBackgroundView.image = dashOpaqueTemplateImage + dashBackgroundView.tintColor = secondaryColor + } + } else { + if let dashBackgroundView = self.dashBackgroundView { + self.dashBackgroundView = nil + dashBackgroundView.removeFromSuperview() + } + + self.backgroundView.alpha = 1.0 + } + + self.layer.masksToBounds = params.secondaryColor != nil || self.isAnimating + + animation.animator.updateFrame(layer: self.backgroundView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)), completion: nil) + + self.updateAnimations() + } +} + public final class MessageInlineBlockBackgroundView: UIView { public final class Pattern: Equatable { public let context: AccountContext @@ -157,6 +367,20 @@ public final class MessageInlineBlockBackgroundView: UIView { var secondaryColor: UIColor? var pattern: Pattern? var displayProgress: Bool + + init( + size: CGSize, + primaryColor: UIColor, + secondaryColor: UIColor?, + pattern: Pattern?, + displayProgress: Bool + ) { + self.size = size + self.primaryColor = primaryColor + self.secondaryColor = secondaryColor + self.pattern = pattern + self.displayProgress = displayProgress + } } private var params: Params? @@ -170,11 +394,7 @@ public final class MessageInlineBlockBackgroundView: UIView { primaryColor: params.primaryColor, secondaryColor: params.secondaryColor, pattern: params.pattern, - animation: .System(duration: 0.2, transition: ControlledTransition( - duration: 0.2, - curve: .easeInOut, - interactive: false - )) + animation: .None ) } } @@ -182,7 +402,7 @@ public final class MessageInlineBlockBackgroundView: UIView { } private let backgroundView: UIImageView - private var dashView: UIImageView? + private var lineView: LineView private var hierarchyTrackingLayer: HierarchyTrackingLayer? private var patternContentsTarget: PatternContentsTarget? @@ -191,13 +411,19 @@ public final class MessageInlineBlockBackgroundView: UIView { private var patternFileDisposable: Disposable? private var patternImage: UIImage? private var patternImageDisposable: Disposable? + + private var progressBackgroundContentsView: UIImageView? + private var progressBackgroundMaskContainer: UIView? + private var progressBackgroundGradientView: UIImageView? override public init(frame: CGRect) { self.backgroundView = UIImageView() + self.lineView = LineView() super.init(frame: frame) self.addSubview(self.backgroundView) + self.addSubview(self.lineView) } required public init?(coder: NSCoder) { @@ -210,6 +436,27 @@ public final class MessageInlineBlockBackgroundView: UIView { } private func updateAnimations() { + guard let hierarchyTrackingLayer = self.hierarchyTrackingLayer, hierarchyTrackingLayer.isInHierarchy else { + return + } + guard let params = self.params else { + return + } + guard let progressBackgroundGradientView = self.progressBackgroundGradientView else { + return + } + let gradientWidth = progressBackgroundGradientView.bounds.width + + if progressBackgroundGradientView.layer.animation(forKey: "shimmer") != nil { + return + } + + let duration: Double = 1.0 + let animation = progressBackgroundGradientView.layer.makeAnimation(from: 0.0 as NSNumber, to: (params.size.width + gradientWidth + params.size.width * 0.1) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = Float.infinity + progressBackgroundGradientView.layer.add(animation, forKey: "shimmer") + + self.lineView.updateAnimations() } private func loadPatternFromFile() { @@ -276,37 +523,12 @@ public final class MessageInlineBlockBackgroundView: UIView { patternContentLayer.layerTintColor = primaryColor.cgColor } - if let secondaryColor = params.secondaryColor { - self.backgroundView.tintColor = params.primaryColor - - if self.dashView == nil { - let dashView = UIImageView() - dashView.layer.cornerRadius = radius - if #available(iOS 13.0, *) { - dashView.layer.cornerCurve = .circular - } - self.dashView = dashView - self.addSubview(dashView) - } - - if secondaryColor.alpha == 0.0 { - self.backgroundView.image = monochromePatternTemplateImage - self.dashView?.image = dashMonochromeTemplateImage - self.dashView?.tintColor = primaryColor - } else { - self.backgroundView.image = plainTemplateImage - self.dashView?.image = dashOpaqueTemplateImage - self.dashView?.tintColor = secondaryColor - } + if params.secondaryColor != nil { + self.backgroundView.image = backgroundDashTemplateImage } else { - self.backgroundView.image = plainTemplateImage - self.backgroundView.tintColor = params.primaryColor - - if let dashView = self.dashView { - self.dashView = dashView - dashView.removeFromSuperview() - } + self.backgroundView.image = backgroundSolidTemplateImage } + self.backgroundView.tintColor = params.primaryColor } if previousParams?.pattern != params.pattern { @@ -359,15 +581,19 @@ public final class MessageInlineBlockBackgroundView: UIView { } } - self.dashView?.layer.masksToBounds = params.pattern == nil && params.secondaryColor != nil - animation.animator.updateFrame(layer: self.backgroundView.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil) - if let dashView = self.dashView { - animation.animator.updateFrame(layer: dashView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height)), completion: nil) - } + + let lineFrame = CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height)) + self.lineView.update( + size: lineFrame.size, + primaryColor: params.primaryColor, + secondaryColor: params.secondaryColor, + displayProgress: params.displayProgress, + animation: animation + ) + animation.animator.updateFrame(layer: lineView.layer, frame: lineFrame, completion: nil) if params.pattern != nil { - var maxIndex = 0 struct Placement { @@ -425,6 +651,79 @@ public final class MessageInlineBlockBackgroundView: UIView { self.patternContentLayers.removeAll() } + let gradientWidth: CGFloat = min(300.0, max(200.0, size.width * 0.9)) + + if previousParams?.displayProgress != params.displayProgress { + if params.displayProgress { + let progressBackgroundContentsView: UIImageView + if let current = self.progressBackgroundContentsView { + progressBackgroundContentsView = current + } else { + progressBackgroundContentsView = UIImageView() + progressBackgroundContentsView.image = generateProgressTemplateImage() + self.insertSubview(progressBackgroundContentsView, aboveSubview: self.backgroundView) + progressBackgroundContentsView.tintColor = primaryColor + } + + let progressBackgroundMaskContainer: UIView + if let current = self.progressBackgroundMaskContainer { + progressBackgroundMaskContainer = current + } else { + progressBackgroundMaskContainer = UIView() + self.progressBackgroundMaskContainer = progressBackgroundMaskContainer + progressBackgroundContentsView.mask = progressBackgroundMaskContainer + } + + let progressBackgroundGradientView: UIImageView + if let current = self.progressBackgroundGradientView { + progressBackgroundGradientView = current + } else { + progressBackgroundGradientView = UIImageView() + self.progressBackgroundGradientView = progressBackgroundGradientView + progressBackgroundMaskContainer.addSubview(progressBackgroundGradientView) + progressBackgroundGradientView.image = generateGradient(gradientWidth: 100.0, baseAlpha: 0.5) + } + + progressBackgroundContentsView.frame = CGRect(origin: CGPoint(), size: size) + progressBackgroundMaskContainer.frame = CGRect(origin: CGPoint(), size: size) + progressBackgroundGradientView.frame = CGRect(origin: CGPoint(x: -gradientWidth, y: 0.0), size: CGSize(width: gradientWidth, height: size.height)) + + if self.hierarchyTrackingLayer == nil { + let hierarchyTrackingLayer = HierarchyTrackingLayer() + self.hierarchyTrackingLayer = hierarchyTrackingLayer + self.layer.addSublayer(hierarchyTrackingLayer) + hierarchyTrackingLayer.isInHierarchyUpdated = { [weak self] _ in + self?.updateAnimations() + } + } + } else { + if let progressBackgroundContentsView = self.progressBackgroundContentsView { + self.progressBackgroundContentsView = nil + let transition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) + transition.updateAlpha(layer: progressBackgroundContentsView.layer, alpha: 0.0) + } + self.progressBackgroundMaskContainer = nil + self.progressBackgroundGradientView = nil + + if let hierarchyTrackingLayer = self.hierarchyTrackingLayer { + self.hierarchyTrackingLayer = nil + hierarchyTrackingLayer.isInHierarchyUpdated = nil + hierarchyTrackingLayer.removeFromSuperlayer() + } + } + } else { + if let progressBackgroundContentsView = self.progressBackgroundContentsView { + animation.animator.updateFrame(layer: progressBackgroundContentsView.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil) + progressBackgroundContentsView.tintColor = primaryColor + } + if let progressBackgroundMaskContainer = self.progressBackgroundMaskContainer { + animation.animator.updateFrame(layer: progressBackgroundMaskContainer.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil) + } + if let progressBackgroundGradientView = self.progressBackgroundGradientView { + animation.animator.updateFrame(layer: progressBackgroundGradientView.layer, frame: CGRect(origin: CGPoint(x: -gradientWidth, y: 0.0), size: CGSize(width: gradientWidth, height: size.height)), completion: nil) + } + } + self.updateAnimations() } } diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 0eedb7d605..44170ef387 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -76,10 +76,12 @@ public protocol ChatMessageTransitionProtocol: ASDisplayNode { public struct NavigateToMessageParams { public var timestamp: Double? public var quote: String? + public var progress: Promise? - public init(timestamp: Double?, quote: String?) { + public init(timestamp: Double?, quote: String?, progress: Promise? = nil) { self.timestamp = timestamp self.quote = quote + self.progress = progress } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 774fd6bb0f..7cb6dbc8f1 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -4092,7 +4092,7 @@ public final class StoryItemSetContainerComponent: Component { } switch action { case let .url(url, concealed): - openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in + let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -4120,7 +4120,7 @@ public final class StoryItemSetContainerComponent: Component { return } self.sendMessageContext.presentTextEntityActions(view: self, action: action, openUrl: { [weak self] url, concealed in - openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in + let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.peer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in guard let self, let component = self.component, let controller = component.controller() else { return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 9ecf47c7fe..2ce0a8cd7a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -823,25 +823,19 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD return ContextController.Items(id: AnyHashable(linkOptions.url), content: .list(items)) } + var webpageCache: [String: TelegramMediaWebpage] = [:] chatController.performOpenURL = { [weak selfController] message, url, progress in guard let selfController else { return } if let (updatedUrlPreviewUrl, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewUrl { - progress?.set(.single(true)) - let _ = (signal - |> afterDisposed { + if let webpage = webpageCache[updatedUrlPreviewUrl] { progress?.set(.single(false)) - } - |> deliverOnMainQueue).start(next: { [weak selfController] result in - guard let selfController else { - return - } selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in if state.interfaceState.editMessage != nil { - if let webpage = result(nil), var urlPreview = state.editingUrlPreview { + if var urlPreview = state.editingUrlPreview { urlPreview.url = updatedUrlPreviewUrl urlPreview.webPage = webpage @@ -850,7 +844,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD return state } } else { - if let webpage = result(nil), var urlPreview = state.urlPreview { + if var urlPreview = state.urlPreview { urlPreview.url = updatedUrlPreviewUrl urlPreview.webPage = webpage @@ -860,7 +854,42 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD } } }) - }) + } else { + progress?.set(.single(true)) + let _ = (signal + |> afterDisposed { + progress?.set(.single(false)) + } + |> deliverOnMainQueue).start(next: { [weak selfController] result in + guard let selfController else { + return + } + + selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in + if state.interfaceState.editMessage != nil { + if let webpage = result(nil), var urlPreview = state.editingUrlPreview { + urlPreview.url = updatedUrlPreviewUrl + urlPreview.webPage = webpage + webpageCache[updatedUrlPreviewUrl] = webpage + + return state.updatedEditingUrlPreview(urlPreview) + } else { + return state + } + } else { + if let webpage = result(nil), var urlPreview = state.urlPreview { + urlPreview.url = updatedUrlPreviewUrl + urlPreview.webPage = webpage + webpageCache[updatedUrlPreviewUrl] = webpage + + return state.updatedUrlPreview(urlPreview) + } else { + return state + } + } + }) + }) + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a1c4f7771e..c81249a517 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2762,8 +2762,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let performOpenURL = strongSelf.performOpenURL { performOpenURL(message, url, progress) } else { - progress?.set(.single(false)) - strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution) + strongSelf.openUrl(url, concealed: concealed, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) } } }, shareCurrentLocation: { [weak self] in @@ -16895,7 +16894,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .id(messageId, params) = messageLocation, params.timestamp != nil { self.scheduledScrollToMessageId = (messageId, params) } + var progress: Promise? + if case let .id(_, params) = messageLocation { + progress = params.progress + } self.loadingMessage.set(.single(statusSubject) |> delay(0.1, queue: .mainQueue())) + let searchLocation: ChatHistoryInitialSearchLocation switch messageLocation { case let .id(id, _): @@ -16909,9 +16913,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G searchLocation = .index(.absoluteUpperBound()) } } - let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: nil), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + var historyView: Signal + historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: nil), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) - let signal = historyView + var signal: Signal<(MessageIndex?, Bool), NoError> + signal = historyView |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in switch historyView { case .Loading: @@ -16932,11 +16938,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return SignalTakeAction(passthrough: true, complete: !index.1) }) + #if DEBUG + signal = .single((nil, true)) |> then(signal |> delay(2.0, queue: .mainQueue())) + #endif + var cancelImpl: (() -> Void)? let presentationData = self.presentationData let displayTime = CACurrentMediaTime() let progressSignal = Signal { [weak self] subscriber in - if case .generic = statusSubject { + if let progress { + progress.set(.single(true)) + return ActionDisposable { + Queue.mainQueue().async() { + progress.set(.single(false)) + } + } + } else if case .generic = statusSubject { let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { if CACurrentMediaTime() - displayTime > 1.5 { cancelImpl?() @@ -17013,21 +17030,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: quote), count: 50, highlight: true), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) - let signal = historyView - |> mapToSignal { historyView -> Signal in - switch historyView { - case .Loading: - return .complete() - case let .HistoryView(view, _, _, _, _, _, _): - for entry in view.entries { - if entry.message.id == messageLocation.messageId { - return .single(entry.message.index) - } + var signal: Signal + signal = historyView + |> mapToSignal { historyView -> Signal in + switch historyView { + case .Loading: + return .complete() + case let .HistoryView(view, _, _, _, _, _, _): + for entry in view.entries { + if entry.message.id == messageLocation.messageId { + return .single(entry.message.index) } - return .single(nil) - } + } + return .single(nil) } - |> take(1) + } + |> take(1) + self.messageIndexDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { [weak self] index in if let strongSelf = self { if let index = index { @@ -17995,7 +18014,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, contentContext: nil) } - func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, allowInlineWebpageResolution: Bool = false, commit: @escaping () -> Void = {}) { + func openUrl(_ url: String, concealed: Bool, forceExternal: Bool = false, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, message: Message? = nil, allowInlineWebpageResolution: Bool = false, progress: Promise? = nil, commit: @escaping () -> Void = {}) { self.commitPurposefulAction() if allowInlineWebpageResolution, let message, let webpage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.url == url { @@ -18005,22 +18024,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .album: break default: + progress?.set(.single(false)) self.context.sharedContext.openChatInstantPage(context: self.context, message: message, sourcePeerType: nil, navigationController: navigationController) return } } } else if let embedUrl = content.embedUrl, !embedUrl.isEmpty { + progress?.set(.single(false)) let _ = self.controllerInteraction?.openMessage(message, .default) return } } - let _ = self.presentVoiceMessageDiscardAlert(action: { - openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in + let _ = self.presentVoiceMessageDiscardAlert(action: { [weak self] in + guard let self else { + return + } + let disposable = openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal, concealed: concealed, commit: commit) - }) + }, progress: progress) + self.navigationActionDisposable.set(disposable) }, performAction: true) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b1deb2b1b5..11e114f784 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4516,7 +4516,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func openUrl(url: String, concealed: Bool, external: Bool, forceExternal: Bool = false, commit: @escaping () -> Void = {}) { - openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in + let _ = openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in self?.controller?.present(c, in: .window(.root)) }, openResolved: { [weak self] tempResolved in guard let strongSelf = self else {