原文链接
前言
在前文Kotlin Compose Multiplatform下全局通知组件构建,我们介绍了在应用软件内的通知方式,这里我们处理应用软件外系统层级的通知的。在安卓端我们正常使用androidx.core.app.NotificationCompat
进行消息的通知处理。而在桌面端,由于常用的使用习惯,我们使用系统托盘闪烁的方式进行消息通知,当然这里小伙伴们也可以选择正常的通知方式,如果这样可以参考使用KMPNotifier进行处理
实现
我们这里使用Koin
进行依赖注入,对这里不太了解的小伙伴可以参考前文Kotlin Compose Multiplatform下导航解决方案中的Koin部分,当然也可以自己使用静态对象做单例做相似处理
我们这里主要需要在公共部分调用三个方法:发送系统通知,清除系统通知,以及检查通知权限(移动端):
expect fun sendAppNotification(title: String, content: String)
expect fun clearAppNotification()
@Composable
expect fun CheckAppNotificationPermission(requestPermission: (()->Unit) -> Unit)
安卓端
安卓端初始化
对于安卓端来说,我们需要先初始化通知渠道:
override fun onCreate(savedInstanceState: Bundle?) {
//...
super.onCreate(savedInstanceState)
createAppNotificationChannel(this)
setContent {
MainApp(
//...
)
}
}
fun createAppNotificationChannel(context: Context) {
val channelId = "your_id"
val channelName = "Your Name"
val channelDescription = "Your Desc"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(channelId, channelName, importance).apply {
description = channelDescription
}
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
对于通知来说,我们需要在AndroidManifest
添加POST_NOTIFICATIONS
权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ...-->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- ...-->
</manifest>
对于检查通知权限的实现:
@SuppressLint("PermissionLaunchedDuringComposition")
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@OptIn(ExperimentalPermissionsApi::class)
@Composable
actual fun CheckAppNotificationPermission(
requestPermission: (() -> Unit) -> Unit
) {
val notificationPermission = rememberPermissionState(
permission = Manifest.permission.POST_NOTIFICATIONS
)
if (!notificationPermission.status.isGranted) {
requestPermission {
notificationPermission.launchPermissionRequest()
}
}
}
我们这里requestPermission
是提供了一个参数为函数的函数,目的是为了将launchPermissionRequest()
方法传入NotificationManager.createDialogAlert
给用户提示,并开启权限。关于createDialogAlert
的内容参考前文Kotlin Compose Multiplatform下全局通知组件构建。使用时代码如下:
CheckAppNotificationPermission { func ->
NotificationManager.createDialogAlert(
MainDialogAlert(
message = "Need Notification Permission",
confirmOperationText = "Confirm",
confirmOperation = {
func()
NotificationManager.removeDialogAlert()
},
)
)
}
这里只是核心代码示例,小伙伴们写的时候还需要更多处理来保证用户体验,比如CheckAppNotificationPermission
的调用时机和次数
安卓端发送和清除通知
对于发送通知:
actual fun sendAppNotification(title: String, content: String) {
val context = MainActivity.mainContext!!
val channelId = "some_channel_idaa"
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId++, builder.build())
}
对于通知清除:
actual fun clearAppNotification() {
val context = MainActivity.mainContext!!
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}
这里对于notificationId
的处理,我们可以使用方法内部生成的id
,也可以从服务端获取处理,根据小伙伴的流程设计决定
桌面端
桌面端系统托盘一般有两种处理方式,一种是图标闪动,一种是角标标红。角标标红比较简单,我们这里以图标闪动作为通知来演示桌面端的处理
桌面端初始化
如果要在桌面端实现系统托盘,一般的处理是使用java.awt.SystemTray
,首先在外部创建全局托盘和Koin
:
lateinit var koin: Koin
val tray: SystemTray = SystemTray.getSystemTray()
在main
函数中初始化:
fun main() {
koin = KoinInit().init()
//...
val showIcon: BufferedImage = koin.get(named("showIcon"))
val trayIcon = TrayIcon(showIcon, "Tomoyo")
tray.add(trayIcon)
application {
//...
trayIcon.apply {
isImageAutoSize = true
addMouseListener(
//...
)
}
Window(
//...
) {
//...
}
}}
由于我们在Koin
初始化的方法中包含了平台初始化模块:
class KoinInit {
fun init(appDeclaration: KoinAppDeclaration = {}): Koin {
return startKoin {
modules(
listOf(
//...
platformModule(),
),
)
appDeclaration()
}.koin
}
}
expect fun platformModule(): Module
而对于桌面端的平台初始化模块中包含两张闪动图标的资源的初始化:
@OptIn(ExperimentalSettingsApi::class)
actual fun platformModule(): Module = module {
//...
single<BufferedImage>(qualifier = named("showIcon")) {
ImageIO.read(
Thread.currentThread().contextClassLoader
.getResource("logo.png")
)
}
single<BufferedImage>(qualifier = named("hideIcon")) {
ImageIO.read(
Thread.currentThread().contextClassLoader
.getResource("notification-lightning.png")
)
}
}
所以我们在主函数中可以直接使用val showIcon: BufferedImage = koin.get(named("showIcon"))
拿到图标资源。由于我们需要图标闪动,故而我们一般的处理方案是开一个定时器切换图标资源进行闪动,如果小伙伴选择红标通知,那么就不需要定时器了只需要切红标图片即可:
val timer = Timer(500, object : ActionListener {
var toggle = true
val showIcon: BufferedImage = koin.get(named("showIcon"))
val hideIcon: BufferedImage = koin.get(named("hideIcon"))
override fun actionPerformed(event: java.awt.event.ActionEvent?) {
tray.trayIcons[0].image = if (toggle) showIcon else hideIcon
toggle = !toggle
}
})
桌面端发送和清除通知
对于发送通知:
actual fun sendAppNotification(title: String, content: String) {
timer.start()
}
对于清除通知:
actual fun clearAppNotification() {
timer.stop()
val showIcon: BufferedImage = koin.get(named("showIcon"))
tray.trayIcons[0].image = showIcon
}
源码
参考资料
Jetpack Compose Permissions Sample