mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Progress indicators
This commit is contained in:
parent
89fbca6fdb
commit
dd46ccd6ed
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -6,15 +6,27 @@ 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<Bool>? = 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<Never, NoError> { subscriber in
|
||||
let progressSignal: Signal<Never, NoError>
|
||||
|
||||
if let progress {
|
||||
progressSignal = Signal<Never, NoError> { subscriber in
|
||||
progress.set(.single(true))
|
||||
return ActionDisposable {
|
||||
progress.set(.single(false))
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.05, queue: Queue.mainQueue())
|
||||
} else {
|
||||
progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
@ -26,13 +38,21 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url:
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
|> delay(0.1, 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<ResolvedUrl, NoError>
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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<Bool> {
|
||||
let progress = Promise<Bool>()
|
||||
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,32 +183,13 @@ 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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if let authorNameColor {
|
||||
mainColor = authorNameColor
|
||||
} else {
|
||||
@ -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
|
||||
|
@ -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<Bool>?
|
||||
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({
|
||||
|
@ -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<Bool> {
|
||||
let progress = Promise<Bool>()
|
||||
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)
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
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?
|
||||
@ -192,12 +412,18 @@ public final class MessageInlineBlockBackgroundView: UIView {
|
||||
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
|
||||
if params.secondaryColor != nil {
|
||||
self.backgroundView.image = backgroundDashTemplateImage
|
||||
} else {
|
||||
self.backgroundView.image = plainTemplateImage
|
||||
self.dashView?.image = dashOpaqueTemplateImage
|
||||
self.dashView?.tintColor = secondaryColor
|
||||
self.backgroundView.image = backgroundSolidTemplateImage
|
||||
}
|
||||
} else {
|
||||
self.backgroundView.image = plainTemplateImage
|
||||
self.backgroundView.tintColor = params.primaryColor
|
||||
|
||||
if let dashView = self.dashView {
|
||||
self.dashView = dashView
|
||||
dashView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -76,10 +76,12 @@ public protocol ChatMessageTransitionProtocol: ASDisplayNode {
|
||||
public struct NavigateToMessageParams {
|
||||
public var timestamp: Double?
|
||||
public var quote: String?
|
||||
public var progress: Promise<Bool>?
|
||||
|
||||
public init(timestamp: Double?, quote: String?) {
|
||||
public init(timestamp: Double?, quote: String?, progress: Promise<Bool>? = nil) {
|
||||
self.timestamp = timestamp
|
||||
self.quote = quote
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -823,12 +823,38 @@ 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 {
|
||||
if let webpage = webpageCache[updatedUrlPreviewUrl] {
|
||||
progress?.set(.single(false))
|
||||
|
||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||
if state.interfaceState.editMessage != nil {
|
||||
if var urlPreview = state.editingUrlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
} else {
|
||||
if var urlPreview = state.urlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
progress?.set(.single(true))
|
||||
let _ = (signal
|
||||
|> afterDisposed {
|
||||
@ -844,6 +870,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
if let webpage = result(nil), var urlPreview = state.editingUrlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
webpageCache[updatedUrlPreviewUrl] = webpage
|
||||
|
||||
return state.updatedEditingUrlPreview(urlPreview)
|
||||
} else {
|
||||
@ -853,6 +880,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
if let webpage = result(nil), var urlPreview = state.urlPreview {
|
||||
urlPreview.url = updatedUrlPreviewUrl
|
||||
urlPreview.webPage = webpage
|
||||
webpageCache[updatedUrlPreviewUrl] = webpage
|
||||
|
||||
return state.updatedUrlPreview(urlPreview)
|
||||
} else {
|
||||
@ -863,6 +891,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
return ContextController.Source(
|
||||
|
@ -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<Bool>?
|
||||
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<ChatHistoryViewUpdate, NoError>
|
||||
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<Never, NoError> { [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,7 +17030,8 @@ 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
|
||||
var signal: Signal<MessageIndex?, NoError>
|
||||
signal = historyView
|
||||
|> mapToSignal { historyView -> Signal<MessageIndex?, NoError> in
|
||||
switch historyView {
|
||||
case .Loading:
|
||||
@ -17028,6 +17046,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|> 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<Bool>? = 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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user