feat: LoTW upload + logbook (port from Iu-yang1's fork)

- Port TQ8 signing/upload pipeline (core/data/lotw: LoTWConfig, LoTWKeyMaterial,
  LoTWSigner, LoTWStorage, LoTWUploadRepository) with bundled ARRL config.tq6
- Multi-grid station locations: primary grid in GRIDSQUARE + full set in
  MY_VUCC_GRIDS (unsigned tSTATION extra, IOTA precedent); signer matches
  myGrid against the whole grid set
- Satellite-name normalization: SatNOGS names ("SO-50 (SaudiOSCAR 50)") resolve
  to official LoTW names by exact-then-substring match
- Logbook storage: separate QsoDatabase (version 1, no migration on existing
  installs), QsoRepository with dedupe/confirmation merge; LoTW sync (manual +
  auto) feeds confirmations into the logbook and marks local uploads confirmed
- Radar Log tab: single-callsign recording, per-satellite mode preset
  (CW/SSB/FT4), English social post generation (copy/share), LoTW upload with
  one-shot preview confirmation + receipts ledger
- Settings: 日志本 section (records + confirmation state, delete on tap) above
  LoTW config; LoTW upload config (TrustedQSL .p12 import + password, station
  grid list incl. IOTA/CQZ/ITUZ)
- i18n strings (en/zh)
This commit is contained in:
atsunatsu committed 2026-09-25 22:37:20 +08:00
1 parent d33629f015
commit 8e549cd43c
36 files changed
+2696 -4

No files matched your search

@@ -26,6 +26,7 @@ import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
import com.rtbishop.look4sat.core.domain.repository.LoTWSyncMode
import com.rtbishop.look4sat.core.domain.repository.LoTWResult
import com.rtbishop.look4sat.core.domain.logbook.toConfirmedRecord
import com.rtbishop.look4sat.core.domain.repository.applyLoTWGridResult
import com.rtbishop.look4sat.core.domain.repository.lotwCursorApi
import com.rtbishop.look4sat.core.domain.repository.lotwSyncToday
@@ -116,6 +117,11 @@ class MainApplication : Application(), IContainerProvider {
val result = container.lotwRepo.fetchConfirmedGridQsos(callsign, lotwSettings.password, since)
if (result is LoTWResult.Success) {
val count = applyLoTWGridResult(settingsRepo, result, mode, callsign, timeNow)
// Feed the logbook so confirmations appear and local uploads get matched.
runCatching {
val confirmed = result.qsos.values.flatten().map { it.toConfirmedRecord(callsign) }
container.qsoRepository.mergeLoTW(confirmed)
}
println("Periodic LoTW grid sync finished: $count worked grids")
} else {
println("Periodic LoTW grid sync skipped (${result::class.simpleName})")