Use shared emoji, reactions and sticker lists in chat

This commit is contained in:
Ali 2023-02-03 20:38:27 +01:00
parent 852c138655
commit 2249261b86
3 changed files with 239 additions and 59 deletions

View File

@ -175,6 +175,72 @@ public final class AccountContextImpl: AccountContext {
private var animatedEmojiStickersDisposable: Disposable?
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
private let animatedEmojiStickersValue = Promise<[String: [StickerPackItem]]>()
public var animatedEmojiStickersSignal: Signal<[String: [StickerPackItem]], NoError> {
return self.animatedEmojiStickersValue.get()
}
private var additionalAnimatedEmojiStickersValue: Promise<[String: [Int: StickerPackItem]]>?
public var additionalAnimatedEmojiStickers: Signal<[String: [Int: StickerPackItem]], NoError> {
let additionalAnimatedEmojiStickersValue: Promise<[String: [Int: StickerPackItem]]>
if let current = self.additionalAnimatedEmojiStickersValue {
additionalAnimatedEmojiStickersValue = current
} else {
additionalAnimatedEmojiStickersValue = Promise<[String: [Int: StickerPackItem]]>()
self.additionalAnimatedEmojiStickersValue = additionalAnimatedEmojiStickersValue
additionalAnimatedEmojiStickersValue.set(self.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
let sequence = "0⃣1⃣2⃣3⃣4⃣5⃣6⃣7⃣8⃣9".strippedEmoji
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
if indexKeys.count > 1, let first = indexKeys.first, let last = indexKeys.last {
let emoji: String?
let indexEmoji: String?
if sequence.contains(first.strippedEmoji) {
emoji = last
indexEmoji = first
} else if sequence.contains(last.strippedEmoji) {
emoji = first
indexEmoji = last
} else {
emoji = nil
indexEmoji = nil
}
if let emoji = emoji?.strippedEmoji, let indexEmoji = indexEmoji?.strippedEmoji.first, let strIndex = sequence.firstIndex(of: indexEmoji) {
let index = sequence.distance(from: sequence.startIndex, to: strIndex)
if animatedEmojiStickers[emoji] != nil {
animatedEmojiStickers[emoji]![index] = item
} else {
animatedEmojiStickers[emoji] = [index: item]
}
}
}
}
default:
break
}
return animatedEmojiStickers
})
}
return additionalAnimatedEmojiStickersValue.get()
}
private var availableReactionsValue: Promise<AvailableReactions?>?
public var availableReactions: Signal<AvailableReactions?, NoError> {
let availableReactionsValue: Promise<AvailableReactions?>
if let current = self.availableReactionsValue {
availableReactionsValue = current
} else {
availableReactionsValue = Promise<AvailableReactions?>()
self.availableReactionsValue = availableReactionsValue
availableReactionsValue.set(self.engine.stickers.availableReactions())
}
return availableReactionsValue.get()
}
private var userLimitsConfigurationDisposable: Disposable?
public private(set) var userLimits: EngineConfiguration.UserLimits
@ -311,6 +377,7 @@ public final class AccountContextImpl: AccountContext {
return
}
strongSelf.animatedEmojiStickers = stickers
strongSelf.animatedEmojiStickersValue.set(.single(stickers))
})
self.userLimitsConfigurationDisposable = (self.account.postbox.peerView(id: self.account.peerId)

View File

@ -36,6 +36,7 @@ import UIKitRuntimeUtils
import StoreKit
import PhoneNumberFormat
import AuthorizationUI
import ManagedFile
#if canImport(AppCenter)
import AppCenter
@ -210,7 +211,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
)
}
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate {
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate, URLSessionDelegate, URLSessionTaskDelegate {
@objc var window: UIWindow?
var nativeWindow: (UIWindow & WindowHost)?
var mainWindow: Window1!
@ -267,6 +268,25 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
private let firebaseSecretStream = Promise<[String: String]>([:])
private var urlSessions: [URLSession] = []
private func urlSession(identifier: String) -> URLSession {
if let existingSession = self.urlSessions.first(where: { $0.configuration.identifier == identifier }) {
return existingSession
}
let baseAppBundleId = Bundle.main.bundleIdentifier!
let appGroupName = "group.\(baseAppBundleId)"
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
configuration.sharedContainerIdentifier = appGroupName
configuration.isDiscretionary = false
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
self.urlSessions.append(session)
return session
}
private var pendingUrlSessionBackgroundEventsCompletion: (() -> Void)?
private var notificationTokenPromise: Promise<Data> {
if let current = self._notificationTokenPromise {
return current
@ -834,7 +854,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.mainWindow.coveringView = nil
}
}
})
}, appDelegate: self)
presentationDataPromise.set(sharedContext.presentationData)
@ -1402,9 +1422,156 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
UIApplication.shared.registerForRemoteNotifications()
}
let _ = self.urlSession(identifier: "\(baseAppBundleId).backroundSession")
return true
}
private var backgroundSessionSourceDataDisposables: [String: Disposable] = [:]
private var backgroundUploadResultSubscribers: [String: Bag<(String?) -> Void>] = [:]
func uploadInBackround(postbox: Postbox, resource: MediaResource) -> Signal<String?, NoError> {
let baseAppBundleId = Bundle.main.bundleIdentifier!
let session = self.urlSession(identifier: "\(baseAppBundleId).backroundSession")
let signal = Signal<Never, NoError> { subscriber in
let disposable = MetaDisposable()
let _ = session.getAllTasks(completionHandler: { tasks in
var alreadyExists = false
for task in tasks {
if let originalRequest = task.originalRequest {
if let requestResourceId = originalRequest.value(forHTTPHeaderField: "tresource") {
if resource.id.stringRepresentation == requestResourceId {
alreadyExists = true
break
}
}
}
}
if !alreadyExists, self.backgroundSessionSourceDataDisposables[resource.id.stringRepresentation] == nil {
self.backgroundSessionSourceDataDisposables[resource.id.stringRepresentation] = (Signal<Never, NoError> { subscriber in
let dataDisposable = (postbox.mediaBox.resourceData(resource)
|> deliverOnMainQueue).start(next: { data in
if data.complete {
self.addBackgroundUploadTask(id: resource.id.stringRepresentation, path: data.path)
}
})
let fetchDisposable = postbox.mediaBox.fetchedResource(resource, parameters: nil).start()
return ActionDisposable {
dataDisposable.dispose()
fetchDisposable.dispose()
}
}).start()
}
})
return disposable
}
|> runOn(.mainQueue())
return Signal { subscriber in
let bag: Bag<(String?) -> Void>
if let current = self.backgroundUploadResultSubscribers[resource.id.stringRepresentation] {
bag = current
} else {
bag = Bag()
self.backgroundUploadResultSubscribers[resource.id.stringRepresentation] = bag
}
let index = bag.add { result in
subscriber.putNext(result)
subscriber.putCompletion()
}
let workDisposable = signal.start()
return ActionDisposable {
workDisposable.dispose()
Queue.mainQueue().async {
if let bag = self.backgroundUploadResultSubscribers[resource.id.stringRepresentation] {
bag.remove(index)
if bag.isEmpty {
//TODO:cancel tasks
}
}
}
}
}
|> runOn(.mainQueue())
}
private func addBackgroundUploadTask(id: String, path: String) {
let baseAppBundleId = Bundle.main.bundleIdentifier!
let session = self.urlSession(identifier: "\(baseAppBundleId).backroundSession")
let fileName = "upload-\(UInt32.random(in: 0 ... UInt32.max))"
let uploadFilePath = NSTemporaryDirectory() + "/" + fileName
guard let sourceFile = ManagedFile(queue: nil, path: uploadFilePath, mode: .readwrite) else {
return
}
guard let inFile = ManagedFile(queue: nil, path: path, mode: .read) else {
return
}
let boundary = UUID().uuidString
var headerData = Data()
headerData.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
headerData.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
headerData.append("Content-Type: image/png\r\n\r\n".data(using: .utf8)!)
var footerData = Data()
footerData.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
let _ = sourceFile.write(headerData)
let bufferSize = 512 * 1024
let buffer = malloc(bufferSize)!
defer {
free(buffer)
}
while true {
let readBytes = inFile.read(buffer, bufferSize)
if readBytes <= 0 {
break
} else {
let _ = sourceFile.write(buffer, count: readBytes)
}
}
let _ = sourceFile.write(footerData)
sourceFile._unsafeClose()
inFile._unsafeClose()
var request = URLRequest(url: URL(string: "http://localhost:25478/upload?token=f9403fc5f537b4ab332d")!)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue(id, forHTTPHeaderField: "tresource")
let task = session.uploadTask(with: request, fromFile: URL(fileURLWithPath: uploadFilePath))
task.resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let response = task.response as? HTTPURLResponse {
if let originalRequest = task.originalRequest {
if let requestResourceId = originalRequest.value(forHTTPHeaderField: "tresource") {
if let bag = self.backgroundUploadResultSubscribers[requestResourceId] {
for item in bag.copyItems() {
item("http server: \(response.allHeaderFields)")
}
}
}
}
}
}
private func runCacheReindexTasks(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
let disposable = MetaDisposable()

View File

@ -954,63 +954,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
|> distinctUntilChanged
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = [item]
}
}
}
default:
break
}
return animatedEmojiStickers
}
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = (context as! AccountContextImpl).animatedEmojiStickersSignal
let additionalAnimatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
let sequence = "0⃣1⃣2⃣3⃣4⃣5⃣6⃣7⃣8⃣9".strippedEmoji
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
if indexKeys.count > 1, let first = indexKeys.first, let last = indexKeys.last {
let emoji: String?
let indexEmoji: String?
if sequence.contains(first.strippedEmoji) {
emoji = last
indexEmoji = first
} else if sequence.contains(last.strippedEmoji) {
emoji = first
indexEmoji = last
} else {
emoji = nil
indexEmoji = nil
}
if let emoji = emoji?.strippedEmoji, let indexEmoji = indexEmoji?.strippedEmoji.first, let strIndex = sequence.firstIndex(of: indexEmoji) {
let index = sequence.distance(from: sequence.startIndex, to: strIndex)
if animatedEmojiStickers[emoji] != nil {
animatedEmojiStickers[emoji]![index] = item
} else {
animatedEmojiStickers[emoji] = [index: item]
}
}
}
}
default:
break
}
return animatedEmojiStickers
}
let additionalAnimatedEmojiStickers = (context as! AccountContextImpl).additionalAnimatedEmojiStickers
let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
@ -1077,7 +1023,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
customThreadOutgoingReadState = .single(nil)
}
let availableReactions = context.engine.stickers.availableReactions()
let availableReactions: Signal<AvailableReactions?, NoError> = (context as! AccountContextImpl).availableReactions
let defaultReaction = combineLatest(
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),