mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(lotw): PBES2 .p12 parsing, station region fields, upload error codes
This commit is contained in:
1 parent
5957f30af2
commit
3be2e4b9e8
8 files changed
+504
-23
No files matched your search
+109
-7
@@ -13,27 +13,37 @@ import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
@@ -43,7 +53,9 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.rtbishop.look4sat.core.domain.repository.LoTWCertificate
|
||||
import com.rtbishop.look4sat.core.domain.repository.LoTWStation
|
||||
import com.rtbishop.look4sat.core.domain.repository.LoTWStationMeta
|
||||
import com.rtbishop.look4sat.core.presentation.CardButton
|
||||
import com.rtbishop.look4sat.core.presentation.LocalSpacing
|
||||
import com.rtbishop.look4sat.core.presentation.R
|
||||
import com.rtbishop.look4sat.core.presentation.SharedDialog
|
||||
|
||||
@@ -77,6 +89,7 @@ fun LoTWUploadCard(
|
||||
fun LoTWUploadConfigDialog(
|
||||
certificate: LoTWCertificate?,
|
||||
station: LoTWStation?,
|
||||
stationMeta: LoTWStationMeta?,
|
||||
busy: Boolean,
|
||||
error: LoTWUploadError?,
|
||||
onDismiss: () -> Unit,
|
||||
@@ -90,6 +103,26 @@ fun LoTWUploadConfigDialog(
|
||||
var cqZone by remember { mutableStateOf(station?.cqZone.orEmpty()) }
|
||||
var ituZone by remember { mutableStateOf(station?.ituZone.orEmpty()) }
|
||||
var iota by remember { mutableStateOf(station?.iota.orEmpty()) }
|
||||
var region by remember { mutableStateOf(station?.region.orEmpty()) }
|
||||
// Countries whose national zonemap has exactly one pair (e.g. Germany, India) get
|
||||
// their CQZ/ITUZ prefilled; multi-zone countries are left blank to avoid a wrong
|
||||
// default that would mislead (e.g. Guangdong would show the Heilongjiang zone).
|
||||
LaunchedEffect(stationMeta) {
|
||||
val meta = stationMeta ?: return@LaunchedEffect
|
||||
val zones = meta.countryZones
|
||||
if (zones.size == 1 && cqZone.isBlank() && ituZone.isBlank()) {
|
||||
cqZone = zones[0].cq.toString()
|
||||
ituZone = zones[0].itu.toString()
|
||||
}
|
||||
// A region saved under a previous certificate (different DXCC) no longer
|
||||
// applies — clear it so saving doesn't fail validation.
|
||||
val metaRegion = meta.regionField
|
||||
if (metaRegion != null && region.isNotBlank() && metaRegion.options.none { it.code == region }) {
|
||||
region = ""
|
||||
}
|
||||
}
|
||||
val regionField = stationMeta?.regionField
|
||||
val selectedRegionName = regionField?.options?.firstOrNull { it.code == region }?.name.orEmpty()
|
||||
val context = LocalContext.current
|
||||
|
||||
// Pick the file first, then ask for the password — matches normal usage.
|
||||
@@ -109,13 +142,17 @@ fun LoTWUploadConfigDialog(
|
||||
onCancel = onDismiss,
|
||||
onAccept = null
|
||||
) {
|
||||
if (busy) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
CircularProgressIndicator(modifier = Modifier.height(20.dp), strokeWidth = 2.dp)
|
||||
Text(stringResource(R.string.prefs_lotw_upload_busy), fontSize = 13.sp)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = LocalSpacing.current.large),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
if (busy) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
CircularProgressIndicator(modifier = Modifier.height(20.dp), strokeWidth = 2.dp)
|
||||
Text(stringResource(R.string.prefs_lotw_upload_busy), fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (certificate == null) {
|
||||
if (certificate == null) {
|
||||
Text(stringResource(R.string.prefs_lotw_upload_cert_hint), fontSize = 13.sp)
|
||||
CardButton(
|
||||
onClick = { filePicker.launch(arrayOf("*/*")) },
|
||||
@@ -130,6 +167,7 @@ fun LoTWUploadConfigDialog(
|
||||
LoTWUploadError.PASSWORD -> R.string.prefs_lotw_upload_error_password
|
||||
LoTWUploadError.EXPIRED -> R.string.prefs_lotw_upload_error_expired
|
||||
LoTWUploadError.INVALID_FILE -> R.string.prefs_lotw_upload_error_invalid
|
||||
LoTWUploadError.FORMAT -> R.string.prefs_lotw_upload_error_format
|
||||
LoTWUploadError.UNKNOWN -> R.string.prefs_lotw_upload_error_unknown
|
||||
}
|
||||
),
|
||||
@@ -185,6 +223,69 @@ fun LoTWUploadConfigDialog(
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(capitalization = KeyboardCapitalization.Characters),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
// Country-specific region field (US_STATE, CN_PROVINCE, …) shown only when
|
||||
// the certificate's DXCC entity defines one; selecting it fills CQZ/ITUZ.
|
||||
// Drawn as a plain Box (not OutlinedTextField): a read-only text field's
|
||||
// internal gesture handler consumes the tap, so clickable never fires.
|
||||
// Options expand inline inside the sheet (no Popup/Dialog window stacking).
|
||||
if (regionField != null) {
|
||||
var regionExpanded by remember { mutableStateOf(false) }
|
||||
val fieldShape = MaterialTheme.shapes.extraSmall
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(fieldShape)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.border(1.dp, MaterialTheme.colorScheme.outline, fieldShape)
|
||||
.clickable { regionExpanded = !regionExpanded }
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
) {
|
||||
Column {
|
||||
Text(regionField.label, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = if (region.isBlank()) {
|
||||
stringResource(R.string.prefs_lotw_upload_region_hint)
|
||||
} else {
|
||||
"$region — $selectedRegionName"
|
||||
},
|
||||
fontSize = 16.sp,
|
||||
color = if (region.isBlank()) {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Text(
|
||||
text = if (regionExpanded) "▴" else "▾",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(visible = regionExpanded) {
|
||||
LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp)) {
|
||||
items(regionField.options, key = { it.code }) { option ->
|
||||
DropdownMenuItem(
|
||||
text = { Text("${option.code} — ${option.name}", fontSize = 13.sp) },
|
||||
onClick = {
|
||||
region = option.code
|
||||
regionExpanded = false
|
||||
option.zones.firstOrNull()?.let { zone ->
|
||||
cqZone = zone.cq.toString()
|
||||
ituZone = zone.itu.toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
OutlinedTextField(
|
||||
value = cqZone,
|
||||
@@ -209,10 +310,11 @@ fun LoTWUploadConfigDialog(
|
||||
)
|
||||
}
|
||||
CardButton(
|
||||
onClick = { onSaveStation(LoTWStation(grid, cqZone, ituZone, "", "", iota)) },
|
||||
onClick = { onSaveStation(LoTWStation(grid, cqZone, ituZone, region, "", iota)) },
|
||||
text = stringResource(R.string.prefs_lotw_upload_save),
|
||||
isEnabled = grid.isNotBlank() && !busy,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user