diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 03788f0d6b..29ce80db73 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1308,14 +1308,14 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let anchorRange = externalUrl.range(of: "#") { anchor = String(externalUrl[anchorRange.upperBound...]) } - strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.context.account, url: externalUrl, webpageId: webpageId) + strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.context.account, urls: [externalUrl], webpageId: webpageId) |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { - case let .result(webpage): - if let webpage = webpage, case .Loaded = webpage.content { + case let .result(webpageResult): + if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content { strongSelf.loadProgress.set(1.0) - strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourceLocation: strongSelf.sourceLocation, anchor: anchor)) + strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpageResult.webpage, sourceLocation: strongSelf.sourceLocation, anchor: anchor)) } break case let .progress(progress): diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 1f559c1891..06c4fbe654 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -136,6 +136,7 @@ private final class MediaBoxKeepResourceContext { public final class MediaBox { public let basePath: String + public let isMainProcess: Bool private let statusQueue = Queue() private let concurrentQueue = Queue.concurrentDefaultQueue() @@ -187,15 +188,16 @@ public final class MediaBox { let _ = try? FileManager.default.createDirectory(atPath: self.basePath + "/short-cache", withIntermediateDirectories: true, attributes: nil) }() - public init(basePath: String) { + public init(basePath: String, isMainProcess: Bool) { self.basePath = basePath + self.isMainProcess = isMainProcess self.storageBox = StorageBox(logger: StorageBox.Logger(impl: { string in postboxLog(string) - }), basePath: basePath + "/storage") + }), basePath: basePath + "/storage", isMainProcess: isMainProcess) self.cacheStorageBox = StorageBox(logger: StorageBox.Logger(impl: { string in postboxLog(string) - }), basePath: basePath + "/cache-storage") + }), basePath: basePath + "/cache-storage", isMainProcess: isMainProcess) self.timeBasedCleanup = TimeBasedCleanup(storageBox: self.storageBox, generalPaths: [ self.basePath + "/cache", diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 7ec48e3fc8..d9f7e8bccc 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1368,7 +1368,7 @@ func debugRestoreState(basePath: String, name: String) { } } -public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal { +public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isMainProcess: Bool, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal { let queue = Postbox.sharedQueue return Signal { subscriber in queue.async { @@ -1496,7 +1496,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, let endTime = CFAbsoluteTimeGetCurrent() postboxLog("Postbox load took \((endTime - startTime) * 1000.0) ms") - subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations, isTemporary: isTemporary, tempDir: tempDir, useCaches: useCaches))) + subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations, isMainProcess: isMainProcess, isTemporary: isTemporary, tempDir: tempDir, useCaches: useCaches))) postboxLog("openPostbox, putCompletion") @@ -4168,6 +4168,7 @@ public class Postbox { seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32, + isMainProcess: Bool, isTemporary: Bool, tempDir: TempBoxDirectory?, useCaches: Bool @@ -4177,7 +4178,7 @@ public class Postbox { self.seedConfiguration = seedConfiguration postboxLog("MediaBox path: \(basePath + "/media")") - self.mediaBox = MediaBox(basePath: basePath + "/media") + self.mediaBox = MediaBox(basePath: basePath + "/media", isMainProcess: isMainProcess) let isInTransaction = self.isInTransaction diff --git a/submodules/Postbox/Sources/StorageBox/StorageBox.swift b/submodules/Postbox/Sources/StorageBox/StorageBox.swift index 2cd2172c73..67bc0ee380 100644 --- a/submodules/Postbox/Sources/StorageBox/StorageBox.swift +++ b/submodules/Postbox/Sources/StorageBox/StorageBox.swift @@ -163,20 +163,37 @@ public final class StorageBox { private var queuedInternalTransactions = Atomic<[() -> Void]>(value: []) - init(queue: Queue, logger: StorageBox.Logger, basePath: String) { + init(queue: Queue, logger: StorageBox.Logger, basePath: String, isMainProcess: Bool) { self.queue = queue self.logger = logger self.basePath = basePath let databasePath = self.basePath + "/db" let _ = try? FileManager.default.createDirectory(atPath: databasePath, withIntermediateDirectories: true) - var valueBox = SqliteValueBox(basePath: databasePath, queue: queue, isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }) - if valueBox == nil { - let _ = try? FileManager.default.removeItem(atPath: databasePath) - valueBox = SqliteValueBox(basePath: databasePath, queue: queue, isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }) + + var valueBox: SqliteValueBox? + for i in 0 ..< 3 { + if let valueBoxValue = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: false, isReadOnly: false, useCaches: isMainProcess, removeDatabaseOnError: isMainProcess, encryptionParameters: nil, upgradeProgress: { _ in }) { + valueBox = valueBoxValue + break + } else { + postboxLog("Could not open value box at \(basePath + "/db") (try \(i))") + postboxLogSync() + + Thread.sleep(forTimeInterval: 0.1 + 0.5 * Double(i)) + } } - guard let valueBox = valueBox else { - preconditionFailure("Could not open database") + + if valueBox == nil, isMainProcess { + postboxLog("Removing value box at \(basePath + "/db")") + let _ = try? FileManager.default.removeItem(atPath: databasePath) + valueBox = SqliteValueBox(basePath: databasePath, queue: queue, isTemporary: false, isReadOnly: false, useCaches: isMainProcess, removeDatabaseOnError: isMainProcess, encryptionParameters: nil, upgradeProgress: { _ in }) + } + + guard let valueBox else { + postboxLog("Giving up on opening value box at \(basePath + "/db")") + postboxLogSync() + preconditionFailure() } self.valueBox = valueBox @@ -1025,11 +1042,11 @@ public final class StorageBox { private let queue: Queue private let impl: QueueLocalObject - public init(logger: StorageBox.Logger, basePath: String) { + public init(logger: StorageBox.Logger, basePath: String, isMainProcess: Bool) { let queue = StorageBox.sharedQueue self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, logger: logger, basePath: basePath) + return Impl(queue: queue, logger: logger, basePath: basePath, isMainProcess: isMainProcess) }) } diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 800efb1de6..e5f385165a 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -183,6 +183,7 @@ public func accountWithId(accountManager: AccountManager { return nil } self.guardValueBox = guardValueBox - guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) else { - postboxLog("Could not open value box at \(basePath + "/db")") + + var valueBox: SqliteValueBox? + for i in 0 ..< 3 { + if let valueBoxValue = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, isReadOnly: isReadOnly, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError, encryptionParameters: nil, upgradeProgress: { _ in }) { + valueBox = valueBoxValue + break + } else { + postboxLog("Could not open value box at \(basePath + "/db") (try \(i))") + postboxLogSync() + + Thread.sleep(forTimeInterval: 0.1 + 0.5 * Double(i)) + } + } + guard let valueBox else { + postboxLog("Giving up on opening value box at \(basePath + "/db")") postboxLogSync() preconditionFailure() - return nil } self.valueBox = valueBox @@ -537,7 +549,7 @@ public final class AccountManager { preconditionFailure() } }) - self.mediaBox = MediaBox(basePath: basePath + "/media") + self.mediaBox = MediaBox(basePath: basePath + "/media", isMainProcess: removeDatabaseOnError) } public func transaction(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier) -> T) -> Signal { diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index f130e86a24..23b2021cb5 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -343,7 +343,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI return (TelegramMediaExpiredContent(data: .file), nil, nil, nil, nil) } case let .messageMediaWebPage(flags, webpage): - if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) { + if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) { var webpageForceLargeMedia: Bool? if (flags & (1 << 0)) != 0 { webpageForceLargeMedia = true diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index 4cd629839f..461472ee2a 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -17,7 +17,7 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa } } -func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? { +func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? { switch webpage { case .webPageNotModified: return nil diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index f195353990..e5d67331be 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -994,7 +994,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } @@ -1217,7 +1217,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } @@ -2993,7 +2993,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo let _ = url updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil) default: - if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) { + if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) { updatedState.updateMedia(webpage.webpageId, media: webpage) } } diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 9e17bb4169..d2d19e91df 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -408,18 +408,18 @@ public final class AccountViewTracker { for messageId in addedMessageIds { if self.webpageDisposables[messageId] == nil { if let (_, url) = localWebpages[messageId] { - self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { result -> Signal in - guard case let .result(webpage) = result else { + self.webpageDisposables[messageId] = (webpagePreview(account: account, urls: [url]) |> mapToSignal { result -> Signal in + guard case let .result(webpageResult) = result else { return .complete() } return account.postbox.transaction { transaction -> Void in - if let webpage = webpage { + if let webpageResult = webpageResult { transaction.updateMessage(messageId, update: { currentMessage in let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init) var media = currentMessage.media for i in 0 ..< media.count { if let _ = media[i] as? TelegramMediaWebpage { - media[i] = webpage + media[i] = webpageResult.webpage break } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index d8195e20b6..a66bfe2507 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -172,7 +172,7 @@ public enum AccountTransactionError { public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, removeDatabaseOnError: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true, isReadOnly: isReadOnly, useCopy: useCopy, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isMainProcess: false, isTemporary: true, isReadOnly: isReadOnly, useCopy: useCopy, useCaches: useCaches, removeDatabaseOnError: removeDatabaseOnError) return postbox |> castError(AccountTransactionError.self) |> mapToSignal { value -> Signal in diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift index 389628d9fa..2fcf26ac99 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift @@ -333,6 +333,15 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage public enum TelegramMediaWebpageContent { case Pending(Int32, String?) case Loaded(TelegramMediaWebpageLoadedContent) + + public var url: String? { + switch self { + case let .Pending(_, value): + return value + case let .Loaded(content): + return content.url + } + } } public final class TelegramMediaWebpage: Media, Equatable { diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index af5e284bf5..17e64c6a2d 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -5,13 +5,18 @@ import TelegramApi import MtProtoKit -public enum WebpagePreviewResult : Equatable { +public enum WebpagePreviewResult: Equatable { + public struct Result: Equatable { + public var webpage: TelegramMediaWebpage + public var sourceUrl: String + } + case progress - case result(TelegramMediaWebpage?) + case result(Result?) } -public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal { - return webpagePreviewWithProgress(account: account, url: url) +public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal { + return webpagePreviewWithProgress(account: account, urls: urls) |> mapToSignal { next -> Signal in if case let .result(result) = next { return .single(.result(result)) @@ -22,57 +27,69 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? = } public enum WebpagePreviewWithProgressResult { - case result(TelegramMediaWebpage?) + case result(WebpagePreviewResult.Result?) case progress(Float) } -public func webpagePreviewWithProgress(account: Account, url: String, webpageId: MediaId? = nil) -> Signal { +public func normalizedWebpagePreviewUrl(url: String) -> String { + return url +} + +public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in - if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage { - return .single(.result(webpage)) + if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage, let url = webpage.content.url { + var sourceUrl = url + if urls.count == 1 { + sourceUrl = urls[0] + } + return .single(.result(WebpagePreviewResult.Result(webpage: webpage, sourceUrl: sourceUrl))) } else { - return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: url, entities: nil), info: .progress) + return account.network.requestWithAdditionalInfo(Api.functions.messages.getWebPagePreview(flags: 0, message: urls.joined(separator: " "), entities: nil), info: .progress) |> `catch` { _ -> Signal, NoError> in return .single(.result(.messageMediaEmpty)) } |> mapToSignal { result -> Signal in switch result { - case .acknowledged: + case .acknowledged: + return .complete() + case let .progress(progress, packetSize): + if packetSize > 1024 { + return .single(.progress(progress)) + } else { return .complete() - case let .progress(progress, packetSize): - if packetSize > 1024 { - return .single(.progress(progress)) - } else { - return .complete() + } + case let .result(result): + if let preCachedResources = result.preCachedResources { + for (resource, data) in preCachedResources { + account.postbox.mediaBox.storeResourceData(resource.id, data: data) } - case let .result(result): - if let preCachedResources = result.preCachedResources { - for (resource, data) in preCachedResources { - account.postbox.mediaBox.storeResourceData(resource.id, data: data) - } - } - switch result { - case let .messageMediaWebPage(flags, webpage): - let _ = flags - if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) { - if case .Loaded = media.content { - return .single(.result(media)) - } else { - return .single(.result(media)) - |> then( - account.stateManager.updatedWebpage(media.webpageId) - |> take(1) - |> map { next -> WebpagePreviewWithProgressResult in - return .result(next) - } - ) + } + switch result { + case let .messageMediaWebPage(flags, webpage): + let _ = flags + if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url { + if case .Loaded = media.content { + return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) + } else { + return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url))) + |> then( + account.stateManager.updatedWebpage(media.webpageId) + |> take(1) + |> map { next -> WebpagePreviewWithProgressResult in + if let url = next.content.url { + return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url)) + } else { + return .result(nil) + } } - } else { - return .single(.result(nil)) - } - default: - return .single(.result(nil)) + ) + } + } else { + return .single(.result(nil)) } + default: + return .single(.result(nil)) + } } } } @@ -95,7 +112,7 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) - let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) - if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { + if let updatedWebpage = telegramMediaWebpageFromApiWebpage(apiWebpage), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId { return .single(updatedWebpage) } else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content { let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent( @@ -143,7 +160,7 @@ func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: Eng return .single(nil) } |> mapToSignal { result -> Signal in - if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id { + if let result = result, case let .webPage(webpage, chats, users) = result, let updatedWebpage = telegramMediaWebpageFromApiWebpage(webpage), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id { return postbox.transaction { transaction -> TelegramMediaWebpage? in let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 52ba4c402c..38ab36d432 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -4034,7 +4034,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI for attribute in item.message.attributes { if let attribute = attribute as? ReplyMessageAttribute { if let threadId = item.message.threadId, makeThreadIdMessageId(peerId: item.message.id.peerId, threadId: threadId) == attribute.messageId, let quotedReply = item.message.attributes.first(where: { $0 is QuotedReplyMessageAttribute }) as? QuotedReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action({ + return .action(InternalBubbleTapAction.Action({ [weak self, weak replyInfoNode] in + guard let self, let item = self.item, let replyInfoNode else { + return + } + if attribute.isQuote, !replyInfoNode.isQuoteExpanded { + replyInfoNode.isQuoteExpanded = true + item.controllerInteraction.requestMessageUpdate(item.message.id, false) + return + } item.controllerInteraction.attemptedNavigationToPrivateQuote(quotedReply.peerId.flatMap { item.message.peers[$0] }) }, contextMenuOnLongPress: true)) } @@ -4054,7 +4062,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI item.controllerInteraction.navigateToStory(item.message, attribute.storyId) }, contextMenuOnLongPress: true)) } else if let attribute = attribute as? QuotedReplyMessageAttribute { - return .action(InternalBubbleTapAction.Action({ + return .action(InternalBubbleTapAction.Action({ [weak self, weak replyInfoNode] in + guard let self, let item = self.item, let replyInfoNode else { + return + } + if attribute.isQuote, !replyInfoNode.isQuoteExpanded { + replyInfoNode.isQuoteExpanded = true + item.controllerInteraction.requestMessageUpdate(item.message.id, false) + return + } + item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] }) }, contextMenuOnLongPress: true)) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 64d8b54e47..a20b2a02ab 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -139,6 +139,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { private var currentProgressDisposable: Disposable? + public var isQuoteExpanded: Bool = false + override public init() { self.backgroundView = MessageInlineBlockBackgroundView(frame: CGRect()) @@ -177,6 +179,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode) let previousMediaReference = maybeNode?.previousMediaReference + let isQuoteExpanded = maybeNode?.isQuoteExpanded ?? false + return { arguments in let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) let titleFont = Font.semibold(fontSize) @@ -558,7 +562,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if isQuote { additionalTitleWidth += 10.0 maxTitleNumberOfLines = 2 - maxTextNumberOfLines = 5 + maxTextNumberOfLines = isQuoteExpanded ? 50 : 5 if imageTextInset != 0.0 { adjustedConstrainedTextSize.width += imageTextInset textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0)) @@ -649,13 +653,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { node.previousMediaReference = updatedMediaReference node.titleNode?.displaysAsynchronously = !arguments.presentationData.isPreview - node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview + //node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview let titleNode = titleApply() var textArguments: TextNodeWithEntities.Arguments? if let cache = arguments.animationCache, let renderer = arguments.animationRenderer { textArguments = TextNodeWithEntities.Arguments(context: arguments.context, cache: cache, renderer: renderer, placeholderColor: placeholderColor, attemptSynchronous: attemptSynchronous) } + let previousTextContents = node.textNode?.textNode.layer.contents let textNode = textApply(textArguments) textNode.visibilityRect = node.visibility ? CGRect.infinite : nil @@ -667,6 +672,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if node.textNode == nil { textNode.textNode.isUserInteractionEnabled = false + textNode.textNode.contentMode = .topLeft + textNode.textNode.clipsToBounds = true + textNode.textNode.contentsScale = UIScreenScale + textNode.textNode.displaysAsynchronously = false node.textNode = textNode node.contentNode.addSubnode(textNode.textNode) } @@ -694,7 +703,28 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size) let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0 - textCutoutWidth, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size) - textNode.textNode.frame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0) + let effectiveTextFrame = textFrame.offsetBy(dx: (isExpiredStory || isStory) ? 18.0 : 0.0, dy: 0.0) + + if textNode.textNode.bounds.isEmpty || !animation.isAnimated || textNode.textNode.bounds.height == effectiveTextFrame.height { + textNode.textNode.frame = effectiveTextFrame + } else { + if textNode.textNode.bounds.height != effectiveTextFrame.height { + animation.animator.updateFrame(layer: textNode.textNode.layer, frame: effectiveTextFrame, completion: nil) + + textNode.textNode.layer.setNeedsDisplay() + textNode.textNode.layer.display() + } else { + animation.animator.updateFrame(layer: textNode.textNode.layer, frame: effectiveTextFrame, completion: nil) + } + + if let previousTextContents, let updatedContents = textNode.textNode.contents { + let previousTextContents = previousTextContents as AnyObject + let updatedContents = updatedContents as AnyObject + if previousTextContents !== updatedContents { + textNode.textNode.layer.animate(from: previousTextContents, to: updatedContents, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) + } + } + } if isExpiredStory || isStory { let expiredStoryIconView: UIImageView @@ -784,16 +814,21 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { ) if isQuote { + let quoteIconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 4.0 - quoteIcon.size.width, y: backgroundFrame.minY + 4.0), size: quoteIcon.size) + let quoteIconView: UIImageView if let current = node.quoteIconView { quoteIconView = current + + animation.animator.updateFrame(layer: quoteIconView.layer, frame: quoteIconFrame, completion: nil) } else { quoteIconView = UIImageView(image: quoteIcon) node.quoteIconView = quoteIconView node.contentNode.view.addSubview(quoteIconView) + + quoteIconView.frame = quoteIconFrame } quoteIconView.tintColor = mainColor - quoteIconView.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - 4.0 - quoteIcon.size.width, y: backgroundFrame.minY + 4.0), size: quoteIcon.size) } else { if let quoteIconView = node.quoteIconView { node.quoteIconView = nil diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 3434154826..e83276df04 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -57,7 +57,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if content.embedUrl == nil && (content.title != nil || content.text != nil) && content.story == nil { var shouldOpenUrl = true if let file = content.file { - if !file.isVideo, !file.isVideoSticker, !file.isAnimated, !file.isAnimatedSticker, !file.isSticker, !file.isMusic { + if file.isVideo { + shouldOpenUrl = false + } else if !file.isVideoSticker, !file.isAnimated, !file.isAnimatedSticker, !file.isSticker, !file.isMusic { shouldOpenUrl = false } else if file.isMusic || file.isVoice { shouldOpenUrl = false diff --git a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift index 2eb9b02955..e89d933895 100644 --- a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift +++ b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift @@ -30,12 +30,12 @@ public final class TextLoadingEffectView: UIView { self.maskContentsView = UIView() self.maskHighlightNode = LinkHighlightingNode(color: .black) - self.maskHighlightNode.useModernPathCalculation = true + //self.maskHighlightNode.useModernPathCalculation = true self.maskBorderContentsView = UIView() self.maskBorderHighlightNode = LinkHighlightingNode(color: .black) self.maskBorderHighlightNode.borderOnly = true - self.maskBorderHighlightNode.useModernPathCalculation = true + //self.maskBorderHighlightNode.useModernPathCalculation = true self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view) self.backgroundView = UIImageView() diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index c9168b7ba2..8e5851a995 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -837,14 +837,14 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD return } - if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState { - if let webpage = webpageCache[updatedUrlPreviewState.detectedUrl] { + if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState, let detectedUrl = updatedUrlPreviewState.detectedUrls.first { + if let webpage = webpageCache[detectedUrl] { progress?.set(.single(false)) selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in if state.interfaceState.editMessage != nil { if var urlPreview = state.editingUrlPreview { - urlPreview.url = updatedUrlPreviewState.detectedUrl + urlPreview.url = detectedUrl urlPreview.webPage = webpage return state.updatedEditingUrlPreview(urlPreview) @@ -853,7 +853,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD } } else { if var urlPreview = state.urlPreview { - urlPreview.url = updatedUrlPreviewState.detectedUrl + urlPreview.url = detectedUrl urlPreview.webPage = webpage return state.updatedUrlPreview(urlPreview) @@ -875,20 +875,20 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in if state.interfaceState.editMessage != nil { - if let webpage = result(nil), var urlPreview = state.editingUrlPreview { - urlPreview.url = updatedUrlPreviewState.detectedUrl + if let (webpage, webpageUrl) = result(nil), var urlPreview = state.editingUrlPreview { + urlPreview.url = webpageUrl urlPreview.webPage = webpage - webpageCache[updatedUrlPreviewState.detectedUrl] = webpage + webpageCache[detectedUrl] = webpage return state.updatedEditingUrlPreview(urlPreview) } else { return state } } else { - if let webpage = result(nil), var urlPreview = state.urlPreview { - urlPreview.url = updatedUrlPreviewState.detectedUrl + if let (webpage, webpageUrl) = result(nil), var urlPreview = state.urlPreview { + urlPreview.url = webpageUrl urlPreview.webPage = webpage - webpageCache[updatedUrlPreviewState.detectedUrl] = webpage + webpageCache[detectedUrl] = webpage return state.updatedUrlPreview(urlPreview) } else { diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 245f78c609..9f6beaff47 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -223,7 +223,7 @@ func updateChatPresentationInterfaceStateImpl( if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) { selfController.urlPreviewQueryState?.1.dispose() var inScope = true - var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? + var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)? let linkPreviews: Signal if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: selfController.context.sharedContext.accountManager, displayAlert: { [weak selfController] f in @@ -252,7 +252,7 @@ func updateChatPresentationInterfaceStateImpl( } let filteredPreviewSignal = linkPreviews |> take(1) - |> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError> in + |> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError> in if value { return updatedUrlPreviewSignal } else { @@ -260,7 +260,7 @@ func updateChatPresentationInterfaceStateImpl( } } - selfController.urlPreviewQueryState = (updatedUrlPreviewState, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] (result) in + selfController.urlPreviewQueryState = (updatedUrlPreviewState, (filteredPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in guard let selfController else { return } @@ -269,9 +269,9 @@ func updateChatPresentationInterfaceStateImpl( inScopeResult = result } else { selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { - if let updatedUrlPreviewState, let webpage = result($0.urlPreview?.webPage) { + if let (webpage, webpageUrl) = result($0.urlPreview?.webPage) { let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedUrlPreviewState.detectedUrl, + url: webpageUrl, webPage: webpage, positionBelowText: $0.urlPreview?.positionBelowText ?? true, largeMedia: $0.urlPreview?.largeMedia @@ -285,9 +285,9 @@ func updateChatPresentationInterfaceStateImpl( })) inScope = false if let inScopeResult = inScopeResult { - if let updatedUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) { + if let (webpage, webpageUrl) = inScopeResult(updatedChatPresentationInterfaceState.urlPreview?.webPage) { let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedUrlPreviewState.detectedUrl, + url: webpageUrl, webPage: webpage, positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true, largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia @@ -304,7 +304,7 @@ func updateChatPresentationInterfaceStateImpl( if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) { selfController.editingUrlPreviewQueryState?.1.dispose() var inScope = true - var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)? + var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)? selfController.editingUrlPreviewQueryState = (updatedEditingUrlPreviewState, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in guard let selfController else { return @@ -314,9 +314,9 @@ func updateChatPresentationInterfaceStateImpl( inScopeResult = result } else { selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { - if let updatedEditingUrlPreviewState, let webpage = result($0.editingUrlPreview?.webPage) { + if let (webpage, webpageUrl) = result($0.editingUrlPreview?.webPage) { let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedEditingUrlPreviewState.detectedUrl, + url: webpageUrl, webPage: webpage, positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true, largeMedia: $0.editingUrlPreview?.largeMedia @@ -330,9 +330,9 @@ func updateChatPresentationInterfaceStateImpl( })) inScope = false if let inScopeResult = inScopeResult { - if let updatedEditingUrlPreviewState, let webpage = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) { + if let (webpage, webpageUrl) = inScopeResult(updatedChatPresentationInterfaceState.editingUrlPreview?.webPage) { let updatedPreview = ChatPresentationInterfaceState.UrlPreview( - url: updatedEditingUrlPreviewState.detectedUrl, + url: webpageUrl, webPage: webpage, positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true, largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 1386845703..6317e1f974 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -507,10 +507,10 @@ func detectUrls(_ inputText: NSAttributedString?) -> [String] { } struct UrlPreviewState { - var detectedUrl: String + var detectedUrls: [String] } -func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError>)? { +func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? { guard let _ = inputText else { if currentQuery != nil { return (nil, .single({ _ in return nil })) @@ -519,15 +519,19 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco } } if let _ = dataDetector { - let detectedUrl = detectUrls(inputText).first - if detectedUrl != currentQuery?.detectedUrl { - if let detectedUrl = detectedUrl { - return (UrlPreviewState(detectedUrl: detectedUrl), webpagePreview(account: context.account, url: detectedUrl) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { + let detectedUrls = detectUrls(inputText) + if detectedUrls != currentQuery?.detectedUrls { + if !detectedUrls.isEmpty { + return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls) + |> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in + guard case let .result(webpageResult) = result else { return .complete() } - return .single(result) + if let webpageResult { + return .single((webpageResult.webpage, webpageResult.sourceUrl)) + } else { + return .single(nil) + } } |> map { value in return { _ in return value } diff --git a/submodules/TranslateUI/Sources/ChatTranslation.swift b/submodules/TranslateUI/Sources/ChatTranslation.swift index 425e168c36..606b23013b 100644 --- a/submodules/TranslateUI/Sources/ChatTranslation.swift +++ b/submodules/TranslateUI/Sources/ChatTranslation.swift @@ -207,12 +207,11 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id) for message in messages { if message.effectivelyIncoming(context.account.peerId), message.text.count >= 10 { var text = String(message.text.prefix(256)) - if var entities = message.textEntitiesAttribute?.entities.filter({ - if [.Code, .Url, .Email, .Mention, .Hashtag, .BotCommand].contains($0.type) { + if var entities = message.textEntitiesAttribute?.entities.filter({ entity in + switch entity.type { + case .Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand: return true - } else if case .Pre = $0.type { - return true - } else { + default: return false } }) { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index ea595eb8dc..c6660b3b37 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -1157,14 +1157,14 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String } public func resolveInstantViewUrl(account: Account, url: String) -> Signal { - return webpagePreview(account: account, url: url) + return webpagePreview(account: account, urls: [url]) |> mapToSignal { result -> Signal in switch result { case .progress: return .single(.progress) - case let .result(webpage): - if let webpage = webpage { - if case let .Loaded(content) = webpage.content { + case let .result(webpageResult): + if let webpageResult = webpageResult { + if case let .Loaded(content) = webpageResult.webpage.content { if content.instantPage != nil { var anchorValue: String? if let anchorRange = url.range(of: "#") { @@ -1173,7 +1173,7 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal