Skip to content

Commit

Permalink
💪 [kmp/sys] 实现了KeychainActivity的任务可恢复性
Browse files Browse the repository at this point in the history
现在旋转屏幕不再会导致异常
现在任务的销毁行为更加合理了
桌面版现在也有了CardHeader Background
  • Loading branch information
Gaubee committed Jul 19, 2024
1 parent ab7d429 commit 81dc71b
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 85 deletions.

This file was deleted.

1 change: 1 addition & 0 deletions next/kmp/sys/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
android:theme="@style/Theme.DwebBrowser.Transparent" />
<activity
android:name="org.dweb_browser.sys.keychain.render.KeychainActivity"
android:configChanges="keyboardHidden|screenSize|screenLayout|orientation|keyboard|navigation|uiMode"
android:exported="true"
android:theme="@style/Theme.DwebBrowser.Transparent" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ actual class KeychainStore actual constructor(val runtime: KeychainNMM.KeyChainR
}
}

private val encryptKey = SuspendOnce1 { params: UseKeyParams -> getOrRecoveryOrCreateKey(params) }
private val encryptKey = SuspendOnce1({
runCatching {
getResult()
}.getOrElse {
reset(doCancel = false)
}
}) { params: UseKeyParams -> getOrRecoveryOrCreateKey(params) }


/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,18 @@
package org.dweb_browser.sys.keychain.core

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.min
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import org.dweb_browser.core.module.MicroModule
import org.dweb_browser.helper.WARNING
import org.dweb_browser.helper.platform.theme.LocalColorful
import org.dweb_browser.helper.utf8Binary
import org.dweb_browser.helper.utf8String
import org.dweb_browser.pure.image.compose.PureImageLoader
import org.dweb_browser.pure.image.compose.StableSmartLoad
import org.dweb_browser.sys.keychain.KeychainNMM
import org.dweb_browser.sys.keychain.render.KeychainActivity
import org.dweb_browser.sys.keychain.render.KeychainAuthentication.Companion.ROOT_KEY_VERSION
import org.dweb_browser.sys.keychain.render.keychainMetadataStore
import org.dweb_browser.sys.toast.ext.showToast
import org.dweb_browser.sys.window.core.helper.pickLargest
import org.dweb_browser.sys.window.core.helper.toStrict
import org.dweb_browser.sys.window.render.blobFetchHook

abstract class EncryptKey {
companion object {
Expand All @@ -45,37 +24,10 @@ abstract class EncryptKey {
params: UseKeyParams,
): EncryptKey {
val secretKeyRawBytes = KeychainActivity.create(params.runtime).start(
runtime = params.runtime,
title = params.reason.title,
subtitle = params.reason.subtitle,
description = params.reason.description,
background = {
BoxWithConstraints(
modifier = Modifier.fillMaxSize().clip(RectangleShape)
.background(LocalColorful.current.Amber.current),
contentAlignment = Alignment.CenterEnd,
) {
remember { params.runtime.icons.toStrict().pickLargest() }?.also { icon ->
val size = min(maxWidth, maxHeight)
PureImageLoader.StableSmartLoad(icon.src, size, size, params.runtime.blobFetchHook)
.with { img ->
AnimatedVisibility(
visibleState = remember {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = true
}
},
enter = slideInHorizontally() + scaleIn()
) {
Image(img, null, modifier = Modifier.graphicsLayer {
rotationY = -15f
translationX = maxWidth.value / 4
})
}
}
}
}
},
)
val version = keychainMetadataStore.getItem(ROOT_KEY_VERSION)?.utf8String ?: run {
keychainMetadataStore.setItem(ROOT_KEY_VERSION, RootKeyV1.VERSION.utf8Binary)
Expand Down Expand Up @@ -103,7 +55,7 @@ abstract class EncryptKey {
}

data class UseKeyParams(
val runtime: MicroModule.Runtime,
val runtime: KeychainNMM.KeyChainRuntime,
val remoteMmid: String,
val reason: UseKeyParams.UseKeyReason = UseKeyParams.UseKeyReason(),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import org.dweb_browser.core.module.MicroModule
import org.dweb_browser.core.module.startAppActivity
import org.dweb_browser.helper.randomUUID
import org.dweb_browser.sys.keychain.KeychainNMM

class KeychainActivity : ComponentActivity() {
companion object {
Expand All @@ -36,17 +35,23 @@ class KeychainActivity : ComponentActivity() {
)

suspend fun start(
runtime: KeychainNMM.KeyChainRuntime,
title: String? = null,
subtitle: String? = null,
description: String? = null,
background: (@Composable (Modifier) -> Unit)? = null,
) = auth.start(title, subtitle, description, background)
) = auth.start(runtime, title, subtitle, description)

private var uid = ""

override fun onResume() {
super.onResume()
uid = intent.getStringExtra("uid") ?: return finish()
creates[uid]?.complete(this)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent.getStringExtra("uid")?.also {
creates[it]?.complete(this)
}

setContent {
auth.Render()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ internal actual fun getDeviceName() = "${Build.BRAND}(power by Android)"
* 获取已经注册的认证方案
*/
internal actual fun getRegisteredMethod(): KeychainMethod? = getCustomRegisteredMethod()
internal actual suspend fun getSupportMethods(): List<KeychainMethod> =
internal actual fun getSupportMethods(): List<KeychainMethod> =
listOf(KeychainMethod.Password)
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package org.dweb_browser.sys.keychain.render

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
Expand Down Expand Up @@ -35,15 +42,17 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.SpanStyle
Expand All @@ -52,7 +61,9 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.min
import androidx.compose.ui.zIndex
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand All @@ -69,34 +80,41 @@ import org.dweb_browser.helper.platform.theme.LocalColorful
import org.dweb_browser.helper.platform.theme.dimens
import org.dweb_browser.helper.utf8String
import org.dweb_browser.helper.withMainContext
import org.dweb_browser.pure.image.compose.PureImageLoader
import org.dweb_browser.pure.image.compose.StableSmartLoad
import org.dweb_browser.sys.keychain.KeychainNMM
import org.dweb_browser.sys.window.core.helper.pickLargest
import org.dweb_browser.sys.window.core.helper.toStrict
import org.dweb_browser.sys.window.render.blobFetchHook

internal expect fun getDeviceName(): String

/**
* 获取已经注册的认证方案
*/
internal expect fun getRegisteredMethod(): KeychainMethod?
internal expect suspend fun getSupportMethods(): List<KeychainMethod>
internal expect fun getSupportMethods(): List<KeychainMethod>
internal fun getCustomRegisteredMethod() =
keychainMetadataStore.getItem(KeychainAuthentication.ROOT_KEY_METHOD)?.utf8String
.let { KeychainMethod.ALL[it] }

internal val keychainMetadataStore = DeviceKeyValueStore(KeychainAuthentication.KEYCHAIN_METADATA)

class KeychainAuthentication(
val onAuthRequestDismiss: () -> Unit,
internal var onAuthRequestDismiss: () -> Unit = {},
val lifecycleScope: CoroutineScope,
) {
) : ViewModel() {
companion object {
internal const val KEYCHAIN_METADATA = "Dweb-Keychain-Metadata"
internal const val ROOT_KEY_METHOD = "root-key-method"
internal const val ROOT_KEY_TIP = "root-key-tip"
internal const val ROOT_KEY_VERIFY = "root-key-verify"
internal const val ROOT_KEY_VERSION = "root-key-version"
private val supportMethods = getSupportMethods()
}

internal val viewModelTask = CompletableDeferred<ByteArray>().also {
it.invokeOnCompletion { throwable ->
internal val viewModelTask = CompletableDeferred<ByteArray>().also { task ->
task.invokeOnCompletion { throwable ->
lifecycleScope.launch {
if (throwable == null) {
val nav = navControllerFlow.first()
Expand All @@ -109,7 +127,6 @@ class KeychainAuthentication(
}
}
}
private val supportMethods = mutableStateListOf<KeychainMethod>()

suspend fun start(
title: String? = null,
Expand All @@ -121,10 +138,6 @@ class KeychainAuthentication(
this.subtitle = subtitle
this.description = description
this.background = background
if (supportMethods.isEmpty()) {
supportMethods.addAll(getSupportMethods())
}

println("QAQ KeychainActivity start")
val nav = navControllerFlow.first()
val route = when (val method = getRegisteredMethod()) {
Expand All @@ -138,13 +151,50 @@ class KeychainAuthentication(
viewModelTask.await()
}

suspend fun start(
runtime: KeychainNMM.KeyChainRuntime,
title: String? = null,
subtitle: String? = null,
description: String? = null,
) = start(
title, subtitle, description,
background = {
BoxWithConstraints(
modifier = Modifier.fillMaxSize().clip(RectangleShape)
.background(LocalColorful.current.Amber.current),
contentAlignment = Alignment.CenterEnd,
) {
remember { runtime.icons.toStrict().pickLargest() }?.also { icon ->
val size = min(maxWidth, maxHeight)
PureImageLoader.StableSmartLoad(icon.src, size, size, runtime.blobFetchHook)
.with { img ->
AnimatedVisibility(
visibleState = remember {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = true
}
},
enter = slideInHorizontally() + scaleIn()
) {
Image(img, null, modifier = Modifier.graphicsLayer {
rotationY = -15f
translationX = maxWidth.value / 4
})
}
}
}
}
},
)


private val navControllerFlow = MutableSharedFlow<NavController>(replay = 1)
private var title by mutableStateOf<String?>(null)
private var subtitle by mutableStateOf<String?>(null)
private var description by mutableStateOf<String?>(null)
private var background by mutableStateOf<(@Composable (Modifier) -> Unit)?>(null)


@Composable
fun ContentRender(closeBoolean: Boolean, modifier: Modifier = Modifier) {
val navController = rememberNavController()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ actual suspend fun tryThrowUserRejectAuth(
// 设置渲染
setContent {
val scope = rememberCoroutineScope()
val auth = remember { KeychainAuthentication({ reject("window close") }, scope) }
val auth = remember {
KeychainAuthentication(
onAuthRequestDismiss = { reject("User cancel") },
lifecycleScope = scope,
)
}

auth.ContentRender(closeBoolean = false, Modifier.fillMaxSize())
LaunchedEffect(Unit) {
val subtitle =
runtime.bootstrapContext.dns.query(remoteMmid)?.name?.let { "$it($remoteMmid)" }
?: remoteMmid
auth.start(title, subtitle, description)
auth.start(runtime, title, subtitle, description)
deferred.complete(Unit)

// 关闭窗口
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ internal actual fun getDeviceName(): String = PureViewController.osName
* 获取已经注册的认证方案
*/
internal actual fun getRegisteredMethod(): KeychainMethod? = KeychainMethod.Biometrics
internal actual suspend fun getSupportMethods(): List<KeychainMethod> =
internal actual fun getSupportMethods(): List<KeychainMethod> =
listOf(KeychainMethod.Biometrics)
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ internal actual fun getDeviceName(): String = "iOS"
* 获取已经注册的认证方案
*/
internal actual fun getRegisteredMethod(): KeychainMethod? = KeychainMethod.Biometrics
internal actual suspend fun getSupportMethods(): List<KeychainMethod> =
internal actual fun getSupportMethods(): List<KeychainMethod> =
listOf(KeychainMethod.Biometrics)

0 comments on commit 81dc71b

Please sign in to comment.