Skip to content

Commit

Permalink
feat: 通知权限
Browse files Browse the repository at this point in the history
  • Loading branch information
xfqwdsj committed Jan 21, 2024
1 parent 77b8175 commit b96b542
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 15 deletions.
2 changes: 1 addition & 1 deletion build-number.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# You should have received a copy of the GNU General Public License along
# with Fhraise. If not, see <https://www.gnu.org/licenses/>.
#
buildNumber=23
buildNumber=28
2 changes: 2 additions & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:allowBackup="true"
android:enableOnBackInvokedCallback="true"
Expand Down
27 changes: 27 additions & 0 deletions composeApp/src/androidMain/kotlin/Permission.android.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* This file is part of Fhraise.
* Copyright (c) 2024 HSAS Foodies. All Rights Reserved.
*
* Fhraise 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.
*
* Fhraise 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 Fhraise. If not, see <https://www.gnu.org/licenses/>.
*/

object Permission {
var checkNotificationPermissionGranted: () -> Boolean? = { null }
var requestNotificationPermission: suspend () -> Boolean? = { null }
}

actual val notificationPermissionGranted: Boolean?
get() = Permission.checkNotificationPermissionGranted()

actual suspend fun requestNotificationPermission(): Boolean? = Permission.requestNotificationPermission()
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,30 @@

package xyz.xfqlittlefan.fhraise

import Permission
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.arkivanov.decompose.defaultComponentContext
import data.AppComponentContextValues.ColorMode.*
import data.components.AppRootComponent
import ui.pages.Root
import xyz.xfqlittlefan.fhraise.compositionLocals.LocalActivity
import xyz.xfqlittlefan.fhraise.utils.isMiui
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -56,6 +63,44 @@ class MainActivity : ComponentActivity() {

super.onCreate(savedInstanceState)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val verifyCodeChannelId = "verifyCode"
val verifyCodeChannelName = "模拟验证码"
val verifyCodeChannelDescription = "模拟验证码的通知"
val verifyCodeChannelImportance = android.app.NotificationManager.IMPORTANCE_HIGH
val verifyCodeChannel = android.app.NotificationChannel(
verifyCodeChannelId, verifyCodeChannelName, verifyCodeChannelImportance
).apply {
description = verifyCodeChannelDescription
}

val notificationManager = getSystemService(android.app.NotificationManager::class.java)
notificationManager.createNotificationChannel(verifyCodeChannel)
}

var notificationPermissionRequestContinuation: Continuation<Boolean>? = null
val notificationPermissionRequestLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
notificationPermissionRequestContinuation?.resumeWith(Result.success(granted))
}

Permission.checkNotificationPermissionGranted = {
ContextCompat.checkSelfPermission(
this, android.Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
}

Permission.requestNotificationPermission = {
if (Permission.checkNotificationPermissionGranted() != true) {
suspendCoroutine {
notificationPermissionRequestContinuation = it
notificationPermissionRequestLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
} else {
true
}
}

val rootComponent = AppRootComponent(componentContext = defaultComponentContext())

setContent {
Expand Down
20 changes: 20 additions & 0 deletions composeApp/src/commonMain/kotlin/Permission.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This file is part of Fhraise.
* Copyright (c) 2024 HSAS Foodies. All Rights Reserved.
*
* Fhraise 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.
*
* Fhraise 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 Fhraise. If not, see <https://www.gnu.org/licenses/>.
*/

expect val notificationPermissionGranted: Boolean?
expect suspend fun requestNotificationPermission(): Boolean?
2 changes: 2 additions & 0 deletions composeApp/src/commonMain/kotlin/data/ComponentContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ interface AppComponentContextValues {
fun SnackbarHost() {
SnackbarHost(hostState = snackbarHostState)
}

suspend fun requestAppNotificationPermission(): Boolean?
}
31 changes: 31 additions & 0 deletions composeApp/src/commonMain/kotlin/data/components/RootComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,23 @@ package data.components

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.*
import com.arkivanov.decompose.value.Value
import com.arkivanov.essenty.backhandler.BackHandlerOwner
import data.AppComponentContext
import data.AppComponentContextValues
import data.componentScope
import data.components.root.AppSignInComponent
import data.components.root.SignInComponent
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import requestNotificationPermission
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine

interface RootComponent : AppComponentContext, BackHandlerOwner {
val stack: Value<ChildStack<*, Child>>
Expand All @@ -38,6 +45,11 @@ interface RootComponent : AppComponentContext, BackHandlerOwner {
class SignIn(val component: SignInComponent) : Child()
}

val showNotificationPermissionDialog: Boolean

fun startNotificationPermissionRequest()
fun cancelNotificationPermissionRequest()

fun onBack()
}

Expand Down Expand Up @@ -112,5 +124,24 @@ class AppRootComponent(
data object SignUp : Configuration()
}

override var showNotificationPermissionDialog by mutableStateOf(false)
private var permissionRequestContinuation: Continuation<Boolean?>? = null

override fun startNotificationPermissionRequest() {
componentScope.launch {
permissionRequestContinuation?.resumeWith(Result.success(requestNotificationPermission()))
showNotificationPermissionDialog = false
}
}

override fun cancelNotificationPermissionRequest() {
showNotificationPermissionDialog = false
}

override suspend fun requestAppNotificationPermission(): Boolean? = suspendCoroutine {
permissionRequestContinuation = it
showNotificationPermissionDialog = true
}

override fun onBack() = navigation.pop()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import data.AppComponentContextValues.ColorMode.*
import data.componentScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import notificationPermissionGranted

interface SignInComponent : AppComponentContext {
val nextColorMode: ColorMode
Expand Down Expand Up @@ -63,12 +64,7 @@ interface SignInComponent : AppComponentContext {
var canInputVerifyCode: Boolean
var verifyCode: String

fun switchCanInputVerifyCode() {
canInputVerifyCode = !canInputVerifyCode
if (!canInputVerifyCode) {
verifyCode = ""
}
}
fun sendVerifyCode()
}

interface UsernamePasswordState : ComponentState, KeyboardDoneState {
Expand Down Expand Up @@ -166,6 +162,27 @@ class AppSignInComponent(
}
}

private var verifyCodeSentSnackbarJob: Job? = null

override fun sendVerifyCode() {
canInputVerifyCode = true
if (!canInputVerifyCode) return
if (notificationPermissionGranted != true) {
componentScope.launch {
val result = requestAppNotificationPermission()
if (result == false) {
verifyCode = "114514" // TODO
verifyCodeSentSnackbarJob?.cancel()
snackbarHostState.showSnackbar("通知权限未授予,验证码已自动填写", withDismissAction = true)
}
}
}
verifyCodeSentSnackbarJob?.cancel()
verifyCodeSentSnackbarJob = componentScope.launch {
snackbarHostState.showSnackbar("验证码已发送", withDismissAction = true)
}
}

override var verifyCode by mutableStateOf(verifyCode)
override var showMoreSignInOptions by mutableStateOf(showMoreSignInOptions)

Expand All @@ -174,20 +191,14 @@ class AppSignInComponent(
}

override val onNext: KeyboardActionScope.() -> Unit = {
this@SignIn.canInputVerifyCode = true
sendVerifyCode()
}

private var verifyCodeSentSnackbarJob: Job? = null

override fun nextOrSubmit() {
if (canInputVerifyCode) {
submit()
} else {
canInputVerifyCode = true
verifyCodeSentSnackbarJob?.cancel()
verifyCodeSentSnackbarJob = componentScope.launch {
snackbarHostState.showSnackbar("验证码已发送", withDismissAction = true)
}
sendVerifyCode()
}
}

Expand Down
21 changes: 21 additions & 0 deletions composeApp/src/commonMain/kotlin/ui/pages/Root.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
package ui.pages

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -61,6 +64,24 @@ fun Root(component: RootComponent) {
is RootComponent.Child.SignIn -> SignIn(component = child.component)
}
}

if (component.showNotificationPermissionDialog) {
AlertDialog(
onDismissRequest = component::cancelNotificationPermissionRequest,
title = { Text("请授予通知权限") },
text = { Text("由于发送短信需要大量资费,本 Demo 版应用程序使用通知模拟发送短信。请在接下来弹出的窗口中为应用程序授予通知权限") },
confirmButton = {
Button(onClick = component::startNotificationPermissionRequest) {
Text("确定")
}
},
dismissButton = {
Button(onClick = component::cancelNotificationPermissionRequest) {
Text("取消")
}
},
)
}
}
}
}
20 changes: 20 additions & 0 deletions composeApp/src/desktopMain/kotlin/Permission.desktop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This file is part of Fhraise.
* Copyright (c) 2024 HSAS Foodies. All Rights Reserved.
*
* Fhraise 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.
*
* Fhraise 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 Fhraise. If not, see <https://www.gnu.org/licenses/>.
*/

actual val notificationPermissionGranted: Boolean? = true
actual suspend fun requestNotificationPermission(): Boolean? = true
37 changes: 37 additions & 0 deletions composeApp/src/wasmJsMain/kotlin/Permission.wasmJs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* This file is part of Fhraise.
* Copyright (c) 2024 HSAS Foodies. All Rights Reserved.
*
* Fhraise 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.
*
* Fhraise 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 Fhraise. If not, see <https://www.gnu.org/licenses/>.
*/

import org.w3c.notifications.DENIED
import org.w3c.notifications.GRANTED
import org.w3c.notifications.Notification
import org.w3c.notifications.NotificationPermission

actual val notificationPermissionGranted: Boolean?
get() = Notification.permission.status

actual suspend fun requestNotificationPermission(): Boolean? {
return notificationPermissionGranted ?: Notification.requestPermission().then { it.status?.toJsBoolean() }.await()
?.toBoolean()
}

val NotificationPermission.status: Boolean?
get() = when (this) {
NotificationPermission.GRANTED -> true
NotificationPermission.DENIED -> false
else -> null
}
Loading

0 comments on commit b96b542

Please sign in to comment.