Merge branch 'beta' into experimental-2

Support more notification actions
This commit is contained in:
Ali
2021-09-17 21:06:58 +03:00
25 changed files with 2075 additions and 177 deletions

View File

@@ -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()
}
}))