Skip to content

Commit

Permalink
Merge pull request #653 from igorescodro/feature/notification-action
Browse files Browse the repository at this point in the history
🔔 Implement notification actions to iOS
  • Loading branch information
igorescodro authored Apr 20, 2024
2 parents dac935e + a4cf6c2 commit 0701e86
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package com.escodro.alarm.di

import com.escodro.alarm.notification.IosNotificationScheduler
import com.escodro.alarm.notification.IosTaskNotification
import com.escodro.alarm.notification.NotificationActionDelegate
import com.escodro.alarm.notification.NotificationScheduler
import com.escodro.alarm.notification.TaskNotification
import com.escodro.alarm.permission.IosAlarmPermission
import com.escodro.alarmapi.AlarmPermission
import org.koin.core.module.Module
import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module

Expand All @@ -16,4 +18,6 @@ actual val platformAlarmModule: Module = module {
factoryOf(::IosTaskNotification) bind TaskNotification::class

factoryOf(::IosAlarmPermission) bind AlarmPermission::class

singleOf(::NotificationActionDelegate)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.escodro.alarm.notification

import com.escodro.alarm.model.Task
import com.escodro.resources.MR
import dev.icerock.moko.resources.desc.Resource
import dev.icerock.moko.resources.desc.StringDesc
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import platform.Foundation.NSCalendar
Expand All @@ -14,14 +17,42 @@ import platform.Foundation.NSLog
import platform.Foundation.dateWithTimeIntervalSince1970
import platform.UserNotifications.UNCalendarNotificationTrigger
import platform.UserNotifications.UNMutableNotificationContent
import platform.UserNotifications.UNNotificationAction
import platform.UserNotifications.UNNotificationActionOptionNone
import platform.UserNotifications.UNNotificationCategory
import platform.UserNotifications.UNNotificationRequest
import platform.UserNotifications.UNUserNotificationCenter

internal class IosNotificationScheduler : NotificationScheduler {

private val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()

init {
registerCategories()
}

private fun registerCategories() {
val doneAction = UNNotificationAction.actionWithIdentifier(
identifier = ACTION_IDENTIFIER_DONE,
title = StringDesc.Resource(MR.strings.notification_action_completed).localized(),
options = UNNotificationActionOptionNone,
)

val category = UNNotificationCategory.categoryWithIdentifier(
identifier = CATEGORY_IDENTIFIER_TASK,
actions = listOf(doneAction),
intentIdentifiers = emptyList<String>(),
options = UNNotificationActionOptionNone,
)

notificationCenter.setNotificationCategories(setOf(category))
}

override fun scheduleTaskNotification(task: Task, timeInMillis: Long) {
val content = UNMutableNotificationContent()
content.setBody(task.title)
content.setCategoryIdentifier(CATEGORY_IDENTIFIER_TASK)
content.setUserInfo(mapOf(USER_INFO_TASK_ID to task.id))

val nsDate = NSDate.dateWithTimeIntervalSince1970(timeInMillis / 1000.0)
val dateComponents = NSCalendar.currentCalendar.components(
Expand All @@ -36,7 +67,7 @@ internal class IosNotificationScheduler : NotificationScheduler {
)

val request = UNNotificationRequest.requestWithIdentifier(
task.id.toString(),
identifier = task.id.toString(),
content = content,
trigger = trigger,
)
Expand All @@ -52,7 +83,6 @@ internal class IosNotificationScheduler : NotificationScheduler {

override fun cancelTaskNotification(task: Task) {
NSLog("Canceling notification with id '${task.title}'")
val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()
notificationCenter.removePendingNotificationRequestsWithIdentifiers(listOf(task.id.toString()))
}

Expand All @@ -64,4 +94,22 @@ internal class IosNotificationScheduler : NotificationScheduler {

scheduleTaskNotification(task, time)
}

companion object {

/**
* Identifier for the task category actions.
*/
const val CATEGORY_IDENTIFIER_TASK = "task_actions"

/**
* Identifier for the done action.
*/
const val ACTION_IDENTIFIER_DONE = "done_action"

/**
* Key to store the task id in the notification content.
*/
const val USER_INFO_TASK_ID = "task_id"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.escodro.alarm.notification

import com.escodro.coroutines.AppCoroutineScope
import com.escodro.domain.usecase.task.CompleteTask
import platform.Foundation.NSLog
import platform.UserNotifications.UNNotificationResponse

/**
* Class to handle the notification actions in iOS.
*
* @param appCoroutineScope the app-wide coroutine scope
* @param completeTaskUseCase the use case to complete a task
*/
class NotificationActionDelegate(
private val appCoroutineScope: AppCoroutineScope,
private val completeTaskUseCase: CompleteTask,
) {

/**
* Handles the notification response and actions.
*
* @param response the notification response
* @param onCompletion the action to be executed after the task is completed
*/
fun userNotificationCenter(response: UNNotificationResponse, onCompletion: () -> Unit) {
NSLog("NotificationActionDelegate - userNotificationCenter")
val content = response.notification.request.content
NSLog("NotificationActionDelegate - content: $content")
val taskId: Long = content.userInfo[IosNotificationScheduler.USER_INFO_TASK_ID] as? Long
?: return

when (response.actionIdentifier) {
IosNotificationScheduler.ACTION_IDENTIFIER_DONE -> completeTask(
taskId = taskId,
onCompletion = onCompletion,
)

else -> NSLog("NotificationActionDelegate - Action not supported")
}
}

private fun completeTask(taskId: Long, onCompletion: () -> Unit) {
appCoroutineScope.launch {
NSLog("NotificationActionDelegate - completeTask")
completeTaskUseCase(taskId)
onCompletion()
}
}
}
14 changes: 14 additions & 0 deletions ios-app/alkaa/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import UIKit
import shared

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {

Expand All @@ -19,4 +20,17 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
{
completionHandler([.banner, .list, .badge, .sound])
}

func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler:
@escaping () -> Void) {

let delegate = InjectionHelper().notificationActionDelegate
delegate.userNotificationCenter(response: response) {
// Call the completion handler after the action handling is finished
// Otherwise the Coroutine will be cancelled if the app is not running in background
completionHandler()
}
}
}
16 changes: 16 additions & 0 deletions shared/src/iosMain/kotlin/com/escodro/shared/di/InjectionHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.escodro.shared.di

import com.escodro.alarm.notification.NotificationActionDelegate
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

/**
* Helper class to inject dependencies in the iOS platform.
*/
class InjectionHelper : KoinComponent {

/**
* Provides the [NotificationActionDelegate] instance.
*/
val notificationActionDelegate: NotificationActionDelegate by inject()
}

0 comments on commit 0701e86

Please sign in to comment.