mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
800494b978 | ||
|
|
f1091acbbd | ||
|
|
99bf3d34a9 | ||
|
|
1dd185971f | ||
|
|
fe85703e56 | ||
|
|
caddba5b81 |
No files matched your search
@@ -24,14 +24,6 @@ internal class LoTWSigner(private val config: LoTWConfig) {
|
|||||||
if (date < key.info.firstQsoDate || (key.info.lastQsoDate.isNotBlank() && date > key.info.lastQsoDate) || record.startUtcMillis > now) {
|
if (date < key.info.firstQsoDate || (key.info.lastQsoDate.isNotBlank() && date > key.info.lastQsoDate) || record.startUtcMillis > now) {
|
||||||
fail(LoTWProblem.QSO_DATE, call)
|
fail(LoTWProblem.QSO_DATE, call)
|
||||||
}
|
}
|
||||||
val grids = buildList {
|
|
||||||
addAll(station.getValue("GRIDSQUARE").split(',').map(String::trim))
|
|
||||||
station["MY_VUCC_GRIDS"]?.split(',')?.map(String::trim)?.let(::addAll)
|
|
||||||
}.filter(String::isNotBlank)
|
|
||||||
if (record.myGrid.isNotBlank() && grids.none { grid ->
|
|
||||||
val local = record.myGrid.trim().uppercase(Locale.US)
|
|
||||||
grid.startsWith(local) || local.startsWith(grid)
|
|
||||||
}) fail(LoTWProblem.LOCATION_MISMATCH, call)
|
|
||||||
fun mhz(hz: Long?): String = hz?.let { BigDecimal.valueOf(it, 6).stripTrailingZeros().toPlainString() }.orEmpty()
|
fun mhz(hz: Long?): String = hz?.let { BigDecimal.valueOf(it, 6).stripTrailingZeros().toPlainString() }.orEmpty()
|
||||||
val fields = linkedMapOf(
|
val fields = linkedMapOf(
|
||||||
"BAND" to config.band(record.band, record.txFrequencyHz, true),
|
"BAND" to config.band(record.band, record.txFrequencyHz, true),
|
||||||
@@ -46,8 +38,12 @@ internal class LoTWSigner(private val config: LoTWConfig) {
|
|||||||
"SAT_NAME" to if (record.isSatellite) config.satellite(record.satelliteName, date) else ""
|
"SAT_NAME" to if (record.isSatellite) config.satellite(record.satelliteName, date) else ""
|
||||||
).filterValues { it.isNotBlank() }
|
).filterValues { it.isNotBlank() }
|
||||||
val signData = (config.stationOrder.map { station[it].orEmpty() } + config.contactOrder.map { fields[it].orEmpty() }).joinToString("")
|
val signData = (config.stationOrder.map { station[it].orEmpty() } + config.contactOrder.map { fields[it].orEmpty() }).joinToString("")
|
||||||
|
// Fingerprint identifies the CONTACT only (call, date/time, band, mode,
|
||||||
|
// satellite, frequency). Station fields (grid, zones, county, IOTA) are
|
||||||
|
// deliberately excluded: changing the station location must not change
|
||||||
|
// the fingerprint, otherwise previously-uploaded contacts would lose
|
||||||
|
// their ledger entry and be re-uploaded (and rejected as duplicates).
|
||||||
val identity = field("CALL", key.info.callsign) + field("DXCC", key.info.dxcc.toString()) +
|
val identity = field("CALL", key.info.callsign) + field("DXCC", key.info.dxcc.toString()) +
|
||||||
station.toSortedMap().entries.joinToString("") { field(it.key, it.value) } +
|
|
||||||
fields.entries.joinToString("") { field(it.key, it.value) }
|
fields.entries.joinToString("") { field(it.key, it.value) }
|
||||||
val hash = MessageDigest.getInstance("SHA-256").digest(identity.toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it) }
|
val hash = MessageDigest.getInstance("SHA-256").digest(identity.toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it) }
|
||||||
return LoTWContact(record, fields, signData, hash)
|
return LoTWContact(record, fields, signData, hash)
|
||||||
|
|||||||
+14
-4
@@ -167,7 +167,7 @@ class LoTWUploadRepository internal constructor(
|
|||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
when {
|
when {
|
||||||
record.status != QsoStatus.COMPLETE -> unavailable++
|
record.status != QsoStatus.COMPLETE -> unavailable++
|
||||||
record.lotwReceived -> uploaded++
|
record.lotwReceived || record.lotwUploaded -> uploaded++
|
||||||
else -> {
|
else -> {
|
||||||
val contact = try {
|
val contact = try {
|
||||||
signing.signer.contact(record, signing.key, signing.location, now())
|
signing.signer.contact(record, signing.key, signing.location, now())
|
||||||
@@ -196,11 +196,19 @@ class LoTWUploadRepository internal constructor(
|
|||||||
val ledger = ledger()
|
val ledger = ledger()
|
||||||
var skipped = 0
|
var skipped = 0
|
||||||
var unknown = 0
|
var unknown = 0
|
||||||
|
var unavailable = 0
|
||||||
val unique = hashSetOf<String>()
|
val unique = hashSetOf<String>()
|
||||||
val contacts = records.sortedBy { it.startUtcMillis }.mapNotNull { record ->
|
val contacts = records.sortedBy { it.startUtcMillis }.mapNotNull { record ->
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
if (record.status != QsoStatus.COMPLETE || (record.lotwReceived && !resubmit)) { skipped++; return@mapNotNull null }
|
if (record.status != QsoStatus.COMPLETE || ((record.lotwReceived || record.lotwUploaded) && !resubmit)) { skipped++; return@mapNotNull null }
|
||||||
val contact = signing.signer.contact(record, signing.key, signing.location, now())
|
val contact = try {
|
||||||
|
signing.signer.contact(record, signing.key, signing.location, now())
|
||||||
|
} catch (_: LoTWOperationException) {
|
||||||
|
// One un-signable record must not abort the whole batch:
|
||||||
|
// skip it, count it, and let the rest upload.
|
||||||
|
unavailable++
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
val previous = ledger[contact.fingerprint]
|
val previous = ledger[contact.fingerprint]
|
||||||
when {
|
when {
|
||||||
(previous == "accepted" && !resubmit) || !unique.add(contact.fingerprint) -> { skipped++; null }
|
(previous == "accepted" && !resubmit) || !unique.add(contact.fingerprint) -> { skipped++; null }
|
||||||
@@ -214,7 +222,9 @@ class LoTWUploadRepository internal constructor(
|
|||||||
contacts.firstOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
contacts.firstOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
||||||
contacts.lastOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
contacts.lastOrNull()?.record?.let { utc(it.startUtcMillis, "yyyy-MM-dd HH:mm:ss") }.orEmpty(),
|
||||||
contacts.map { "${utc(it.record.startUtcMillis, "MM-dd HH:mm")} ${it.record.theirCallsign} ${it.fields["MODE"]} ${it.fields["SAT_NAME"].orEmpty()}" },
|
contacts.map { "${utc(it.record.startUtcMillis, "MM-dd HH:mm")} ${it.record.theirCallsign} ${it.fields["MODE"]} ${it.fields["SAT_NAME"].orEmpty()}" },
|
||||||
unknown
|
unknown,
|
||||||
|
unavailable,
|
||||||
|
contacts.map { it.record.id }
|
||||||
)
|
)
|
||||||
if (contacts.isNotEmpty()) pending = Pending(
|
if (contacts.isNotEmpty()) pending = Pending(
|
||||||
preview,
|
preview,
|
||||||
|
|||||||
+6
-1
@@ -66,7 +66,12 @@ data class LoTWUploadPreview(
|
|||||||
val firstUtc: String,
|
val firstUtc: String,
|
||||||
val lastUtc: String,
|
val lastUtc: String,
|
||||||
val contacts: List<String>,
|
val contacts: List<String>,
|
||||||
val unknownSkipped: Int = 0
|
val unknownSkipped: Int = 0,
|
||||||
|
/** Un-signable records (invalid call/date/…) skipped instead of aborting the batch. */
|
||||||
|
val unavailableSkipped: Int = 0,
|
||||||
|
/** Ids of the records that actually made it into this TQ8 batch. Only these
|
||||||
|
* may be marked "uploaded" after an accepted POST — never the full candidate list. */
|
||||||
|
val submittedIds: List<Long> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Local comparison against downloaded LoTW receipt flags and this app's durable upload receipts. */
|
/** Local comparison against downloaded LoTW receipt flags and this app's durable upload receipts. */
|
||||||
|
|||||||
@@ -294,6 +294,14 @@ private fun UploadPreviewDialog(
|
|||||||
}.parse(it).time }.getOrDefault(System.currentTimeMillis())
|
}.parse(it).time }.getOrDefault(System.currentTimeMillis())
|
||||||
}))
|
}))
|
||||||
}Z", fontSize = 13.sp)
|
}Z", fontSize = 13.sp)
|
||||||
|
if (preview.skipped > 0 || preview.unknownSkipped > 0 || preview.unavailableSkipped > 0) {
|
||||||
|
val parts = buildList {
|
||||||
|
if (preview.skipped > 0) add("${preview.skipped} already uploaded/duplicate")
|
||||||
|
if (preview.unknownSkipped > 0) add("${preview.unknownSkipped} unknown result")
|
||||||
|
if (preview.unavailableSkipped > 0) add("${preview.unavailableSkipped} un-uploadable")
|
||||||
|
}
|
||||||
|
Text(parts.joinToString(" · "), fontSize = 12.sp, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -160,11 +160,18 @@ class LogViewModel(
|
|||||||
val pending = all.filter { !it.lotwConfirmed && it.status == QsoStatus.COMPLETE }
|
val pending = all.filter { !it.lotwConfirmed && it.status == QsoStatus.COMPLETE }
|
||||||
val audit = lotwUploadRepository.audit(pending)
|
val audit = lotwUploadRepository.audit(pending)
|
||||||
if (audit.pending == 0) {
|
if (audit.pending == 0) {
|
||||||
_uiState.update { it.copy(busy = false, message = "No pending QSOs to upload") }
|
val msg = when {
|
||||||
|
audit.unavailable > 0 -> "${audit.unavailable} QSO(s) can't be uploaded (invalid call/date — check the logbook)"
|
||||||
|
audit.unknown > 0 -> "${audit.unknown} QSO(s) had an unknown upload result — not retried automatically"
|
||||||
|
else -> "No pending QSOs to upload"
|
||||||
|
}
|
||||||
|
_uiState.update { it.copy(busy = false, message = msg) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val preview = lotwUploadRepository.prepare(pending, false)
|
val preview = lotwUploadRepository.prepare(pending, false)
|
||||||
lastUploadedIds = pending.map { it.id }
|
// Only the records that actually made it into the TQ8 may be
|
||||||
|
// marked uploaded later — never the whole candidate list.
|
||||||
|
lastUploadedIds = preview.submittedIds
|
||||||
_uiState.update { it.copy(busy = false, preview = preview) }
|
_uiState.update { it.copy(busy = false, preview = preview) }
|
||||||
} catch (e: LoTWOperationException) {
|
} catch (e: LoTWOperationException) {
|
||||||
_uiState.update { it.copy(busy = false, message = "Upload unavailable: ${e.reason}") }
|
_uiState.update { it.copy(busy = false, message = "Upload unavailable: ${e.reason}") }
|
||||||
|
|||||||
@@ -30,12 +30,14 @@ import androidx.compose.animation.core.tween
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
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.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
@@ -74,7 +76,6 @@ import com.rtbishop.look4sat.core.presentation.TimerRow
|
|||||||
import com.rtbishop.look4sat.core.presentation.TopBar
|
import com.rtbishop.look4sat.core.presentation.TopBar
|
||||||
import com.rtbishop.look4sat.core.presentation.formatFrequency
|
import com.rtbishop.look4sat.core.presentation.formatFrequency
|
||||||
import com.rtbishop.look4sat.core.presentation.getDefaultPass
|
import com.rtbishop.look4sat.core.presentation.getDefaultPass
|
||||||
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
|
||||||
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -87,6 +88,9 @@ private enum class RadarPage(val title: String) {
|
|||||||
Sstv("SSTV")
|
Sstv("SSTV")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Compact pager strip height in split-screen / small windows. */
|
||||||
|
private val COMPACT_PAGER_HEIGHT = 132.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RadarDestination(navigateUp: () -> Unit) {
|
fun RadarDestination(navigateUp: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -172,36 +176,54 @@ private fun RadarScreen(
|
|||||||
time = it.time
|
time = it.time
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
BoxWithConstraints(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.layoutPadding()
|
.layoutPadding()
|
||||||
.padding(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
|
.padding(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
|
||||||
.keepScreenOn(),
|
.keepScreenOn()
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
|
||||||
) {
|
) {
|
||||||
val isVertical = isVerticalLayout()
|
// Measure the ACTUAL window constraints instead of the window size
|
||||||
if (isVertical) {
|
// class: currentWindowAdaptiveInfo() often reports the full-screen
|
||||||
TopBar {
|
// size in split-screen / multi-window, so the compact branch never
|
||||||
IconCard(action = navigateUp, resId = R.drawable.ic_back)
|
// triggered there. maxHeight/maxWidth are the real window bounds.
|
||||||
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
|
val isVertical = maxWidth < 600.dp
|
||||||
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
|
// In split-screen / small windows the vertical space is scarce: let the
|
||||||
|
// radar card fill ALL remaining space so the square plot is as large as
|
||||||
|
// possible, and give the pager a compact fixed strip — its pages are all
|
||||||
|
// scrollable, so nothing gets clipped. Full-screen layouts keep the 1:1 split.
|
||||||
|
val compact = isVertical && maxHeight < 480.dp
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
if (isVertical) {
|
||||||
|
TopBar {
|
||||||
|
IconCard(action = navigateUp, resId = R.drawable.ic_back)
|
||||||
|
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
|
||||||
|
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
|
||||||
|
}
|
||||||
|
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
|
||||||
|
} else {
|
||||||
|
TopBar {
|
||||||
|
IconCard(action = navigateUp, resId = R.drawable.ic_back)
|
||||||
|
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
|
||||||
|
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
|
||||||
|
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
|
if (isVertical) {
|
||||||
} else {
|
if (compact) {
|
||||||
TopBar {
|
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
|
||||||
IconCard(action = navigateUp, resId = R.drawable.ic_back)
|
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.height(COMPACT_PAGER_HEIGHT))
|
||||||
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
|
} else {
|
||||||
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
|
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
|
||||||
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
|
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (isVertical) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
|
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
|
||||||
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
|
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
|
||||||
} else {
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
||||||
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
|
|
||||||
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,14 @@ private fun LogbookUploadPreviewDialog(
|
|||||||
"${preview.count} QSO(s) · ${preview.firstUtc} – ${preview.lastUtc}",
|
"${preview.count} QSO(s) · ${preview.firstUtc} – ${preview.lastUtc}",
|
||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
)
|
)
|
||||||
|
if (preview.skipped > 0 || preview.unknownSkipped > 0 || preview.unavailableSkipped > 0) {
|
||||||
|
val parts = buildList {
|
||||||
|
if (preview.skipped > 0) add("${preview.skipped} already uploaded/duplicate")
|
||||||
|
if (preview.unknownSkipped > 0) add("${preview.unknownSkipped} unknown result")
|
||||||
|
if (preview.unavailableSkipped > 0) add("${preview.unavailableSkipped} un-uploadable")
|
||||||
|
}
|
||||||
|
Text(parts.joinToString(" · "), fontSize = 12.sp, color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
Text(preview.contacts.joinToString("\n") { it }, fontSize = 12.sp, maxLines = 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+13
-2
@@ -163,6 +163,10 @@ class SettingsViewModel(
|
|||||||
_uiState.update { it.copy(logbookRecords = records) }
|
_uiState.update { it.copy(logbookRecords = records) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Load the LoTW upload certificate + station once at startup so the
|
||||||
|
// settings card reflects the real state on first frame (previously it
|
||||||
|
// stayed "not imported" until the config dialog was opened).
|
||||||
|
loadLoTWUploadStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -363,11 +367,18 @@ class SettingsViewModel(
|
|||||||
val pending = all.filter { !it.lotwConfirmed && it.status == com.rtbishop.look4sat.core.domain.logbook.QsoStatus.COMPLETE }
|
val pending = all.filter { !it.lotwConfirmed && it.status == com.rtbishop.look4sat.core.domain.logbook.QsoStatus.COMPLETE }
|
||||||
val audit = lotwUploadRepository.audit(pending)
|
val audit = lotwUploadRepository.audit(pending)
|
||||||
if (audit.pending == 0) {
|
if (audit.pending == 0) {
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "No pending QSOs to upload") }
|
val msg = when {
|
||||||
|
audit.unavailable > 0 -> "${audit.unavailable} QSO(s) can't be uploaded (invalid call/date — check the logbook)"
|
||||||
|
audit.unknown > 0 -> "${audit.unknown} QSO(s) had an unknown upload result — not retried automatically"
|
||||||
|
else -> "No pending QSOs to upload"
|
||||||
|
}
|
||||||
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = msg) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val preview = lotwUploadRepository.prepare(pending, false)
|
val preview = lotwUploadRepository.prepare(pending, false)
|
||||||
lastLogbookUploadIds = pending.map { it.id }
|
// Only the records that actually made it into the TQ8 may be
|
||||||
|
// marked uploaded later — never the whole candidate list.
|
||||||
|
lastLogbookUploadIds = preview.submittedIds
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookPreview = preview) }
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookPreview = preview) }
|
||||||
} catch (e: com.rtbishop.look4sat.core.domain.repository.LoTWOperationException) {
|
} catch (e: com.rtbishop.look4sat.core.domain.repository.LoTWOperationException) {
|
||||||
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "Upload unavailable: ${e.reason}") }
|
_uiState.update { it.copy(logbookUploadBusy = false, logbookUploadMessage = "Upload unavailable: ${e.reason}") }
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "526"
|
appVersionCode = "529"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.4.7-ba7opf.17.3"
|
appVersionName = "4.4.7-ba7opf.17.6"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
|
|||||||
Reference in new issue
Block a user