mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
c4618f19db
@ -1308,14 +1308,14 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if let anchorRange = externalUrl.range(of: "#") {
|
if let anchorRange = externalUrl.range(of: "#") {
|
||||||
anchor = String(externalUrl[anchorRange.upperBound...])
|
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
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
switch result {
|
switch result {
|
||||||
case let .result(webpage):
|
case let .result(webpageResult):
|
||||||
if let webpage = webpage, case .Loaded = webpage.content {
|
if let webpageResult = webpageResult, case .Loaded = webpageResult.webpage.content {
|
||||||
strongSelf.loadProgress.set(1.0)
|
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
|
break
|
||||||
case let .progress(progress):
|
case let .progress(progress):
|
||||||
|
@ -136,6 +136,7 @@ private final class MediaBoxKeepResourceContext {
|
|||||||
|
|
||||||
public final class MediaBox {
|
public final class MediaBox {
|
||||||
public let basePath: String
|
public let basePath: String
|
||||||
|
public let isMainProcess: Bool
|
||||||
|
|
||||||
private let statusQueue = Queue()
|
private let statusQueue = Queue()
|
||||||
private let concurrentQueue = Queue.concurrentDefaultQueue()
|
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)
|
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.basePath = basePath
|
||||||
|
self.isMainProcess = isMainProcess
|
||||||
|
|
||||||
self.storageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
self.storageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
||||||
postboxLog(string)
|
postboxLog(string)
|
||||||
}), basePath: basePath + "/storage")
|
}), basePath: basePath + "/storage", isMainProcess: isMainProcess)
|
||||||
self.cacheStorageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
self.cacheStorageBox = StorageBox(logger: StorageBox.Logger(impl: { string in
|
||||||
postboxLog(string)
|
postboxLog(string)
|
||||||
}), basePath: basePath + "/cache-storage")
|
}), basePath: basePath + "/cache-storage", isMainProcess: isMainProcess)
|
||||||
|
|
||||||
self.timeBasedCleanup = TimeBasedCleanup(storageBox: self.storageBox, generalPaths: [
|
self.timeBasedCleanup = TimeBasedCleanup(storageBox: self.storageBox, generalPaths: [
|
||||||
self.basePath + "/cache",
|
self.basePath + "/cache",
|
||||||
|
@ -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
|
let queue = Postbox.sharedQueue
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
queue.async {
|
queue.async {
|
||||||
@ -1496,7 +1496,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
|||||||
let endTime = CFAbsoluteTimeGetCurrent()
|
let endTime = CFAbsoluteTimeGetCurrent()
|
||||||
postboxLog("Postbox load took \((endTime - startTime) * 1000.0) ms")
|
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")
|
postboxLog("openPostbox, putCompletion")
|
||||||
|
|
||||||
@ -4168,6 +4168,7 @@ public class Postbox {
|
|||||||
seedConfiguration: SeedConfiguration,
|
seedConfiguration: SeedConfiguration,
|
||||||
valueBox: SqliteValueBox,
|
valueBox: SqliteValueBox,
|
||||||
timestampForAbsoluteTimeBasedOperations: Int32,
|
timestampForAbsoluteTimeBasedOperations: Int32,
|
||||||
|
isMainProcess: Bool,
|
||||||
isTemporary: Bool,
|
isTemporary: Bool,
|
||||||
tempDir: TempBoxDirectory?,
|
tempDir: TempBoxDirectory?,
|
||||||
useCaches: Bool
|
useCaches: Bool
|
||||||
@ -4177,7 +4178,7 @@ public class Postbox {
|
|||||||
self.seedConfiguration = seedConfiguration
|
self.seedConfiguration = seedConfiguration
|
||||||
|
|
||||||
postboxLog("MediaBox path: \(basePath + "/media")")
|
postboxLog("MediaBox path: \(basePath + "/media")")
|
||||||
self.mediaBox = MediaBox(basePath: basePath + "/media")
|
self.mediaBox = MediaBox(basePath: basePath + "/media", isMainProcess: isMainProcess)
|
||||||
|
|
||||||
let isInTransaction = self.isInTransaction
|
let isInTransaction = self.isInTransaction
|
||||||
|
|
||||||
|
@ -163,20 +163,37 @@ public final class StorageBox {
|
|||||||
|
|
||||||
private var queuedInternalTransactions = Atomic<[() -> Void]>(value: [])
|
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.queue = queue
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.basePath = basePath
|
self.basePath = basePath
|
||||||
|
|
||||||
let databasePath = self.basePath + "/db"
|
let databasePath = self.basePath + "/db"
|
||||||
let _ = try? FileManager.default.createDirectory(atPath: databasePath, withIntermediateDirectories: true)
|
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 {
|
var valueBox: SqliteValueBox?
|
||||||
let _ = try? FileManager.default.removeItem(atPath: databasePath)
|
for i in 0 ..< 3 {
|
||||||
valueBox = SqliteValueBox(basePath: databasePath, queue: queue, isTemporary: false, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in })
|
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
|
self.valueBox = valueBox
|
||||||
|
|
||||||
@ -1025,11 +1042,11 @@ public final class StorageBox {
|
|||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let impl: QueueLocalObject<Impl>
|
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
|
let queue = StorageBox.sharedQueue
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, logger: logger, basePath: basePath)
|
return Impl(queue: queue, logger: logger, basePath: basePath, isMainProcess: isMainProcess)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +183,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||||||
seedConfiguration: telegramPostboxSeedConfiguration,
|
seedConfiguration: telegramPostboxSeedConfiguration,
|
||||||
encryptionParameters: encryptionParameters,
|
encryptionParameters: encryptionParameters,
|
||||||
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
|
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
|
||||||
|
isMainProcess: !supplementary,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
useCopy: false,
|
useCopy: false,
|
||||||
@ -1445,6 +1446,7 @@ public func standaloneStateManager(
|
|||||||
seedConfiguration: telegramPostboxSeedConfiguration,
|
seedConfiguration: telegramPostboxSeedConfiguration,
|
||||||
encryptionParameters: encryptionParameters,
|
encryptionParameters: encryptionParameters,
|
||||||
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
|
timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970),
|
||||||
|
isMainProcess: false,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
useCopy: false,
|
useCopy: false,
|
||||||
|
@ -92,11 +92,23 @@ final class AccountManagerImpl<Types: AccountManagerTypes> {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.guardValueBox = guardValueBox
|
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()
|
postboxLogSync()
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
self.valueBox = valueBox
|
self.valueBox = valueBox
|
||||||
|
|
||||||
@ -537,7 +549,7 @@ public final class AccountManager<Types: AccountManagerTypes> {
|
|||||||
preconditionFailure()
|
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> {
|
public func transaction<T>(ignoreDisabled: Bool = false, _ f: @escaping (AccountManagerModifier<Types>) -> T) -> Signal<T, NoError> {
|
||||||
|
@ -343,7 +343,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
return (TelegramMediaExpiredContent(data: .file), nil, nil, nil, nil)
|
return (TelegramMediaExpiredContent(data: .file), nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
case let .messageMediaWebPage(flags, webpage):
|
case let .messageMediaWebPage(flags, webpage):
|
||||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) {
|
||||||
var webpageForceLargeMedia: Bool?
|
var webpageForceLargeMedia: Bool?
|
||||||
if (flags & (1 << 0)) != 0 {
|
if (flags & (1 << 0)) != 0 {
|
||||||
webpageForceLargeMedia = true
|
webpageForceLargeMedia = true
|
||||||
|
@ -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 {
|
switch webpage {
|
||||||
case .webPageNotModified:
|
case .webPageNotModified:
|
||||||
return nil
|
return nil
|
||||||
|
@ -994,7 +994,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
let _ = url
|
let _ = url
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1217,7 +1217,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
let _ = url
|
let _ = url
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2993,7 +2993,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo
|
|||||||
let _ = url
|
let _ = url
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,18 +408,18 @@ public final class AccountViewTracker {
|
|||||||
for messageId in addedMessageIds {
|
for messageId in addedMessageIds {
|
||||||
if self.webpageDisposables[messageId] == nil {
|
if self.webpageDisposables[messageId] == nil {
|
||||||
if let (_, url) = localWebpages[messageId] {
|
if let (_, url) = localWebpages[messageId] {
|
||||||
self.webpageDisposables[messageId] = (webpagePreview(account: account, url: url) |> mapToSignal { result -> Signal<Void, NoError> in
|
self.webpageDisposables[messageId] = (webpagePreview(account: account, urls: [url]) |> mapToSignal { result -> Signal<Void, NoError> in
|
||||||
guard case let .result(webpage) = result else {
|
guard case let .result(webpageResult) = result else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
if let webpage = webpage {
|
if let webpageResult = webpageResult {
|
||||||
transaction.updateMessage(messageId, update: { currentMessage in
|
transaction.updateMessage(messageId, update: { currentMessage in
|
||||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
var media = currentMessage.media
|
var media = currentMessage.media
|
||||||
for i in 0 ..< media.count {
|
for i in 0 ..< media.count {
|
||||||
if let _ = media[i] as? TelegramMediaWebpage {
|
if let _ = media[i] as? TelegramMediaWebpage {
|
||||||
media[i] = webpage
|
media[i] = webpageResult.webpage
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
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 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
|
return postbox
|
||||||
|> castError(AccountTransactionError.self)
|
|> castError(AccountTransactionError.self)
|
||||||
|> mapToSignal { value -> Signal<T, AccountTransactionError> in
|
|> mapToSignal { value -> Signal<T, AccountTransactionError> in
|
||||||
|
@ -333,6 +333,15 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
|
|||||||
public enum TelegramMediaWebpageContent {
|
public enum TelegramMediaWebpageContent {
|
||||||
case Pending(Int32, String?)
|
case Pending(Int32, String?)
|
||||||
case Loaded(TelegramMediaWebpageLoadedContent)
|
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 {
|
public final class TelegramMediaWebpage: Media, Equatable {
|
||||||
|
@ -5,13 +5,18 @@ import TelegramApi
|
|||||||
import MtProtoKit
|
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 progress
|
||||||
case result(TelegramMediaWebpage?)
|
case result(Result?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func webpagePreview(account: Account, url: String, webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
|
public func webpagePreview(account: Account, urls: [String], webpageId: MediaId? = nil) -> Signal<WebpagePreviewResult, NoError> {
|
||||||
return webpagePreviewWithProgress(account: account, url: url)
|
return webpagePreviewWithProgress(account: account, urls: urls)
|
||||||
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> in
|
|> mapToSignal { next -> Signal<WebpagePreviewResult, NoError> in
|
||||||
if case let .result(result) = next {
|
if case let .result(result) = next {
|
||||||
return .single(.result(result))
|
return .single(.result(result))
|
||||||
@ -22,57 +27,69 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? =
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum WebpagePreviewWithProgressResult {
|
public enum WebpagePreviewWithProgressResult {
|
||||||
case result(TelegramMediaWebpage?)
|
case result(WebpagePreviewResult.Result?)
|
||||||
case progress(Float)
|
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
|
return account.postbox.transaction { transaction -> Signal<WebpagePreviewWithProgressResult, NoError> in
|
||||||
if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage {
|
if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage, let url = webpage.content.url {
|
||||||
return .single(.result(webpage))
|
var sourceUrl = url
|
||||||
|
if urls.count == 1 {
|
||||||
|
sourceUrl = urls[0]
|
||||||
|
}
|
||||||
|
return .single(.result(WebpagePreviewResult.Result(webpage: webpage, sourceUrl: sourceUrl)))
|
||||||
} else {
|
} 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
|
|> `catch` { _ -> Signal<NetworkRequestResult<Api.MessageMedia>, NoError> in
|
||||||
return .single(.result(.messageMediaEmpty))
|
return .single(.result(.messageMediaEmpty))
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in
|
|> mapToSignal { result -> Signal<WebpagePreviewWithProgressResult, NoError> in
|
||||||
switch result {
|
switch result {
|
||||||
case .acknowledged:
|
case .acknowledged:
|
||||||
|
return .complete()
|
||||||
|
case let .progress(progress, packetSize):
|
||||||
|
if packetSize > 1024 {
|
||||||
|
return .single(.progress(progress))
|
||||||
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
case let .progress(progress, packetSize):
|
}
|
||||||
if packetSize > 1024 {
|
case let .result(result):
|
||||||
return .single(.progress(progress))
|
if let preCachedResources = result.preCachedResources {
|
||||||
} else {
|
for (resource, data) in preCachedResources {
|
||||||
return .complete()
|
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||||
}
|
}
|
||||||
case let .result(result):
|
}
|
||||||
if let preCachedResources = result.preCachedResources {
|
switch result {
|
||||||
for (resource, data) in preCachedResources {
|
case let .messageMediaWebPage(flags, webpage):
|
||||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
let _ = flags
|
||||||
}
|
if let media = telegramMediaWebpageFromApiWebpage(webpage), let url = media.content.url {
|
||||||
}
|
if case .Loaded = media.content {
|
||||||
switch result {
|
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
|
||||||
case let .messageMediaWebPage(flags, webpage):
|
} else {
|
||||||
let _ = flags
|
return .single(.result(WebpagePreviewResult.Result(webpage: media, sourceUrl: url)))
|
||||||
if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) {
|
|> then(
|
||||||
if case .Loaded = media.content {
|
account.stateManager.updatedWebpage(media.webpageId)
|
||||||
return .single(.result(media))
|
|> take(1)
|
||||||
} else {
|
|> map { next -> WebpagePreviewWithProgressResult in
|
||||||
return .single(.result(media))
|
if let url = next.content.url {
|
||||||
|> then(
|
return .result(WebpagePreviewResult.Result(webpage: next, sourceUrl: url))
|
||||||
account.stateManager.updatedWebpage(media.webpageId)
|
} else {
|
||||||
|> take(1)
|
return .result(nil)
|
||||||
|> map { next -> WebpagePreviewWithProgressResult in
|
}
|
||||||
return .result(next)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
)
|
||||||
return .single(.result(nil))
|
}
|
||||||
}
|
} else {
|
||||||
default:
|
return .single(.result(nil))
|
||||||
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)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
|
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)
|
return .single(updatedWebpage)
|
||||||
} else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content {
|
} else if case let .webPageNotModified(_, viewsValue) = apiWebpage, let views = viewsValue, case let .Loaded(content) = webpage.content {
|
||||||
let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(
|
let updatedContent: TelegramMediaWebpageContent = .Loaded(TelegramMediaWebpageLoadedContent(
|
||||||
@ -143,7 +160,7 @@ func updatedRemoteWebpage(postbox: Postbox, network: Network, accountPeerId: Eng
|
|||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
|> 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
|
return postbox.transaction { transaction -> TelegramMediaWebpage? in
|
||||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||||
|
@ -4034,7 +4034,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
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 {
|
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] })
|
item.controllerInteraction.attemptedNavigationToPrivateQuote(quotedReply.peerId.flatMap { item.message.peers[$0] })
|
||||||
}, contextMenuOnLongPress: true))
|
}, contextMenuOnLongPress: true))
|
||||||
}
|
}
|
||||||
@ -4054,7 +4062,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
item.controllerInteraction.navigateToStory(item.message, attribute.storyId)
|
||||||
}, contextMenuOnLongPress: true))
|
}, contextMenuOnLongPress: true))
|
||||||
} else if let attribute = attribute as? QuotedReplyMessageAttribute {
|
} 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] })
|
item.controllerInteraction.attemptedNavigationToPrivateQuote(attribute.peerId.flatMap { item.message.peers[$0] })
|
||||||
}, contextMenuOnLongPress: true))
|
}, contextMenuOnLongPress: true))
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var currentProgressDisposable: Disposable?
|
private var currentProgressDisposable: Disposable?
|
||||||
|
|
||||||
|
public var isQuoteExpanded: Bool = false
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
self.backgroundView = MessageInlineBlockBackgroundView(frame: CGRect())
|
self.backgroundView = MessageInlineBlockBackgroundView(frame: CGRect())
|
||||||
|
|
||||||
@ -177,6 +179,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode)
|
let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode)
|
||||||
let previousMediaReference = maybeNode?.previousMediaReference
|
let previousMediaReference = maybeNode?.previousMediaReference
|
||||||
|
|
||||||
|
let isQuoteExpanded = maybeNode?.isQuoteExpanded ?? false
|
||||||
|
|
||||||
return { arguments in
|
return { arguments in
|
||||||
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
|
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
|
||||||
let titleFont = Font.semibold(fontSize)
|
let titleFont = Font.semibold(fontSize)
|
||||||
@ -558,7 +562,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
if isQuote {
|
if isQuote {
|
||||||
additionalTitleWidth += 10.0
|
additionalTitleWidth += 10.0
|
||||||
maxTitleNumberOfLines = 2
|
maxTitleNumberOfLines = 2
|
||||||
maxTextNumberOfLines = 5
|
maxTextNumberOfLines = isQuoteExpanded ? 50 : 5
|
||||||
if imageTextInset != 0.0 {
|
if imageTextInset != 0.0 {
|
||||||
adjustedConstrainedTextSize.width += imageTextInset
|
adjustedConstrainedTextSize.width += imageTextInset
|
||||||
textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0))
|
textCutout = TextNodeCutout(topLeft: CGSize(width: imageTextInset + 6.0, height: 10.0))
|
||||||
@ -649,13 +653,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
node.previousMediaReference = updatedMediaReference
|
node.previousMediaReference = updatedMediaReference
|
||||||
|
|
||||||
node.titleNode?.displaysAsynchronously = !arguments.presentationData.isPreview
|
node.titleNode?.displaysAsynchronously = !arguments.presentationData.isPreview
|
||||||
node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview
|
//node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview
|
||||||
|
|
||||||
let titleNode = titleApply()
|
let titleNode = titleApply()
|
||||||
var textArguments: TextNodeWithEntities.Arguments?
|
var textArguments: TextNodeWithEntities.Arguments?
|
||||||
if let cache = arguments.animationCache, let renderer = arguments.animationRenderer {
|
if let cache = arguments.animationCache, let renderer = arguments.animationRenderer {
|
||||||
textArguments = TextNodeWithEntities.Arguments(context: arguments.context, cache: cache, renderer: renderer, placeholderColor: placeholderColor, attemptSynchronous: attemptSynchronous)
|
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)
|
let textNode = textApply(textArguments)
|
||||||
textNode.visibilityRect = node.visibility ? CGRect.infinite : nil
|
textNode.visibilityRect = node.visibility ? CGRect.infinite : nil
|
||||||
|
|
||||||
@ -667,6 +672,10 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
|
|
||||||
if node.textNode == nil {
|
if node.textNode == nil {
|
||||||
textNode.textNode.isUserInteractionEnabled = false
|
textNode.textNode.isUserInteractionEnabled = false
|
||||||
|
textNode.textNode.contentMode = .topLeft
|
||||||
|
textNode.textNode.clipsToBounds = true
|
||||||
|
textNode.textNode.contentsScale = UIScreenScale
|
||||||
|
textNode.textNode.displaysAsynchronously = false
|
||||||
node.textNode = textNode
|
node.textNode = textNode
|
||||||
node.contentNode.addSubnode(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)
|
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)
|
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 {
|
if isExpiredStory || isStory {
|
||||||
let expiredStoryIconView: UIImageView
|
let expiredStoryIconView: UIImageView
|
||||||
@ -784,16 +814,21 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isQuote {
|
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
|
let quoteIconView: UIImageView
|
||||||
if let current = node.quoteIconView {
|
if let current = node.quoteIconView {
|
||||||
quoteIconView = current
|
quoteIconView = current
|
||||||
|
|
||||||
|
animation.animator.updateFrame(layer: quoteIconView.layer, frame: quoteIconFrame, completion: nil)
|
||||||
} else {
|
} else {
|
||||||
quoteIconView = UIImageView(image: quoteIcon)
|
quoteIconView = UIImageView(image: quoteIcon)
|
||||||
node.quoteIconView = quoteIconView
|
node.quoteIconView = quoteIconView
|
||||||
node.contentNode.view.addSubview(quoteIconView)
|
node.contentNode.view.addSubview(quoteIconView)
|
||||||
|
|
||||||
|
quoteIconView.frame = quoteIconFrame
|
||||||
}
|
}
|
||||||
quoteIconView.tintColor = mainColor
|
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 {
|
} else {
|
||||||
if let quoteIconView = node.quoteIconView {
|
if let quoteIconView = node.quoteIconView {
|
||||||
node.quoteIconView = nil
|
node.quoteIconView = nil
|
||||||
|
@ -57,7 +57,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
|
|||||||
if content.embedUrl == nil && (content.title != nil || content.text != nil) && content.story == nil {
|
if content.embedUrl == nil && (content.title != nil || content.text != nil) && content.story == nil {
|
||||||
var shouldOpenUrl = true
|
var shouldOpenUrl = true
|
||||||
if let file = content.file {
|
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
|
shouldOpenUrl = false
|
||||||
} else if file.isMusic || file.isVoice {
|
} else if file.isMusic || file.isVoice {
|
||||||
shouldOpenUrl = false
|
shouldOpenUrl = false
|
||||||
|
@ -30,12 +30,12 @@ public final class TextLoadingEffectView: UIView {
|
|||||||
|
|
||||||
self.maskContentsView = UIView()
|
self.maskContentsView = UIView()
|
||||||
self.maskHighlightNode = LinkHighlightingNode(color: .black)
|
self.maskHighlightNode = LinkHighlightingNode(color: .black)
|
||||||
self.maskHighlightNode.useModernPathCalculation = true
|
//self.maskHighlightNode.useModernPathCalculation = true
|
||||||
|
|
||||||
self.maskBorderContentsView = UIView()
|
self.maskBorderContentsView = UIView()
|
||||||
self.maskBorderHighlightNode = LinkHighlightingNode(color: .black)
|
self.maskBorderHighlightNode = LinkHighlightingNode(color: .black)
|
||||||
self.maskBorderHighlightNode.borderOnly = true
|
self.maskBorderHighlightNode.borderOnly = true
|
||||||
self.maskBorderHighlightNode.useModernPathCalculation = true
|
//self.maskBorderHighlightNode.useModernPathCalculation = true
|
||||||
self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view)
|
self.maskBorderContentsView.addSubview(self.maskBorderHighlightNode.view)
|
||||||
|
|
||||||
self.backgroundView = UIImageView()
|
self.backgroundView = UIImageView()
|
||||||
|
@ -837,14 +837,14 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState {
|
if let (updatedUrlPreviewState, signal) = urlPreviewStateForInputText(NSAttributedString(string: url), context: selfController.context, currentQuery: nil), let updatedUrlPreviewState, let detectedUrl = updatedUrlPreviewState.detectedUrls.first {
|
||||||
if let webpage = webpageCache[updatedUrlPreviewState.detectedUrl] {
|
if let webpage = webpageCache[detectedUrl] {
|
||||||
progress?.set(.single(false))
|
progress?.set(.single(false))
|
||||||
|
|
||||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||||
if state.interfaceState.editMessage != nil {
|
if state.interfaceState.editMessage != nil {
|
||||||
if var urlPreview = state.editingUrlPreview {
|
if var urlPreview = state.editingUrlPreview {
|
||||||
urlPreview.url = updatedUrlPreviewState.detectedUrl
|
urlPreview.url = detectedUrl
|
||||||
urlPreview.webPage = webpage
|
urlPreview.webPage = webpage
|
||||||
|
|
||||||
return state.updatedEditingUrlPreview(urlPreview)
|
return state.updatedEditingUrlPreview(urlPreview)
|
||||||
@ -853,7 +853,7 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if var urlPreview = state.urlPreview {
|
if var urlPreview = state.urlPreview {
|
||||||
urlPreview.url = updatedUrlPreviewState.detectedUrl
|
urlPreview.url = detectedUrl
|
||||||
urlPreview.webPage = webpage
|
urlPreview.webPage = webpage
|
||||||
|
|
||||||
return state.updatedUrlPreview(urlPreview)
|
return state.updatedUrlPreview(urlPreview)
|
||||||
@ -875,20 +875,20 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD
|
|||||||
|
|
||||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, { state in
|
||||||
if state.interfaceState.editMessage != nil {
|
if state.interfaceState.editMessage != nil {
|
||||||
if let webpage = result(nil), var urlPreview = state.editingUrlPreview {
|
if let (webpage, webpageUrl) = result(nil), var urlPreview = state.editingUrlPreview {
|
||||||
urlPreview.url = updatedUrlPreviewState.detectedUrl
|
urlPreview.url = webpageUrl
|
||||||
urlPreview.webPage = webpage
|
urlPreview.webPage = webpage
|
||||||
webpageCache[updatedUrlPreviewState.detectedUrl] = webpage
|
webpageCache[detectedUrl] = webpage
|
||||||
|
|
||||||
return state.updatedEditingUrlPreview(urlPreview)
|
return state.updatedEditingUrlPreview(urlPreview)
|
||||||
} else {
|
} else {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let webpage = result(nil), var urlPreview = state.urlPreview {
|
if let (webpage, webpageUrl) = result(nil), var urlPreview = state.urlPreview {
|
||||||
urlPreview.url = updatedUrlPreviewState.detectedUrl
|
urlPreview.url = webpageUrl
|
||||||
urlPreview.webPage = webpage
|
urlPreview.webPage = webpage
|
||||||
webpageCache[updatedUrlPreviewState.detectedUrl] = webpage
|
webpageCache[detectedUrl] = webpage
|
||||||
|
|
||||||
return state.updatedUrlPreview(urlPreview)
|
return state.updatedUrlPreview(urlPreview)
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,7 +223,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) {
|
if let (updatedUrlPreviewState, updatedUrlPreviewSignal) = urlPreviewStateForInputText(updatedChatPresentationInterfaceState.interfaceState.composeInputState.inputText, context: selfController.context, currentQuery: selfController.urlPreviewQueryState?.0) {
|
||||||
selfController.urlPreviewQueryState?.1.dispose()
|
selfController.urlPreviewQueryState?.1.dispose()
|
||||||
var inScope = true
|
var inScope = true
|
||||||
var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)?
|
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?
|
||||||
let linkPreviews: Signal<Bool, NoError>
|
let linkPreviews: Signal<Bool, NoError>
|
||||||
if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
|
if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: selfController.context.sharedContext.accountManager, displayAlert: { [weak selfController] f in
|
linkPreviews = interactiveChatLinkPreviewsEnabled(accountManager: selfController.context.sharedContext.accountManager, displayAlert: { [weak selfController] f in
|
||||||
@ -252,7 +252,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
}
|
}
|
||||||
let filteredPreviewSignal = linkPreviews
|
let filteredPreviewSignal = linkPreviews
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> TelegramMediaWebpage?, NoError> in
|
|> mapToSignal { value -> Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError> in
|
||||||
if value {
|
if value {
|
||||||
return updatedUrlPreviewSignal
|
return updatedUrlPreviewSignal
|
||||||
} else {
|
} 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 {
|
guard let selfController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -269,9 +269,9 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
inScopeResult = result
|
inScopeResult = result
|
||||||
} else {
|
} else {
|
||||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
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(
|
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||||
url: updatedUrlPreviewState.detectedUrl,
|
url: webpageUrl,
|
||||||
webPage: webpage,
|
webPage: webpage,
|
||||||
positionBelowText: $0.urlPreview?.positionBelowText ?? true,
|
positionBelowText: $0.urlPreview?.positionBelowText ?? true,
|
||||||
largeMedia: $0.urlPreview?.largeMedia
|
largeMedia: $0.urlPreview?.largeMedia
|
||||||
@ -285,9 +285,9 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
}))
|
}))
|
||||||
inScope = false
|
inScope = false
|
||||||
if let inScopeResult = inScopeResult {
|
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(
|
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||||
url: updatedUrlPreviewState.detectedUrl,
|
url: webpageUrl,
|
||||||
webPage: webpage,
|
webPage: webpage,
|
||||||
positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true,
|
positionBelowText: updatedChatPresentationInterfaceState.urlPreview?.positionBelowText ?? true,
|
||||||
largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia
|
largeMedia: updatedChatPresentationInterfaceState.urlPreview?.largeMedia
|
||||||
@ -304,7 +304,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) {
|
if let (updatedEditingUrlPreviewState, updatedEditingUrlPreviewSignal) = urlPreviewStateForInputText(editingUrlPreviewText, context: selfController.context, currentQuery: selfController.editingUrlPreviewQueryState?.0) {
|
||||||
selfController.editingUrlPreviewQueryState?.1.dispose()
|
selfController.editingUrlPreviewQueryState?.1.dispose()
|
||||||
var inScope = true
|
var inScope = true
|
||||||
var inScopeResult: ((TelegramMediaWebpage?) -> TelegramMediaWebpage?)?
|
var inScopeResult: ((TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?)?
|
||||||
selfController.editingUrlPreviewQueryState = (updatedEditingUrlPreviewState, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in
|
selfController.editingUrlPreviewQueryState = (updatedEditingUrlPreviewState, (updatedEditingUrlPreviewSignal |> deliverOnMainQueue).startStrict(next: { [weak selfController] result in
|
||||||
guard let selfController else {
|
guard let selfController else {
|
||||||
return
|
return
|
||||||
@ -314,9 +314,9 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
inScopeResult = result
|
inScopeResult = result
|
||||||
} else {
|
} else {
|
||||||
selfController.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
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(
|
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||||
url: updatedEditingUrlPreviewState.detectedUrl,
|
url: webpageUrl,
|
||||||
webPage: webpage,
|
webPage: webpage,
|
||||||
positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true,
|
positionBelowText: $0.editingUrlPreview?.positionBelowText ?? true,
|
||||||
largeMedia: $0.editingUrlPreview?.largeMedia
|
largeMedia: $0.editingUrlPreview?.largeMedia
|
||||||
@ -330,9 +330,9 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
}))
|
}))
|
||||||
inScope = false
|
inScope = false
|
||||||
if let inScopeResult = inScopeResult {
|
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(
|
let updatedPreview = ChatPresentationInterfaceState.UrlPreview(
|
||||||
url: updatedEditingUrlPreviewState.detectedUrl,
|
url: webpageUrl,
|
||||||
webPage: webpage,
|
webPage: webpage,
|
||||||
positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true,
|
positionBelowText: updatedChatPresentationInterfaceState.editingUrlPreview?.positionBelowText ?? true,
|
||||||
largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia
|
largeMedia: updatedChatPresentationInterfaceState.editingUrlPreview?.largeMedia
|
||||||
|
@ -507,10 +507,10 @@ func detectUrls(_ inputText: NSAttributedString?) -> [String] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct UrlPreviewState {
|
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 {
|
guard let _ = inputText else {
|
||||||
if currentQuery != nil {
|
if currentQuery != nil {
|
||||||
return (nil, .single({ _ in return nil }))
|
return (nil, .single({ _ in return nil }))
|
||||||
@ -519,15 +519,19 @@ func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: Acco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let _ = dataDetector {
|
if let _ = dataDetector {
|
||||||
let detectedUrl = detectUrls(inputText).first
|
let detectedUrls = detectUrls(inputText)
|
||||||
if detectedUrl != currentQuery?.detectedUrl {
|
if detectedUrls != currentQuery?.detectedUrls {
|
||||||
if let detectedUrl = detectedUrl {
|
if !detectedUrls.isEmpty {
|
||||||
return (UrlPreviewState(detectedUrl: detectedUrl), webpagePreview(account: context.account, url: detectedUrl)
|
return (UrlPreviewState(detectedUrls: detectedUrls), webpagePreview(account: context.account, urls: detectedUrls)
|
||||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
|> mapToSignal { result -> Signal<(TelegramMediaWebpage, String)?, NoError> in
|
||||||
guard case let .result(result) = result else {
|
guard case let .result(webpageResult) = result else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
return .single(result)
|
if let webpageResult {
|
||||||
|
return .single((webpageResult.webpage, webpageResult.sourceUrl))
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> map { value in
|
|> map { value in
|
||||||
return { _ in return value }
|
return { _ in return value }
|
||||||
|
@ -207,12 +207,11 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
|
|||||||
for message in messages {
|
for message in messages {
|
||||||
if message.effectivelyIncoming(context.account.peerId), message.text.count >= 10 {
|
if message.effectivelyIncoming(context.account.peerId), message.text.count >= 10 {
|
||||||
var text = String(message.text.prefix(256))
|
var text = String(message.text.prefix(256))
|
||||||
if var entities = message.textEntitiesAttribute?.entities.filter({
|
if var entities = message.textEntitiesAttribute?.entities.filter({ entity in
|
||||||
if [.Code, .Url, .Email, .Mention, .Hashtag, .BotCommand].contains($0.type) {
|
switch entity.type {
|
||||||
|
case .Pre, .Code, .Url, .Email, .Mention, .Hashtag, .BotCommand:
|
||||||
return true
|
return true
|
||||||
} else if case .Pre = $0.type {
|
default:
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
@ -1157,14 +1157,14 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func resolveInstantViewUrl(account: Account, url: String) -> Signal<ResolveUrlResult, NoError> {
|
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
|
|> mapToSignal { result -> Signal<ResolveUrlResult, NoError> in
|
||||||
switch result {
|
switch result {
|
||||||
case .progress:
|
case .progress:
|
||||||
return .single(.progress)
|
return .single(.progress)
|
||||||
case let .result(webpage):
|
case let .result(webpageResult):
|
||||||
if let webpage = webpage {
|
if let webpageResult = webpageResult {
|
||||||
if case let .Loaded(content) = webpage.content {
|
if case let .Loaded(content) = webpageResult.webpage.content {
|
||||||
if content.instantPage != nil {
|
if content.instantPage != nil {
|
||||||
var anchorValue: String?
|
var anchorValue: String?
|
||||||
if let anchorRange = url.range(of: "#") {
|
if let anchorRange = url.range(of: "#") {
|
||||||
@ -1173,7 +1173,7 @@ public func resolveInstantViewUrl(account: Account, url: String) -> Signal<Resol
|
|||||||
anchorValue = String(anchor)
|
anchorValue = String(anchor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .single(.result(.instantView(webpage, anchorValue)))
|
return .single(.result(.instantView(webpageResult.webpage, anchorValue)))
|
||||||
} else {
|
} else {
|
||||||
return .single(.result(.externalUrl(url)))
|
return .single(.result(.externalUrl(url)))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user