mixin
This commit is contained in:
@@ -5,15 +5,17 @@ import android.content.Intent
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import net.bluemangoo.indpjsk.data.CaptureProfile
|
||||
import net.bluemangoo.indpjsk.data.MixinProfiles
|
||||
import net.bluemangoo.indpjsk.data.PredefinedProfiles
|
||||
import net.bluemangoo.indpjsk.data.SettingsStore
|
||||
import net.bluemangoo.indpjsk.state.CaptureStateHolder
|
||||
import net.bluemangoo.indpjsk.state.CaptureStatus
|
||||
import net.bluemangoo.indpjsk.upload.UploadStore
|
||||
import net.bluemangoo.indpjsk.vpn.CaptureVpnService
|
||||
import net.bluemangoo.indpjsk.vpn.VpnLog
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.io.File
|
||||
|
||||
@@ -27,24 +29,28 @@ data class CaptureUiState(
|
||||
|
||||
class CaptureViewModel : ViewModel() {
|
||||
|
||||
val uiState: StateFlow<CaptureUiState> = CaptureStateHolder.state
|
||||
.map { s ->
|
||||
CaptureUiState(
|
||||
profiles = PredefinedProfiles.all,
|
||||
selectedProfile = s.profile,
|
||||
status = s.status,
|
||||
lastCapturedFile = s.lastCapturedFile,
|
||||
errorMessage = s.errorMessage
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = CaptureUiState(
|
||||
profiles = PredefinedProfiles.all,
|
||||
selectedProfile = PredefinedProfiles.all.firstOrNull()
|
||||
)
|
||||
private fun allProfiles(): List<CaptureProfile> = MixinProfiles.merged()
|
||||
|
||||
val uiState: StateFlow<CaptureUiState> = combine(
|
||||
CaptureStateHolder.state,
|
||||
UploadStore.revision,
|
||||
) { s, _ ->
|
||||
val profiles = allProfiles()
|
||||
CaptureUiState(
|
||||
profiles = profiles,
|
||||
selectedProfile = profiles.firstOrNull { it.id == s.profile.id } ?: s.profile,
|
||||
status = s.status,
|
||||
lastCapturedFile = s.lastCapturedFile,
|
||||
errorMessage = s.errorMessage
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = CaptureUiState(
|
||||
profiles = allProfiles(),
|
||||
selectedProfile = PredefinedProfiles.all.firstOrNull()
|
||||
)
|
||||
)
|
||||
|
||||
fun selectProfile(profile: CaptureProfile) {
|
||||
CaptureStateHolder.selectProfile(profile)
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.bluemangoo.indpjsk.R
|
||||
import net.bluemangoo.indpjsk.data.MixinProfiles
|
||||
import net.bluemangoo.indpjsk.upload.*
|
||||
import org.json.JSONObject
|
||||
|
||||
@@ -216,10 +217,6 @@ private fun UploadTableRow(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 / 编辑共用的界面。
|
||||
* 只有构造函数需要的字段需要用户填写;额外信息(extraInfo)只在下方展示。
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun UploadEditScreen(
|
||||
@@ -246,7 +243,7 @@ private fun UploadEditScreen(
|
||||
}
|
||||
var urlMode by remember(initial) {
|
||||
mutableStateOf(
|
||||
when (val url = (original as? UploadProfileCustom)?.uploadUrl) {
|
||||
when ((original as? UploadProfileCustom)?.uploadUrl) {
|
||||
is UploadUrlEach -> UploadStore.URL_MODE_EACH
|
||||
is UploadUrlGeneral -> UploadStore.URL_MODE_GENERAL
|
||||
null -> UploadStore.URL_MODE_GENERAL
|
||||
@@ -270,6 +267,13 @@ private fun UploadEditScreen(
|
||||
(original as? UploadProfileCustom)?.customHeaders?.map { it.key to it.value } ?: emptyList()
|
||||
)
|
||||
}
|
||||
var mixinText by remember(initial) {
|
||||
mutableStateOf(
|
||||
(original as? UploadProfileCustom)?.mixin
|
||||
?.let { UploadStore.encodeMixin(it).toString() }
|
||||
?: ""
|
||||
)
|
||||
}
|
||||
|
||||
var typeExpanded by remember { mutableStateOf(false) }
|
||||
var urlModeExpanded by remember { mutableStateOf(false) }
|
||||
@@ -291,7 +295,13 @@ private fun UploadEditScreen(
|
||||
|
||||
else -> null
|
||||
}
|
||||
val canSave = idError == null && codeError == null
|
||||
|
||||
val parsedMixin = runCatching { UploadStore.parseMixin(mixinText) }
|
||||
val mixinError = parsedMixin.exceptionOrNull()
|
||||
?.let { "Mixin JSON 解析失败:${it.message}" }
|
||||
?: parsedMixin.getOrNull()?.let { MixinProfiles.validate(it) }
|
||||
|
||||
val canSave = idError == null && codeError == null && mixinError == null
|
||||
|
||||
fun buildProfile(): UploadProfile = when (type) {
|
||||
UploadProfileType.HARUKI -> UploadProfileHaruki(trimmedId, uploadCode.trim())
|
||||
@@ -312,6 +322,7 @@ private fun UploadEditScreen(
|
||||
.map { it.first.trim() to it.second.trim() }
|
||||
.filter { it.first.isNotEmpty() }
|
||||
.toMap(),
|
||||
mixin = parsedMixin.getOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -340,7 +351,6 @@ private fun UploadEditScreen(
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// ID 和类型放在同一行
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -481,6 +491,12 @@ private fun UploadEditScreen(
|
||||
|
||||
HeadersEditor(headers = headers, onChange = { headers = it })
|
||||
|
||||
MixinEditor(
|
||||
value = mixinText,
|
||||
onValueChange = { mixinText = it.prependIndent().replace("\n", " ") },
|
||||
error = mixinError,
|
||||
)
|
||||
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
@@ -550,7 +566,14 @@ private fun UploadEditScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 160.dp),
|
||||
placeholder = { Text("{ ... }") },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "{\"type\":\"CUSTOM\",\"id\":\"my-upload\"," +
|
||||
"\"uploadUrl\":{\"mode\":\"general\",\"url\":\"https://example.com/upload\"}," +
|
||||
"\"headers\":{}}",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
importError?.let {
|
||||
@@ -585,6 +608,9 @@ private fun UploadEditScreen(
|
||||
}
|
||||
}
|
||||
headers = imported.customHeaders.map { it.key to it.value }
|
||||
mixinText = imported.mixin
|
||||
?.let { UploadStore.encodeMixin(it).toString() }
|
||||
?: ""
|
||||
importDialog = false
|
||||
}
|
||||
|
||||
@@ -702,6 +728,31 @@ private fun HeadersEditor(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MixinEditor(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
error: String?,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
FormRow("Mixin") {
|
||||
CompactTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier.weight(1f),
|
||||
isError = error != null,
|
||||
)
|
||||
}
|
||||
if (error != null) {
|
||||
Text(
|
||||
text = error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FormRow(
|
||||
label: String,
|
||||
@@ -803,7 +854,7 @@ private fun CompactDropdown(
|
||||
}
|
||||
}
|
||||
|
||||
private val helpLinkRegex = Regex("""\[([^\]]+)]\(([^)]+)\)""")
|
||||
private val helpLinkRegex = Regex("""\[([^]]+)]\(([^)]+)\)""")
|
||||
|
||||
@Composable
|
||||
private fun HelpText(text: String) {
|
||||
|
||||
Reference in New Issue
Block a user