parent
fea1d84682
commit
eff7041d62
@ -0,0 +1,253 @@
|
||||
import Tauri
|
||||
import UserNotifications
|
||||
|
||||
enum NotificationError: LocalizedError {
|
||||
case contentNoId
|
||||
case contentNoTitle
|
||||
case contentNoBody
|
||||
case triggerConstructionFailed
|
||||
case triggerRepeatIntervalTooShort
|
||||
case attachmentNoId
|
||||
case attachmentNoUrl
|
||||
case attachmentFileNotFound(path: String)
|
||||
case attachmentUnableToCreate(String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .attachmentFileNotFound(let path):
|
||||
return "Unable to find file \(path) for attachment"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeNotificationContent(_ notification: JSObject) throws -> UNNotificationContent {
|
||||
guard let title = notification["title"] as? String else {
|
||||
throw NotificationError.contentNoTitle
|
||||
}
|
||||
guard let body = notification["body"] as? String else {
|
||||
throw NotificationError.contentNoBody
|
||||
}
|
||||
|
||||
let extra = notification["extra"] as? JSObject ?? [:]
|
||||
let schedule = notification["schedule"] as? JSObject ?? [:]
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = NSString.localizedUserNotificationString(forKey: title, arguments: nil)
|
||||
content.body = NSString.localizedUserNotificationString(
|
||||
forKey: body,
|
||||
arguments: nil)
|
||||
|
||||
content.userInfo = [
|
||||
"__EXTRA__": extra,
|
||||
"__SCHEDULE__": schedule,
|
||||
]
|
||||
|
||||
if let actionTypeId = notification["actionTypeId"] as? String {
|
||||
content.categoryIdentifier = actionTypeId
|
||||
}
|
||||
|
||||
if let threadIdentifier = notification["group"] as? String {
|
||||
content.threadIdentifier = threadIdentifier
|
||||
}
|
||||
|
||||
if let summaryArgument = notification["summary"] as? String {
|
||||
content.summaryArgument = summaryArgument
|
||||
}
|
||||
|
||||
if let sound = notification["sound"] as? String {
|
||||
content.sound = UNNotificationSound(named: UNNotificationSoundName(sound))
|
||||
}
|
||||
|
||||
if let attachments = notification["attachments"] as? [JSObject] {
|
||||
content.attachments = try makeAttachments(attachments)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func makeAttachments(_ attachments: [JSObject]) throws -> [UNNotificationAttachment] {
|
||||
var createdAttachments = [UNNotificationAttachment]()
|
||||
|
||||
for attachment in attachments {
|
||||
guard let id = attachment["id"] as? String else {
|
||||
throw NotificationError.attachmentNoId
|
||||
}
|
||||
guard let url = attachment["url"] as? String else {
|
||||
throw NotificationError.attachmentNoUrl
|
||||
}
|
||||
guard let urlObject = makeAttachmentUrl(url) else {
|
||||
throw NotificationError.attachmentFileNotFound(path: url)
|
||||
}
|
||||
|
||||
let options = attachment["options"] as? JSObject ?? [:]
|
||||
|
||||
do {
|
||||
let newAttachment = try UNNotificationAttachment(
|
||||
identifier: id, url: urlObject, options: makeAttachmentOptions(options))
|
||||
createdAttachments.append(newAttachment)
|
||||
} catch {
|
||||
throw NotificationError.attachmentUnableToCreate(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
return createdAttachments
|
||||
}
|
||||
|
||||
func makeAttachmentUrl(_ path: String) -> URL? {
|
||||
return URL(string: path)
|
||||
}
|
||||
|
||||
func makeAttachmentOptions(_ options: JSObject) -> JSObject {
|
||||
var opts: JSObject = [:]
|
||||
|
||||
if let iosUNNotificationAttachmentOptionsTypeHintKey = options[
|
||||
"iosUNNotificationAttachmentOptionsTypeHintKey"] as? String
|
||||
{
|
||||
opts[UNNotificationAttachmentOptionsTypeHintKey] = iosUNNotificationAttachmentOptionsTypeHintKey
|
||||
}
|
||||
if let iosUNNotificationAttachmentOptionsThumbnailHiddenKey = options[
|
||||
"iosUNNotificationAttachmentOptionsThumbnailHiddenKey"] as? String
|
||||
{
|
||||
opts[UNNotificationAttachmentOptionsThumbnailHiddenKey] =
|
||||
iosUNNotificationAttachmentOptionsThumbnailHiddenKey
|
||||
}
|
||||
if let iosUNNotificationAttachmentOptionsThumbnailClippingRectKey = options[
|
||||
"iosUNNotificationAttachmentOptionsThumbnailClippingRectKey"] as? String
|
||||
{
|
||||
opts[UNNotificationAttachmentOptionsThumbnailClippingRectKey] =
|
||||
iosUNNotificationAttachmentOptionsThumbnailClippingRectKey
|
||||
}
|
||||
if let iosUNNotificationAttachmentOptionsThumbnailTimeKey = options[
|
||||
"iosUNNotificationAttachmentOptionsThumbnailTimeKey"] as? String
|
||||
{
|
||||
opts[UNNotificationAttachmentOptionsThumbnailTimeKey] =
|
||||
iosUNNotificationAttachmentOptionsThumbnailTimeKey
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func handleScheduledNotification(_ invoke: Invoke, _ schedule: JSObject) throws
|
||||
-> UNNotificationTrigger?
|
||||
{
|
||||
let kind = schedule["kind"] as? String ?? ""
|
||||
let payload = schedule["data"] as? JSObject ?? [:]
|
||||
switch kind {
|
||||
case "At":
|
||||
let date = payload["at"] as? String ?? ""
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
if let at = dateFormatter.date(from: date) {
|
||||
let repeats = payload["repeats"] as? Bool ?? false
|
||||
|
||||
let dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: at)
|
||||
|
||||
if dateInfo.date! < Date() {
|
||||
invoke.reject("Scheduled time must be *after* current time")
|
||||
return nil
|
||||
}
|
||||
|
||||
let dateInterval = DateInterval(start: Date(), end: dateInfo.date!)
|
||||
|
||||
// Notifications that repeat have to be at least a minute between each other
|
||||
if repeats && dateInterval.duration < 60 {
|
||||
throw NotificationError.triggerRepeatIntervalTooShort
|
||||
}
|
||||
|
||||
return UNTimeIntervalNotificationTrigger(
|
||||
timeInterval: dateInterval.duration, repeats: repeats)
|
||||
|
||||
} else {
|
||||
invoke.reject("could not parse `at` date")
|
||||
}
|
||||
case "Interval":
|
||||
let dateComponents = getDateComponents(payload)
|
||||
return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
|
||||
case "Every":
|
||||
let interval = payload["interval"] as? String ?? ""
|
||||
let count = schedule["count"] as? Int ?? 1
|
||||
|
||||
if let repeatDateInterval = getRepeatDateInterval(interval, count) {
|
||||
// Notifications that repeat have to be at least a minute between each other
|
||||
if repeatDateInterval.duration < 60 {
|
||||
throw NotificationError.triggerRepeatIntervalTooShort
|
||||
}
|
||||
|
||||
return UNTimeIntervalNotificationTrigger(
|
||||
timeInterval: repeatDateInterval.duration, repeats: true)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Given our schedule format, return a DateComponents object
|
||||
/// that only contains the components passed in.
|
||||
|
||||
func getDateComponents(_ at: JSObject) -> DateComponents {
|
||||
// var dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: Date())
|
||||
// dateInfo.calendar = Calendar.current
|
||||
var dateInfo = DateComponents()
|
||||
|
||||
if let year = at["year"] as? Int {
|
||||
dateInfo.year = year
|
||||
}
|
||||
if let month = at["month"] as? Int {
|
||||
dateInfo.month = month
|
||||
}
|
||||
if let day = at["day"] as? Int {
|
||||
dateInfo.day = day
|
||||
}
|
||||
if let hour = at["hour"] as? Int {
|
||||
dateInfo.hour = hour
|
||||
}
|
||||
if let minute = at["minute"] as? Int {
|
||||
dateInfo.minute = minute
|
||||
}
|
||||
if let second = at["second"] as? Int {
|
||||
dateInfo.second = second
|
||||
}
|
||||
if let weekday = at["weekday"] as? Int {
|
||||
dateInfo.weekday = weekday
|
||||
}
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/// Compute the difference between the string representation of a date
|
||||
/// interval and today. For example, if every is "month", then we
|
||||
/// return the interval between today and a month from today.
|
||||
|
||||
func getRepeatDateInterval(_ every: String, _ count: Int) -> DateInterval? {
|
||||
let cal = Calendar.current
|
||||
let now = Date()
|
||||
switch every {
|
||||
case "Year":
|
||||
let newDate = cal.date(byAdding: .year, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Month":
|
||||
let newDate = cal.date(byAdding: .month, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "TwoWeeks":
|
||||
let newDate = cal.date(byAdding: .weekOfYear, value: 2 * count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Week":
|
||||
let newDate = cal.date(byAdding: .weekOfYear, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Day":
|
||||
let newDate = cal.date(byAdding: .day, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Hour":
|
||||
let newDate = cal.date(byAdding: .hour, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Minute":
|
||||
let newDate = cal.date(byAdding: .minute, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
case "Second":
|
||||
let newDate = cal.date(byAdding: .second, value: count, to: now)!
|
||||
return DateInterval(start: now, end: newDate)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
import Tauri
|
||||
import UserNotifications
|
||||
|
||||
public func makeCategories(_ actionTypes: [JSObject]) {
|
||||
var createdCategories = [UNNotificationCategory]()
|
||||
|
||||
let generalCategory = UNNotificationCategory(
|
||||
identifier: "GENERAL",
|
||||
actions: [],
|
||||
intentIdentifiers: [],
|
||||
options: .customDismissAction)
|
||||
|
||||
createdCategories.append(generalCategory)
|
||||
for type in actionTypes {
|
||||
guard let id = type["id"] as? String else {
|
||||
Logger.error("Action type must have an id field")
|
||||
continue
|
||||
}
|
||||
let hiddenBodyPlaceholder = type["iosHiddenPreviewsBodyPlaceholder"] as? String ?? ""
|
||||
let actions = type["actions"] as? [JSObject] ?? []
|
||||
|
||||
let newActions = makeActions(actions)
|
||||
|
||||
// Create the custom actions for the TIMER_EXPIRED category.
|
||||
var newCategory: UNNotificationCategory?
|
||||
|
||||
newCategory = UNNotificationCategory(
|
||||
identifier: id,
|
||||
actions: newActions,
|
||||
intentIdentifiers: [],
|
||||
hiddenPreviewsBodyPlaceholder: hiddenBodyPlaceholder,
|
||||
options: makeCategoryOptions(type))
|
||||
|
||||
createdCategories.append(newCategory!)
|
||||
}
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.setNotificationCategories(Set(createdCategories))
|
||||
}
|
||||
|
||||
func makeActions(_ actions: [JSObject]) -> [UNNotificationAction] {
|
||||
var createdActions = [UNNotificationAction]()
|
||||
|
||||
for action in actions {
|
||||
guard let id = action["id"] as? String else {
|
||||
Logger.error("Action must have an id field")
|
||||
continue
|
||||
}
|
||||
let title = action["title"] as? String ?? ""
|
||||
let input = action["input"] as? Bool ?? false
|
||||
|
||||
var newAction: UNNotificationAction
|
||||
if input {
|
||||
let inputButtonTitle = action["inputButtonTitle"] as? String
|
||||
let inputPlaceholder = action["inputPlaceholder"] as? String ?? ""
|
||||
|
||||
if inputButtonTitle != nil {
|
||||
newAction = UNTextInputNotificationAction(
|
||||
identifier: id,
|
||||
title: title,
|
||||
options: makeActionOptions(action),
|
||||
textInputButtonTitle: inputButtonTitle!,
|
||||
textInputPlaceholder: inputPlaceholder)
|
||||
} else {
|
||||
newAction = UNTextInputNotificationAction(
|
||||
identifier: id, title: title, options: makeActionOptions(action))
|
||||
}
|
||||
} else {
|
||||
// Create the custom actions for the TIMER_EXPIRED category.
|
||||
newAction = UNNotificationAction(
|
||||
identifier: id,
|
||||
title: title,
|
||||
options: makeActionOptions(action))
|
||||
}
|
||||
createdActions.append(newAction)
|
||||
}
|
||||
|
||||
return createdActions
|
||||
}
|
||||
|
||||
func makeActionOptions(_ action: JSObject) -> UNNotificationActionOptions {
|
||||
let foreground = action["foreground"] as? Bool ?? false
|
||||
let destructive = action["destructive"] as? Bool ?? false
|
||||
let requiresAuthentication = action["requiresAuthentication"] as? Bool ?? false
|
||||
|
||||
if foreground {
|
||||
return .foreground
|
||||
}
|
||||
if destructive {
|
||||
return .destructive
|
||||
}
|
||||
if requiresAuthentication {
|
||||
return .authenticationRequired
|
||||
}
|
||||
return UNNotificationActionOptions(rawValue: 0)
|
||||
}
|
||||
|
||||
func makeCategoryOptions(_ type: JSObject) -> UNNotificationCategoryOptions {
|
||||
let customDismiss = type["iosCustomDismissAction"] as? Bool ?? false
|
||||
let carPlay = type["iosAllowInCarPlay"] as? Bool ?? false
|
||||
let hiddenPreviewsShowTitle = type["iosHiddenPreviewsShowTitle"] as? Bool ?? false
|
||||
let hiddenPreviewsShowSubtitle = type["iosHiddenPreviewsShowSubtitle"] as? Bool ?? false
|
||||
|
||||
if customDismiss {
|
||||
return .customDismissAction
|
||||
}
|
||||
if carPlay {
|
||||
return .allowInCarPlay
|
||||
}
|
||||
|
||||
if hiddenPreviewsShowTitle {
|
||||
return .hiddenPreviewsShowTitle
|
||||
}
|
||||
if hiddenPreviewsShowSubtitle {
|
||||
return .hiddenPreviewsShowSubtitle
|
||||
}
|
||||
|
||||
return UNNotificationCategoryOptions(rawValue: 0)
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import Tauri
|
||||
import UserNotifications
|
||||
|
||||
public class NotificationHandler: NSObject, NotificationHandlerProtocol {
|
||||
|
||||
public weak var plugin: Plugin?
|
||||
|
||||
var notificationsMap = [String: JSObject]()
|
||||
|
||||
public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
|
||||
completion?(granted, error)
|
||||
}
|
||||
}
|
||||
|
||||
public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.getNotificationSettings { settings in
|
||||
completion?(settings.authorizationStatus)
|
||||
}
|
||||
}
|
||||
|
||||
public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions {
|
||||
// let notificationData = makeNotificationRequestJSObject(notification.request)
|
||||
// TODO self.plugin?.trigger("received", data: notificationData)
|
||||
|
||||
if let options = notificationsMap[notification.request.identifier] {
|
||||
let silent = options["silent"] as? Bool ?? false
|
||||
if silent {
|
||||
return UNNotificationPresentationOptions.init(rawValue: 0)
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
.badge,
|
||||
.sound,
|
||||
.alert,
|
||||
]
|
||||
}
|
||||
|
||||
public func didReceive(response: UNNotificationResponse) {
|
||||
var data = JSObject()
|
||||
|
||||
let originalNotificationRequest = response.notification.request
|
||||
let actionId = response.actionIdentifier
|
||||
|
||||
// We turn the two default actions (open/dismiss) into generic strings
|
||||
if actionId == UNNotificationDefaultActionIdentifier {
|
||||
data["actionId"] = "tap"
|
||||
} else if actionId == UNNotificationDismissActionIdentifier {
|
||||
data["actionId"] = "dismiss"
|
||||
} else {
|
||||
data["actionId"] = actionId
|
||||
}
|
||||
|
||||
// If the type of action was for an input type, get the value
|
||||
if let inputType = response as? UNTextInputNotificationResponse {
|
||||
data["inputValue"] = inputType.userText
|
||||
}
|
||||
|
||||
data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest)
|
||||
|
||||
// TODO self.plugin?.trigger("localNotificationActionPerformed", data: data, retainUntilConsumed: true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a UNNotificationRequest into a JSObject to return back to the client.
|
||||
*/
|
||||
func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
|
||||
let notificationRequest = notificationsMap[request.identifier] ?? [:]
|
||||
var notification = makePendingNotificationRequestJSObject(request)
|
||||
notification["sound"] = notificationRequest["sound"] ?? ""
|
||||
notification["actionTypeId"] = request.content.categoryIdentifier
|
||||
notification["attachments"] = notificationRequest["attachments"] ?? []
|
||||
return notification
|
||||
}
|
||||
|
||||
func makePendingNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject {
|
||||
var notification: JSObject = [
|
||||
"id": Int(request.identifier) ?? -1,
|
||||
"title": request.content.title,
|
||||
"body": request.content.body,
|
||||
]
|
||||
|
||||
if let userInfo = JSTypes.coerceDictionaryToJSObject(request.content.userInfo) {
|
||||
var extra = userInfo["__EXTRA__"] as? JSObject ?? userInfo
|
||||
|
||||
// check for any dates and convert them to strings
|
||||
for (key, value) in extra {
|
||||
if let date = value as? Date {
|
||||
let dateString = ISO8601DateFormatter().string(from: date)
|
||||
extra[key] = dateString
|
||||
}
|
||||
}
|
||||
|
||||
notification["extra"] = extra
|
||||
|
||||
if var schedule = userInfo["__SCHEDULE__"] as? JSObject {
|
||||
// convert schedule at date to string
|
||||
if let date = schedule["at"] as? Date {
|
||||
let dateString = ISO8601DateFormatter().string(from: date)
|
||||
schedule["at"] = dateString
|
||||
}
|
||||
|
||||
notification["schedule"] = schedule
|
||||
}
|
||||
}
|
||||
|
||||
return notification
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
|
||||
@objc public protocol NotificationHandlerProtocol {
|
||||
func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions
|
||||
func didReceive(response: UNNotificationResponse)
|
||||
}
|
||||
|
||||
@objc public class NotificationManager: NSObject, UNUserNotificationCenterDelegate {
|
||||
public weak var notificationHandler: NotificationHandlerProtocol?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.delegate = self
|
||||
}
|
||||
|
||||
public func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification,
|
||||
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
var presentationOptions: UNNotificationPresentationOptions? = nil
|
||||
|
||||
if notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true {
|
||||
presentationOptions = notificationHandler?.willPresent(notification: notification)
|
||||
}
|
||||
|
||||
completionHandler(presentationOptions ?? [])
|
||||
}
|
||||
|
||||
public func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse,
|
||||
withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
if response.notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true {
|
||||
notificationHandler?.didReceive(response: response)
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
}
|
||||
}
|
@ -1,24 +1,228 @@
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
import WebKit
|
||||
import Tauri
|
||||
import SwiftRs
|
||||
|
||||
enum ShowNotificationError: LocalizedError {
|
||||
case noId
|
||||
case make(Error)
|
||||
case create(Error)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .noId:
|
||||
return "notification `id` missing"
|
||||
case .make(let error):
|
||||
return "Unable to make notification: \(error)"
|
||||
case .create(let error):
|
||||
return "Unable to create notification: \(error)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showNotification(invoke: Invoke, notification: JSObject)
|
||||
throws -> UNNotificationRequest
|
||||
{
|
||||
guard let identifier = notification["id"] as? Int else {
|
||||
throw ShowNotificationError.noId
|
||||
}
|
||||
|
||||
var content: UNNotificationContent
|
||||
do {
|
||||
content = try makeNotificationContent(notification)
|
||||
} catch {
|
||||
throw ShowNotificationError.make(error)
|
||||
}
|
||||
|
||||
var trigger: UNNotificationTrigger?
|
||||
|
||||
do {
|
||||
if let schedule = notification["schedule"] as? JSObject {
|
||||
try trigger = handleScheduledNotification(invoke, schedule)
|
||||
} else {
|
||||
let match = Calendar.current.dateComponents(
|
||||
[.timeZone, .year, .month, .day, .hour, .minute], from: Date())
|
||||
trigger = UNCalendarNotificationTrigger(dateMatching: match, repeats: false)
|
||||
}
|
||||
} catch {
|
||||
throw ShowNotificationError.create(error)
|
||||
}
|
||||
|
||||
// Schedule the request.
|
||||
let request = UNNotificationRequest(
|
||||
identifier: "\(identifier)", content: content, trigger: trigger
|
||||
)
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.add(request) { (error: Error?) in
|
||||
if let theError = error {
|
||||
invoke.reject(theError.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
class NotificationPlugin: Plugin {
|
||||
@objc public func requestPermission(_ invoke: Invoke) throws {
|
||||
invoke.resolve(["permissionState": "granted"])
|
||||
let notificationHandler = NotificationHandler()
|
||||
let notificationManager = NotificationManager()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
notificationHandler.plugin = self
|
||||
notificationManager.notificationHandler = notificationHandler
|
||||
}
|
||||
|
||||
@objc public func show(_ invoke: Invoke) throws {
|
||||
let request = try showNotification(invoke: invoke, notification: invoke.data)
|
||||
// TODO self.notificationHandler.notificationsMap[request.identifier] = invoke.data
|
||||
invoke.resolve([
|
||||
"id": Int(request.identifier) ?? -1
|
||||
])
|
||||
}
|
||||
|
||||
@objc public func batch(_ invoke: Invoke) throws {
|
||||
guard let notifications = invoke.getArray("notifications", JSObject.self) else {
|
||||
invoke.reject("`notifications` array is required")
|
||||
return
|
||||
}
|
||||
var ids = [String]()
|
||||
|
||||
for notification in notifications {
|
||||
let request = try showNotification(invoke: invoke, notification: notification)
|
||||
// TODO self.notificationHandler.notificationsMap[request.identifier] = notification
|
||||
ids.append(request.identifier)
|
||||
}
|
||||
|
||||
let ret = ids.map({ (id) -> JSObject in
|
||||
return [
|
||||
"id": Int(id) ?? -1
|
||||
]
|
||||
})
|
||||
invoke.resolve([
|
||||
"notifications": ret
|
||||
])
|
||||
}
|
||||
|
||||
@objc public override func requestPermissions(_ invoke: Invoke) {
|
||||
notificationHandler.requestPermissions { granted, error in
|
||||
guard error == nil else {
|
||||
invoke.reject(error!.localizedDescription)
|
||||
return
|
||||
}
|
||||
invoke.resolve(["permissionState": granted ? "granted" : "denied"])
|
||||
}
|
||||
}
|
||||
|
||||
@objc public override func checkPermissions(_ invoke: Invoke) {
|
||||
notificationHandler.checkPermissions { status in
|
||||
let permission: String
|
||||
|
||||
switch status {
|
||||
case .authorized, .ephemeral, .provisional:
|
||||
permission = "granted"
|
||||
case .denied:
|
||||
permission = "denied"
|
||||
case .notDetermined:
|
||||
permission = "default"
|
||||
@unknown default:
|
||||
permission = "default"
|
||||
}
|
||||
|
||||
invoke.resolve(["permissionState": permission])
|
||||
}
|
||||
}
|
||||
|
||||
@objc func cancel(_ invoke: Invoke) {
|
||||
guard let notifications = invoke.getArray("notifications", JSObject.self),
|
||||
notifications.count > 0
|
||||
else {
|
||||
invoke.reject("`notifications` input is required")
|
||||
return
|
||||
}
|
||||
|
||||
let ids = notifications.map({ (value: JSObject) -> String in
|
||||
if let idString = value["id"] as? String {
|
||||
return idString
|
||||
} else if let idNum = value["id"] as? NSNumber {
|
||||
return idNum.stringValue
|
||||
}
|
||||
return ""
|
||||
})
|
||||
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func getPending(_ invoke: Invoke) {
|
||||
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: {
|
||||
(notifications) in
|
||||
let ret = notifications.compactMap({ [weak self] (notification) -> JSObject? in
|
||||
return self?.notificationHandler.makePendingNotificationRequestJSObject(notification)
|
||||
})
|
||||
|
||||
invoke.resolve([
|
||||
"notifications": ret
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
@objc func registerActionTypes(_ invoke: Invoke) {
|
||||
guard let types = invoke.getArray("types", JSObject.self) else {
|
||||
return
|
||||
}
|
||||
makeCategories(types)
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc public func permissionState(_ invoke: Invoke) throws {
|
||||
invoke.resolve(["permissionState": "granted"])
|
||||
@objc func removeDeliveredNotifications(_ invoke: Invoke) {
|
||||
guard let notifications = invoke.getArray("notifications", JSObject.self) else {
|
||||
invoke.reject("`notifications` input is required")
|
||||
return
|
||||
}
|
||||
|
||||
@objc public func notify(_ invoke: Invoke) throws {
|
||||
// TODO
|
||||
let ids = notifications.map { "\($0["id"] ?? "")" }
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func removeAllDeliveredNotifications(_ invoke: Invoke) {
|
||||
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
||||
DispatchQueue.main.async(execute: {
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
})
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
@objc func getDeliveredNotifications(_ invoke: Invoke) {
|
||||
UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: {
|
||||
(notifications) in
|
||||
let ret = notifications.map({ (notification) -> [String: Any] in
|
||||
return self.notificationHandler.makeNotificationRequestJSObject(
|
||||
notification.request)
|
||||
})
|
||||
invoke.resolve([
|
||||
"notifications": ret
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
@objc func createChannel(_ invoke: Invoke) {
|
||||
invoke.reject("not implemented")
|
||||
}
|
||||
|
||||
@objc func deleteChannel(_ invoke: Invoke) {
|
||||
invoke.reject("not implemented")
|
||||
}
|
||||
|
||||
@objc func listChannels(_ invoke: Invoke) {
|
||||
invoke.reject("not implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_notification")
|
||||
func initPlugin(name: SRString, webview: WKWebView?) {
|
||||
Tauri.registerPlugin(webview: webview, name: name.toString(), plugin: NotificationPlugin())
|
||||
func initPlugin() -> Plugin {
|
||||
return NotificationPlugin()
|
||||
}
|
||||
|
Loading…
Reference in new issue