通知
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
package com.taskttl.core.alarm
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.taskttl.core.notification.NotificationManager
|
||||||
|
import com.taskttl.core.notification.NotificationPayload
|
||||||
|
import com.taskttl.core.notification.NotificationRepeatType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AlarmReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action != "com.taskttl.ALARM_TRIGGER") return
|
||||||
|
|
||||||
|
val id = intent.getStringExtra("id") ?: return
|
||||||
|
val title = intent.getStringExtra("title") ?: "TaskTTL"
|
||||||
|
val message = intent.getStringExtra("message") ?: ""
|
||||||
|
|
||||||
|
// 使用协程调用统一通知方法
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
NotificationManager.scheduleNotification(
|
||||||
|
NotificationPayload(
|
||||||
|
id = id,
|
||||||
|
title = title,
|
||||||
|
message = message,
|
||||||
|
triggerTimeMillis = System.currentTimeMillis(),
|
||||||
|
repeatType = NotificationRepeatType.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// val id = intent.getStringExtra("id") ?: return
|
||||||
|
// val title = intent.getStringExtra("title") ?: "TaskTTL"
|
||||||
|
// val message = intent.getStringExtra("message") ?: ""
|
||||||
|
//
|
||||||
|
// val channelId = "taskttl_channel"
|
||||||
|
//
|
||||||
|
// // 确保通道存在
|
||||||
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
// if (nm.getNotificationChannel(channelId) == null) {
|
||||||
|
// val channel = android.app.NotificationChannel(
|
||||||
|
// channelId,
|
||||||
|
// "TaskTTL Notifications",
|
||||||
|
// android.app.NotificationManager.IMPORTANCE_HIGH
|
||||||
|
// )
|
||||||
|
// channel.description = "TaskTTL 提醒通知"
|
||||||
|
// nm.createNotificationChannel(channel)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 构建通知
|
||||||
|
// val builder = NotificationCompat.Builder(context, channelId)
|
||||||
|
// .setContentTitle(title)
|
||||||
|
// .setContentText(message)
|
||||||
|
// .setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
// .setAutoCancel(true)
|
||||||
|
// .setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
//
|
||||||
|
// val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
// nm.notify(id.hashCode(), builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.taskttl.core.alarm
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.taskttl.core.notification.NotificationPayload
|
||||||
|
|
||||||
|
class BootReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||||
|
// 重新注册所有闹钟(可从数据库恢复)
|
||||||
|
|
||||||
|
// 这里假设你有一个持久化存储保存了待触发通知列表
|
||||||
|
// 例如 Room / SharedPreferences / 数据库
|
||||||
|
// val pendingNotifications = loadPendingNotifications(context)
|
||||||
|
//
|
||||||
|
// // 使用协程重新注册通知
|
||||||
|
// CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
// pendingNotifications.forEach { payload ->
|
||||||
|
// NotificationManager.scheduleNotification(payload)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从存储中获取设备重启后需要重新注册的通知
|
||||||
|
*/
|
||||||
|
private fun loadPendingNotifications(context: Context): List<NotificationPayload> {
|
||||||
|
// TODO: 这里你需要实现真实数据恢复逻辑
|
||||||
|
// 示例:返回一个空列表
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.work.*
|
||||||
|
import com.taskttl.MainApplication
|
||||||
|
import com.taskttl.R
|
||||||
|
import com.taskttl.core.alarm.AlarmReceiver
|
||||||
|
import com.taskttl.core.utils.LogUtils
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
actual object NotificationManager {
|
||||||
|
|
||||||
|
private val channelId = "taskttl_channel"
|
||||||
|
|
||||||
|
init {
|
||||||
|
setupNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getActivity(): Activity = MainApplication.currentActivity()
|
||||||
|
?: throw IllegalStateException("No activity available")
|
||||||
|
|
||||||
|
/** 创建通知通道(Android 8+) */
|
||||||
|
private fun setupNotificationChannel() {
|
||||||
|
val activity = MainApplication.currentActivity() ?: return
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
if (nm.getNotificationChannel(channelId) == null) {
|
||||||
|
val channel = android.app.NotificationChannel(
|
||||||
|
channelId,
|
||||||
|
"TaskTTL Notifications",
|
||||||
|
android.app.NotificationManager.IMPORTANCE_HIGH
|
||||||
|
).apply { description = "TaskTTL 提醒通知" }
|
||||||
|
nm.createNotificationChannel(channel)
|
||||||
|
LogUtils.d("DevTTL_NotificationTest", "Notification channel created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 调度通知 */
|
||||||
|
actual suspend fun scheduleNotification(payload: NotificationPayload) {
|
||||||
|
val activity = getActivity()
|
||||||
|
|
||||||
|
// Android 13+ 权限检查
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
|
ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
1001
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val delay = max(0, payload.triggerTimeMillis - System.currentTimeMillis())
|
||||||
|
if (delay == 0L) {
|
||||||
|
showImmediateNotification(payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay <= 24 * 60 * 60 * 1000L && payload.repeatType == NotificationRepeatType.NONE) {
|
||||||
|
scheduleExactAlarm(payload)
|
||||||
|
} else {
|
||||||
|
scheduleWorkManager(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** AlarmManager 精确闹钟 */
|
||||||
|
private fun scheduleExactAlarm(payload: NotificationPayload) {
|
||||||
|
val activity = getActivity()
|
||||||
|
val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
|
||||||
|
scheduleWorkManager(payload) // 回退 WorkManager
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(activity, AlarmReceiver::class.java).apply {
|
||||||
|
action = "com.taskttl.ALARM_TRIGGER"
|
||||||
|
putExtra("id", payload.id)
|
||||||
|
putExtra("title", payload.title)
|
||||||
|
putExtra("message", payload.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
activity,
|
||||||
|
payload.id.hashCode(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
payload.triggerTimeMillis,
|
||||||
|
pendingIntent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WorkManager 长期/周期通知 */
|
||||||
|
private fun scheduleWorkManager(payload: NotificationPayload) {
|
||||||
|
val activity = getActivity()
|
||||||
|
val data = workDataOf(
|
||||||
|
"id" to payload.id,
|
||||||
|
"title" to payload.title,
|
||||||
|
"message" to payload.message,
|
||||||
|
"repeatType" to payload.repeatType.name
|
||||||
|
)
|
||||||
|
|
||||||
|
val workManager = WorkManager.getInstance(activity)
|
||||||
|
|
||||||
|
if (payload.repeatType == NotificationRepeatType.NONE) {
|
||||||
|
val delay = max(0, payload.triggerTimeMillis - System.currentTimeMillis())
|
||||||
|
val request = OneTimeWorkRequestBuilder<NotificationWorker>()
|
||||||
|
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(data)
|
||||||
|
.addTag(payload.id)
|
||||||
|
.build()
|
||||||
|
workManager.enqueue(request)
|
||||||
|
} else {
|
||||||
|
val interval = when (payload.repeatType) {
|
||||||
|
NotificationRepeatType.DAILY -> 24 * 60 * 60 * 1000L
|
||||||
|
NotificationRepeatType.WEEKLY -> 7 * 24 * 60 * 60 * 1000L
|
||||||
|
NotificationRepeatType.MONTHLY -> 30 * 24 * 60 * 60 * 1000L
|
||||||
|
else -> 24 * 60 * 60 * 1000L
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = PeriodicWorkRequestBuilder<NotificationWorker>(interval, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(data)
|
||||||
|
.addTag(payload.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
workManager.enqueueUniquePeriodicWork(
|
||||||
|
payload.id,
|
||||||
|
ExistingPeriodicWorkPolicy.UPDATE,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 立即显示通知 */
|
||||||
|
private fun showImmediateNotification(payload: NotificationPayload) {
|
||||||
|
val activity = getActivity()
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(activity, channelId)
|
||||||
|
.setContentTitle(payload.title)
|
||||||
|
.setContentText(payload.message)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
// .setDefaults(NotificationCompat.DEFAULT_SOUND)
|
||||||
|
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
nm.notify(payload.id.hashCode(), notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 取消通知 */
|
||||||
|
actual suspend fun cancelNotification(id: String) {
|
||||||
|
val activity = getActivity()
|
||||||
|
WorkManager.getInstance(activity).cancelAllWorkByTag(id)
|
||||||
|
|
||||||
|
val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val intent = Intent(activity, AlarmReceiver::class.java)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
activity,
|
||||||
|
id.hashCode(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
alarmManager.cancel(pendingIntent)
|
||||||
|
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
nm.cancel(id.hashCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual suspend fun cancelAll() {
|
||||||
|
val activity = getActivity()
|
||||||
|
WorkManager.getInstance(activity).cancelAllWork()
|
||||||
|
val nm = activity.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
nm.cancelAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.taskttl.MainApplication
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
|
actual object NotificationPermissionManager {
|
||||||
|
private const val REQUEST_CODE = 2025
|
||||||
|
private var callback: NotificationPermissionCallback? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Activity 的 onRequestPermissionsResult 中调用
|
||||||
|
*/
|
||||||
|
fun handlePermissionResult(requestCode: Int, grantResults: IntArray) {
|
||||||
|
if (requestCode == REQUEST_CODE) {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
callback?.onGranted()
|
||||||
|
} else {
|
||||||
|
callback?.onDenied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect/actual 适配:Android 需要 Activity 参数
|
||||||
|
actual fun requestPermission(callback: NotificationPermissionCallback) {
|
||||||
|
// Android 13 以下无需权限
|
||||||
|
if (Build.VERSION.SDK_INT < 33) {
|
||||||
|
callback.onGranted()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val activity = MainApplication.currentActivity()
|
||||||
|
if (activity == null) {
|
||||||
|
Log.w("NotificationPermission", "No current activity found")
|
||||||
|
callback.onDenied()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.callback = callback
|
||||||
|
|
||||||
|
val prefs = activity.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
val permission = Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
val granted = ContextCompat.checkSelfPermission(
|
||||||
|
activity, permission
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (granted) {
|
||||||
|
callback.onGranted()
|
||||||
|
} else {
|
||||||
|
val isFirstRequest = prefs.getBoolean("notification_first_request", true)
|
||||||
|
val shouldShowRationale =
|
||||||
|
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)
|
||||||
|
|
||||||
|
if (!shouldShowRationale && !isFirstRequest) {
|
||||||
|
openNotificationSettings()
|
||||||
|
callback.onDenied()
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_CODE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs.edit { putBoolean("notification_first_request", false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
actual fun verifyPermission(): Boolean {
|
||||||
|
// Android 13 以下无需权限
|
||||||
|
if (Build.VERSION.SDK_INT < 33) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val activity = MainApplication.currentActivity()
|
||||||
|
if (activity == null) {
|
||||||
|
Log.w("NotificationPermission", "No current activity found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val permission = Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
val granted = ContextCompat.checkSelfPermission(
|
||||||
|
activity, permission
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
return granted
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 引导用户到系统设置页关闭通知权限
|
||||||
|
*/
|
||||||
|
actual fun disablePermission() {
|
||||||
|
openNotificationSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun openNotificationSettings() {
|
||||||
|
val context = MainApplication.currentActivity() ?: MainApplication.instance
|
||||||
|
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
// API 26 及以上,打开通知设置页
|
||||||
|
Intent().apply {
|
||||||
|
action = android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||||
|
putExtra(android.provider.Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API 26 以下,打开应用详情页
|
||||||
|
Intent().apply {
|
||||||
|
action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||||
|
data = "package:${context.packageName}".toUri()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(intent)
|
||||||
|
Log.d("NotificationPermission", "打开通知设置页")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("NotificationPermission", "无法打开通知设置页: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.taskttl.MainActivity
|
||||||
|
import com.taskttl.R
|
||||||
|
|
||||||
|
class NotificationWorker(
|
||||||
|
context: Context,
|
||||||
|
params: WorkerParameters
|
||||||
|
) : CoroutineWorker(context, params) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val id = inputData.getString("id") ?: return Result.failure()
|
||||||
|
val title = inputData.getString("title") ?: "TaskTTL"
|
||||||
|
val message = inputData.getString("message") ?: ""
|
||||||
|
|
||||||
|
sendNotification(id, title, message)
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendNotification(id: String, title: String, message: String) {
|
||||||
|
val context = applicationContext
|
||||||
|
val channelId = "taskttl_channel"
|
||||||
|
|
||||||
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
id.hashCode(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, channelId)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(message)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
|
||||||
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
||||||
|
nm.notify(id.hashCode(), builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知管理器
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/16
|
||||||
|
* @constructor 创建[NotificationManager]
|
||||||
|
*/
|
||||||
|
expect object NotificationManager {
|
||||||
|
/**
|
||||||
|
* 日程通知
|
||||||
|
* @param [payload] 有效载荷
|
||||||
|
*/
|
||||||
|
suspend fun scheduleNotification(payload: NotificationPayload)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消通知
|
||||||
|
* @param [id] ID
|
||||||
|
*/
|
||||||
|
suspend fun cancelNotification(id: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全部取消
|
||||||
|
*/
|
||||||
|
suspend fun cancelAll()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知有效载荷
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/16
|
||||||
|
* @constructor 创建[NotificationPayload]
|
||||||
|
* @param [id] ID
|
||||||
|
* @param [title] 标题
|
||||||
|
* @param [message] 消息
|
||||||
|
* @param [triggerTimeMillis] 触发时间毫秒
|
||||||
|
* @param [repeatType] 重复类型
|
||||||
|
*/
|
||||||
|
data class NotificationPayload(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val message: String,
|
||||||
|
val triggerTimeMillis: Long,
|
||||||
|
val repeatType: NotificationRepeatType = NotificationRepeatType.NONE,
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知权限回调接口
|
||||||
|
*/
|
||||||
|
interface NotificationPermissionCallback {
|
||||||
|
/**
|
||||||
|
* 授予
|
||||||
|
*/
|
||||||
|
fun onGranted()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被否认
|
||||||
|
*/
|
||||||
|
fun onDenied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨平台通知权限处理器
|
||||||
|
*/
|
||||||
|
expect object NotificationPermissionManager {
|
||||||
|
/**
|
||||||
|
* 请求通知权限
|
||||||
|
* @return 是否允许
|
||||||
|
*/
|
||||||
|
fun requestPermission(callback: NotificationPermissionCallback)
|
||||||
|
|
||||||
|
fun verifyPermission(): Boolean
|
||||||
|
|
||||||
|
fun disablePermission()
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知重复类型
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/16
|
||||||
|
* @constructor 创建[NotificationRepeatType]
|
||||||
|
*/
|
||||||
|
enum class NotificationRepeatType {
|
||||||
|
NONE,
|
||||||
|
DAILY,
|
||||||
|
WEEKLY,
|
||||||
|
MONTHLY
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
import platform.UserNotifications.*
|
||||||
|
|
||||||
|
actual class NotificationManager {
|
||||||
|
actual suspend fun scheduleNotification(payload: NotificationPayload) {
|
||||||
|
val content = UNMutableNotificationContent().apply {
|
||||||
|
title = payload.title
|
||||||
|
body = payload.message
|
||||||
|
sound = UNNotificationSound.defaultSound()
|
||||||
|
}
|
||||||
|
|
||||||
|
val interval = (payload.triggerTimeMillis - getCurrentTimeMillis()) / 1000.0
|
||||||
|
val trigger = UNTimeIntervalNotificationTrigger.triggerWithTimeInterval(
|
||||||
|
interval,
|
||||||
|
repeats = payload.repeatType != NotificationRepeatType.NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
val request = UNNotificationRequest.requestWithIdentifier(
|
||||||
|
payload.id,
|
||||||
|
content,
|
||||||
|
trigger
|
||||||
|
)
|
||||||
|
|
||||||
|
UNUserNotificationCenter.currentNotificationCenter()
|
||||||
|
.addNotificationRequest(request) { error ->
|
||||||
|
error?.let { println("❌ Notification Error: ${it.localizedDescription}") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual suspend fun cancelNotification(id: String) {
|
||||||
|
UNUserNotificationCenter.currentNotificationCenter()
|
||||||
|
.removePendingNotificationRequestsWithIdentifiers(listOf(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
actual suspend fun cancelAll() {
|
||||||
|
UNUserNotificationCenter.currentNotificationCenter()
|
||||||
|
.removeAllPendingNotificationRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentTimeMillis(): Long =
|
||||||
|
(platform.Foundation.NSDate().timeIntervalSince1970 * 1000).toLong()
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.taskttl.core.notification
|
||||||
|
|
||||||
|
import platform.UserNotifications.*
|
||||||
|
|
||||||
|
actual object NotificationPermissionManager {
|
||||||
|
actual fun requestPermission(callback: NotificationPermissionCallback) {
|
||||||
|
val center = UNUserNotificationCenter.currentNotificationCenter()
|
||||||
|
center.requestAuthorizationWithOptions(
|
||||||
|
UNAuthorizationOptionAlert or UNAuthorizationOptionSound or UNAuthorizationOptionBadge
|
||||||
|
) { granted, _ ->
|
||||||
|
if (granted) callback.onGranted() else callback.onDenied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user