Feat(ToolView): Support alert, confirm and prompt

This commit is contained in:
2024-10-14 16:49:10 +08:00
parent fc4baca21d
commit 2823788765
3 changed files with 155 additions and 9 deletions

View File

@@ -164,7 +164,7 @@ internal fun LibrariesScreen(
onSearch("") onSearch("")
} }
) )
Box(modifier = Modifier) { Box {
when (librariesScreenUiState) { when (librariesScreenUiState) {
LibrariesScreenUiState.Loading -> { LibrariesScreenUiState.Loading -> {
Indicator() Indicator()
@@ -252,10 +252,7 @@ internal fun LibrariesScreen(
Column( Column(
modifier = Modifier.verticalScroll(state = rememberScrollState()) modifier = Modifier.verticalScroll(state = rememberScrollState())
) { ) {
Text( Text(text = dialogContent)
modifier = Modifier,
text = dialogContent
)
} }
}, },
confirmButton = { confirmButton = {

View File

@@ -167,7 +167,6 @@ private fun ToolAction(
.padding(horizontal = 6.dp, vertical = 6.dp) .padding(horizontal = 6.dp, vertical = 6.dp)
) { ) {
Icon( Icon(
modifier = Modifier,
imageVector = actionIcon, imageVector = actionIcon,
contentDescription = actionIconContentDescription contentDescription = actionIconContentDescription
) )

View File

@@ -8,6 +8,8 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import android.webkit.ConsoleMessage import android.webkit.ConsoleMessage
import android.webkit.JsPromptResult
import android.webkit.JsResult
import android.webkit.ValueCallback import android.webkit.ValueCallback
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.ManagedActivityResultLauncher
@@ -16,18 +18,30 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -35,9 +49,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.kevinnzou.web.AccompanistWebChromeClient import com.kevinnzou.web.AccompanistWebChromeClient
@@ -139,14 +155,21 @@ private fun Content(
toolViewUiState: ToolViewUiState, toolViewUiState: ToolViewUiState,
webViewInstanceState: WebViewInstanceState webViewInstanceState: WebViewInstanceState
) { ) {
val configuration = LocalConfiguration.current
val context = LocalContext.current val context = LocalContext.current
var fileChooserCallback by remember { mutableStateOf<ValueCallback<Array<Uri>>?>(null) } var fileChooserCallback by remember { mutableStateOf<ValueCallback<Array<Uri>>?>(null) }
val fileChooserLauncher = rememberFileChooserLauncher(fileChooserCallback) val fileChooserLauncher = rememberFileChooserLauncher(fileChooserCallback)
val permissionLauncher = rememberPermissionLauncher() val permissionLauncher = rememberPermissionLauncher()
var isShowDialog = remember { mutableStateOf(false) }
var dialogType = remember { mutableStateOf(DialogType.Alert) }
var dialogTitle by remember { mutableStateOf("") }
var dialogText = remember { mutableStateOf("") }
var dialogInputValue = remember { mutableStateOf("") }
var onDialogConfirm = remember { mutableStateOf<((String) -> Unit)?>(null) }
var onDialogCancel = remember { mutableStateOf<(() -> Unit)?>(null) }
when (webViewInstanceState) { when (webViewInstanceState) {
WebViewInstanceState.Loading -> { WebViewInstanceState.Loading -> {
Indicator() Indicator()
@@ -169,6 +192,7 @@ private fun Content(
} }
is ToolViewUiState.Success -> { is ToolViewUiState.Success -> {
dialogTitle = toolViewUiState.toolName
val webViewState = rememberWebViewStateWithHTMLData( val webViewState = rememberWebViewStateWithHTMLData(
data = toolViewUiState.htmlData, data = toolViewUiState.htmlData,
) )
@@ -179,7 +203,13 @@ private fun Content(
state = webViewState, state = webViewState,
chromeClient = rememberChromeClient( chromeClient = rememberChromeClient(
context = context, context = context,
fileChooserLauncher = fileChooserLauncher fileChooserLauncher = fileChooserLauncher,
isShowDialog = isShowDialog,
dialogType = dialogType,
dialogText = dialogText,
dialogInputValue = dialogInputValue,
onDialogConfirm = onDialogConfirm,
onDialogCancel = onDialogCancel
) { ) {
fileChooserCallback = it fileChooserCallback = it
}, },
@@ -195,6 +225,60 @@ private fun Content(
} }
} }
} }
if (isShowDialog.value) {
AlertDialog(
modifier = Modifier
.widthIn(max = configuration.screenWidthDp.dp - 80.dp)
.heightIn(max = configuration.screenHeightDp.dp - 40.dp),
onDismissRequest = {},
title = {
Text(
text = dialogTitle,
style = MaterialTheme.typography.titleLarge
)
},
text = {
Column {
Column(
modifier = Modifier.verticalScroll(state = rememberScrollState())
) {
Text(text = dialogText.value)
}
if (dialogType.value == DialogType.Prompt) {
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = dialogInputValue.value,
onValueChange = {
dialogInputValue.value = it
},
textStyle = MaterialTheme.typography.bodyMedium
)
}
}
},
confirmButton = {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextButton(onClick = {
isShowDialog.value = false
onDialogConfirm.value?.invoke(dialogInputValue.value)
}) {
Text(text = stringResource(R.string.core_ok))
}
if (dialogType.value != DialogType.Alert) {
TextButton(onClick = {
isShowDialog.value = false
onDialogCancel.value?.invoke()
}) {
Text(text = stringResource(R.string.core_cancel))
}
}
}
}
)
}
} }
@Composable @Composable
@@ -256,6 +340,12 @@ private fun initWebView(
private fun rememberChromeClient( private fun rememberChromeClient(
context: Context, context: Context,
fileChooserLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>, fileChooserLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
isShowDialog: MutableState<Boolean>,
dialogType: MutableState<DialogType>,
dialogText: MutableState<String>,
dialogInputValue: MutableState<String>,
onDialogConfirm: MutableState<((String) -> Unit)?>,
onDialogCancel: MutableState<(() -> Unit)?>,
processCallback: (ValueCallback<Array<Uri>>?) -> Unit processCallback: (ValueCallback<Array<Uri>>?) -> Unit
) = remember { ) = remember {
object : AccompanistWebChromeClient() { object : AccompanistWebChromeClient() {
@@ -299,5 +389,65 @@ private fun rememberChromeClient(
return true return true
} }
override fun onJsAlert(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Alert
dialogText.value = message ?: ""
onDialogConfirm.value = {
result?.confirm()
}
return true
}
override fun onJsConfirm(
view: WebView?,
url: String?,
message: String?,
result: JsResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Confirm
dialogText.value = message ?: ""
onDialogConfirm.value = {
result?.confirm()
}
onDialogCancel.value = {
result?.cancel()
}
return true
}
override fun onJsPrompt(
view: WebView?,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult?
): Boolean {
isShowDialog.value = true
dialogType.value = DialogType.Prompt
dialogText.value = message ?: ""
dialogInputValue.value = defaultValue ?: ""
onDialogConfirm.value = {
result?.confirm(dialogInputValue.value)
}
onDialogCancel.value = {
result?.cancel()
}
return true
} }
} }
}
private enum class DialogType {
Alert, Confirm, Prompt
}