mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Use shared emoji, reactions and sticker lists in chat
This commit is contained in:
parent
852c138655
commit
2249261b86
@ -175,6 +175,72 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
|
|
||||||
private var animatedEmojiStickersDisposable: Disposable?
|
private var animatedEmojiStickersDisposable: Disposable?
|
||||||
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
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?
|
private var userLimitsConfigurationDisposable: Disposable?
|
||||||
public private(set) var userLimits: EngineConfiguration.UserLimits
|
public private(set) var userLimits: EngineConfiguration.UserLimits
|
||||||
@ -311,6 +377,7 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.animatedEmojiStickers = stickers
|
strongSelf.animatedEmojiStickers = stickers
|
||||||
|
strongSelf.animatedEmojiStickersValue.set(.single(stickers))
|
||||||
})
|
})
|
||||||
|
|
||||||
self.userLimitsConfigurationDisposable = (self.account.postbox.peerView(id: self.account.peerId)
|
self.userLimitsConfigurationDisposable = (self.account.postbox.peerView(id: self.account.peerId)
|
||||||
|
@ -36,6 +36,7 @@ import UIKitRuntimeUtils
|
|||||||
import StoreKit
|
import StoreKit
|
||||||
import PhoneNumberFormat
|
import PhoneNumberFormat
|
||||||
import AuthorizationUI
|
import AuthorizationUI
|
||||||
|
import ManagedFile
|
||||||
|
|
||||||
#if canImport(AppCenter)
|
#if canImport(AppCenter)
|
||||||
import 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?
|
@objc var window: UIWindow?
|
||||||
var nativeWindow: (UIWindow & WindowHost)?
|
var nativeWindow: (UIWindow & WindowHost)?
|
||||||
var mainWindow: Window1!
|
var mainWindow: Window1!
|
||||||
@ -267,6 +268,25 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
}
|
}
|
||||||
private let firebaseSecretStream = Promise<[String: String]>([:])
|
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> {
|
private var notificationTokenPromise: Promise<Data> {
|
||||||
if let current = self._notificationTokenPromise {
|
if let current = self._notificationTokenPromise {
|
||||||
return current
|
return current
|
||||||
@ -834,7 +854,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.mainWindow.coveringView = nil
|
self.mainWindow.coveringView = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}, appDelegate: self)
|
||||||
|
|
||||||
presentationDataPromise.set(sharedContext.presentationData)
|
presentationDataPromise.set(sharedContext.presentationData)
|
||||||
|
|
||||||
@ -1402,9 +1422,156 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
UIApplication.shared.registerForRemoteNotifications()
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = self.urlSession(identifier: "\(baseAppBundleId).backroundSession")
|
||||||
|
|
||||||
return true
|
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 {
|
private func runCacheReindexTasks(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable {
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
@ -954,63 +954,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = (context as! AccountContextImpl).animatedEmojiStickersSignal
|
||||||
|> 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 additionalAnimatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|
let additionalAnimatedEmojiStickers = (context as! AccountContextImpl).additionalAnimatedEmojiStickers
|
||||||
|> 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 previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
|
let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
|
||||||
|
|
||||||
@ -1077,7 +1023,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
customThreadOutgoingReadState = .single(nil)
|
customThreadOutgoingReadState = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableReactions = context.engine.stickers.availableReactions()
|
let availableReactions: Signal<AvailableReactions?, NoError> = (context as! AccountContextImpl).availableReactions
|
||||||
|
|
||||||
let defaultReaction = combineLatest(
|
let defaultReaction = combineLatest(
|
||||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
|
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user