mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-21 05:39:01 +00:00
Merge branch 'beta'
This commit is contained in:
commit
cfcfd5e13b
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
@ -31,15 +31,15 @@
|
|||||||
SUBQUERY (
|
SUBQUERY (
|
||||||
$extensionItem.attachments,
|
$extensionItem.attachments,
|
||||||
$attachment,
|
$attachment,
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.audio" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.audio" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard" ||
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.vcard" ||
|
||||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.pkpass"
|
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.pkpass"
|
||||||
).@count == $extensionItem.attachments.@count
|
).@count == $extensionItem.attachments.@count
|
||||||
).@count > 0</string>
|
).@count > 0</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|||||||
@ -98,30 +98,30 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
|
||||||
|
|
||||||
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil))), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil))), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|
||||||
|> mapToSignal { account -> Signal<Account?, NoError> in
|
|> mapToSignal { account -> Signal<Account?, NoError> in
|
||||||
if let account = account {
|
if let account = account {
|
||||||
switch account {
|
switch account {
|
||||||
case .upgrading:
|
case .upgrading:
|
||||||
return .complete()
|
return .complete()
|
||||||
case let .authorized(account):
|
case let .authorized(account):
|
||||||
return applicationSettings(accountManager: accountManager)
|
return applicationSettings(accountManager: accountManager)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { settings -> Account in
|
|> map { settings -> Account in
|
||||||
accountCache = account
|
accountCache = account
|
||||||
Logger.shared.logToFile = settings.logging.logToFile
|
Logger.shared.logToFile = settings.logging.logToFile
|
||||||
Logger.shared.logToConsole = settings.logging.logToConsole
|
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||||
|
|
||||||
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
case .unauthorized:
|
case .unauthorized:
|
||||||
return .complete()
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
}
|
|> take(1)
|
||||||
|> take(1)
|
|
||||||
}
|
}
|
||||||
self.accountPromise.set(account)
|
self.accountPromise.set(account)
|
||||||
}
|
}
|
||||||
@ -145,31 +145,31 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
@available(iOSApplicationExtension 11.0, *)
|
@available(iOSApplicationExtension 11.0, *)
|
||||||
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
||||||
switch self {
|
switch self {
|
||||||
case let .success(person):
|
case let .success(person):
|
||||||
return .success(with: person)
|
return .success(with: person)
|
||||||
case let .disambiguation(persons):
|
case let .disambiguation(persons):
|
||||||
return .disambiguation(with: persons)
|
return .disambiguation(with: persons)
|
||||||
case .needsValue:
|
case .needsValue:
|
||||||
return .needsValue()
|
return .needsValue()
|
||||||
case .noResult:
|
case .noResult:
|
||||||
return .unsupported()
|
return .unsupported()
|
||||||
case .skip:
|
case .skip:
|
||||||
return .notRequired()
|
return .notRequired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var personResolutionResult: INPersonResolutionResult {
|
var personResolutionResult: INPersonResolutionResult {
|
||||||
switch self {
|
switch self {
|
||||||
case let .success(person):
|
case let .success(person):
|
||||||
return .success(with: person)
|
return .success(with: person)
|
||||||
case let .disambiguation(persons):
|
case let .disambiguation(persons):
|
||||||
return .disambiguation(with: persons)
|
return .disambiguation(with: persons)
|
||||||
case .needsValue:
|
case .needsValue:
|
||||||
return .needsValue()
|
return .needsValue()
|
||||||
case .noResult:
|
case .noResult:
|
||||||
return .unsupported()
|
return .unsupported()
|
||||||
case .skip:
|
case .skip:
|
||||||
return .notRequired()
|
return .notRequired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,29 +239,29 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
let account = self.accountPromise.get()
|
let account = self.accountPromise.get()
|
||||||
|
|
||||||
let signal = matchingDeviceContacts(stableIds: stableIds)
|
let signal = matchingDeviceContacts(stableIds: stableIds)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { matchedContacts in
|
|> mapToSignal { matchedContacts in
|
||||||
return account
|
return account
|
||||||
|> introduceError(IntentContactsError.self)
|
|
||||||
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
|
||||||
if let account = account {
|
|
||||||
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
|
||||||
|> introduceError(IntentContactsError.self)
|
|> introduceError(IntentContactsError.self)
|
||||||
} else {
|
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
||||||
return .fail(.generic)
|
if let account = account {
|
||||||
|
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
||||||
|
|> introduceError(IntentContactsError.self)
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.resolvePersonsDisposable.set((signal
|
self.resolvePersonsDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { peers in
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
if peers.isEmpty {
|
if peers.isEmpty {
|
||||||
completion([.needsValue])
|
completion([.needsValue])
|
||||||
} else {
|
} else {
|
||||||
completion(peers.map { .success(personWithUser(stableId: $0, user: $1)) })
|
completion(peers.map { .success(personWithUser(stableId: $0, user: $1)) })
|
||||||
}
|
}
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
completion([.skip])
|
completion([.skip])
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSendMessageIntentHandling
|
// MARK: - INSendMessageIntentHandling
|
||||||
@ -282,33 +282,33 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
let account = self.accountPromise.get()
|
let account = self.accountPromise.get()
|
||||||
|
|
||||||
let signal = account
|
let signal = account
|
||||||
|> introduceError(IntentHandlingError.self)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
||||||
if let account = account {
|
if let account = account {
|
||||||
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
||||||
|> introduceError(IntentHandlingError.self)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> map { user -> INPerson? in
|
|> map { user -> INPerson? in
|
||||||
if let user = user {
|
if let user = user {
|
||||||
return personWithUser(stableId: "tg\(peerId)", user: user)
|
return personWithUser(stableId: "tg\(peerId)", user: user)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.resolvePersonsDisposable.set((signal
|
self.resolvePersonsDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { person in
|
|> deliverOnMainQueue).start(next: { person in
|
||||||
if let person = person {
|
if let person = person {
|
||||||
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
||||||
} else {
|
} else {
|
||||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||||
}
|
}
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
||||||
completion([INSendMessageRecipientResolutionResult.notRequired()])
|
completion([INSendMessageRecipientResolutionResult.notRequired()])
|
||||||
@ -345,48 +345,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|
||||||
return .generic
|
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
let peerId = PeerId(peerIdValue)
|
|
||||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil)
|
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||||
return .complete()
|
guard let account = account else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerId = PeerId(peerIdValue)
|
||||||
|
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
|
return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil)
|
||||||
|
|> mapError { _ -> IntentHandlingError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> afterDisposed {
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||||
}
|
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
}
|
completion(response)
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
}, completed: {
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||||
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
||||||
completion(response)
|
completion(response)
|
||||||
}, completed: {
|
}))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
|
||||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
|
||||||
completion(response)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSearchForMessagesIntentHandling
|
// MARK: - INSearchForMessagesIntentHandling
|
||||||
@ -397,42 +397,42 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
account.resetStateManagement()
|
|
||||||
|
|
||||||
let completion: Signal<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
|
||||||
|> map { _ in
|
|
||||||
return Void()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
|> introduceError(IntentHandlingError.self)
|
||||||
return unreadMessages(account: account)
|
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
||||||
|> introduceError(IntentHandlingError.self)
|
guard let account = account else {
|
||||||
|> afterDisposed {
|
return .fail(.generic)
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
}
|
||||||
|
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
|
account.resetStateManagement()
|
||||||
|
|
||||||
|
let completion: Signal<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
||||||
|
|> map { _ in
|
||||||
|
return Void()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
||||||
|
|> introduceError(IntentHandlingError.self)
|
||||||
|
|> take(1)
|
||||||
|
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
||||||
|
return unreadMessages(account: account)
|
||||||
|
|> introduceError(IntentHandlingError.self)
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|> deliverOnMainQueue).start(next: { messages in
|
||||||
|> deliverOnMainQueue).start(next: { messages in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
response.messages = messages
|
||||||
response.messages = messages
|
completion(response)
|
||||||
completion(response)
|
}, error: { _ in
|
||||||
}, error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSetMessageAttributeIntentHandling
|
// MARK: - INSetMessageAttributeIntentHandling
|
||||||
@ -452,61 +452,61 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||||
var signals: [Signal<Void, IntentHandlingError>] = []
|
guard let account = account else {
|
||||||
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
return .fail(.generic)
|
||||||
if let identifiers = intent.identifiers {
|
}
|
||||||
for identifier in identifiers {
|
|
||||||
let components = identifier.components(separatedBy: "_")
|
var signals: [Signal<Void, IntentHandlingError>] = []
|
||||||
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
||||||
let peerId = PeerId(peerId)
|
if let identifiers = intent.identifiers {
|
||||||
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
for identifier in identifiers {
|
||||||
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
let components = identifier.components(separatedBy: "_")
|
||||||
if currentMessageId < messageId {
|
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
||||||
|
let peerId = PeerId(peerId)
|
||||||
|
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||||
|
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
||||||
|
if currentMessageId < messageId {
|
||||||
|
maxMessageIdsToApply[peerId] = messageId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
maxMessageIdsToApply[peerId] = messageId
|
maxMessageIdsToApply[peerId] = messageId
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
maxMessageIdsToApply[peerId] = messageId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
for (_, messageId) in maxMessageIdsToApply {
|
||||||
for (_, messageId) in maxMessageIdsToApply {
|
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
|> introduceError(IntentHandlingError.self))
|
||||||
|> introduceError(IntentHandlingError.self))
|
}
|
||||||
}
|
|
||||||
|
if signals.isEmpty {
|
||||||
if signals.isEmpty {
|
|
||||||
return .complete()
|
|
||||||
} else {
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
return combineLatest(signals)
|
|
||||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
} else {
|
||||||
|> afterDisposed {
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
return combineLatest(signals)
|
||||||
|
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}, completed: {
|
||||||
}, completed: {
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INStartAudioCallIntentHandling
|
// MARK: - INStartAudioCallIntentHandling
|
||||||
@ -520,39 +520,39 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
completion(result.map { $0.personResolutionResult })
|
completion(result.map { $0.personResolutionResult })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
|
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
|
||||||
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
||||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
let peerId = PeerId(peerIdValue)
|
||||||
|
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .single(peerId)
|
||||||
}
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { peerId in
|
||||||
let peerId = PeerId(peerIdValue)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
||||||
return .fail(.generic)
|
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||||
}
|
completion(response)
|
||||||
|
}, error: { _ in
|
||||||
return .single(peerId)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
}
|
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
|> deliverOnMainQueue).start(next: { peerId in
|
completion(response)
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
}))
|
||||||
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
|
||||||
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
|
||||||
completion(response)
|
|
||||||
}, error: { _ in
|
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
|
||||||
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
|
||||||
completion(response)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSearchCallHistoryIntentHandling
|
// MARK: - INSearchCallHistoryIntentHandling
|
||||||
@ -568,34 +568,34 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) {
|
func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> mapToSignal { account -> Signal<[CallRecord], IntentHandlingError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
return missedCalls(account: account)
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> afterDisposed {
|
|> mapToSignal { account -> Signal<[CallRecord], IntentHandlingError> in
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
guard let account = account else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
|
return missedCalls(account: account)
|
||||||
|
|> introduceError(IntentHandlingError.self)
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|> deliverOnMainQueue).start(next: { calls in
|
||||||
|> deliverOnMainQueue).start(next: { calls in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
let response: INSearchCallHistoryIntentResponse
|
||||||
let response: INSearchCallHistoryIntentResponse
|
if #available(iOSApplicationExtension 11.0, *) {
|
||||||
if #available(iOSApplicationExtension 11.0, *) {
|
response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity)
|
||||||
response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity)
|
response.callRecords = calls.map { $0.intentCall }
|
||||||
response.callRecords = calls.map { $0.intentCall }
|
} else {
|
||||||
} else {
|
response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||||
response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity)
|
}
|
||||||
}
|
completion(response)
|
||||||
completion(response)
|
}, error: { _ in
|
||||||
}, error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,7 +185,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.icloud-services</key>
|
<key>com.apple.developer.icloud-services</key>
|
||||||
<array>
|
<array>
|
||||||
<string>CloudDocuments</string>
|
<string>CloudDocuments</string>
|
||||||
<string>CloudKit</string>
|
<string>CloudKit</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iCloud.$(CFBundleIdentifier)</string>
|
<string>iCloud.$(CFBundleIdentifier)</string>
|
||||||
</array>
|
</array>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>production</string>
|
<string>production</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.10</string>
|
<string>5.10.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${BUILD_NUMBER}</string>
|
<string>${BUILD_NUMBER}</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
public var maximumNumberOfLines: Int = 1
|
public var maximumNumberOfLines: Int = 1
|
||||||
public var lineSpacing: CGFloat = 0.0
|
public var lineSpacing: CGFloat = 0.0
|
||||||
public var insets: UIEdgeInsets = UIEdgeInsets()
|
public var insets: UIEdgeInsets = UIEdgeInsets()
|
||||||
|
public var textShadowColor: UIColor?
|
||||||
|
|
||||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||||
private var linkHighlightingNode: LinkHighlightingNode?
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
@ -34,7 +35,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
|
|
||||||
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
|
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
|
||||||
let makeLayout = TextNode.asyncLayout(self)
|
let makeLayout = TextNode.asyncLayout(self)
|
||||||
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets))
|
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets, textShadowColor: self.textShadowColor))
|
||||||
let _ = apply()
|
let _ = apply()
|
||||||
if layout.numberOfLines > 1 {
|
if layout.numberOfLines > 1 {
|
||||||
self.trailingLineWidth = layout.trailingLineWidth
|
self.trailingLineWidth = layout.trailingLineWidth
|
||||||
|
|||||||
@ -275,7 +275,14 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver))
|
return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver))
|
||||||
}
|
}
|
||||||
case .single:
|
case .single:
|
||||||
return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: layout.size.height > 900.0 ? .regular : .compact), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver))
|
var heightClass: ContainerViewLayoutSizeClass
|
||||||
|
if (layout.size.width < 375.0 && layout.size.height > 700.0) || layout.size.height > 900.0 {
|
||||||
|
heightClass = .regular
|
||||||
|
} else {
|
||||||
|
heightClass = .compact
|
||||||
|
}
|
||||||
|
|
||||||
|
return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: heightClass), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,58 +303,54 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||||||
self.controllerView.addSubview(self.controllerView.separatorView)
|
self.controllerView.addSubview(self.controllerView.separatorView)
|
||||||
}
|
}
|
||||||
|
|
||||||
//let navigationBackgroundFrame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: lastControllerFrameAndLayout.0.width, height: (layout.statusBarHeight ?? 0.0) + 44.0))
|
|
||||||
|
|
||||||
if let backgroundDetailsMode = self.backgroundDetailsMode {
|
if let backgroundDetailsMode = self.backgroundDetailsMode {
|
||||||
|
|
||||||
switch backgroundDetailsMode {
|
switch backgroundDetailsMode {
|
||||||
case let .image(image):
|
case let .image(image):
|
||||||
if let detailsBackground = self.controllerView.detailsBackground {
|
if let detailsBackground = self.controllerView.detailsBackground {
|
||||||
self.controllerView.detailsBackground = nil
|
self.controllerView.detailsBackground = nil
|
||||||
transition.updateAlpha(node: detailsBackground, alpha: 0.0, completion: { [weak detailsBackground] _ in
|
transition.updateAlpha(node: detailsBackground, alpha: 0.0, completion: { [weak detailsBackground] _ in
|
||||||
detailsBackground?.removeFromSupernode()
|
detailsBackground?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let emptyDetailView: UIImageView
|
let emptyDetailView: UIImageView
|
||||||
if let emptyView = self.controllerView.emptyDetailView {
|
if let emptyView = self.controllerView.emptyDetailView {
|
||||||
emptyDetailView = emptyView
|
emptyDetailView = emptyView
|
||||||
} else {
|
} else {
|
||||||
emptyDetailView = UIImageView()
|
emptyDetailView = UIImageView()
|
||||||
emptyDetailView.alpha = 0.0
|
emptyDetailView.alpha = 0.0
|
||||||
self.controllerView.emptyDetailView = emptyDetailView
|
self.controllerView.emptyDetailView = emptyDetailView
|
||||||
}
|
}
|
||||||
emptyDetailView.image = image
|
emptyDetailView.image = image
|
||||||
if emptyDetailView.superview == nil {
|
if emptyDetailView.superview == nil {
|
||||||
self.controllerView.insertSubview(emptyDetailView, at: 0)
|
self.controllerView.insertSubview(emptyDetailView, at: 0)
|
||||||
}
|
}
|
||||||
transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0)
|
transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0)
|
||||||
|
|
||||||
emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)
|
emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)
|
||||||
|
|
||||||
|
|
||||||
case let .wallpaper(image):
|
case let .wallpaper(image):
|
||||||
if let emptyDetailView = self.controllerView.emptyDetailView {
|
if let emptyDetailView = self.controllerView.emptyDetailView {
|
||||||
self.controllerView.emptyDetailView = nil
|
self.controllerView.emptyDetailView = nil
|
||||||
transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in
|
transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in
|
||||||
emptyDetailView?.removeFromSuperview()
|
emptyDetailView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let detailsBackground: WallpaperbackgroundNode
|
let detailsBackground: WallpaperbackgroundNode
|
||||||
if let background = self.controllerView.detailsBackground {
|
if let background = self.controllerView.detailsBackground {
|
||||||
detailsBackground = background
|
detailsBackground = background
|
||||||
} else {
|
} else {
|
||||||
detailsBackground = WallpaperbackgroundNode()
|
detailsBackground = WallpaperbackgroundNode()
|
||||||
detailsBackground.alpha = 0.0
|
detailsBackground.alpha = 0.0
|
||||||
self.controllerView.detailsBackground = detailsBackground
|
self.controllerView.detailsBackground = detailsBackground
|
||||||
}
|
}
|
||||||
detailsBackground.image = image
|
detailsBackground.image = image
|
||||||
if detailsBackground.supernode == nil {
|
if detailsBackground.supernode == nil {
|
||||||
self.controllerView.insertSubview(detailsBackground.view, at: 0)
|
self.controllerView.insertSubview(detailsBackground.view, at: 0)
|
||||||
}
|
}
|
||||||
transition.updateAlpha(node: detailsBackground, alpha: 1.0)
|
transition.updateAlpha(node: detailsBackground, alpha: 1.0)
|
||||||
detailsBackground.frame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: lastControllerFrameAndLayout.0.size)
|
detailsBackground.frame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: lastControllerFrameAndLayout.0.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if let emptyDetailView = self.controllerView.emptyDetailView {
|
if let emptyDetailView = self.controllerView.emptyDetailView {
|
||||||
self.controllerView.emptyDetailView = nil
|
self.controllerView.emptyDetailView = nil
|
||||||
|
|||||||
@ -92,8 +92,9 @@ public final class TextNodeLayoutArguments {
|
|||||||
public let cutout: TextNodeCutout?
|
public let cutout: TextNodeCutout?
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let lineColor: UIColor?
|
public let lineColor: UIColor?
|
||||||
|
public let textShadowColor: UIColor?
|
||||||
|
|
||||||
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil) {
|
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.maximumNumberOfLines = maximumNumberOfLines
|
self.maximumNumberOfLines = maximumNumberOfLines
|
||||||
@ -104,6 +105,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
self.cutout = cutout
|
self.cutout = cutout
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
|
self.textShadowColor = textShadowColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +125,10 @@ public final class TextNodeLayout: NSObject {
|
|||||||
fileprivate let lines: [TextNodeLine]
|
fileprivate let lines: [TextNodeLine]
|
||||||
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
||||||
fileprivate let lineColor: UIColor?
|
fileprivate let lineColor: UIColor?
|
||||||
|
fileprivate let textShadowColor: UIColor?
|
||||||
public let hasRTL: Bool
|
public let hasRTL: Bool
|
||||||
|
|
||||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?) {
|
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.maximumNumberOfLines = maximumNumberOfLines
|
self.maximumNumberOfLines = maximumNumberOfLines
|
||||||
self.truncationType = truncationType
|
self.truncationType = truncationType
|
||||||
@ -141,6 +144,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
self.blockQuotes = blockQuotes
|
self.blockQuotes = blockQuotes
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
|
self.textShadowColor = textShadowColor
|
||||||
var hasRTL = false
|
var hasRTL = false
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.isRTL {
|
if line.isRTL {
|
||||||
@ -575,7 +579,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?) -> TextNodeLayout {
|
private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?) -> TextNodeLayout {
|
||||||
if let attributedString = attributedString {
|
if let attributedString = attributedString {
|
||||||
let stringLength = attributedString.length
|
let stringLength = attributedString.length
|
||||||
|
|
||||||
@ -601,7 +605,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
var maybeTypesetter: CTTypesetter?
|
var maybeTypesetter: CTTypesetter?
|
||||||
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
||||||
if maybeTypesetter == nil {
|
if maybeTypesetter == nil {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let typesetter = maybeTypesetter!
|
let typesetter = maybeTypesetter!
|
||||||
@ -793,9 +797,9 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
} else {
|
} else {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,11 +832,15 @@ public class TextNode: ASDisplayNode {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let textShadowColor = layout.textShadowColor {
|
||||||
|
context.setTextDrawingMode(.fill)
|
||||||
|
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
let textMatrix = context.textMatrix
|
let textMatrix = context.textMatrix
|
||||||
let textPosition = context.textPosition
|
let textPosition = context.textPosition
|
||||||
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
|
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
|
||||||
|
|
||||||
|
|
||||||
let alignment = layout.alignment
|
let alignment = layout.alignment
|
||||||
let offset = CGPoint(x: layout.insets.left, y: layout.insets.top)
|
let offset = CGPoint(x: layout.insets.left, y: layout.insets.top)
|
||||||
|
|
||||||
@ -927,11 +935,11 @@ public class TextNode: ASDisplayNode {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -166,6 +166,7 @@ private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMed
|
|||||||
|
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
||||||
|
|
||||||
let disposable = (thumbnailData
|
let disposable = (thumbnailData
|
||||||
|> map { thumbnailData -> Data? in
|
|> map { thumbnailData -> Data? in
|
||||||
return thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil
|
return thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil
|
||||||
@ -230,6 +231,7 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, resource: Med
|
|||||||
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
||||||
let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier)
|
let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier)
|
||||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||||
|
|
||||||
return maybeFetched
|
return maybeFetched
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { maybeData in
|
|> mapToSignal { maybeData in
|
||||||
|
|||||||
@ -324,9 +324,13 @@ private func makeDarkPresentationTheme(accentColor: UIColor, baseColor: Presenta
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public let defaultDarkAccentPresentationTheme = makeDarkAccentPresentationTheme(accentColor: UIColor(rgb: 0x2ea6ff), baseColor: .blue, preview: false)
|
public let defaultDarkAccentColor = UIColor(rgb: 0x2ea6ff)
|
||||||
|
public let defaultDarkAccentPresentationTheme = makeDarkAccentPresentationTheme(accentColor: UIColor(rgb: 0x2ea6ff), preview: false)
|
||||||
|
|
||||||
public func makeDarkAccentPresentationTheme(accentColor: UIColor?, baseColor: PresentationThemeBaseColor?, preview: Bool) -> PresentationTheme {
|
public func makeDarkAccentPresentationTheme(accentColor: UIColor?, preview: Bool) -> PresentationTheme {
|
||||||
let accentColor = accentColor ?? defaultDayAccentColor
|
var accentColor = accentColor ?? defaultDarkAccentColor
|
||||||
return makeDarkPresentationTheme(accentColor: accentColor, baseColor: baseColor, preview: preview)
|
if accentColor == PresentationThemeBaseColor.blue.color {
|
||||||
|
accentColor = defaultDarkAccentColor
|
||||||
|
}
|
||||||
|
return makeDarkPresentationTheme(accentColor: accentColor, preview: preview)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -205,7 +205,6 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout)
|
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout)
|
||||||
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)!
|
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)!
|
||||||
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
|
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
|
||||||
|
|
||||||
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
|
||||||
|
|||||||
@ -127,7 +127,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
|||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.data = data
|
self.data = data
|
||||||
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
|
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
var width = 0
|
var width = 0
|
||||||
var height = 0
|
var height = 0
|
||||||
@ -404,7 +404,7 @@ final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||||
self.addSubnode(self.renderer!)
|
self.addSubnode(self.renderer!)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||||
if width < 2 || height < 2 {
|
if width < 2 || height < 2 {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -14,12 +14,7 @@ import MobileCoreServices
|
|||||||
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
|
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
|
||||||
|
|
||||||
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
|
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
|
||||||
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
|
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
|
||||||
let color1: UIColor
|
|
||||||
let color2: UIColor
|
|
||||||
let color3: UIColor
|
|
||||||
let color4: UIColor
|
|
||||||
|
|
||||||
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
|
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
|
||||||
let replacementColors: [UIColor]
|
let replacementColors: [UIColor]
|
||||||
switch fitzModifier {
|
switch fitzModifier {
|
||||||
@ -201,7 +196,7 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize,
|
|||||||
var appendingTime: Double = 0
|
var appendingTime: Double = 0
|
||||||
var deltaTime: Double = 0
|
var deltaTime: Double = 0
|
||||||
var compressionTime: Double = 0
|
var compressionTime: Double = 0
|
||||||
|
|
||||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
||||||
if let decompressedData = decompressedData {
|
if let decompressedData = decompressedData {
|
||||||
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
||||||
|
|||||||
@ -881,7 +881,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
|||||||
}, reportChannel: {
|
}, reportChannel: {
|
||||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||||
presentControllerImpl?(c, a)
|
presentControllerImpl?(c, a)
|
||||||
}), nil)
|
}, completion: { _ in }), nil)
|
||||||
}, leaveChannel: {
|
}, leaveChannel: {
|
||||||
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
||||||
return transaction.getPeer(peerId)
|
return transaction.getPeer(peerId)
|
||||||
|
|||||||
@ -2748,14 +2748,14 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar
|
|||||||
if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, reportMessages: { [weak self] messages in
|
}, reportMessages: { [weak self] messages in
|
||||||
if let strongSelf = self, !messages.isEmpty {
|
if let strongSelf = self, !messages.isEmpty {
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(messages.map({ $0.id }).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(messages.map({ $0.id }).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, deleteMessages: { [weak self] messages in
|
}, deleteMessages: { [weak self] messages in
|
||||||
if let strongSelf = self, !messages.isEmpty {
|
if let strongSelf = self, !messages.isEmpty {
|
||||||
@ -3443,7 +3443,7 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar
|
|||||||
}
|
}
|
||||||
|
|
||||||
if canManagePin {
|
if canManagePin {
|
||||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_No, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
|
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_UnpinMessageAlert, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let disposable: MetaDisposable
|
let disposable: MetaDisposable
|
||||||
if let current = strongSelf.unpinMessageDisposable {
|
if let current = strongSelf.unpinMessageDisposable {
|
||||||
@ -6255,6 +6255,12 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar
|
|||||||
if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty {
|
if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty {
|
||||||
self.present(peerReportOptionsController(context: self.context, subject: .peer(peer.id), present: { [weak self] c, a in
|
self.present(peerReportOptionsController(context: self.context, subject: .peer(peer.id), present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root))
|
self?.present(c, in: .window(.root))
|
||||||
|
}, completion: { [weak self] success in
|
||||||
|
guard let strongSelf = self, success else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: false).start()
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.filterController(strongSelf, animated: true)
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
} else if let _ = peer as? TelegramUser {
|
} else if let _ = peer as? TelegramUser {
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
|
|||||||
@ -90,7 +90,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
case .installed:
|
case .installed:
|
||||||
scope = [.installed]
|
scope = [.installed]
|
||||||
}
|
}
|
||||||
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
|
return searchStickers(account: context.account, query: query.basicEmoji.0, scope: scope)
|
||||||
|> introduceError(ChatContextQueryError.self)
|
|> introduceError(ChatContextQueryError.self)
|
||||||
}
|
}
|
||||||
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||||
|
|||||||
@ -172,7 +172,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
|
|
||||||
private let messageContextDisposable = MetaDisposable()
|
private let messageContextDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var videoFramePreviewNode: ASImageNode?
|
private var videoFramePreviewNode: (ASImageNode, ImmediateTextNode)?
|
||||||
|
|
||||||
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -235,6 +235,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var scrubbingHandleRelativePosition: CGFloat = 0.0
|
private var scrubbingHandleRelativePosition: CGFloat = 0.0
|
||||||
|
private var scrubbingVisualTimestamp: Double?
|
||||||
|
|
||||||
var scrubberView: ChatVideoGalleryItemScrubberView? = nil {
|
var scrubberView: ChatVideoGalleryItemScrubberView? = nil {
|
||||||
willSet {
|
willSet {
|
||||||
@ -245,6 +246,23 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
didSet {
|
didSet {
|
||||||
if let scrubberView = self.scrubberView {
|
if let scrubberView = self.scrubberView {
|
||||||
self.view.addSubview(scrubberView)
|
self.view.addSubview(scrubberView)
|
||||||
|
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let value = value {
|
||||||
|
strongSelf.scrubbingVisualTimestamp = value
|
||||||
|
if let (videoFramePreviewNode, videoFrameTextNode) = strongSelf.videoFramePreviewNode {
|
||||||
|
videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(value)), font: Font.regular(13.0), textColor: .white)
|
||||||
|
let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
|
let imageFrame = videoFramePreviewNode.frame
|
||||||
|
let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: textSize)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.scrubbingVisualTimestamp = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
scrubberView.updateScrubbingHandlePosition = { [weak self] value in
|
scrubberView.updateScrubbingHandlePosition = { [weak self] value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -643,15 +661,31 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let videoFramePreviewNode = self.videoFramePreviewNode {
|
if let (videoFramePreviewNode, videoFrameTextNode) = self.videoFramePreviewNode {
|
||||||
let intrinsicImageSize = videoFramePreviewNode.image?.size ?? CGSize(width: 320.0, height: 240.0)
|
let intrinsicImageSize = videoFramePreviewNode.image?.size ?? CGSize(width: 320.0, height: 240.0)
|
||||||
let imageSize = intrinsicImageSize.aspectFitted(CGSize(width: 200.0, height: 200.0))
|
let fitSize: CGSize
|
||||||
var imageFrame = CGRect(origin: CGPoint(x: leftInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 10.0 - imageSize.height), size: imageSize)
|
if intrinsicImageSize.width < intrinsicImageSize.height {
|
||||||
|
fitSize = CGSize(width: 90.0, height: 160.0)
|
||||||
|
} else {
|
||||||
|
fitSize = CGSize(width: 160.0, height: 90.0)
|
||||||
|
}
|
||||||
|
let scrubberInset: CGFloat
|
||||||
|
if size.width > size.height {
|
||||||
|
scrubberInset = 58.0
|
||||||
|
} else {
|
||||||
|
scrubberInset = 13.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageSize = intrinsicImageSize.aspectFitted(fitSize)
|
||||||
|
var imageFrame = CGRect(origin: CGPoint(x: leftInset + scrubberInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset - scrubberInset * 2.0) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 6.0 - imageSize.height), size: imageSize)
|
||||||
imageFrame.origin.x = min(imageFrame.origin.x, width - rightInset - 10.0 - imageSize.width)
|
imageFrame.origin.x = min(imageFrame.origin.x, width - rightInset - 10.0 - imageSize.width)
|
||||||
imageFrame.origin.x = max(imageFrame.origin.x, leftInset + 10.0)
|
imageFrame.origin.x = max(imageFrame.origin.x, leftInset + 10.0)
|
||||||
|
|
||||||
videoFramePreviewNode.frame = imageFrame
|
videoFramePreviewNode.frame = imageFrame
|
||||||
videoFramePreviewNode.subnodes?.first?.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
videoFramePreviewNode.subnodes?.first?.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
|
|
||||||
|
let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: videoFrameTextNode.bounds.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
return panelHeight
|
return panelHeight
|
||||||
@ -1028,7 +1062,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setFramePreviewImageIsLoading() {
|
func setFramePreviewImageIsLoading() {
|
||||||
if self.videoFramePreviewNode?.image != nil {
|
if self.videoFramePreviewNode?.0.image != nil {
|
||||||
//self.videoFramePreviewNode?.subnodes?.first?.alpha = 1.0
|
//self.videoFramePreviewNode?.subnodes?.first?.alpha = 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1036,17 +1070,34 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
func setFramePreviewImage(image: UIImage?) {
|
func setFramePreviewImage(image: UIImage?) {
|
||||||
if let image = image {
|
if let image = image {
|
||||||
let videoFramePreviewNode: ASImageNode
|
let videoFramePreviewNode: ASImageNode
|
||||||
|
let videoFrameTextNode: ImmediateTextNode
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
if let current = self.videoFramePreviewNode {
|
if let current = self.videoFramePreviewNode {
|
||||||
videoFramePreviewNode = current
|
videoFramePreviewNode = current.0
|
||||||
|
videoFrameTextNode = current.1
|
||||||
} else {
|
} else {
|
||||||
videoFramePreviewNode = ASImageNode()
|
videoFramePreviewNode = ASImageNode()
|
||||||
videoFramePreviewNode.displaysAsynchronously = false
|
videoFramePreviewNode.displaysAsynchronously = false
|
||||||
videoFramePreviewNode.displayWithoutProcessing = true
|
videoFramePreviewNode.displayWithoutProcessing = true
|
||||||
|
videoFramePreviewNode.clipsToBounds = true
|
||||||
|
videoFramePreviewNode.cornerRadius = 6.0
|
||||||
|
|
||||||
let dimNode = ASDisplayNode()
|
let dimNode = ASDisplayNode()
|
||||||
dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
videoFramePreviewNode.addSubnode(dimNode)
|
videoFramePreviewNode.addSubnode(dimNode)
|
||||||
self.videoFramePreviewNode = videoFramePreviewNode
|
|
||||||
|
videoFrameTextNode = ImmediateTextNode()
|
||||||
|
videoFrameTextNode.displaysAsynchronously = false
|
||||||
|
videoFrameTextNode.maximumNumberOfLines = 1
|
||||||
|
videoFrameTextNode.textShadowColor = .black
|
||||||
|
if let scrubbingVisualTimestamp = self.scrubbingVisualTimestamp {
|
||||||
|
videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(scrubbingVisualTimestamp)), font: Font.regular(13.0), textColor: .white)
|
||||||
|
}
|
||||||
|
let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(), size: textSize)
|
||||||
|
videoFramePreviewNode.addSubnode(videoFrameTextNode)
|
||||||
|
|
||||||
|
self.videoFramePreviewNode = (videoFramePreviewNode, videoFrameTextNode)
|
||||||
self.addSubnode(videoFramePreviewNode)
|
self.addSubnode(videoFramePreviewNode)
|
||||||
animateIn = true
|
animateIn = true
|
||||||
}
|
}
|
||||||
@ -1059,7 +1110,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
if animateIn {
|
if animateIn {
|
||||||
videoFramePreviewNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
videoFramePreviewNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||||
}
|
}
|
||||||
} else if let videoFramePreviewNode = self.videoFramePreviewNode {
|
} else if let (videoFramePreviewNode, _) = self.videoFramePreviewNode {
|
||||||
self.videoFramePreviewNode = nil
|
self.videoFramePreviewNode = nil
|
||||||
videoFramePreviewNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak videoFramePreviewNode] _ in
|
videoFramePreviewNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak videoFramePreviewNode] _ in
|
||||||
videoFramePreviewNode?.removeFromSupernode()
|
videoFramePreviewNode?.removeFromSupernode()
|
||||||
|
|||||||
@ -64,7 +64,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
var requestAddContact: ((String) -> Void)?
|
var requestAddContact: ((String) -> Void)?
|
||||||
var dismissSelf: (() -> Void)?
|
var dismissSelf: (() -> Void)?
|
||||||
var isEmptyUpdated: ((Bool) -> Void)?
|
var isEmptyUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
let debugListView = ListView()
|
let debugListView = ListView()
|
||||||
|
|
||||||
init(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, presentationData: PresentationData, controller: ChatListController) {
|
init(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, presentationData: PresentationData, controller: ChatListController) {
|
||||||
@ -100,7 +100,6 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout {
|
if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.isEmptyUpdated?(true)
|
strongSelf.isEmptyUpdated?(true)
|
||||||
}
|
}
|
||||||
case .notEmpty(false):
|
case .notEmpty(false):
|
||||||
|
|||||||
@ -56,7 +56,7 @@ private class ChatMessageHeartbeatHaptic {
|
|||||||
if time > 2.0 {
|
if time > 2.0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var startTime: Double = 0.0
|
var startTime: Double = 0.0
|
||||||
var delay: Double = 0.0
|
var delay: Double = 0.0
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var visibility: ListViewItemNodeVisibility {
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
let wasVisible = oldValue != .none
|
let wasVisible = oldValue != .none
|
||||||
@ -234,7 +234,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (emoji, fitz) = item.message.text.basicEmoji
|
let (emoji, fitz) = item.message.text.basicEmoji
|
||||||
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file {
|
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file {
|
||||||
if self.emojiFile?.id != emojiFile.id {
|
if self.emojiFile?.id != emojiFile.id {
|
||||||
@ -245,7 +245,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
||||||
}
|
}
|
||||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false))
|
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false))
|
||||||
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: emojiFile)).start())
|
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var playbackMode: AnimatedStickerPlaybackMode = .loop
|
var playbackMode: AnimatedStickerPlaybackMode = .loop
|
||||||
var isEmoji = false
|
var isEmoji = false
|
||||||
var fitzModifier: EmojiFitzModifier?
|
var fitzModifier: EmojiFitzModifier?
|
||||||
|
|
||||||
if let telegramFile = self.telegramFile {
|
if let telegramFile = self.telegramFile {
|
||||||
file = telegramFile
|
file = telegramFile
|
||||||
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
|
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
|
||||||
@ -348,23 +348,23 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var hasAvatar = false
|
var hasAvatar = false
|
||||||
|
|
||||||
switch item.chatLocation {
|
switch item.chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
if peerId != item.context.account.peerId {
|
if peerId != item.context.account.peerId {
|
||||||
if peerId.isGroupOrChannel && item.message.author != nil {
|
if peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isBroadcastChannel {
|
if !isBroadcastChannel {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if incoming {
|
|
||||||
hasAvatar = true
|
|
||||||
}
|
}
|
||||||
|
} else if incoming {
|
||||||
|
hasAvatar = true
|
||||||
|
}
|
||||||
/*case .group:
|
/*case .group:
|
||||||
hasAvatar = true*/
|
hasAvatar = true*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasAvatar {
|
if hasAvatar {
|
||||||
@ -448,7 +448,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
statusType = .FreeOutgoing(.Sent(read: item.read))
|
statusType = .FreeOutgoing(.Sent(read: item.read))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewCount: Int? = nil
|
var viewCount: Int? = nil
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? ViewCountMessageAttribute {
|
if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
@ -706,128 +706,128 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .ended:
|
case .ended:
|
||||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
switch gesture {
|
switch gesture {
|
||||||
case .tap:
|
case .tap:
|
||||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||||
if let item = self.item, let author = item.content.firstMessage.author {
|
if let item = self.item, let author = item.content.firstMessage.author {
|
||||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||||
var navigate: ChatControllerInteractionNavigateToPeer
|
var navigate: ChatControllerInteractionNavigateToPeer
|
||||||
|
|
||||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||||
navigate = .chat(textInputState: nil, messageId: nil)
|
navigate = .chat(textInputState: nil, messageId: nil)
|
||||||
|
} else {
|
||||||
|
navigate = .info
|
||||||
|
}
|
||||||
|
|
||||||
|
for attribute in item.content.firstMessage.attributes {
|
||||||
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
|
openPeerId = attribute.messageId.peerId
|
||||||
|
navigate = .chat(textInputState: nil, messageId: attribute.messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
||||||
|
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
||||||
|
} else {
|
||||||
|
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
||||||
|
if case .member = channel.participationStatus {
|
||||||
} else {
|
} else {
|
||||||
navigate = .info
|
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||||
}
|
return
|
||||||
|
|
||||||
for attribute in item.content.firstMessage.attributes {
|
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
|
||||||
openPeerId = attribute.messageId.peerId
|
|
||||||
navigate = .chat(textInputState: nil, messageId: attribute.messageId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
|
|
||||||
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
|
|
||||||
} else {
|
|
||||||
if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
|
|
||||||
if case .member = channel.participationStatus {
|
|
||||||
} else {
|
|
||||||
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
return
|
||||||
if let item = self.item {
|
|
||||||
for attribute in item.message.attributes {
|
|
||||||
if let attribute = attribute as? InlineBotMessageAttribute {
|
|
||||||
var botAddressName: String?
|
|
||||||
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
|
||||||
botAddressName = addressName
|
|
||||||
} else {
|
|
||||||
botAddressName = attribute.title
|
|
||||||
}
|
|
||||||
|
|
||||||
if let botAddressName = botAddressName {
|
|
||||||
item.controllerInteraction.updateInputState { textInputState in
|
|
||||||
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
|
||||||
}
|
|
||||||
item.controllerInteraction.updateInputMode { _ in
|
|
||||||
return .text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
|
||||||
if let item = self.item {
|
|
||||||
for attribute in item.message.attributes {
|
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
|
||||||
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
|
||||||
if self.telegramFile != nil {
|
|
||||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
|
||||||
} else if let _ = self.emojiFile {
|
|
||||||
var startTime: Signal<Double, NoError>
|
|
||||||
if self.animationNode.playIfNeeded() {
|
|
||||||
startTime = .single(0.0)
|
|
||||||
} else {
|
|
||||||
startTime = self.animationNode.status
|
|
||||||
|> map { $0.timestamp }
|
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.item?.message.text == "❤️" {
|
|
||||||
let _ = startTime.start(next: { [weak self] time in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let heartbeatHaptic: ChatMessageHeartbeatHaptic
|
|
||||||
if let current = strongSelf.heartbeatHaptic {
|
|
||||||
heartbeatHaptic = current
|
|
||||||
} else {
|
|
||||||
heartbeatHaptic = ChatMessageHeartbeatHaptic()
|
|
||||||
heartbeatHaptic.enabled = true
|
|
||||||
strongSelf.heartbeatHaptic = heartbeatHaptic
|
|
||||||
}
|
|
||||||
if !heartbeatHaptic.active {
|
|
||||||
heartbeatHaptic.start(time: time)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.item?.controllerInteraction.clickThroughMessage()
|
|
||||||
case .longTap, .doubleTap:
|
|
||||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
|
||||||
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame)
|
|
||||||
}
|
|
||||||
case .hold:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
||||||
|
if let item = self.item {
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let attribute = attribute as? InlineBotMessageAttribute {
|
||||||
|
var botAddressName: String?
|
||||||
|
if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName {
|
||||||
|
botAddressName = addressName
|
||||||
|
} else {
|
||||||
|
botAddressName = attribute.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if let botAddressName = botAddressName {
|
||||||
|
item.controllerInteraction.updateInputState { textInputState in
|
||||||
|
return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " "))
|
||||||
|
}
|
||||||
|
item.controllerInteraction.updateInputMode { _ in
|
||||||
|
return .text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
||||||
|
if let item = self.item {
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
|
item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||||
|
if self.telegramFile != nil {
|
||||||
|
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||||
|
} else if let _ = self.emojiFile {
|
||||||
|
var startTime: Signal<Double, NoError>
|
||||||
|
if self.animationNode.playIfNeeded() {
|
||||||
|
startTime = .single(0.0)
|
||||||
|
} else {
|
||||||
|
startTime = self.animationNode.status
|
||||||
|
|> map { $0.timestamp }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first, firstScalar.value == 0x2764 {
|
||||||
|
let _ = startTime.start(next: { [weak self] time in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let heartbeatHaptic: ChatMessageHeartbeatHaptic
|
||||||
|
if let current = strongSelf.heartbeatHaptic {
|
||||||
|
heartbeatHaptic = current
|
||||||
|
} else {
|
||||||
|
heartbeatHaptic = ChatMessageHeartbeatHaptic()
|
||||||
|
heartbeatHaptic.enabled = true
|
||||||
|
strongSelf.heartbeatHaptic = heartbeatHaptic
|
||||||
|
}
|
||||||
|
if !heartbeatHaptic.active {
|
||||||
|
heartbeatHaptic.start(time: time)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.item?.controllerInteraction.clickThroughMessage()
|
||||||
|
case .longTap, .doubleTap:
|
||||||
|
if let item = self.item, self.imageNode.frame.contains(location) {
|
||||||
|
item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame)
|
||||||
|
}
|
||||||
|
case .hold:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
break
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updateScrubbing: (Double?) -> Void = { _ in }
|
var updateScrubbing: (Double?) -> Void = { _ in }
|
||||||
|
var updateScrubbingVisual: (Double?) -> Void = { _ in }
|
||||||
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
||||||
var seek: (Double) -> Void = { _ in }
|
var seek: (Double) -> Void = { _ in }
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
|
|
||||||
self.scrubberNode.update = { [weak self] timestamp, position in
|
self.scrubberNode.update = { [weak self] timestamp, position in
|
||||||
self?.updateScrubbing(timestamp)
|
self?.updateScrubbing(timestamp)
|
||||||
|
self?.updateScrubbingVisual(timestamp)
|
||||||
self?.updateScrubbingHandlePosition(position)
|
self?.updateScrubbingHandlePosition(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -203,7 +203,7 @@ func galleryItemForEntry(context: AccountContext, presentationData: Presentation
|
|||||||
content = SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0))
|
content = SystemVideoContent(url: embedUrl, imageReference: .webPage(webPage: WebpageReference(webpage), media: image), dimensions: webpageContent.embedSize ?? CGSize(width: 640.0, height: 640.0), duration: Int32(webpageContent.duration ?? 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if content == nil, let webEmbedContent = WebEmbedVideoContent(webPage: webpage, webpageContent: webpageContent) {
|
if content == nil, let webEmbedContent = WebEmbedVideoContent(webPage: webpage, webpageContent: webpageContent, forcedTimestamp: timecode.flatMap(Int.init)) {
|
||||||
content = webEmbedContent
|
content = webEmbedContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -917,16 +917,13 @@ class GalleryController: ViewController {
|
|||||||
if let (media, _) = mediaForMessage(message: message) {
|
if let (media, _) = mediaForMessage(message: message) {
|
||||||
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments, let transitionArguments = presentationArguments.transitionArguments(message.id, media) {
|
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments, let transitionArguments = presentationArguments.transitionArguments(message.id, media) {
|
||||||
nodeAnimatesItself = true
|
nodeAnimatesItself = true
|
||||||
centralItemNode.activateAsInitial()
|
|
||||||
|
|
||||||
if presentationArguments.animated {
|
if presentationArguments.animated {
|
||||||
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
|
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
self._hiddenMedia.set(.single((message.id, media)))
|
self._hiddenMedia.set(.single((message.id, media)))
|
||||||
} else if self.isPresentedInPreviewingContext() {
|
|
||||||
centralItemNode.activateAsInitial()
|
|
||||||
}
|
}
|
||||||
|
centralItemNode.activateAsInitial()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -279,7 +279,7 @@ final class ManagedAudioRecorderContext {
|
|||||||
strongSelf.toneTimer?.invalidate()
|
strongSelf.toneTimer?.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, queue: queue)
|
}, queue: queue)
|
||||||
self.toneTimer = toneTimer
|
self.toneTimer = toneTimer
|
||||||
toneTimer.start()
|
toneTimer.start()
|
||||||
} else {
|
} else {
|
||||||
@ -287,25 +287,25 @@ final class ManagedAudioRecorderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*if beginWithTone, let beginToneData = beginToneData {
|
/*if beginWithTone, let beginToneData = beginToneData {
|
||||||
self.tonePlayer = TonePlayer()
|
self.tonePlayer = TonePlayer()
|
||||||
self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in
|
self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in
|
||||||
queue.async {
|
queue.async {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in
|
let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.processSamples = true
|
strongSelf.processSamples = true
|
||||||
}, queue: queue)
|
}, queue: queue)
|
||||||
strongSelf.toneTimer = toneTimer
|
strongSelf.toneTimer = toneTimer
|
||||||
toneTimer.start()
|
toneTimer.start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.processSamples = true
|
self.processSamples = true
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
addAudioRecorderContext(self.id, self)
|
addAudioRecorderContext(self.id, self)
|
||||||
addAudioUnitHolder(self.id, queue, self.audioUnit)
|
addAudioUnitHolder(self.id, queue, self.audioUnit)
|
||||||
@ -314,7 +314,7 @@ final class ManagedAudioRecorderContext {
|
|||||||
|
|
||||||
self.idleTimerExtensionDisposable = (Signal<Void, NoError> { subscriber in
|
self.idleTimerExtensionDisposable = (Signal<Void, NoError> { subscriber in
|
||||||
return pushIdleTimerExtension()
|
return pushIdleTimerExtension()
|
||||||
} |> delay(5.0, queue: queue)).start()
|
} |> delay(5.0, queue: queue)).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -401,19 +401,19 @@ final class ManagedAudioRecorderContext {
|
|||||||
strongSelf.audioSessionAcquired(headset: state.isHeadsetConnected)
|
strongSelf.audioSessionAcquired(headset: state.isHeadsetConnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, deactivate: { [weak self] in
|
}, deactivate: { [weak self] in
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
queue.async {
|
queue.async {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hasAudioSession = false
|
strongSelf.hasAudioSession = false
|
||||||
strongSelf.stop()
|
strongSelf.stop()
|
||||||
strongSelf.recordingState.set(.stopped)
|
strongSelf.recordingState.set(.stopped)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
return EmptyDisposable
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -171,6 +171,8 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlCon
|
|||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
navigationController?.pushViewController(ChatController(context: context, chatLocation: .peer(peerId)))
|
navigationController?.pushViewController(ChatController(context: context, chatLocation: .peer(peerId)))
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
navigationController?.pushViewController(ChatController(context: context, chatLocation: .peer(peerId), messageId: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -297,7 +297,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
|||||||
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, reportMessages: { _ in
|
}, reportMessages: { _ in
|
||||||
}, deleteMessages: { _ in
|
}, deleteMessages: { _ in
|
||||||
|
|||||||
@ -22,7 +22,7 @@ private enum PeerReportOption {
|
|||||||
case other
|
case other
|
||||||
}
|
}
|
||||||
|
|
||||||
func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void) -> ViewController {
|
func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (Bool) -> Void) -> ViewController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
|
let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
|
||||||
|
|
||||||
@ -75,15 +75,17 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub
|
|||||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
completion(true)
|
||||||
})
|
})
|
||||||
case let .messages(messageIds):
|
case let .messages(messageIds):
|
||||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
completion(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller?.present(peerReportController(context: context, subject: subject), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
controller?.present(peerReportController(context: context, subject: subject, completion: completion), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
|
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
@ -95,6 +97,7 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub
|
|||||||
ActionSheetItemGroup(items: [
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak controller] in
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak controller] in
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
|
completion(false)
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@ -187,7 +190,7 @@ private func peerReportControllerEntries(presentationData: PresentationData, sta
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
private func peerReportController(context: AccountContext, subject: PeerReportSubject) -> ViewController {
|
private func peerReportController(context: AccountContext, subject: PeerReportSubject, completion: @escaping (Bool) -> Void) -> ViewController {
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
|
|
||||||
@ -228,6 +231,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
|||||||
let completed: () -> Void = {
|
let completed: () -> Void = {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
completion(true)
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
}
|
}
|
||||||
switch subject {
|
switch subject {
|
||||||
@ -248,6 +252,7 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
|||||||
|
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
|
completion(false)
|
||||||
}), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
}), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text)
|
let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text)
|
||||||
|
|
||||||
|
|||||||
@ -45,30 +45,30 @@ private final class PrefetchManagerImpl {
|
|||||||
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|
||||||
|> mapToSignal { orderedPreloadMedia in
|
|> mapToSignal { orderedPreloadMedia in
|
||||||
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|
||||||
|> map { result -> [PrefetchMediaItem] in
|
|> map { result -> [PrefetchMediaItem] in
|
||||||
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
|
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
|
||||||
switch result {
|
switch result {
|
||||||
case let .result(_, items, _):
|
case let .result(_, items, _):
|
||||||
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
||||||
for case let item as StickerPackItem in items {
|
for case let item as StickerPackItem in items {
|
||||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||||
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stickerItems: [PrefetchMediaItem] = []
|
||||||
|
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
|
||||||
|
for emoji in popularEmoji {
|
||||||
|
if let sticker = animatedEmojiStickers[emoji] {
|
||||||
|
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {
|
||||||
|
} else {
|
||||||
|
stickerItems.append(.animatedEmojiSticker(sticker.file))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var stickerItems: [PrefetchMediaItem] = []
|
}
|
||||||
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
|
return stickerItems + chatHistoryMediaItems
|
||||||
for emoji in popularEmoji {
|
default:
|
||||||
if let sticker = animatedEmojiStickers[emoji] {
|
return chatHistoryMediaItems
|
||||||
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {
|
}
|
||||||
} else {
|
|
||||||
stickerItems.append(.animatedEmojiSticker(sticker.file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stickerItems + chatHistoryMediaItems
|
|
||||||
default:
|
|
||||||
return chatHistoryMediaItems
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,18 +168,18 @@ private final class PrefetchManagerImpl {
|
|||||||
if validIds.contains(id) {
|
if validIds.contains(id) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||||
let peerType = MediaAutoDownloadPeerType.contact
|
let peerType = MediaAutoDownloadPeerType.contact
|
||||||
|
|
||||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) {
|
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) {
|
||||||
automaticDownload = .full
|
automaticDownload = .full
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .none = automaticDownload {
|
if case .none = automaticDownload {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
validIds.insert(id)
|
validIds.insert(id)
|
||||||
let context: PrefetchMediaContext
|
let context: PrefetchMediaContext
|
||||||
if let current = self.contexts[id] {
|
if let current = self.contexts[id] {
|
||||||
@ -196,7 +196,7 @@ private final class PrefetchManagerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
order += 1
|
order += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var removeIds: [MediaId] = []
|
var removeIds: [MediaId] = []
|
||||||
|
|||||||
@ -271,8 +271,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
|
|
||||||
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if query.isSingleEmoji {
|
if query.isSingleEmoji {
|
||||||
signals = .single([searchStickers(account: account, query: text.trimmedEmoji)
|
signals = .single([searchStickers(account: account, query: text.basicEmoji.0)
|
||||||
//|> take(1)
|
|
||||||
|> map { (nil, $0) }])
|
|> map { (nil, $0) }])
|
||||||
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
||||||
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
||||||
@ -294,7 +293,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
||||||
let emoticons = keywords.flatMap { $0.emoticons }
|
let emoticons = keywords.flatMap { $0.emoticons }
|
||||||
for emoji in emoticons {
|
for emoji in emoticons {
|
||||||
signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji)
|
signals.append(searchStickers(account: self.context.account, query: emoji.basicEmoji.0)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { (emoji, $0) })
|
|> map { (emoji, $0) })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1164,7 +1164,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
|
|||||||
}, report: {
|
}, report: {
|
||||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||||
presentControllerImpl?(c, a)
|
presentControllerImpl?(c, a)
|
||||||
}), nil)
|
}, completion: { _ in }), nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
let deviceContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> = peerView.get()
|
let deviceContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> = peerView.get()
|
||||||
|
|||||||
@ -33,11 +33,9 @@ enum WebEmbedType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func webEmbedType(content: TelegramMediaWebpageLoadedContent) -> WebEmbedType {
|
func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) -> WebEmbedType {
|
||||||
if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) {
|
if let (videoId, timestamp) = extractYoutubeVideoIdAndTimestamp(url: content.url) {
|
||||||
return .youtube(videoId: videoId, timestamp: timestamp)
|
return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
|
||||||
// } else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) {
|
|
||||||
// return .vimeo(videoId: videoId, timestamp: timestamp)
|
|
||||||
} else {
|
} else {
|
||||||
return .iframe(url: content.embedUrl ?? content.url)
|
return .iframe(url: content.embedUrl ?? content.url)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,9 @@ final class WebEmbedVideoContent: UniversalVideoContent {
|
|||||||
let webpageContent: TelegramMediaWebpageLoadedContent
|
let webpageContent: TelegramMediaWebpageLoadedContent
|
||||||
let dimensions: CGSize
|
let dimensions: CGSize
|
||||||
let duration: Int32
|
let duration: Int32
|
||||||
|
let forcedTimestamp: Int?
|
||||||
|
|
||||||
init?(webPage: TelegramMediaWebpage, webpageContent: TelegramMediaWebpageLoadedContent) {
|
init?(webPage: TelegramMediaWebpage, webpageContent: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) {
|
||||||
guard let embedUrl = webpageContent.embedUrl else {
|
guard let embedUrl = webpageContent.embedUrl else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -26,10 +27,11 @@ final class WebEmbedVideoContent: UniversalVideoContent {
|
|||||||
self.webpageContent = webpageContent
|
self.webpageContent = webpageContent
|
||||||
self.dimensions = webpageContent.embedSize ?? CGSize(width: 128.0, height: 128.0)
|
self.dimensions = webpageContent.embedSize ?? CGSize(width: 128.0, height: 128.0)
|
||||||
self.duration = Int32(webpageContent.duration ?? (0 as Int))
|
self.duration = Int32(webpageContent.duration ?? (0 as Int))
|
||||||
|
self.forcedTimestamp = forcedTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode {
|
func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode {
|
||||||
return WebEmbedVideoContentNode(postbox: postbox, audioSessionManager: audioSession, webPage: self.webPage, webpageContent: self.webpageContent)
|
return WebEmbedVideoContentNode(postbox: postbox, audioSessionManager: audioSession, webPage: self.webPage, webpageContent: self.webpageContent, forcedTimestamp: self.forcedTimestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
|||||||
|
|
||||||
private var readyDisposable = MetaDisposable()
|
private var readyDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, webPage: TelegramMediaWebpage, webpageContent: TelegramMediaWebpageLoadedContent) {
|
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, webPage: TelegramMediaWebpage, webpageContent: TelegramMediaWebpageLoadedContent, forcedTimestamp: Int? = nil) {
|
||||||
self.webpageContent = webpageContent
|
self.webpageContent = webpageContent
|
||||||
|
|
||||||
if let embedSize = webpageContent.embedSize {
|
if let embedSize = webpageContent.embedSize {
|
||||||
@ -73,7 +75,7 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
|||||||
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
|
|
||||||
let embedType = webEmbedType(content: webpageContent)
|
let embedType = webEmbedType(content: webpageContent, forcedTimestamp: forcedTimestamp)
|
||||||
let embedImpl = webEmbedImplementation(for: embedType)
|
let embedImpl = webEmbedImplementation(for: embedType)
|
||||||
self.playerNode = WebEmbedPlayerNode(impl: embedImpl, intrinsicDimensions: self.intrinsicDimensions)
|
self.playerNode = WebEmbedPlayerNode(impl: embedImpl, intrinsicDimensions: self.intrinsicDimensions)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user