Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-10-27 16:44:34 +04:00
commit c4618f19db
22 changed files with 243 additions and 126 deletions

View File

@ -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):

View File

@ -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",

View File

@ -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<PostboxResult, NoError> {
public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isMainProcess: Bool, isTemporary: Bool, isReadOnly: Bool, useCopy: Bool, useCaches: Bool, removeDatabaseOnError: Bool) -> Signal<PostboxResult, NoError> {
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

View File

@ -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<Impl>
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)
})
}

View File

@ -183,6 +183,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
seedConfiguration: telegramPostboxSeedConfiguration,
encryptionParameters: encryptionParameters,
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
isMainProcess: !supplementary,
isTemporary: false,
isReadOnly: false,
useCopy: false,
@ -1445,6 +1446,7 @@ public func standaloneStateManager(
seedConfiguration: telegramPostboxSeedConfiguration,
encryptionParameters: encryptionParameters,
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
isMainProcess: false,
isTemporary: false,
isReadOnly: false,
useCopy: false,

View File

@ -92,11 +92,23 @@ final class AccountManagerImpl<Types: AccountManagerTypes> {
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<Types: AccountManagerTypes> {
preconditionFailure()
}
})
self.mediaBox = MediaBox(basePath: basePath + "/media")
self.mediaBox = MediaBox(basePath: basePath + "/media", isMainProcess: removeDatabaseOnError)
}
public func transaction<T>(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier<Types>) -> T) -> Signal<T, NoError> {

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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<Void, NoError> in
guard case let .result(webpage) = result else {
self.webpageDisposables[messageId] = (webpagePreview(account: account, urls: [url]) |> mapToSignal { result -> Signal<Void, NoError> 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
}
}

View File

@ -172,7 +172,7 @@ public enum AccountTransactionError {
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, isReadOnly: Bool, useCopy: Bool = false, useCaches: Bool = true, removeDatabaseOnError: Bool = true, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, AccountTransactionError> {
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<T, AccountTransactionError> in

View File

@ -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 {

View File

@ -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<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, url: url)
public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
return webpagePreviewWithProgress(account: account, urls: urls)
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> 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<WebpagePreviewWithProgressResult, NoError> {
public func normalizedWebpagePreviewUrl(url: String) -> String {
return url
}
public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewWithProgressResult, NoError> {
return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> 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<NetworkRequestResult<Api.MessageMedia>, NoError> in
return .single(.result(.messageMediaEmpty))
}
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> 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<TelegramMediaWebpage?, NoError> 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)

View File

@ -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))
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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 {

View File

@ -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<Bool, NoError>
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

View File

@ -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<TelegramMediaWebpage?, NoError> 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 }

View File

@ -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
}
}) {

View File

@ -1157,14 +1157,14 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
}
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolveUrlResult, NoError> {
return webpagePreview(account: account, url: url)
return webpagePreview(account: account, urls: [url])
|> mapToSignal { result -> Signal<ResolveUrlResult, NoError> 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<Resol
anchorValue = String(anchor)
}
}
return .single(.result(.instantView(webpage, anchorValue)))
return .single(.result(.instantView(webpageResult.webpage, anchorValue)))
} else {
return .single(.result(.externalUrl(url)))
}