mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Merge branch 'beta' into experimental-2
Support more notification actions
This commit is contained in:
@@ -6,6 +6,10 @@ import TelegramCore
|
||||
import BuildConfig
|
||||
import OpenSSLEncryptionProvider
|
||||
import TelegramUIPreferences
|
||||
import WebPBinding
|
||||
import RLottieBinding
|
||||
import GZip
|
||||
import UIKit
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
@@ -30,6 +34,249 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
}
|
||||
|
||||
private let deviceColorSpace: CGColorSpace = {
|
||||
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||
return colorSpace
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
}()
|
||||
|
||||
private func getSharedDevideGraphicsContextSettings() -> DeviceGraphicsContextSettings {
|
||||
struct OpaqueSettings {
|
||||
let rowAlignment: Int
|
||||
let bitsPerPixel: Int
|
||||
let bitsPerComponent: Int
|
||||
let opaqueBitmapInfo: CGBitmapInfo
|
||||
let colorSpace: CGColorSpace
|
||||
|
||||
init(context: CGContext) {
|
||||
self.rowAlignment = context.bytesPerRow
|
||||
self.bitsPerPixel = context.bitsPerPixel
|
||||
self.bitsPerComponent = context.bitsPerComponent
|
||||
self.opaqueBitmapInfo = context.bitmapInfo
|
||||
if #available(iOS 10.0, *) {
|
||||
if UIScreen.main.traitCollection.displayGamut == .P3 {
|
||||
self.colorSpace = CGColorSpace(name: CGColorSpace.displayP3) ?? context.colorSpace!
|
||||
} else {
|
||||
self.colorSpace = context.colorSpace!
|
||||
}
|
||||
} else {
|
||||
self.colorSpace = context.colorSpace!
|
||||
}
|
||||
assert(self.rowAlignment == 32)
|
||||
assert(self.bitsPerPixel == 32)
|
||||
assert(self.bitsPerComponent == 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransparentSettings {
|
||||
let transparentBitmapInfo: CGBitmapInfo
|
||||
|
||||
init(context: CGContext) {
|
||||
self.transparentBitmapInfo = context.bitmapInfo
|
||||
}
|
||||
}
|
||||
|
||||
var opaqueSettings: OpaqueSettings?
|
||||
var transparentSettings: TransparentSettings?
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
let opaqueFormat = UIGraphicsImageRendererFormat()
|
||||
let transparentFormat = UIGraphicsImageRendererFormat()
|
||||
if #available(iOS 12.0, *) {
|
||||
opaqueFormat.preferredRange = .standard
|
||||
transparentFormat.preferredRange = .standard
|
||||
}
|
||||
opaqueFormat.opaque = true
|
||||
transparentFormat.opaque = false
|
||||
|
||||
let opaqueRenderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), format: opaqueFormat)
|
||||
let _ = opaqueRenderer.image(actions: { context in
|
||||
opaqueSettings = OpaqueSettings(context: context.cgContext)
|
||||
})
|
||||
|
||||
let transparentRenderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), format: transparentFormat)
|
||||
let _ = transparentRenderer.image(actions: { context in
|
||||
transparentSettings = TransparentSettings(context: context.cgContext)
|
||||
})
|
||||
} else {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1.0, height: 1.0), true, 1.0)
|
||||
let refContext = UIGraphicsGetCurrentContext()!
|
||||
opaqueSettings = OpaqueSettings(context: refContext)
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1.0, height: 1.0), false, 1.0)
|
||||
let refCtxTransparent = UIGraphicsGetCurrentContext()!
|
||||
transparentSettings = TransparentSettings(context: refCtxTransparent)
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
return DeviceGraphicsContextSettings(
|
||||
rowAlignment: opaqueSettings!.rowAlignment,
|
||||
bitsPerPixel: opaqueSettings!.bitsPerPixel,
|
||||
bitsPerComponent: opaqueSettings!.bitsPerComponent,
|
||||
opaqueBitmapInfo: opaqueSettings!.opaqueBitmapInfo,
|
||||
transparentBitmapInfo: transparentSettings!.transparentBitmapInfo,
|
||||
colorSpace: opaqueSettings!.colorSpace
|
||||
)
|
||||
}
|
||||
|
||||
public struct DeviceGraphicsContextSettings {
|
||||
public static let shared: DeviceGraphicsContextSettings = getSharedDevideGraphicsContextSettings()
|
||||
|
||||
public let rowAlignment: Int
|
||||
public let bitsPerPixel: Int
|
||||
public let bitsPerComponent: Int
|
||||
public let opaqueBitmapInfo: CGBitmapInfo
|
||||
public let transparentBitmapInfo: CGBitmapInfo
|
||||
public let colorSpace: CGColorSpace
|
||||
|
||||
public func bytesPerRow(forWidth width: Int) -> Int {
|
||||
let baseValue = self.bitsPerPixel * width / 8
|
||||
return (baseValue + 31) & ~0x1F
|
||||
}
|
||||
}
|
||||
|
||||
private final class DrawingContext {
|
||||
let size: CGSize
|
||||
let scale: CGFloat
|
||||
let scaledSize: CGSize
|
||||
let bytesPerRow: Int
|
||||
private let bitmapInfo: CGBitmapInfo
|
||||
let length: Int
|
||||
let bytes: UnsafeMutableRawPointer
|
||||
private let data: Data
|
||||
private let context: CGContext
|
||||
|
||||
private var hasGeneratedImage = false
|
||||
|
||||
func withContext(_ f: (CGContext) -> ()) {
|
||||
let context = self.context
|
||||
|
||||
context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
|
||||
f(context)
|
||||
|
||||
context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
}
|
||||
|
||||
func withFlippedContext(_ f: (CGContext) -> ()) {
|
||||
f(self.context)
|
||||
}
|
||||
|
||||
init(size: CGSize, scale: CGFloat = 1.0, opaque: Bool = false, clear: Bool = false) {
|
||||
assert(!size.width.isZero && !size.height.isZero)
|
||||
let size: CGSize = CGSize(width: max(1.0, size.width), height: max(1.0, size.height))
|
||||
|
||||
let actualScale: CGFloat
|
||||
if scale.isZero {
|
||||
actualScale = 1.0
|
||||
} else {
|
||||
actualScale = scale
|
||||
}
|
||||
self.size = size
|
||||
self.scale = actualScale
|
||||
self.scaledSize = CGSize(width: size.width * actualScale, height: size.height * actualScale)
|
||||
|
||||
self.bytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(self.scaledSize.width))
|
||||
|
||||
self.length = self.bytesPerRow * Int(self.scaledSize.height)
|
||||
|
||||
self.bytes = malloc(self.length)
|
||||
self.data = Data(bytesNoCopy: self.bytes, count: self.length, deallocator: .custom({ bytes, _ in
|
||||
free(bytes)
|
||||
}))
|
||||
|
||||
if opaque {
|
||||
self.bitmapInfo = DeviceGraphicsContextSettings.shared.opaqueBitmapInfo
|
||||
} else {
|
||||
self.bitmapInfo = DeviceGraphicsContextSettings.shared.transparentBitmapInfo
|
||||
}
|
||||
|
||||
self.context = CGContext(
|
||||
data: self.bytes,
|
||||
width: Int(self.scaledSize.width),
|
||||
height: Int(self.scaledSize.height),
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: self.bytesPerRow,
|
||||
space: deviceColorSpace,
|
||||
bitmapInfo: self.bitmapInfo.rawValue,
|
||||
releaseCallback: nil,
|
||||
releaseInfo: nil
|
||||
)!
|
||||
self.context.scaleBy(x: self.scale, y: self.scale)
|
||||
|
||||
if clear {
|
||||
memset(self.bytes, 0, self.length)
|
||||
}
|
||||
}
|
||||
|
||||
func generateImage() -> UIImage? {
|
||||
if self.scaledSize.width.isZero || self.scaledSize.height.isZero {
|
||||
return nil
|
||||
}
|
||||
if self.hasGeneratedImage {
|
||||
preconditionFailure()
|
||||
}
|
||||
self.hasGeneratedImage = true
|
||||
|
||||
guard let dataProvider = CGDataProvider(data: self.data as CFData) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let image = CGImage(
|
||||
width: Int(self.scaledSize.width),
|
||||
height: Int(self.scaledSize.height),
|
||||
bitsPerComponent: self.context.bitsPerComponent,
|
||||
bitsPerPixel: self.context.bitsPerPixel,
|
||||
bytesPerRow: self.context.bytesPerRow,
|
||||
space: DeviceGraphicsContextSettings.shared.colorSpace,
|
||||
bitmapInfo: self.context.bitmapInfo,
|
||||
provider: dataProvider,
|
||||
decode: nil,
|
||||
shouldInterpolate: true,
|
||||
intent: .defaultIntent
|
||||
) {
|
||||
return UIImage(cgImage: image, scale: self.scale, orientation: .up)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CGSize {
|
||||
func fitted(_ size: CGSize) -> CGSize {
|
||||
var fittedSize = self
|
||||
if fittedSize.width > size.width {
|
||||
fittedSize = CGSize(width: size.width, height: floor((fittedSize.height * size.width / max(fittedSize.width, 1.0))))
|
||||
}
|
||||
if fittedSize.height > size.height {
|
||||
fittedSize = CGSize(width: floor((fittedSize.width * size.height / max(fittedSize.height, 1.0))), height: size.height)
|
||||
}
|
||||
return fittedSize
|
||||
}
|
||||
}
|
||||
|
||||
private func convertLottieImage(data: Data) -> UIImage? {
|
||||
let decompressedData = TGGUnzipData(data, 512 * 1024) ?? data
|
||||
guard let animation = LottieInstance(data: decompressedData, cacheKey: "") else {
|
||||
return nil
|
||||
}
|
||||
let size = animation.dimensions.fitted(CGSize(width: 200.0, height: 200.0))
|
||||
let context = DrawingContext(size: size, scale: 1.0, opaque: false, clear: true)
|
||||
animation.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(context.scaledSize.width), height: Int32(context.scaledSize.height), bytesPerRow: Int32(context.bytesPerRow))
|
||||
return context.generateImage()
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
private struct NotificationContent {
|
||||
var title: String?
|
||||
@@ -40,31 +287,38 @@ private struct NotificationContent {
|
||||
var badge: Int?
|
||||
var category: String?
|
||||
var userInfo: [AnyHashable: Any] = [:]
|
||||
var attachments: [UNNotificationAttachment] = []
|
||||
|
||||
func asNotificationContent() -> UNNotificationContent {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
content.title = self.title ?? ""
|
||||
content.subtitle = self.subtitle ?? ""
|
||||
content.body = self.body ?? ""
|
||||
|
||||
if let title = self.title {
|
||||
content.title = title
|
||||
}
|
||||
if let subtitle = self.subtitle {
|
||||
content.subtitle = subtitle
|
||||
}
|
||||
if let body = self.body {
|
||||
content.body = body
|
||||
}
|
||||
if let threadId = self.threadId {
|
||||
content.threadIdentifier = threadId
|
||||
}
|
||||
|
||||
if let sound = self.sound {
|
||||
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: sound))
|
||||
}
|
||||
|
||||
if let badge = self.badge {
|
||||
content.badge = badge as NSNumber
|
||||
}
|
||||
|
||||
if let category = self.category {
|
||||
content.categoryIdentifier = category
|
||||
}
|
||||
|
||||
content.userInfo = self.userInfo
|
||||
if !self.userInfo.isEmpty {
|
||||
content.userInfo = self.userInfo
|
||||
}
|
||||
if !self.attachments.isEmpty {
|
||||
content.attachments = self.attachments
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
@@ -175,146 +429,441 @@ private final class NotificationServiceHandler {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
guard let aps = payloadJson["aps"] as? [String: Any] else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
var content: NotificationContent = NotificationContent()
|
||||
if let alert = aps["alert"] as? [String: Any] {
|
||||
content.title = alert["title"] as? String
|
||||
content.subtitle = alert["subtitle"] as? String
|
||||
content.body = alert["body"] as? String
|
||||
} else if let alert = aps["alert"] as? String {
|
||||
content.body = alert
|
||||
} else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
var peerId: PeerId?
|
||||
var messageId: MessageId.Id?
|
||||
var mediaAttachment: Media?
|
||||
|
||||
if let messageIdString = payloadJson["msg_id"] as? String {
|
||||
content.userInfo["msg_id"] = messageIdString
|
||||
messageId = Int32(messageIdString)
|
||||
}
|
||||
|
||||
if let fromIdString = payloadJson["from_id"] as? String {
|
||||
content.userInfo["from_id"] = fromIdString
|
||||
if let userIdValue = Int64(fromIdString) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userIdValue))
|
||||
}
|
||||
} else if let chatIdString = payloadJson["chat_id"] as? String {
|
||||
content.userInfo["chat_id"] = chatIdString
|
||||
if let chatIdValue = Int64(chatIdString) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatIdValue))
|
||||
}
|
||||
} else if let channelIdString = payloadJson["channel_id"] as? String {
|
||||
content.userInfo["channel_id"] = channelIdString
|
||||
if let channelIdValue = Int64(channelIdString) {
|
||||
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelIdValue))
|
||||
}
|
||||
}
|
||||
|
||||
if let silentString = payloadJson["silent"] as? String {
|
||||
if let silentValue = Int(silentString), silentValue != 0 {
|
||||
if let title = content.title {
|
||||
content.title = "\(title) 🔕"
|
||||
enum Action {
|
||||
case logout
|
||||
case poll(peerId: PeerId, content: NotificationContent)
|
||||
case deleteMessage([MessageId])
|
||||
case readMessage(MessageId)
|
||||
}
|
||||
|
||||
var action: Action?
|
||||
|
||||
if let locKey = payloadJson["loc-key"] as? String {
|
||||
switch locKey {
|
||||
case "SESSION_REVOKE":
|
||||
action = .logout
|
||||
case "MESSAGE_MUTED":
|
||||
if let peerId = peerId {
|
||||
action = .poll(peerId: peerId, content: NotificationContent())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let threadId = aps["thread-id"] as? String {
|
||||
content.threadId = threadId
|
||||
}
|
||||
|
||||
if let sound = aps["sound"] as? String {
|
||||
content.sound = sound
|
||||
}
|
||||
|
||||
if let category = aps["category"] as? String {
|
||||
content.category = category
|
||||
|
||||
let _ = messageId
|
||||
|
||||
/*if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
|
||||
userInfo[@"peerId"] = @(peerId);
|
||||
userInfo[@"messageId.namespace"] = @(0);
|
||||
userInfo[@"messageId.id"] = @(messageId);
|
||||
|
||||
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
|
||||
|
||||
if (isExpandableMedia) {
|
||||
if ([categoryString isEqualToString:@"r"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
|
||||
} else if ([categoryString isEqualToString:@"m"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
|
||||
case "MESSAGE_DELETED":
|
||||
if let peerId = peerId {
|
||||
if let messageId = messageId {
|
||||
action = .deleteMessage([MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId)])
|
||||
} else if let messageIds = payloadJson["messages"] as? String {
|
||||
var messagesDeleted: [MessageId] = []
|
||||
for messageId in messageIds.split(separator: ",") {
|
||||
if let messageIdValue = Int32(messageId) {
|
||||
messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue))
|
||||
}
|
||||
}
|
||||
action = .deleteMessage(messagesDeleted)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/*if (accountInfos.accounts.count > 1) {
|
||||
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
|
||||
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
|
||||
}
|
||||
}*/
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
if let stateManager = strongSelf.stateManager, let peerId = peerId {
|
||||
let pollCompletion: () -> Void = {
|
||||
queue.async {
|
||||
guard let strongSelf = self, let stateManager = strongSelf.stateManager else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (renderedTotalUnreadCount(
|
||||
accountManager: strongSelf.accountManager,
|
||||
postbox: stateManager.postbox
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
content.badge = Int(value.0)
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
completed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
stateManager.network.shouldKeepConnection.set(.single(true))
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
strongSelf.pollDisposable.set(standalonePollChannelOnce(
|
||||
postbox: stateManager.postbox,
|
||||
network: stateManager.network,
|
||||
peerId: peerId,
|
||||
stateManager: stateManager
|
||||
).start(completed: {
|
||||
pollCompletion()
|
||||
}))
|
||||
} else {
|
||||
enum ControlError {
|
||||
case restart
|
||||
}
|
||||
let signal = stateManager.standalonePollDifference()
|
||||
|> castError(ControlError.self)
|
||||
|> mapToSignal { result -> Signal<Never, ControlError> in
|
||||
if result {
|
||||
return .complete()
|
||||
} else {
|
||||
return .fail(.restart)
|
||||
case "READ_HISTORY":
|
||||
if let peerId = peerId {
|
||||
if let messageIdString = payloadJson["max_id"] as? String {
|
||||
if let maxId = Int32(messageIdString) {
|
||||
action = .readMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: maxId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|> restartIfError
|
||||
|
||||
strongSelf.pollDisposable.set(signal.start(completed: {
|
||||
pollCompletion()
|
||||
}))
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if let aps = payloadJson["aps"] as? [String: Any], let peerId = peerId {
|
||||
var content: NotificationContent = NotificationContent()
|
||||
if let alert = aps["alert"] as? [String: Any] {
|
||||
content.title = alert["title"] as? String
|
||||
content.subtitle = alert["subtitle"] as? String
|
||||
content.body = alert["body"] as? String
|
||||
} else if let alert = aps["alert"] as? String {
|
||||
content.body = alert
|
||||
} else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
if let messageId = messageId {
|
||||
content.userInfo["msg_id"] = "\(messageId)"
|
||||
}
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
content.userInfo["from_id"] = "\(peerId.id._internalGetInt64Value())"
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
content.userInfo["chat_id"] = "\(peerId.id._internalGetInt64Value())"
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
content.userInfo["channel_id"] = "\(peerId.id._internalGetInt64Value())"
|
||||
}
|
||||
|
||||
content.userInfo["peerId"] = "\(peerId.toInt64())"
|
||||
content.userInfo["accountId"] = "\(record.0.int64)"
|
||||
|
||||
if let silentString = payloadJson["silent"] as? String {
|
||||
if let silentValue = Int(silentString), silentValue != 0 {
|
||||
if let title = content.title {
|
||||
content.title = "\(title) 🔕"
|
||||
}
|
||||
}
|
||||
}
|
||||
if var attachmentDataString = payloadJson["attachb64"] as? String {
|
||||
attachmentDataString = attachmentDataString.replacingOccurrences(of: "-", with: "+")
|
||||
attachmentDataString = attachmentDataString.replacingOccurrences(of: "_", with: "/")
|
||||
while attachmentDataString.count % 4 != 0 {
|
||||
attachmentDataString.append("=")
|
||||
}
|
||||
if let attachmentData = Data(base64Encoded: attachmentDataString) {
|
||||
mediaAttachment = _internal_parseMediaAttachment(data: attachmentData)
|
||||
}
|
||||
}
|
||||
|
||||
if let threadId = aps["thread-id"] as? String {
|
||||
content.threadId = threadId
|
||||
}
|
||||
|
||||
if let sound = aps["sound"] as? String {
|
||||
content.sound = sound
|
||||
}
|
||||
|
||||
if let category = aps["category"] as? String {
|
||||
content.category = category
|
||||
|
||||
let _ = messageId
|
||||
|
||||
/*if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
|
||||
userInfo[@"peerId"] = @(peerId);
|
||||
userInfo[@"messageId.namespace"] = @(0);
|
||||
userInfo[@"messageId.id"] = @(messageId);
|
||||
|
||||
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
|
||||
|
||||
if (isExpandableMedia) {
|
||||
if ([categoryString isEqualToString:@"r"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
|
||||
} else if ([categoryString isEqualToString:@"m"]) {
|
||||
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/*if (accountInfos.accounts.count > 1) {
|
||||
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
|
||||
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
|
||||
}
|
||||
}*/
|
||||
|
||||
action = .poll(peerId: peerId, content: content)
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
}
|
||||
}
|
||||
|
||||
if let action = action {
|
||||
switch action {
|
||||
case .logout:
|
||||
completed()
|
||||
case .poll(let peerId, var content):
|
||||
if let stateManager = strongSelf.stateManager {
|
||||
let pollCompletion: () -> Void = {
|
||||
queue.async {
|
||||
guard let strongSelf = self, let stateManager = strongSelf.stateManager else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
var fetchMediaSignal: Signal<Data?, NoError> = .single(nil)
|
||||
if let mediaAttachment = mediaAttachment {
|
||||
var fetchResource: TelegramMultipartFetchableResource?
|
||||
if let image = mediaAttachment as? TelegramMediaImage, let representation = largestImageRepresentation(image.representations), let resource = representation.resource as? TelegramMultipartFetchableResource {
|
||||
fetchResource = resource
|
||||
} else if let file = mediaAttachment as? TelegramMediaFile {
|
||||
if file.isSticker {
|
||||
fetchResource = file.resource as? TelegramMultipartFetchableResource
|
||||
} else if file.isVideo {
|
||||
fetchResource = file.previewRepresentations.first?.resource as? TelegramMultipartFetchableResource
|
||||
}
|
||||
}
|
||||
|
||||
if let resource = fetchResource {
|
||||
if let _ = strongSelf.stateManager?.postbox.mediaBox.completedResourcePath(resource) {
|
||||
} else {
|
||||
let intervals: Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int(Int32.max), MediaBoxFetchPriority.maximum)])
|
||||
fetchMediaSignal = Signal { subscriber in
|
||||
let collectedData = Atomic<Data>(value: Data())
|
||||
return standaloneMultipartFetch(
|
||||
postbox: stateManager.postbox,
|
||||
network: stateManager.network,
|
||||
resource: resource,
|
||||
datacenterId: resource.datacenterId,
|
||||
size: nil,
|
||||
intervals: intervals,
|
||||
parameters: MediaResourceFetchParameters(
|
||||
tag: nil,
|
||||
info: resourceFetchInfo(resource: resource),
|
||||
isRandomAccessAllowed: true
|
||||
),
|
||||
encryptionKey: nil,
|
||||
decryptedSize: nil,
|
||||
continueInBackground: false,
|
||||
useMainConnection: true
|
||||
).start(next: { result in
|
||||
switch result {
|
||||
case let .dataPart(_, data, _, _):
|
||||
let _ = collectedData.modify { current in
|
||||
var current = current
|
||||
current.append(data)
|
||||
return current
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}, error: { _ in
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
}, completed: {
|
||||
subscriber.putNext(collectedData.with({ $0 }))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (fetchMediaSignal
|
||||
|> timeout(10.0, queue: queue, alternate: .single(nil))
|
||||
|> deliverOn(queue)).start(next: { mediaData in
|
||||
guard let strongSelf = self, let stateManager = strongSelf.stateManager else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (renderedTotalUnreadCount(
|
||||
accountManager: strongSelf.accountManager,
|
||||
postbox: stateManager.postbox
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
guard let strongSelf = self, let stateManager = strongSelf.stateManager else {
|
||||
completed()
|
||||
return
|
||||
}
|
||||
|
||||
content.badge = Int(value.0)
|
||||
|
||||
if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource {
|
||||
if let mediaData = mediaData {
|
||||
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
|
||||
}
|
||||
if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource, pathExtension: "jpg") {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
} else if let file = mediaAttachment as? TelegramMediaFile {
|
||||
if file.isStaticSticker {
|
||||
let resource = file.resource
|
||||
|
||||
if let mediaData = mediaData {
|
||||
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
|
||||
}
|
||||
if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource) {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: storedPath)), let image = WebP.convert(fromWebP: data) {
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "image.png")
|
||||
let _ = try? image.pngData()?.write(to: URL(fileURLWithPath: tempFile.path))
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: tempFile.path), options: nil) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if file.isAnimatedSticker {
|
||||
let resource = file.resource
|
||||
|
||||
if let mediaData = mediaData {
|
||||
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
|
||||
}
|
||||
if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource) {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: storedPath)), let image = convertLottieImage(data: data) {
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "image.png")
|
||||
let _ = try? image.pngData()?.write(to: URL(fileURLWithPath: tempFile.path))
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: tempFile.path), options: nil) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if file.isVideo, let representation = file.previewRepresentations.first {
|
||||
let resource = representation.resource
|
||||
|
||||
if let mediaData = mediaData {
|
||||
stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true)
|
||||
}
|
||||
if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource, pathExtension: "jpg") {
|
||||
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
completed()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let pollSignal: Signal<Never, NoError>
|
||||
|
||||
stateManager.network.shouldKeepConnection.set(.single(true))
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
pollSignal = standalonePollChannelOnce(
|
||||
postbox: stateManager.postbox,
|
||||
network: stateManager.network,
|
||||
peerId: peerId,
|
||||
stateManager: stateManager
|
||||
)
|
||||
} else {
|
||||
enum ControlError {
|
||||
case restart
|
||||
}
|
||||
let signal = stateManager.standalonePollDifference()
|
||||
|> castError(ControlError.self)
|
||||
|> mapToSignal { result -> Signal<Never, ControlError> in
|
||||
if result {
|
||||
return .complete()
|
||||
} else {
|
||||
return .fail(.restart)
|
||||
}
|
||||
}
|
||||
|> restartIfError
|
||||
|
||||
pollSignal = signal
|
||||
}
|
||||
|
||||
strongSelf.pollDisposable.set(pollSignal.start(completed: {
|
||||
pollCompletion()
|
||||
}))
|
||||
} else {
|
||||
completed()
|
||||
}
|
||||
case let .deleteMessage(ids):
|
||||
let mediaBox = stateManager.postbox.mediaBox
|
||||
let _ = (stateManager.postbox.transaction { transaction -> Void in
|
||||
_internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, deleteMedia: true)
|
||||
}
|
||||
|> deliverOn(strongSelf.queue)).start(completed: {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||
var removeIdentifiers: [String] = []
|
||||
for notification in notifications {
|
||||
if let peerIdString = notification.request.content.userInfo["peerId"] as? String, let peerIdValue = Int64(peerIdString), let messageIdString = notification.request.content.userInfo["msg_id"] as? String, let messageIdValue = Int32(messageIdString) {
|
||||
for id in ids {
|
||||
if PeerId(peerIdValue) == id.peerId && messageIdValue == id.id {
|
||||
removeIdentifiers.append(notification.request.identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let completeRemoval: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (renderedTotalUnreadCount(
|
||||
accountManager: strongSelf.accountManager,
|
||||
postbox: stateManager.postbox
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
var content = NotificationContent()
|
||||
content.badge = Int(value.0)
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
completed()
|
||||
})
|
||||
}
|
||||
|
||||
if !removeIdentifiers.isEmpty {
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removeIdentifiers)
|
||||
queue.after(1.0, {
|
||||
completeRemoval()
|
||||
})
|
||||
} else {
|
||||
completeRemoval()
|
||||
}
|
||||
})
|
||||
})
|
||||
case let .readMessage(id):
|
||||
let _ = (stateManager.postbox.transaction { transaction -> Void in
|
||||
transaction.applyIncomingReadMaxId(id)
|
||||
}
|
||||
|> deliverOn(strongSelf.queue)).start(completed: {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in
|
||||
var removeIdentifiers: [String] = []
|
||||
for notification in notifications {
|
||||
if let peerIdString = notification.request.content.userInfo["peerId"] as? String, let peerIdValue = Int64(peerIdString), let messageIdString = notification.request.content.userInfo["msg_id"] as? String, let messageIdValue = Int32(messageIdString) {
|
||||
if PeerId(peerIdValue) == id.peerId && messageIdValue <= id.id {
|
||||
removeIdentifiers.append(notification.request.identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let completeRemoval: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (renderedTotalUnreadCount(
|
||||
accountManager: strongSelf.accountManager,
|
||||
postbox: stateManager.postbox
|
||||
)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { value in
|
||||
var content = NotificationContent()
|
||||
content.badge = Int(value.0)
|
||||
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
completed()
|
||||
})
|
||||
}
|
||||
|
||||
if !removeIdentifiers.isEmpty {
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removeIdentifiers)
|
||||
queue.after(1.0, {
|
||||
completeRemoval()
|
||||
})
|
||||
} else {
|
||||
completeRemoval()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let content = NotificationContent()
|
||||
updateCurrentContent(content.asNotificationContent())
|
||||
|
||||
completed()
|
||||
}
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user