Antidote/Antidote/NotificationCoordinator.swift

399 lines
14 KiB
Swift

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import Foundation
private enum NotificationType {
case newMessage(OCTMessageAbstract)
case friendRequest(OCTFriendRequest)
}
private struct Constants {
static let NotificationVisibleDuration = 3.0
}
protocol NotificationCoordinatorDelegate: class {
func notificationCoordinator(_ coordinator: NotificationCoordinator, showChat chat: OCTChat)
func notificationCoordinatorShowFriendRequest(_ coordinator: NotificationCoordinator, showRequest request: OCTFriendRequest)
func notificationCoordinatorAnswerIncomingCall(_ coordinator: NotificationCoordinator, userInfo: String)
func notificationCoordinator(_ coordinator: NotificationCoordinator, updateFriendsBadge badge: Int)
func notificationCoordinator(_ coordinator: NotificationCoordinator, updateChatsBadge badge: Int)
}
class NotificationCoordinator: NSObject {
weak var delegate: NotificationCoordinatorDelegate?
fileprivate let theme: Theme
fileprivate let userDefaults = UserDefaultsManager()
fileprivate let notificationWindow: NotificationWindow
fileprivate weak var submanagerObjects: OCTSubmanagerObjects!
fileprivate var messagesToken: RLMNotificationToken?
fileprivate var chats: Results<OCTChat>
fileprivate var chatsToken: RLMNotificationToken?
fileprivate var requests: Results<OCTFriendRequest>
fileprivate var requestsToken: RLMNotificationToken?
fileprivate let avatarManager: AvatarManager
fileprivate let audioPlayer = AlertAudioPlayer()
fileprivate var notificationQueue = [NotificationType]()
fileprivate var inAppNotificationAppIdsRegistered = [String: Bool]()
fileprivate var bannedChatIdentifiers = Set<String>()
init(theme: Theme, submanagerObjects: OCTSubmanagerObjects) {
self.theme = theme
self.notificationWindow = NotificationWindow(theme: theme)
self.submanagerObjects = submanagerObjects
self.avatarManager = AvatarManager(theme: theme)
let predicate = NSPredicate(format: "lastMessage.dateInterval > lastReadDateInterval")
self.chats = submanagerObjects.chats(predicate: predicate)
self.requests = submanagerObjects.friendRequests()
super.init()
addNotificationBlocks()
NotificationCenter.default.addObserver(self, selector: #selector(NotificationCoordinator.applicationDidBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
messagesToken?.invalidate()
chatsToken?.invalidate()
requestsToken?.invalidate()
}
/**
Show or hide connnecting view.
*/
func toggleConnectingView(show: Bool, animated: Bool) {
notificationWindow.showConnectingView(show, animated: animated)
}
/**
Stops showing notifications for given chat.
Also removes all related to that chat notifications from queue.
*/
func banNotificationsForChat(_ chat: OCTChat) {
bannedChatIdentifiers.insert(chat.uniqueIdentifier)
notificationQueue = notificationQueue.filter {
switch $0 {
case .newMessage(let messageAbstract):
return messageAbstract.chatUniqueIdentifier != chat.uniqueIdentifier
case .friendRequest:
return true
}
}
LNNotificationCenter.default().clearPendingNotifications(forApplicationIdentifier: chat.uniqueIdentifier);
}
/**
Unban notifications for given chat (if they were banned before).
*/
func unbanNotificationsForChat(_ chat: OCTChat) {
bannedChatIdentifiers.remove(chat.uniqueIdentifier)
}
func handleLocalNotification(_ notification: UILocalNotification) {
guard let userInfo = notification.userInfo as? [String: String] else {
return
}
guard let action = NotificationAction(dictionary: userInfo) else {
return
}
performAction(action)
}
func showCallNotificationWithCaller(_ caller: String, userInfo: String) {
let object = NotificationObject(
title: caller,
body: String(localized: "notification_is_calling"),
action: .answerIncomingCall(userInfo: userInfo),
soundName: "isotoxin_Ringtone.aac")
showLocalNotificationObject(object)
}
func registerInAppNotificationAppId(_ appId: String) {
if inAppNotificationAppIdsRegistered[appId] == nil {
LNNotificationCenter.default().registerApplication(withIdentifier: appId, name: Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String, icon: UIImage(named: "notification-app-icon"), defaultSettings: LNNotificationAppSettings.default())
inAppNotificationAppIdsRegistered[appId] = true
}
}
}
extension NotificationCoordinator: CoordinatorProtocol {
func startWithOptions(_ options: CoordinatorOptions?) {
let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
let application = UIApplication.shared
application.registerUserNotificationSettings(settings)
application.cancelAllLocalNotifications()
updateBadges()
}
}
// MARK: Notifications
extension NotificationCoordinator {
@objc func applicationDidBecomeActive() {
UIApplication.shared.cancelAllLocalNotifications()
}
}
private extension NotificationCoordinator {
func addNotificationBlocks() {
let messages = submanagerObjects.messages().sortedResultsUsingProperty("dateInterval", ascending: false)
messagesToken = messages.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update(let messages, _, let insertions, _):
guard let messages = messages else {
break
}
if insertions.contains(0) {
let message = messages[0]
self.playSoundForMessageIfNeeded(message)
if self.shouldEnqueueMessage(message) {
self.enqueueNotification(.newMessage(message))
}
}
case .error(let error):
fatalError("\(error)")
}
}
chatsToken = chats.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update:
self.updateBadges()
case .error(let error):
fatalError("\(error)")
}
}
requestsToken = requests.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update(let requests, _, let insertions, _):
guard let requests = requests else {
break
}
for index in insertions {
let request = requests[index]
self.audioPlayer.playSound(.NewMessage)
self.enqueueNotification(.friendRequest(request))
}
self.updateBadges()
case .error(let error):
fatalError("\(error)")
}
}
}
func playSoundForMessageIfNeeded(_ message: OCTMessageAbstract) {
if message.isOutgoing() {
return
}
if message.messageText != nil || message.messageFile != nil {
audioPlayer.playSound(.NewMessage)
}
}
func shouldEnqueueMessage(_ message: OCTMessageAbstract) -> Bool {
if message.isOutgoing() {
return false
}
if UIApplication.isActive && bannedChatIdentifiers.contains(message.chatUniqueIdentifier) {
return false
}
if message.messageText != nil || message.messageFile != nil {
return true
}
return false
}
func enqueueNotification(_ notification: NotificationType) {
notificationQueue.append(notification)
showNextNotification()
}
func showNextNotification() {
if notificationQueue.isEmpty {
return
}
let notification = notificationQueue.removeFirst()
let object = notificationObjectFromNotification(notification)
if UIApplication.isActive {
switch notification {
case .newMessage(let messageAbstract):
showInAppNotificationObject(object, chatUniqueIdentifier: messageAbstract.chatUniqueIdentifier)
default:
showInAppNotificationObject(object, chatUniqueIdentifier: nil)
}
}
else {
showLocalNotificationObject(object)
}
}
func showInAppNotificationObject(_ object: NotificationObject, chatUniqueIdentifier: String?) {
var appId:String
if chatUniqueIdentifier != nil {
appId = chatUniqueIdentifier!
} else {
appId = Bundle.main.bundleIdentifier!
}
registerInAppNotificationAppId(appId);
let notification = LNNotification.init(message: object.body, title: object.title)
notification?.defaultAction = LNNotificationAction.init(title: nil, handler: { [weak self] _ in
self?.performAction(object.action)
})
LNNotificationCenter.default().present(notification, forApplicationIdentifier: appId)
showNextNotification()
}
func showLocalNotificationObject(_ object: NotificationObject) {
let local = UILocalNotification()
local.alertBody = "\(object.title): \(object.body)"
local.userInfo = object.action.archive()
local.soundName = object.soundName
UIApplication.shared.presentLocalNotificationNow(local)
showNextNotification()
}
func notificationObjectFromNotification(_ notification: NotificationType) -> NotificationObject {
switch notification {
case .friendRequest(let request):
return notificationObjectFromRequest(request)
case .newMessage(let message):
return notificationObjectFromMessage(message)
}
}
func notificationObjectFromRequest(_ request: OCTFriendRequest) -> NotificationObject {
let title = String(localized: "notification_incoming_contact_request")
let body = request.message ?? ""
let action = NotificationAction.openRequest(requestUniqueIdentifier: request.uniqueIdentifier)
return NotificationObject(title: title, body: body, action: action, soundName: "isotoxin_NewMessage.aac")
}
func notificationObjectFromMessage(_ message: OCTMessageAbstract) -> NotificationObject {
let title: String
if let friend = submanagerObjects.object(withUniqueIdentifier: message.senderUniqueIdentifier, for: .friend) as? OCTFriend {
title = friend.nickname
}
else {
title = ""
}
var body: String = ""
let action = NotificationAction.openChat(chatUniqueIdentifier: message.chatUniqueIdentifier)
if let messageText = message.messageText {
let defaultString = String(localized: "notification_new_message")
if userDefaults.showNotificationPreview {
body = messageText.text ?? defaultString
}
else {
body = defaultString
}
}
else if let messageFile = message.messageFile {
let defaultString = String(localized: "notification_incoming_file")
if userDefaults.showNotificationPreview {
body = messageFile.fileName ?? defaultString
}
else {
body = defaultString
}
}
return NotificationObject(title: title, body: body, action: action, soundName: "isotoxin_NewMessage.aac")
}
func performAction(_ action: NotificationAction) {
switch action {
case .openChat(let identifier):
guard let chat = submanagerObjects.object(withUniqueIdentifier: identifier, for: .chat) as? OCTChat else {
return
}
delegate?.notificationCoordinator(self, showChat: chat)
banNotificationsForChat(chat)
case .openRequest(let identifier):
guard let request = submanagerObjects.object(withUniqueIdentifier: identifier, for: .friendRequest) as? OCTFriendRequest else {
return
}
delegate?.notificationCoordinatorShowFriendRequest(self, showRequest: request)
case .answerIncomingCall(let userInfo):
delegate?.notificationCoordinatorAnswerIncomingCall(self, userInfo: userInfo)
}
}
func updateBadges() {
let chatsCount = chats.count
let requestsCount = requests.count
delegate?.notificationCoordinator(self, updateChatsBadge: chatsCount)
delegate?.notificationCoordinator(self, updateFriendsBadge: requestsCount)
UIApplication.shared.applicationIconBadgeNumber = chatsCount + requestsCount
}
// func chatsBadge() -> Int {
// // TODO update to new Realm and filter unread chats with predicate "lastMessage.dateInterval > lastReadDateInterval"
// var badge = 0
// for index in 0..<chats.count {
// guard let chat = chats[index] as? OCTChat else {
// continue
// }
// if chat.hasUnreadMessages() {
// badge += 1
// }
// }
// return badge
// }
}