notifications: add local notifications
Present local notifications when receiving incoming calls or
messages.
Change-Id: Ib650474c466678778bed26b564485316ed964a2f
Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index 3ff8e2f..1591ff5 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -104,6 +104,7 @@
0E2D5F551F9145F200D574BF /* LinkNewDeviceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */; };
0E403F811F7D797300C80BC2 /* MessageCellGenerated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */; };
0E403F831F7D79B000C80BC2 /* MessageCellGenerated.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */; };
+ 0E44B62F202B9DE40060F71B /* LocalNotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */; };
0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */; };
0E4909611FE97A94005CAA50 /* ActiveLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E4909601FE97A94005CAA50 /* ActiveLabel.framework */; };
0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0E4909691FEAB156005CAA50 /* CallsAdapter.mm */; };
@@ -126,6 +127,7 @@
0EB1A5CF1F8EBE03009923E2 /* DeviceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0EB1A5CE1F8EBE03009923E2 /* DeviceCell.xib */; };
0EB1A5D11F8EBE23009923E2 /* DeviceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB1A5D01F8EBE23009923E2 /* DeviceCell.swift */; };
0EB479951FA28A7300106AFD /* ButtonTransparentBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB479941FA28A7300106AFD /* ButtonTransparentBackground.swift */; };
+ 0EBCAA4E202E60F000E2A545 /* default.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0EBCAA4D202E60F000E2A545 /* default.wav */; };
0ED2B6FA1F96A075001572F0 /* LinkNewDeviceViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */; };
0ED2B6FC1F96A158001572F0 /* LinkNewDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */; };
0ED2B6FE1F96A16C001572F0 /* LinkNewDeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */; };
@@ -372,6 +374,7 @@
0E2D5F541F9145F200D574BF /* LinkNewDeviceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LinkNewDeviceCell.xib; sourceTree = "<group>"; };
0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellGenerated.swift; sourceTree = "<group>"; };
0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellGenerated.xib; sourceTree = "<group>"; };
+ 0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationsHelper.swift; sourceTree = "<group>"; };
0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestManager.swift; sourceTree = "<group>"; };
0E4909601FE97A94005CAA50 /* ActiveLabel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActiveLabel.framework; path = Carthage/Build/iOS/ActiveLabel.framework; sourceTree = "<group>"; };
0E4909681FEAB156005CAA50 /* CallsAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CallsAdapter.h; sourceTree = "<group>"; };
@@ -396,6 +399,7 @@
0EB1A5CE1F8EBE03009923E2 /* DeviceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DeviceCell.xib; sourceTree = "<group>"; };
0EB1A5D01F8EBE23009923E2 /* DeviceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceCell.swift; sourceTree = "<group>"; };
0EB479941FA28A7300106AFD /* ButtonTransparentBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTransparentBackground.swift; sourceTree = "<group>"; };
+ 0EBCAA4D202E60F000E2A545 /* default.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = default.wav; sourceTree = "<group>"; };
0ED2B6F91F96A075001572F0 /* LinkNewDeviceViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LinkNewDeviceViewController.storyboard; sourceTree = "<group>"; };
0ED2B6FB1F96A158001572F0 /* LinkNewDeviceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewController.swift; sourceTree = "<group>"; };
0ED2B6FD1F96A16C001572F0 /* LinkNewDeviceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkNewDeviceViewModel.swift; sourceTree = "<group>"; };
@@ -852,6 +856,7 @@
043999F51D1C2D9D00E99CD9 /* Ring */ = {
isa = PBXGroup;
children = (
+ 0E44B62D202B9DC40060F71B /* Helpers */,
0E63F1F3202907090001F248 /* Ring.entitlements */,
0E0FF1A81FC38409003898C2 /* Database */,
02E1A0261DDE4C2E00D75B59 /* Services */,
@@ -983,6 +988,14 @@
path = DBHelpers;
sourceTree = "<group>";
};
+ 0E44B62D202B9DC40060F71B /* Helpers */ = {
+ isa = PBXGroup;
+ children = (
+ 0E44B62E202B9DE40060F71B /* LocalNotificationsHelper.swift */,
+ );
+ path = Helpers;
+ sourceTree = "<group>";
+ };
0E4909711FEAC822005CAA50 /* Calls */ = {
isa = PBXGroup;
children = (
@@ -1290,6 +1303,7 @@
1ABE07C61F0D86B300D36361 /* Resources */ = {
isa = PBXGroup;
children = (
+ 0EBCAA4D202E60F000E2A545 /* default.wav */,
1ABE07DA1F0D915100D36361 /* Localizable.strings */,
04399A021D1C2D9D00E99CD9 /* Images.xcassets */,
);
@@ -1522,6 +1536,7 @@
files = (
1A2D18FD1F292DAD00B2C785 /* ConversationCell.xib in Resources */,
1ABE07DC1F0D915100D36361 /* Localizable.strings in Resources */,
+ 0EBCAA4E202E60F000E2A545 /* default.wav in Resources */,
1A2D18E61F29197100B2C785 /* MessageAccessoryView.xib in Resources */,
1A2D18F81F292D7200B2C785 /* MessageCellSent.xib in Resources */,
1A2D18F61F292D7200B2C785 /* MessageCellReceived.xib in Resources */,
@@ -1715,6 +1730,7 @@
621231FB1F8D6FEE009B86F0 /* MessageCell.swift in Sources */,
56AC650E1E85694D00EA1AA9 /* DesignableTextField.swift in Sources */,
1A2D189A1F2642C000B2C785 /* NotificationCenter+Ring.swift in Sources */,
+ 0E44B62F202B9DE40060F71B /* LocalNotificationsHelper.swift in Sources */,
1A2D18FC1F292DAD00B2C785 /* ConversationCell.swift in Sources */,
0E48F9D31FDF150700D6CC08 /* ContactRequestManager.swift in Sources */,
0E7CF4DD20165BFB00CD967D /* ButtonsContainerViewModel.swift in Sources */,
diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift
index ab7c1a8..89d0743 100644
--- a/Ring/Ring/AppDelegate.swift
+++ b/Ring/Ring/AppDelegate.swift
@@ -28,7 +28,7 @@
import PushKit
@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
private let daemonService = DaemonService(dRingAdaptor: DRingAdapter())
private let accountService = AccountsService(withAccountAdapter: AccountAdapter())
@@ -70,7 +70,9 @@
self.window = UIWindow(frame: UIScreen.main.bounds)
UserDefaults.standard.setValue(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable")
-
+ if #available(iOS 10.0, *) {
+ UNUserNotificationCenter.current().delegate = self
+ }
// initialize log format
let console = ConsoleDestination()
console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c: $M"
@@ -101,7 +103,7 @@
// load accounts during splashscreen
// and ask the AppCoordinator to handle the first screen once loading is finished
- self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService)
+ self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService, nameService: self.nameService)
self.startDB()
self.accountService.loadAccounts().subscribe { [unowned self] (_) in
guard let currentAccount = self.accountService.currentAccount else {
@@ -134,6 +136,7 @@
NotificationCenter.default.addObserver(self, selector: #selector(unregisterVoipNotifications),
name: NSNotification.Name(rawValue: NotificationName.disablePushNotifications.rawValue),
object: nil)
+ self.clearBadgeNumber()
return true
}
@@ -150,6 +153,10 @@
self.stopDaemon()
}
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ self.clearBadgeNumber()
+ }
+
// MARK: - Ring Daemon
fileprivate func startDaemon() {
@@ -200,7 +207,7 @@
self.accountService.setPushNotificationToken(token: "")
}
- func requestNotificationAuthorization() {
+ private func requestNotificationAuthorization() {
let application = UIApplication.shared
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = application.delegate as? UNUserNotificationCenterDelegate
@@ -211,6 +218,50 @@
application.registerUserNotificationSettings(settings)
}
}
+
+ private func clearBadgeNumber() {
+ UIApplication.shared.applicationIconBadgeNumber = 0
+ if #available(iOS 10.0, *) {
+ let center = UNUserNotificationCenter.current()
+ center.removeAllDeliveredNotifications()
+ center.removeAllPendingNotificationRequests()
+ } else {
+ UIApplication.shared.cancelAllLocalNotifications()
+ }
+ }
+
+ @available(iOS 10.0, *)
+ func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+ let data = response.notification.request.content.userInfo
+ self.handleNotificationActions(data: data, responseIdentifier: response.actionIdentifier)
+ completionHandler()
+ }
+
+ func handleNotificationActions(data: [AnyHashable: Any], responseIdentifier: String) {
+ guard let callID = data[NotificationUserInfoKeys.callID.rawValue] as? String else {
+ return
+ }
+ switch responseIdentifier {
+ case CallAcition.accept.rawValue:
+ NotificationCenter.default.post(name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue),
+ object: nil,
+ userInfo: data)
+ case CallAcition.refuse.rawValue:
+ self.callService.refuse(callId: callID)
+ .subscribe({_ in
+ print("Call ignored")
+ }).disposed(by: self.disposeBag)
+ default:
+ print("Other Action")
+ }
+ }
+
+ func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
+ if let identifier = identifier, let data = notification.userInfo {
+ self.handleNotificationActions(data: data, responseIdentifier: identifier)
+ }
+ completionHandler()
+ }
}
extension AppDelegate: PKPushRegistryDelegate {
diff --git a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
index 9ec07ba..612ecf3 100644
--- a/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
+++ b/Ring/Ring/Features/Conversations/ConversationsCoordinator.swift
@@ -65,6 +65,15 @@
}).disposed(by: self.disposeBag)
self.navigationViewController.viewModel = ChatTabBarItemViewModel(with: self.injectionBag)
self.callbackPlaceCall()
+ NotificationCenter.default.addObserver(self, selector: #selector(self.incomingCall(_:)), name: NSNotification.Name(NotificationName.answerCallFromNotifications.rawValue), object: nil)
+ }
+
+ @objc func incomingCall(_ notification: NSNotification) {
+ guard let callid = notification.userInfo?[NotificationUserInfoKeys.callID.rawValue] as? String,
+ let call = self.callService.call(callID: callid) else {
+ return
+ }
+ self.answerIncomingCall(call: call)
}
func start () {
@@ -78,7 +87,7 @@
self.present(viewController: conversationViewController, withStyle: .show, withAnimation: true, withStateable: conversationViewController.viewModel)
}
- private func answerIncomingCall(call: CallModel) {
+ func answerIncomingCall(call: CallModel) {
let callViewController = CallViewController.instantiate(with: self.injectionBag)
callViewController.viewModel.call = call
callViewController.viewModel.answerCall()
@@ -88,19 +97,27 @@
}
private func showCallAlert(call: CallModel) {
- let alertStyle = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad) ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet
- let alert = UIAlertController(title: L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)", message: nil, preferredStyle: alertStyle)
- alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonAccept, style: UIAlertActionStyle.default, handler: { (_) in
- self.answerIncomingCall(call: call)
- alert.dismiss(animated: true, completion: nil)}))
- alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonIgnore, style: UIAlertActionStyle.default, handler: { (_) in
- self.injectionBag.callService.refuse(callId: call.callId)
- .subscribe({_ in
- print("Call ignored")
- }).disposed(by: self.disposeBag)
- alert.dismiss(animated: true, completion: nil)
- }))
+ if UIApplication.shared.applicationState != .active && !call.callId.isEmpty {
+ var data = [String: String]()
+ data [NotificationUserInfoKeys.name.rawValue] = call.participantRingId
+ data [NotificationUserInfoKeys.callID.rawValue] = call.callId
+ let helper = LocalNotificationsHelper()
+ helper.presentCallNotification(data: data, callService: self.callService)
+ } else {
+ let alertStyle = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad) ? UIAlertControllerStyle.alert : UIAlertControllerStyle.actionSheet
+ let alert = UIAlertController(title: L10n.Alerts.incomingCallAllertTitle + "\(call.displayName)", message: nil, preferredStyle: alertStyle)
+ alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonAccept, style: UIAlertActionStyle.default, handler: { (_) in
+ self.answerIncomingCall(call: call)
+ alert.dismiss(animated: true, completion: nil)}))
+ alert.addAction(UIAlertAction(title: L10n.Alerts.incomingCallButtonIgnore, style: UIAlertActionStyle.default, handler: { (_) in
+ self.injectionBag.callService.refuse(callId: call.callId)
+ .subscribe({_ in
+ print("Call ignored")
+ }).disposed(by: self.disposeBag)
+ alert.dismiss(animated: true, completion: nil)
+ }))
- self.present(viewController: alert, withStyle: .present, withAnimation: true)
+ self.present(viewController: alert, withStyle: .present, withAnimation: true)
+ }
}
}
diff --git a/Ring/Ring/Helpers/LocalNotificationsHelper.swift b/Ring/Ring/Helpers/LocalNotificationsHelper.swift
new file mode 100644
index 0000000..d75e9f5
--- /dev/null
+++ b/Ring/Ring/Helpers/LocalNotificationsHelper.swift
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018 Savoir-faire Linux Inc.
+ *
+ * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+import Foundation
+import RxSwift
+
+enum NotificationUserInfoKeys: String {
+ case callID
+ case name
+ case messageContent
+}
+
+enum NotificationCallTitle: String {
+ case incomingCall = "Incoming Call"
+ case missedCall = "Missed Call"
+}
+
+enum CallAcition: String {
+ case accept = "ACCEPT_ACTION"
+ case refuse = "REFUSE_ACTION"
+
+ func title() -> String {
+ switch self {
+ case .accept:
+ return "ACCEPT"
+ case .refuse:
+ return "REFUSE"
+ }
+ }
+}
+
+class LocalNotificationsHelper {
+ let disposeBag = DisposeBag()
+ let callCategory = "CALL_CATEGORY"
+ var timer: Timer?
+
+ init() {
+ self.createCallCategory()
+ }
+
+ func presentMessageNotification(data: [String: String]) {
+ guard let title = data [NotificationUserInfoKeys.name.rawValue],
+ let body = data [NotificationUserInfoKeys.messageContent.rawValue] else {
+ return
+ }
+ if #available(iOS 10.0, *) {
+ let content = UNMutableNotificationContent()
+ content.title = title
+ content.body = body
+ content.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
+ let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+ let identifier = Int64(arc4random_uniform(10000000))
+ let notificationRequest = UNNotificationRequest(identifier: "\(identifier)", content: content, trigger: notificationTrigger)
+ UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+ if let error = error {
+ print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+ }
+ }
+ } else {
+ let notification = UILocalNotification()
+ notification.alertTitle = title
+ notification.alertBody = body
+ notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+ UIApplication.shared.scheduleLocalNotification(notification)
+ }
+ }
+
+ func createCallCategory() {
+ if #available(iOS 10.0, *) {
+ let acceptAction = UNNotificationAction(identifier: CallAcition.accept.rawValue,
+ title: CallAcition.accept.title(),
+ options: [.foreground])
+ let refuseAction = UNNotificationAction(identifier: CallAcition.refuse.rawValue,
+ title: CallAcition.refuse.title(),
+ options: [])
+
+ let callCategory = UNNotificationCategory(identifier: self.callCategory,
+ actions: [acceptAction, refuseAction],
+ intentIdentifiers: [], options: [])
+ UNUserNotificationCenter.current().setNotificationCategories([callCategory])
+ } else {
+ let notificationTypes: UIUserNotificationType = (UIApplication.shared.currentUserNotificationSettings?.types)!
+ let acceptAction = UIMutableUserNotificationAction()
+ acceptAction.identifier = CallAcition.accept.rawValue
+ acceptAction.title = CallAcition.accept.title()
+ acceptAction.activationMode = UIUserNotificationActivationMode.foreground
+ let refuseAction = UIMutableUserNotificationAction()
+ refuseAction.identifier = CallAcition.refuse.rawValue
+ refuseAction.title = CallAcition.refuse.title()
+ refuseAction.activationMode = UIUserNotificationActivationMode.background
+ let callCategory = UIMutableUserNotificationCategory()
+ callCategory.identifier = self.callCategory
+ // A. Set actions for the default context
+ callCategory.setActions([acceptAction, refuseAction],
+ for: UIUserNotificationActionContext.default)
+ // B. Set actions for the minimal context
+ callCategory.setActions([acceptAction, refuseAction],
+ for: UIUserNotificationActionContext.minimal)
+ guard let categoriesForSettings: Set<UIUserNotificationCategory> = NSSet(objects: callCategory) as? Set<UIUserNotificationCategory> else {
+ return
+ }
+ let newNotificationSettings = UIUserNotificationSettings(types: notificationTypes, categories: categoriesForSettings)
+ UIApplication.shared.registerUserNotificationSettings(newNotificationSettings)
+ }
+ }
+
+ @objc func cancelCall(timer: Timer!) {
+ guard let info = timer.userInfo as? [String: String],
+ let callID = info[NotificationUserInfoKeys.callID.rawValue] else {
+ self.timer?.invalidate()
+ self.timer = nil
+ return
+ }
+ var data = [String: String]()
+ data[NotificationUserInfoKeys.callID.rawValue] = callID
+ NotificationCenter.default.post(name: NSNotification.Name(NotificationName.refuseCallFromNotifications.rawValue), object: nil, userInfo: data)
+ self.timer?.invalidate()
+ self.timer = nil
+ }
+
+ func presentCallNotification(data: [String: String], callService: CallsService) {
+ let title = NotificationCallTitle.incomingCall.rawValue
+ guard let name = data [NotificationUserInfoKeys.name.rawValue],
+ let callID = data [NotificationUserInfoKeys.callID.rawValue] else {
+ return
+ }
+ timer = Timer.scheduledTimer(timeInterval: 10,
+ target: self,
+ selector: #selector(cancelCall),
+ userInfo: [NotificationUserInfoKeys.callID.rawValue: callID],
+ repeats: false)
+ if #available(iOS 10.0, *) {
+ let content = UNMutableNotificationContent()
+ content.title = title
+ content.body = name
+ content.userInfo = data
+ content.categoryIdentifier = self.callCategory
+ content.sound = UNNotificationSound(named: "defaul.wav")
+ let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+ let notificationRequest = UNNotificationRequest(identifier: callID, content: content, trigger: notificationTrigger)
+ UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+ if let error = error {
+ print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+ }
+ }
+ callService.currentCall.filter({ call in
+ return call.callId == callID && (call.state == .over || call.state == .failure)
+ }).single()
+ .subscribe(onNext: { _ in
+ let content = UNMutableNotificationContent()
+ content.title = NotificationCallTitle.missedCall.rawValue
+ content.body = name
+ content.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumber
+ let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
+ let notificationRequest = UNNotificationRequest(identifier: callID, content: content, trigger: notificationTrigger)
+ UNUserNotificationCenter.current().add(notificationRequest) { (error) in
+ if let error = error {
+ print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
+ }
+ }
+ }).disposed(by: self.disposeBag)
+ } else {
+ let notification = UILocalNotification()
+ notification.userInfo = data
+ notification.alertTitle = title
+ notification.alertBody = name
+ notification.category = self.callCategory
+ notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+ UIApplication.shared.scheduleLocalNotification(notification)
+ callService.currentCall.filter({ call in
+ return call.callId == callID && (call.state == .over || call.state == .failure)
+ }).single()
+ .subscribe(onNext: { _ in
+ let notification = UILocalNotification()
+ notification.userInfo = data
+ notification.alertTitle = NotificationCallTitle.missedCall.rawValue
+ notification.alertBody = name
+ notification.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
+ UIApplication.shared.scheduleLocalNotification(notification)
+ }).disposed(by: self.disposeBag)
+ }
+ }
+}
diff --git a/Ring/Ring/Resources/default.wav b/Ring/Ring/Resources/default.wav
new file mode 100644
index 0000000..f68346e
--- /dev/null
+++ b/Ring/Ring/Resources/default.wav
Binary files differ
diff --git a/Ring/Ring/Services/AccountsService.swift b/Ring/Ring/Services/AccountsService.swift
index a32504d..2898916 100644
--- a/Ring/Ring/Services/AccountsService.swift
+++ b/Ring/Ring/Services/AccountsService.swift
@@ -36,6 +36,8 @@
enum NotificationName: String {
case enablePushNotifications
case disablePushNotifications
+ case answerCallFromNotifications
+ case refuseCallFromNotifications
}
class AccountsService: AccountAdapterDelegate {
diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift
index 1188056..0a2488c 100644
--- a/Ring/Ring/Services/CallsService.swift
+++ b/Ring/Ring/Services/CallsService.swift
@@ -69,6 +69,28 @@
self.responseStream.disposed(by: disposeBag)
self.sharedResponseStream = responseStream.share()
CallsAdapter.delegate = self
+ NotificationCenter.default.addObserver(self, selector: #selector(self.refuseUnansweredCall(_:)),
+ name: NSNotification.Name(rawValue: NotificationName.refuseCallFromNotifications.rawValue),
+ object: nil)
+ }
+
+ @objc func refuseUnansweredCall(_ notification: NSNotification) {
+ guard let callid = notification.userInfo?[NotificationUserInfoKeys.callID.rawValue] as? String else {
+ return
+ }
+ guard let call = self.call(callID: callid) else {
+ return
+ }
+
+ if call.state == .incoming {
+ self.refuse(callId: callid).subscribe({_ in
+ print("Call ignored")
+ }).disposed(by: self.disposeBag)
+ }
+ }
+
+ func call(callID: String) -> CallModel? {
+ return self.calls[callID]
}
func accept(call: CallModel?) -> Completable {
diff --git a/Ring/Ring/Services/ConversationsManager.swift b/Ring/Ring/Services/ConversationsManager.swift
index bd9a2a2..a10548d 100644
--- a/Ring/Ring/Services/ConversationsManager.swift
+++ b/Ring/Ring/Services/ConversationsManager.swift
@@ -25,12 +25,15 @@
let conversationService: ConversationsService
let accountsService: AccountsService
- let disposeBag = DisposeBag()
+ let nameService: NameService
+ private let disposeBag = DisposeBag()
fileprivate let textPlainMIMEType = "text/plain"
+ private let notificationHandler = LocalNotificationsHelper()
- init(with conversationService: ConversationsService, accountsService: AccountsService) {
+ init(with conversationService: ConversationsService, accountsService: AccountsService, nameService: NameService) {
self.conversationService = conversationService
self.accountsService = accountsService
+ self.nameService = nameService
MessagesAdapter.delegate = self
self.accountsService
.sharedResponseStream
@@ -66,6 +69,27 @@
return
}
+ if UIApplication.shared.applicationState != .active {
+ var data = [String: String]()
+ data [NotificationUserInfoKeys.messageContent.rawValue] = content
+ self.nameService.usernameLookupStatus.single()
+ .filter({ lookupNameResponse in
+ return lookupNameResponse.address != nil &&
+ lookupNameResponse.address == senderAccount
+ })
+ .subscribe(onNext: { [weak self] lookupNameResponse in
+ if let name = lookupNameResponse.name, !name.isEmpty {
+ data [NotificationUserInfoKeys.name.rawValue] = name
+ self?.notificationHandler.presentMessageNotification(data: data)
+ } else if let address = lookupNameResponse.address {
+ data [NotificationUserInfoKeys.name.rawValue] = address
+ self?.notificationHandler.presentMessageNotification(data: data)
+ }
+ }).disposed(by: self.disposeBag)
+
+ self.nameService.lookupAddress(withAccount: "", nameserver: "", address: senderAccount)
+ }
+
guard let currentAccount = self.accountsService.currentAccount else {
return
}
@@ -107,5 +131,4 @@
from: accountId,
to: uri)
}
-
}
diff --git a/Ring/Ring/Services/ConversationsService.swift b/Ring/Ring/Services/ConversationsService.swift
index d35129d..623f4cd 100644
--- a/Ring/Ring/Services/ConversationsService.swift
+++ b/Ring/Ring/Services/ConversationsService.swift
@@ -237,6 +237,10 @@
})
}
+ func getProfile(uri: String) -> Observable<Profile> {
+ return self.dbManager.profileObservable(for: uri, createIfNotExists: false)
+ }
+
func deleteConversation(conversation: ConversationModel) {
self.dbManager.removeConversationBetween(accountUri: conversation.accountUri, and: conversation.recipientRingId)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))