From e19f098aa5483915534aa9f94f9d82d3fa0e6358 Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Fri, 25 Sep 2026 22:56:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20p12=20import=20=E2=80=94=20pick=20file?= =?UTF-8?q?=20first,=20then=20password=20(matches=20normal=20usage)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Choose .p12 button was gated on a non-blank password, which forced users to type the password before selecting the file. Now the file picker is always available; after selection the filename is shown, then the password field and an Import button appear. --- .../src/main/res/values-zh/strings.xml | 1 + .../src/main/res/values/strings.xml | 1 + .../settings/LoTWUploadConfigDialog.kt | 62 +++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/core/presentation/src/main/res/values-zh/strings.xml b/core/presentation/src/main/res/values-zh/strings.xml index 49097479..2cc39e27 100644 --- a/core/presentation/src/main/res/values-zh/strings.xml +++ b/core/presentation/src/main/res/values-zh/strings.xml @@ -202,6 +202,7 @@ 导入你的 TrustedQSL .p12 证书(与 TQSL 同款)用于签名上传通联。密码仅加密保存在手机本地。 证书密码 选择 .p12 文件 + 导入 %1$s · DXCC %2$d · 有效期至 %3$s 移除证书 台址 diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index ca8c90be..a51eb2aa 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -233,6 +233,7 @@ Import your TrustedQSL .p12 certificate (same one you use in TQSL) to sign and upload QSOs. The password is stored encrypted on this device only. Certificate password Choose .p12 file + Import %1$s · DXCC %2$d · expires %3$s Remove certificate Station location diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/LoTWUploadConfigDialog.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/LoTWUploadConfigDialog.kt index d76e0e7e..f3599c1c 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/LoTWUploadConfigDialog.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/LoTWUploadConfigDialog.kt @@ -9,6 +9,8 @@ */ package com.rtbishop.look4sat.feature.settings +import android.net.Uri +import android.provider.OpenableColumns import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable @@ -39,6 +41,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.rtbishop.look4sat.core.domain.repository.LoTWCertificate @@ -82,24 +85,24 @@ fun LoTWUploadConfigDialog( onSaveStation: (LoTWStation) -> Unit ) { var password by remember { mutableStateOf("") } + var selectedFile by remember { mutableStateOf(null) } var grid by remember { mutableStateOf(station?.grid.orEmpty()) } var cqZone by remember { mutableStateOf(station?.cqZone.orEmpty()) } var ituZone by remember { mutableStateOf(station?.ituZone.orEmpty()) } var iota by remember { mutableStateOf(station?.iota.orEmpty()) } val context = LocalContext.current + // Pick the file first, then ask for the password — matches normal usage. val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - if (uri != null && password.isNotBlank()) { - val bytes = runCatching { - context.contentResolver.openInputStream(uri)?.use { it.readBytes() } - }.getOrNull() - if (bytes != null && bytes.isNotEmpty()) { - onImport(bytes, password.toCharArray()) - password = "" - } - } + if (uri != null) selectedFile = uri } + fun displayName(uri: Uri): String = runCatching { + context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) cursor.getString(0) else null + } + }.getOrNull() ?: uri.lastPathSegment.orEmpty() + AlertDialog( onDismissRequest = onDismiss, title = { Text(stringResource(R.string.prefs_lotw_upload_title)) }, @@ -113,19 +116,42 @@ fun LoTWUploadConfigDialog( } if (certificate == null) { Text(stringResource(R.string.prefs_lotw_upload_cert_hint), fontSize = 13.sp) - OutlinedTextField( - value = password, - onValueChange = { password = it }, - label = { Text(stringResource(R.string.prefs_lotw_upload_password), fontSize = 13.sp) }, - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - modifier = Modifier.fillMaxWidth() - ) Button( onClick = { filePicker.launch(arrayOf("*/*")) }, - enabled = password.isNotBlank() && !busy, + enabled = !busy, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.prefs_lotw_upload_import), fontSize = 13.sp) } + selectedFile?.let { uri -> + Text( + text = displayName(uri), + fontSize = 12.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text(stringResource(R.string.prefs_lotw_upload_password), fontSize = 13.sp) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth() + ) + Button( + onClick = { + val bytes = runCatching { + context.contentResolver.openInputStream(uri)?.use { it.readBytes() } + }.getOrNull() + if (bytes != null && bytes.isNotEmpty()) { + onImport(bytes, password.toCharArray()) + password = "" + selectedFile = null + } + }, + enabled = password.isNotBlank() && !busy, + modifier = Modifier.fillMaxWidth() + ) { Text(stringResource(R.string.prefs_lotw_upload_import_confirm), fontSize = 13.sp) } + } } else { Text(stringResource(R.string.prefs_lotw_upload_cert_info, certificate.callsign, certificate.dxcc, certificate.expires), fontSize = 13.sp) OutlinedButton(onClick = onRemove, enabled = !busy) {