Swiftgram/Telegram-iOS/NotificationManager.swift
2018-11-14 23:03:33 +04:00

798 lines
40 KiB
Swift

import Foundation
import Postbox
import TelegramCore
import SwiftSignalKit
import UserNotifications
import TelegramUI
private func notificationKey(_ requestId: NotificationManagedNotificationRequestId) -> String {
switch requestId {
case let .messageId(id):
return "m\(id.peerId.toInt64()):\(id.namespace):\(id.id)"
case let .globallyUniqueId(id, _):
return "m\(id)"
}
}
private let messageNotificationKeyExpr = try? NSRegularExpression(pattern: "m([-\\d]+):([-\\d]+):([-\\d]+)_?", options: [])
enum NotificationManagedNotificationRequestId: Hashable {
case messageId(MessageId)
case globallyUniqueId(Int64, PeerId?)
init?(string: String) {
if string.hasPrefix("m") {
let matches = messageNotificationKeyExpr!.matches(in: string, options: [], range: NSRange(location: 0, length: string.count))
if let match = matches.first {
let nsString = string as NSString
let peerIdString = nsString.substring(with: match.range(at: 1))
let namespaceString = nsString.substring(with: match.range(at: 2))
let idString = nsString.substring(with: match.range(at: 3))
guard let peerId = Int64(peerIdString) else {
return nil
}
guard let namespace = Int32(namespaceString) else {
return nil
}
guard let id = Int32(idString) else {
return nil
}
self = .messageId(MessageId(peerId: PeerId(peerId), namespace: namespace, id: id))
return
}
}
return nil
}
var hashValue: Int {
switch self {
case let .messageId(messageId):
return messageId.id.hashValue
case let .globallyUniqueId(id, _):
return id.hashValue
}
}
static func ==(lhs: NotificationManagedNotificationRequestId, rhs: NotificationManagedNotificationRequestId) -> Bool {
switch lhs {
case let .messageId(id):
if case .messageId(id) = rhs {
return true
} else {
return false
}
case let .globallyUniqueId(id, peerId):
if case .globallyUniqueId(id, peerId) = rhs {
return true
} else {
return false
}
}
}
}
private func processedSoundName(_ name: String) -> String {
if name.hasSuffix("m4a") {
return name
} else {
return "\(name).m4a"
}
}
final class NotificationManager {
private var processedMessages = Set<MessageId>()
var account: Account? {
didSet {
assert(Queue.mainQueue().isCurrent())
if let account = self.account {
self.notificationMessagesDisposable.set((account.stateManager.notificationMessages
|> deliverOn(Queue.mainQueue())).start(next: { [weak self] messages in
guard let strongSelf = self else {
return
}
var list: [([Message], PeerMessageSound, Bool, Bool)] = []
for (messageGroup, _, notify) in messages {
list.append((messageGroup, .default, !strongSelf.isApplicationLocked, false))
}
//self?.processNotificationMessages(list, isLocked: strongSelf.isApplicationLocked)
}))
} else {
self.notificationMessagesDisposable.set(nil)
}
}
}
private let notificationCallStateDisposable = MetaDisposable()
var notificationCall: PresentationCall? {
didSet {
if self.notificationCall?.internalId != oldValue?.internalId {
if let notificationCall = self.notificationCall {
let peer = notificationCall.peer
let internalId = notificationCall.internalId
let isIntegratedWithCallKit = notificationCall.isIntegratedWithCallKit
self.notificationCallStateDisposable.set((notificationCall.state
|> map { state -> (Peer?, CallSessionInternalId)? in
if isIntegratedWithCallKit {
return nil
}
if case .ringing = state {
return (peer, internalId)
} else {
return nil
}
}
|> distinctUntilChanged(isEqual: { $0?.1 == $1?.1 })).start(next: { [weak self] peerAndInternalId in
self?.updateNotificationCall(call: peerAndInternalId)
}))
} else {
self.notificationCallStateDisposable.set(nil)
self.updateNotificationCall(call: nil)
}
}
}
}
private let notificationMessagesDisposable = MetaDisposable()
private var notificationRequests: [NotificationManagedNotificationRequestId: Double] = [:]
private var processedRequestIds = Set<NotificationManagedNotificationRequestId>()
var isApplicationInForeground: Bool = false
var isApplicationLocked: Bool = false
deinit {
self.notificationMessagesDisposable.dispose()
}
func enqueueRemoteNotification(title: String, text: String, apnsSound: String?, requestId: NotificationManagedNotificationRequestId, strings: PresentationStrings, accessChallengeData: PostboxAccessChallengeData) {
if notificationRequests[requestId] == nil && !processedRequestIds.contains(requestId) {
var isLocked = false
if isAccessLocked(data: accessChallengeData, at: Int32(CFAbsoluteTimeGetCurrent())) {
isLocked = true
}
var userInfo: [AnyHashable: Any]?
let category: String
let delay: Bool
var threadIdentifier: String?
switch requestId {
case let .messageId(messageId):
if messageId.namespace == Namespaces.Message.Local {
delay = false
} else {
delay = true
}
userInfo = ["peerId": messageId.peerId.toInt64()]
threadIdentifier = "peer_\(messageId.peerId.toInt64())"
if messageId.peerId.namespace == Namespaces.Peer.CloudUser || messageId.peerId.namespace == Namespaces.Peer.CloudGroup {
category = "withReply"
} else {
category = "withMute"
}
case let .globallyUniqueId(_, peerId):
delay = false
category = "secret"
if let peerId = peerId {
userInfo = ["peerId": peerId.toInt64()]
threadIdentifier = "peer_\(peerId.toInt64())"
}
}
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
if isLocked {
content.body = strings.LOCKED_MESSAGE("").0
} else {
if title.isEmpty {
content.body = text
} else {
content.body = "\(title): \(text)"
}
}
if let apnsSound = apnsSound {
if apnsSound == "0" {
content.sound = nil
} else {
content.sound = UNNotificationSound(named: processedSoundName(apnsSound))
}
} else {
content.sound = UNNotificationSound(named: "0.m4a")
}
if let threadIdentifier = threadIdentifier {
content.threadIdentifier = threadIdentifier
}
content.categoryIdentifier = category
if let userInfo = userInfo {
content.userInfo = userInfo
}
let request = UNNotificationRequest(identifier: notificationKey(requestId), content: content, trigger: delay ? UNTimeIntervalNotificationTrigger(timeInterval: 25.0, repeats: false) : nil)
let center = UNUserNotificationCenter.current()
Logger.shared.log("NotificationManager", "adding \(requestId), delay: \(delay)")
center.add(request, withCompletionHandler: { error in
if let error = error {
Logger.shared.log("NotificationManager", "error adding \(requestId), delay: \(delay), error: \(String(describing: error))")
}
})
if delay {
notificationRequests[requestId] = CFAbsoluteTimeGetCurrent() + 25.0
}
} else {
let notification = UILocalNotification()
if isLocked {
notification.alertBody = strings.LOCKED_MESSAGE("").0
} else {
if #available(iOS 8.2, *) {
notification.alertTitle = title
notification.alertBody = text
} else {
if !title.isEmpty {
notification.alertBody = title + ": " + text
} else {
notification.alertBody = text
}
}
}
notification.category = category
var updatedUserInfo = userInfo ?? [:]
updatedUserInfo["id"] = notificationKey(requestId)
notification.userInfo = updatedUserInfo
if delay {
notification.fireDate = Date(timeIntervalSinceNow: 25.0)
}
if let apnsSound = apnsSound {
if apnsSound == "0" {
notification.soundName = nil
} else {
notification.soundName = processedSoundName(apnsSound)
}
} else {
notification.soundName = "0.m4a"
}
UIApplication.shared.scheduleLocalNotification(notification)
if delay {
notificationRequests[requestId] = CFAbsoluteTimeGetCurrent() + 25.0
}
}
}
}
func commitRemoteNotification(originalRequestId: NotificationManagedNotificationRequestId?, messageIds: [MessageId]) -> Signal<Void, NoError> {
if let account = self.account {
return account.postbox.transaction { transaction -> ([(MessageId, [Message], Bool, PeerMessageSound, Bool)], Bool) in
var isLocked = false
if isAccessLocked(data: transaction.getAccessChallengeData(), at: Int32(CFAbsoluteTimeGetCurrent())) {
isLocked = true
}
var results: [(MessageId, [Message], Bool, PeerMessageSound, Bool)] = []
var updatedMessageIds = messageIds
if let originalRequestId = originalRequestId {
switch originalRequestId {
case let .messageId(id):
if !updatedMessageIds.contains(id) {
updatedMessageIds.append(id)
}
case .globallyUniqueId:
break
}
}
for id in updatedMessageIds {
let (messages, notify, sound, displayContents) = messagesForNotification(transaction: transaction, id: id, alwaysReturnMessage: true)
if results.contains(where: { result in
return result.1.contains(where: { message in
return messages.contains(where: {
message.id == $0.id
})
})
}) {
continue
}
results.append((id, messages, notify, sound, displayContents))
}
return (results, isLocked)
}
|> deliverOnMainQueue
|> beforeNext {
[weak self] results, isLocked in
if let strongSelf = self {
let delayUntilTimestamp: Int32 = strongSelf.account?.stateManager.getDelayNotificatonsUntil() ?? 0
for (id, messages, notify, sound, displayContents) in results {
let requestId: NotificationManagedNotificationRequestId = .messageId(id)
if let message = messages.first, message.id.peerId.namespace != Namespaces.Peer.SecretChat, !strongSelf.processedRequestIds.contains(requestId) {
let notificationRequestTimeout = strongSelf.notificationRequests[requestId]
if notificationRequestTimeout == nil || CFAbsoluteTimeGetCurrent() < notificationRequestTimeout! {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removePendingNotificationRequests(withIdentifiers: [notificationKey(requestId)])
} else {
let key = notificationKey(requestId)
if let notifications = UIApplication.shared.scheduledLocalNotifications {
for notification in notifications {
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
if id == key {
UIApplication.shared.cancelLocalNotification(notification)
break
}
}
}
}
}
if !strongSelf.processedRequestIds.contains(requestId) {
strongSelf.processedRequestIds.insert(requestId)
if notify {
var delayMessage = false
if message.timestamp <= delayUntilTimestamp && message.id.peerId.namespace != Namespaces.Peer.SecretChat {
delayMessage = true
}
strongSelf.processNotificationMessages([(messages, sound, displayContents, delayMessage)], isLocked: isLocked)
}
}
}
}
}
}
} |> map { _ in
return Void()
}
} else {
return .complete()
}
}
private func processNotificationMessages(_ messageList: [([Message], PeerMessageSound, Bool, Bool)], isLocked: Bool) {
guard let account = self.account else {
Logger.shared.log("NotificationManager", "account missing")
return
}
let strings = (account.telegramApplicationContext.currentPresentationData.with { $0 }).strings
for (messages, sound, initialDisplayContents, delayMessage) in messageList {
for message in messages {
self.processedMessages.insert(message.id)
}
guard let firstMessage = messages.first else {
continue
}
let displayContents = initialDisplayContents && !isLocked
let requestId: NotificationManagedNotificationRequestId
if let globallyUniqueId = firstMessage.globallyUniqueId, firstMessage.id.peerId.namespace == Namespaces.Peer.SecretChat {
requestId = .globallyUniqueId(globallyUniqueId, firstMessage.id.peerId)
} else {
requestId = .messageId(firstMessage.id)
}
let notificationRequestTimeout = notificationRequests[requestId]
if notificationRequestTimeout == nil || CFAbsoluteTimeGetCurrent() < notificationRequestTimeout! {
if self.isApplicationInForeground {
processedRequestIds.insert(requestId)
if notificationRequestTimeout != nil {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notificationKey(requestId)])
} else {
let key = notificationKey(requestId)
if let notifications = UIApplication.shared.scheduledLocalNotifications {
for notification in notifications {
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
if id == key {
UIApplication.shared.cancelLocalNotification(notification)
break
}
}
}
}
}
}
} else {
var title: String?
var body: String
var mediaRepresentations: [TelegramMediaImageRepresentation]?
var mediaInfo: [String: Any]? = nil
body = firstMessage.text
if let peer = messageMainPeer(firstMessage) {
var displayAuthor = true
if let channel = peer as? TelegramChannel {
switch channel.info {
case .group:
displayAuthor = true
case .broadcast:
displayAuthor = false
}
} else if let _ = peer as? TelegramUser {
displayAuthor = false
}
if let author = firstMessage.author, displayAuthor {
title = author.compactDisplayTitle + "@" + peer.displayTitle
} else {
title = peer.displayTitle
}
if messages.count > 1 {
if messages[0].forwardInfo != nil {
if let author = firstMessage.author, displayAuthor {
title = nil
body = strings.CHAT_MESSAGE_FWDS(author.compactDisplayTitle, peer.displayTitle, "\(messages.count)").0
} else {
title = nil
body = strings.MESSAGE_FWDS(peer.displayTitle, "\(messages.count)").0
}
} else if messages[0].groupingKey != nil {
var kind = messageContentKind(messages[0], strings: strings, accountPeerId: account.peerId).key
for i in 1 ..< messages.count {
let nextKind = messageContentKind(messages[i], strings: strings, accountPeerId: account.peerId)
if kind != nextKind.key {
kind = .text
break
}
}
var isChannel = false
var isGroup = false
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel {
if case .broadcast = peer.info {
isChannel = true
} else {
isGroup = true
}
} else if firstMessage.id.peerId.namespace == Namespaces.Peer.CloudGroup {
isGroup = true
}
title = nil
if isChannel {
switch kind {
case .image:
body = strings.CHANNEL_MESSAGE_PHOTOS(peer.compactDisplayTitle, "\(messages.count)").0
default:
body = strings.CHANNEL_MESSAGES(peer.compactDisplayTitle, "\(messages.count)").0
}
} else if isGroup, let author = firstMessage.author {
switch kind {
case .image:
body = strings.CHAT_MESSAGE_PHOTOS(author.compactDisplayTitle, peer.displayTitle, "\(messages.count)").0
default:
body = strings.CHAT_MESSAGES(author.compactDisplayTitle, peer.displayTitle, "\(messages.count)").0
}
} else {
switch kind {
case .image:
body = strings.MESSAGE_PHOTOS(peer.displayTitle, "\(messages.count)").0
default:
body = strings.MESSAGES(peer.displayTitle, "\(messages.count)").0
}
}
}
}
if messages.count == 1 {
let additionalPeers = firstMessage.peers
var isPin = false
for media in messages[0].media {
if let action = media as? TelegramMediaAction {
if case .pinnedMessageUpdated = action.action {
isPin = true
}
}
}
if let channel = peer as? TelegramChannel, case .broadcast = channel.info, isPin {
title = nil
}
let chatPeer = RenderedPeer(peerId: firstMessage.id.peerId, peers: additionalPeers)
let (_, _, messageText) = chatListItemStrings(strings: strings, message: firstMessage, chatPeer: chatPeer, accountPeerId: account.peerId)
body = messageText
loop: for media in firstMessage.media {
if let image = media as? TelegramMediaImage {
mediaRepresentations = image.representations
if !firstMessage.containsSecretMedia, let account = self.account, let smallest = smallestImageRepresentation(image.representations), let largest = largestImageRepresentation(image.representations) {
var imageInfo: [String: Any] = [:]
var thumbnailInfo: [String: Any] = [:]
thumbnailInfo["path"] = account.postbox.mediaBox.resourcePath(smallest.resource)
imageInfo["thumbnail"] = thumbnailInfo
var fullSizeInfo: [String: Any] = [:]
fullSizeInfo["path"] = account.postbox.mediaBox.resourcePath(largest.resource)
imageInfo["fullSize"] = fullSizeInfo
imageInfo["width"] = Int(largest.dimensions.width)
imageInfo["height"] = Int(largest.dimensions.height)
mediaInfo = ["image": imageInfo]
}
break loop
} else if let file = media as? TelegramMediaFile {
if !firstMessage.containsSecretMedia {
//mediaRepresentations = file.previewRepresentations
}
break loop
} else if let location = media as? TelegramMediaMap {
if location.liveBroadcastingTimeout != nil {
if let chatMainPeer = chatPeer.chatMainPeer {
if let user = chatMainPeer as? TelegramUser {
body = strings.MESSAGE_GEOLIVE(user.displayTitle).0
} else if let _ = chatMainPeer as? TelegramGroup, let author = firstMessage.author {
body = strings.MESSAGE_GEOLIVE(author.displayTitle).0
} else if let channel = chatMainPeer as? TelegramChannel {
switch channel.info {
case .group:
if let author = firstMessage.author {
body = strings.MESSAGE_GEOLIVE(author.displayTitle).0
}
case .broadcast:
body = strings.CHANNEL_MESSAGE_GEOLIVE(chatMainPeer.displayTitle).0
}
}
}
break loop
}
}
}
}
} else {
body = strings.ENCRYPTED_MESSAGE("").0
}
if isLocked {
title = nil
}
if !displayContents {
body = strings.ENCRYPTED_MESSAGE("").0
}
var userInfo: [AnyHashable: Any] = ["peerId": firstMessage.id.peerId.toInt64()]
userInfo["messageId.namespace"] = firstMessage.id.namespace
userInfo["messageId.id"] = firstMessage.id.id
let category: String
if displayContents, let peer = firstMessage.peers[firstMessage.id.peerId] {
switch peer {
case _ as TelegramUser:
if let mediaInfo = mediaInfo {
category = "withReplyMedia"
userInfo["mediaInfo"] = mediaInfo
} else {
category = "withReply"
}
case _ as TelegramGroup:
if let mediaInfo = mediaInfo {
category = "withReplyMedia"
userInfo["mediaInfo"] = mediaInfo
} else {
category = "withReply"
}
case let channel as TelegramChannel:
if case .group = channel.info {
if let mediaInfo = mediaInfo {
category = "withReplyMedia"
userInfo["mediaInfo"] = mediaInfo
} else {
category = "withReply"
}
} else {
if let mediaInfo = mediaInfo {
category = "withMuteMedia"
userInfo["mediaInfo"] = mediaInfo
} else {
category = "withMute"
}
}
default:
category = "withMute"
}
} else {
category = "withMute"
}
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notificationKey(requestId)])
let content = UNMutableNotificationContent()
if let title = title {
content.title = title
}
content.body = body
switch sound {
case .none:
content.sound = nil
case .default:
content.sound = UNNotificationSound(named: "0.m4a")
default:
content.sound = UNNotificationSound(named: fileNameForNotificationSound(sound, defaultSound: nil) + ".m4a")
}
content.categoryIdentifier = category
content.userInfo = userInfo
content.threadIdentifier = "peer_\(firstMessage.id.peerId.toInt64())"
if mediaInfo != nil, let mediaRepresentations = mediaRepresentations {
if let account = self.account, let smallest = smallestImageRepresentation(mediaRepresentations) {
/*if let path = account.postbox.mediaBox.completedResourcePath(smallest.resource) {
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let tempPath = NSTemporaryDirectory() + "/\(randomId).jpg"
if let _ = try? FileManager.default.copyItem(atPath: path, toPath: tempPath) {
if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: tempPath)) {
content.attachments = [attachment]
}
}
}*/
}
}
var trigger: UNTimeIntervalNotificationTrigger?
if delayMessage {
trigger = UNTimeIntervalNotificationTrigger(timeInterval: 25.0, repeats: false)
}
let request = UNNotificationRequest(identifier: notificationKey(requestId) + "_", content: content, trigger: trigger)
let center = UNUserNotificationCenter.current()
Logger.shared.log("NotificationManager", "adding \(requestId), delay: \(delayMessage)")
center.add(request, withCompletionHandler: { error in
if let error = error {
Logger.shared.log("NotificationManager", "error adding \(requestId), delay: \(delayMessage), error: \(String(describing: error))")
}
})
processedRequestIds.insert(requestId)
} else {
let key = notificationKey(requestId)
if let notifications = UIApplication.shared.scheduledLocalNotifications {
for notification in notifications {
if let userInfo = notification.userInfo, let id = userInfo["id"] as? String {
if id == key {
UIApplication.shared.cancelLocalNotification(notification)
break
}
}
}
}
let notification = UILocalNotification()
if #available(iOS 10.0, *) {
notification.alertTitle = title
notification.alertBody = body
} else {
if let title = title {
notification.alertBody = title + ": " + body
} else {
notification.alertBody = body
}
}
notification.category = category
var updatedUserInfo = userInfo
updatedUserInfo["id"] = notificationKey(requestId) + "_"
notification.userInfo = userInfo
switch sound {
case .none:
notification.soundName = nil
case .default:
notification.soundName = "0.m4a"
default:
notification.soundName = fileNameForNotificationSound(sound, defaultSound: nil) + ".m4a"
}
UIApplication.shared.presentLocalNotificationNow(notification)
}
}
} else {
Logger.shared.log("NotificationManager", "not showing message because of timeout")
}
self.notificationRequests.removeValue(forKey: requestId)
}
}
private var currentNotificationCall: (peer: Peer?, internalId: CallSessionInternalId)?
private func updateNotificationCall(call: (peer: Peer?, internalId: CallSessionInternalId)?) {
if let previousCall = currentNotificationCall {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeDeliveredNotifications(withIdentifiers: ["call_\(previousCall.internalId)"])
} else {
if let notifications = UIApplication.shared.scheduledLocalNotifications {
for notification in notifications {
if let userInfo = notification.userInfo, let callId = userInfo["callId"] as? String, callId == String(describing: previousCall.internalId) {
UIApplication.shared.cancelLocalNotification(notification)
}
}
}
}
}
self.currentNotificationCall = call
guard let account = self.account else {
return
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if let notificationCall = call {
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.body = presentationData.strings.PHONE_CALL_REQUEST(notificationCall.peer?.displayTitle ?? "").0
content.sound = UNNotificationSound(named: "0.m4a")
content.categoryIdentifier = "incomingCall"
content.userInfo = [:]
let request = UNNotificationRequest(identifier: "call_\(notificationCall.internalId)", content: content, trigger: nil)
let center = UNUserNotificationCenter.current()
Logger.shared.log("NotificationManager", "adding call \(notificationCall.internalId)")
center.add(request, withCompletionHandler: { error in
if let error = error {
Logger.shared.log("NotificationManager", "error adding call \(notificationCall.internalId), error: \(String(describing: error))")
}
})
} else {
let notification = UILocalNotification()
notification.alertBody = presentationData.strings.PHONE_CALL_REQUEST(notificationCall.peer?.displayTitle ?? "").0
notification.category = "incomingCall"
notification.userInfo = ["callId": String(describing: notificationCall.internalId)]
notification.soundName = "0.m4a"
UIApplication.shared.presentLocalNotificationNow(notification)
}
}
}
func presentWatchContinuityNotification(messageId: MessageId) {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.removeDeliveredNotifications(withIdentifiers: ["watch"])
} else {
if let notifications = UIApplication.shared.scheduledLocalNotifications {
for notification in notifications {
if let category = notification.category, category == "watch" {
UIApplication.shared.cancelLocalNotification(notification)
}
}
}
}
guard let account = self.account else {
return
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
var userInfo: [AnyHashable : Any] = [:]
userInfo["peerId"] = messageId.peerId.toInt64()
userInfo["messageId.namespace"] = messageId.namespace
userInfo["messageId.id"] = messageId.id
if #available(iOS 10.0, *) {
let content = UNMutableNotificationContent()
content.body = presentationData.strings.WatchRemote_NotificationText
content.sound = UNNotificationSound(named: "0.m4a")
content.categoryIdentifier = "watch"
content.userInfo = userInfo
let request = UNNotificationRequest(identifier: "watch", content: content, trigger: nil)
let center = UNUserNotificationCenter.current()
Logger.shared.log("NotificationManager", "adding watch continuity")
center.add(request, withCompletionHandler: { error in
if let error = error {
Logger.shared.log("NotificationManager", "error adding watch continuity, error: \(String(describing: error))")
}
})
} else {
let notification = UILocalNotification()
notification.alertBody = presentationData.strings.WatchRemote_NotificationText
notification.category = "watch"
notification.userInfo = userInfo
notification.soundName = "0.m4a"
UIApplication.shared.presentLocalNotificationNow(notification)
}
}
}