Commit Graph
238 Commits
Author SHA1 Message Date
mckero d6b61eaa4b license: switch the root LICENSE to AGPL-3.0 for the combined work
用户指出 GitHub 上显示的许可证仍是 GPL-3.0,未反映合并 AGPL 组件的事实。

根因: 之前只在 README 加了 License 章节,根 LICENSE 文件仍是 GPL-3.0 原文,
GitHub 的自动检测(只扫 LICENSE 文件)自然还是 GPL-3.0。

修复: 合并作品按 GPL-3.0 §13 / AGPL-3.0 §13 处理,根 LICENSE 改为:
- 顶部 LICENSING NOTICE 说明构成 (Look4Sat 代码 GPL-3.0 + DeepCW 模型
  AGPL-3.0-only, 合并作品按 AGPL-3.0 分发)
- 正文为 AGPL-3.0 原文 (从 DeepCW-AGPL-3.0.txt 复制)

原 GPL-3.0 原文保留在 feature/cw/licenses/Look4Sat-GPL-3.0.txt。
README License 章节同步更新,与新 LICENSE 一致。

此后 GitHub 侧边栏将识别为 AGPL-3.0。
2026-08-13 10:19:57 +00:00
mckero 2e1d8b9c00 feat(cw): archive decoded history, polish the waterfall, document licensing
三个用户反馈一并解决:

1. 解码文字不再消失 (核心)
   旧实现: 20 秒环形缓冲满了就静默覆盖最旧样本, 文字随之从屏幕消失。
   新实现: CwDeepBuffer 新增 overflow —— 满时被覆盖的旧样本先进 overflow,
   解码器累积到 15 秒就单独解码一次, 结果追加到 historyText (只增不减)。
   UI 记录区显示 historyText + decodedText (历史稳定 + 当前窗口实时)。
   ICwDecoder 接口新增 historyText StateFlow。

   归档音频已离开主窗口, 不再被 CTC 修正, 故其文本是"最终版", 追加安全。
   归档窗口 15 秒: 内容已在 20 秒窗口里解过多次, 短一点几乎无损, 且推理
   开销小。

2. 瀑布图更好看
   配色从"深蓝->青->黄"换成 matplotlib inferno (黑->紫->品红->橙->黄),
   与静态频谱图保持一致。相邻 bin 之间用水平渐变做线性插值, 消除 65 列
   离散方块的像素感。

3. AGPL 合规补漏 (用户提醒: 仓库许可证没体现 AGPL 组件)
   README 新增 License 章节: 声明项目主体 GPL-3.0 + feature/cw 的 DeepCW
   模型 AGPL-3.0-only, 并说明合并作品按 GPL-3.0 §13 / AGPL-3.0 §13 处理。

验证:
- :core:domain:test => 31 个 CW 测试全绿 (CwDeepBufferTest 新增 3 个
  overflow 归档测试: 顺序/清空/reset)
- :core:domain:compileKotlin + :core:data + :feature:cw:compileDebugKotlin
  => BUILD SUCCESSFUL
2026-08-13 08:04:05 +00:00
Arty Bishop 7cdc2952dc v4.4.6 - AMSAT status page, fully customizable data sources 2026-08-13 09:47:26 +02:00
mckero f42d1f6c57 docs(cw): add DEEPCW.md recording DeepCW architecture, pitfalls, verification
记录 feature:cw 模块的完整集成知识, 供后续维护者免于重走弯路:
- 架构分层 (core:domain 纯 Kotlin 前后处理 / core:data ONNX 推理 / feature:cw UI)
- 模型规格与 int8/fp32 双版本取舍 (APK 内置 int8, fp32 作为 release 资产)
- 真机 release-only 的五个坑 (R8 keep / 惰性加载 / 线程 / 跨线程状态 / 双录音)
- 验证结果 (golden vector 79 测试 / 服务器端到端逐字符一致 / 真机 40WPM)

该文档与 skill 的 §7.55 根因坑互为参照: skill 记方法论, 这里记本模块实况。
2026-08-13 04:14:01 +00:00
mckero e0405b541f fix(cw): prevent double AudioRecord when toggling capture
CwDecodeScreen 的 LaunchedEffect 原先用内层 launch{} 启动音频采集。
isListening 在权限授予后由 false->true->(授予回调再次置 true), 加上
LaunchedEffect(isListening, permissionGranted) 的双 key 重启, 会造成
前一个 audioFlow().collect 尚未取消、新的又启动 —— 两个 AudioRecord
同时录音, 内存翻倍、音频混叠, 是低内存机 OOM 崩溃的实锤级嫌疑。

改为在 LaunchedEffect 协程体内直接 collect (取消时随父协程及时停止),
并保持 (isListening, permissionGranted) 双 key 以正确处理权限授予后
需要重启采集的情形。

同时清理不再使用的 kotlinx.coroutines.launch import。

验证: :feature:cw:compileDebugKotlin => BUILD SUCCESSFUL
2026-08-13 03:16:36 +00:00
mckero c374058e1d perf(cw): ship int8-quantized DeepCW model (15MB -> 4MB)
用户真机闪退回桌面且无任何 Java 日志 => 高度怀疑进程被系统 LMK 杀掉
(内存不足) 或 native 层崩溃。模型加载 + ONNX 引擎初始化存在瞬时内存峰值,
fp32 模型 15MB 会放大这个峰值。

用 onnxruntime.quantization.quantize_dynamic 把模型权重动态量化为 QUInt8
(激活保持 float32):

- 体积: 15,139,839 -> 4,248,808 字节 (缩小 71%)
- 输入/输出名、形状、dtype 完全不变 -> Kotlin 代码无需改动
- 实测 CER (合成 CW 音频, fp32 vs int8 逐字符对比):
  SNR>=0dB 完全一致; -4dB 起两者一同劣化, 无显著差异
- onnx.checker 校验通过

AGPL 合规: NOTICE.md 记录派生信息 (原文件 SHA/量化后 SHA/转换步骤),
量化属模型修改, 按 AGPL 要求如实声明。复现命令已更新。

验证: :core:domain:test => 79 tests 全绿 (golden vector 测试锁定的是
频谱图前处理, 与模型文件无关)
2026-08-13 01:02:04 +00:00
Arty Bishop bd51044690 Added custom frequency offset setting to network reporting 2026-08-12 20:17:26 +02:00
mckero 5f297f9233 fix(cw): stop the waterfall crashing and cut APK size by 87MB
三个真机实测暴露的问题:

1. 闪退 (给权限后 3-4 秒必崩, 且注入日志抓不到堆栈)
   CwWaterfallState 用 mutableIntStateOf 记录重绘版本号, 却从音频采集线程
   写入。Compose 快照状态只能在合成线程修改, 从后台线程写会在运行时崩溃 ——
   崩在 Compose 内部, 所以业务类的日志注入抓不到。
   改用 MutableStateFlow (本身线程安全), Canvas 侧 collectAsState 读取。

2. 同一状态的跨线程数据竞争
   pushSamples 在采集线程写 rows/pending, snapshot() 在绘制线程读, 而
   ArrayDeque 非线程安全 —— 并发 removeFirst()/toList() 会抛
   ConcurrentModificationException 或 IndexOutOfBoundsException。
   两侧统一加锁; FFT 计算放在锁外, 只有队列追加持锁。

3. APK 从 8.4MB 暴涨到 135MB
   onnxruntime-android 的 AAR 自带 4 个架构原生库: arm64 28M + armv7 20M +
   x86 33M + x86_64 34M = 115MB。上次提交移除 abiFilters 时把 x86 系列也
   打包了进去 (仅模拟器需要)。
   重新加上 abiFilters, 保留 arm64-v8a + armeabi-v7a 两个真机 ABI,
   预计降至约 43MB。注意这与上次"恢复 64 位"不冲突: 那次删的是只留
   armeabi-v7a 的限制, 这次是排除 x86 系列。

另外两处加固:
- CwDeepDecoder 的模型加载从 init{} 移入惰性 ensureLoaded(): 加载会触发
  ONNX Runtime 原生库装载, 失败时抛 UnsatisfiedLinkError; 在构造函数中抛出
  会连带崩掉创建它的 composable, try-catch 也救不回来。移到首次使用时执行,
  失败经 errorMessage 上报给 UI。
- ONNX 会话限制 intraOp 线程数为 (核数-1) 且上限 4, 给音频采集和 UI 留出
  余量, 默认行为会铺满所有核心。

验证:
./gradlew :feature:cw:compileDebugKotlin :core:data:compileDebugKotlin => 通过
./gradlew :core:domain:test => 79 个测试全绿
2026-08-12 13:55:03 +00:00
mckero d93e3b4028 feat(settings): credit DeepCW (AGPL-3.0) in the about section
AGPL-3.0 要求署名。在设置页"关于"卡片的许可说明下方显示 DeepCW 模型的
作者 (e04)、许可证 (AGPL-3.0-only) 与上游仓库链接。

字符串标 translatable="false": 署名与许可证名称不应翻译。
完整声明见 feature/cw/licenses/NOTICE.md。

验证: ./gradlew :feature:settings:compileDebugKotlin => BUILD SUCCESSFUL
2026-08-12 13:03:59 +00:00
mckero 9b0d543f12 refactor(cw)!: replace legacy CW engine with DeepCW in both entry points
DeepCW 成为唯一 CW 解码内核, 不保留旧引擎作兜底 (用户决定: 完全移植)。

删除旧内核 (1298 行):
- CwDecoder.kt / CwBayesianDecoder.kt / CwChannelTracker.kt / CwSpectrogram.kt
- CwDsp / CwFFT / CwSTFFT / CwGoertzel / CwFilter / CwResampler.kt
- CwDecoderTest.kt
旧内核的自动定频与 squelch 门控由 DeepCW 的 400-1200Hz 固定频窗替代 ——
模型自带定频, 不再需要频谱峰值跟踪和噪声门。

两个 CW 入口都改接 DeepCW:
1. feature:cw 独立整页 CwDecodeScreen.kt 重写为纯 Compose (瀑布图 -> 实时行
   -> 历史区), 移除 AndroidView/LayoutInflater 和被删的 activity_main 布局;
   删除 CwSettingsDialog.kt (旧引擎的手动音调/带宽面板, DeepCW 全自动无需)
2. feature:radar 内嵌可折叠面板改走 ViewModel 的 CW state/action, 移除
   Morse Expert 控制器与 cw_panel_main 布局
   (该面板此前注释写明 "no longer feeds it", ViewModel 里的 CW 通路是死代码)

新增 CwWaterfall.kt: 复用 CwDeepSpectrogram 绘制瀑布图, 显示的正是模型
分析的 400-1200Hz 频段与同一批幅值。

接口接线:
- IMainContainer.provideCwDecoder() 提供 ICwDecoder (实现需 Context 读 assets)
- RadarViewModel 构造新增 cwDecoderFactory, onCleared() 中 close() 释放
  OrtSession 避免原生内存泄漏
- 删除已无调用方的 RadarAction.CwSetToneFreq (DeepCW 定频固定, 无参数可调);
  CwSubState.cwToneFreq 语义改为只读显示检测到的音调

依赖清理:
- feature:radar 不再依赖 feature:cw 与 constraintlayout
- feature:cw 不再依赖 constraintlayout

字符串: feature:cw 清理 Morse Expert 遗留串 (premium/rate/help/
tap_back_again_to_close/purple_500), 按 en/zh/tr/in/id 五语补全新串;
core:presentation 补 radar_cw_tone / radar_cw_waiting 五语。

验证:
./gradlew :core:domain:test => 4 个测试类全绿
./gradlew :feature:cw:compileDebugKotlin => BUILD SUCCESSFUL
./gradlew :feature:radar:compileDebugKotlin => BUILD SUCCESSFUL
./gradlew :core:data:compileDebugKotlin => BUILD SUCCESSFUL
2026-08-12 13:00:25 +00:00
mckero 86ab1b26b7 feat(cw): vendor DeepCW ONNX model with full AGPL-3.0 compliance
背景:
DeepCW (e04/deepcw-engine) 是基于 CTC 的神经网络 CW 解码模型。本地实测
弱信号解码率显著优于传统 DSP 方案: SNR<0dB 时 CER 16.0% vs ggmorse 52.6%;
SNR>=0dB 时两者均接近 0%。模型 400-1200Hz 固定频窗自带定频, 无需频谱峰值
跟踪与噪声门。

改动:
- assets/deepcw/model.onnx      15,139,839 字节, 原样收录未作修改
- assets/deepcw/model.onnx.json 模型元数据 (采样率 3200, FFT 256, hop 48,
  65 个频段, log1p 归一化, 42 类含 CTC blank)
- licenses/DeepCW-AGPL-3.0.txt  AGPL-3.0 许可证全文
- licenses/NOTICE.md            来源仓库/作者/取用 commit/SHA-256/
  许可证兼容性说明/复现步骤
- app/build.gradle.kts: androidResources.noCompress += "onnx"

关于 noCompress:
ONNX Runtime 通过 mmap 直接读取 assets, 压缩后无法内存映射, createSession
会失败。该配置只在打包 APK 的 app 模块生效, 在 feature 库模块声明无效。

许可证:
Look4Sat 为 GPL-3.0-or-later, DeepCW 模型为 AGPL-3.0-only。GPL v3 第 13 条
明确允许两者合并分发。推理完全在本地设备进行, 不通过网络向远程用户提供
模型功能, 故 AGPL 第 13 条的网络源码提供义务不被触发。本仓库公开, 完整
对应源码提供义务已满足。

上游取用 commit: 8e264d243bbd4467bd19f3f28292219405b47e0e
模型 SHA-256: ef120799457bca042d4690944f0faf93268eb4654e7f50f28784ad63bdc1fe02
2026-08-12 11:47:02 +00:00
mckero de6c964136 build!: drop armeabi-v7a abiFilters, restore 64-bit builds
背景:
abiFilters=armeabi-v7a 的唯一成因是 Morse Expert 的 32 位
libnativedecoderjni.so, 该 .so 已在上一提交删除。此前整个应用被迫以
32 位运行: 64 位设备走兼容层、寻址受限, 且不满足应用市场的 64 位要求。
原注释称"用户设备为 32 位软件"已与实际不符 (测试机为 64 位)。

改动:
- app/build.gradle.kts: 移除 defaultConfig.ndk.abiFilters 及相关注释
- feature/cw/build.gradle.kts: 移除 abiFilters; 移除 constraintlayout 依赖
  (仅被已删除的 activity_main.xml 使用); 移除 UTF-8 JavaCompile 配置
  (模块已无 Java 源码)
- feature/cw 改为依赖 core:domain + core:data (DeepCW 前后处理落在 core)

验证:
grep -rn 'abiFilters|jniLibs' --include=*.kts . (排除 build/) => 无匹配
APK ABI 覆盖将在 CI 产物中验证 (预期含 arm64-v8a)
2026-08-12 11:44:54 +00:00
mckero e477851adf refactor(cw)!: remove reverse-engineered Morse Expert engine and 32-bit JNI blob
背景:
feature/cw 内含 Morse Expert 1.15 (VE3NEA, 闭源免费软件) 的 jadx 反编译产物,
外加仅 armeabi-v7a 的 libnativedecoderjni.so (1.4MB)。该 .so 无 64 位版本,
迫使 app 模块设置 abiFilters=armeabi-v7a, 把整个应用锁死在 32 位。
闭源软件的反编译产物不具备任何分发授权。

改动 (共 58 项):
- 删除 pas/* (JNI 入口 nativedecoder/decoder/cwstru/system)
- 删除 com/ve3nea/morse_expert/* (MainActivity/DecodedTextView/ScaleView/
  WaterfallSurfaceView)
- 删除混淆包 B B0 D E2 F2 H2 I0 I2 J2 K1 d1 g3 i3 j1 j3 k3 s
- 删除 suncompat/* (sun.misc.Unsafe/Cleaner 存根)
- 删除 jniLibs/armeabi-v7a/libnativedecoderjni.so
- 删除配套 layout (activity_main, cw_panel_main)、menu、4 个 ic_baseline_* 图标
- proguard-rules.pro 清空 keep 规则 (已无 JNI 按类名注册依赖)

验证:
find feature/cw/src -name '*.java' | wc -l   => 0
find feature/cw/src -name '*.so'   | wc -l   => 0
git ls-files feature/cw 仅剩 build.gradle.kts, proguard-rules.pro,
CwDecodeScreen.kt, CwSettingsDialog.kt 及 5 份 app_values.xml

注意: 本提交后 CwDecodeScreen.kt 暂时无法编译 (它 inflate 了已删除的
R.layout.activity_main), 将在 DeepCW 内核接入后重写为纯 Compose。

后续: DeepCW ONNX 内核接入 (纯 Kotlin 前后处理), abiFilters 解除恢复 arm64。
2026-08-12 11:42:53 +00:00
Arty Bishop 2f4e3f5802 Added the ability to fully customize data sources via import 2026-08-12 13:01:45 +02:00
Arty Bishop 24eebdef74 Consolidated app dialogs and tweaked bottom sheets 2026-08-11 14:14:14 +02:00
Arty Bishop 3f5b48f270 Integrated the AMSAT status page created by MCKero6423 2026-08-11 14:05:55 +02:00
atsunatsuandatsunatsu 060fa2dfcd Added remembering per-satellite doppler offset (#237)
Co-authored-by: atsunatsu <atsunatsu@users.noreply.github.com>
2026-08-11 14:02:15 +02:00
atsunatsuandatsunatsu 8b5960282f Broaden linear transponder detection, logic fixes (#236)
Co-authored-by: atsunatsu <atsunatsu@users.noreply.github.com>
2026-08-08 21:31:44 +02:00
mckero 10eb84690e Added AMSAT satellite status tracking page (#234) 2026-08-08 14:26:36 +02:00
bf25292bf8 Fixed recalculating Radar track on station position change (#235)
Co-authored-by: atsunatsu <atsunatsu@users.noreply.github.com>
Co-authored-by: wty2019wty <74123961+wty2019wty@users.noreply.github.com>
2026-08-08 14:10:55 +02:00
mckero de85aa1e8b refactor(amsat): align with Material3 conventions per PR #233 review
Addresses feedback from rt-bishop/Look4Sat#233:

1. Migrate hardcoded colors to MainTheme colorScheme
   - Extend darkScheme: tertiary (Active 0xFF648FFF), tertiaryContainer (Telemetry 0xFFFFB000)
   - Extend lightScheme: tertiary (0xFF3C6FE0), tertiaryContainer (0xFFE09800) for contrast
   - Remove top-level Color() constants from SatStatusScreen.kt
   - Add statusColorOf() mapper using MaterialTheme.colorScheme

2. Move HTTP implementation from domain to data layer (Clean Architecture)
   - Delete AmSatApiClient.kt from core:domain (violates AGENTS.md: "Pure Kotlin, NO Android deps")
   - Migrate to IRemoteSource/RemoteSource in core:data (uses existing OkHttp3)
   - AmSatRepository now depends on IRemoteSource instead of AmSatApiClient

3. Inline JSON parsing (prepare for java.time migration)
   - Parse AMSAT API responses (names, reports) in AmSatRepository
   - Time parsing still uses manual logic (java.time desugaring in follow-up)

Before:
  - Hardcoded Color(0xFFXXXXXX) in UI + Repository (no theme support)
  - HttpURLConnection in domain layer (architecture violation)
  - AMSAT colors duplicated across modules

After:
  - MaterialTheme.colorScheme.tertiary/tertiaryContainer (light/dark adaptive)
  - HTTP via data layer RemoteSource (follows AGENTS.md architecture)
  - Single source of truth for AMSAT colors

Ref: https://github.com/rt-bishop/Look4Sat/pull/233#discussion_r1868599947
Ref: AGENTS.md "core:domain - Pure Kotlin (JVM). NO Android dependencies."
2026-08-05 15:26:42 +00:00
Arty Bishop 8fbfcb3712 v4.4.5 - Pass progress hotfix, swapped modes/filter dialogs 2026-08-05 13:14:50 +02:00
mckero 90bb584813 fix(status): drop appcompat tint attr from ic_refresh (library module has no such attr) 2026-08-05 08:53:05 +00:00
mckero f789a15338 fix(status): AMSAT page load failure, refresh spinner, retry button
- P0: fetchStatus() now runs on Dispatchers.IO - the previous
  synchronous URLConnection on the main thread threw
  NetworkOnMainThreadException and showed "load failed" on every open
- P1: refresh button rotates a vector icon (ic_refresh) instead of the
  "↻" text glyph, whose off-center font metrics made the spinner
  orbit around a shifted pivot
- P2: error state gains a Retry button (4 locales); amsat_refresh
  string added (5 locales)
- versionCode 456 (bump for reinstalling over 455), versionName stays 4.5.5
2026-08-05 08:50:01 +00:00
mckero 6221516893 feat(amsat): 4.5.5 official AMSAT API + edge-to-edge status page
- Replace HTML parsing with the official AMSAT Satellite Status API v1
  (catalog.php + reports.php, JSON): AmSatApiClient (pure JVM, hand
  rolled ISO-8601/epoch parsing for minSdk 24) + rewritten
  AmSatRepository (satellite list from catalog, reports slotted into
  6 days x 12 two-hour slots, status colors per report value)
- Fix edge-to-edge: status bar / navigation bar insets on the status
  page (refresh button and update time were unreachable)
- Version 4.5.5 (455), What's new in all 5 locales
2026-08-05 08:12:17 +00:00
mckero c9cab8457d chore(i18n): translate all code comments to English
All Chinese comments (//, /* */, KDoc) across core/app/feature/build-
logic translated to English (550 lines, 73 files after FT8 rollback).
Code logic untouched - comment text only. Verified: all modules
compileDebugKotlin BUILD SUCCESSFUL.
2026-08-05 07:59:35 +00:00
mckero 19926c58ac Revert "feat(ft8): 4.5.5 integrate FT8CN 0.93 (full port, original UI)"
This reverts commit 8a2b0a9aa3.
2026-08-05 07:47:18 +00:00
mckero 955871c27b Revert "fix(ft8): register FT8 in settings page-order list (UI settings)"
This reverts commit 4d7952d293.
2026-08-05 07:47:17 +00:00
mckero 449d1a9eec Revert "fix(ft8): unify osmdroid version to fix duplicate classes"
This reverts commit fc1bec1915.
2026-08-05 07:47:17 +00:00
mckero fe97339ece Revert "fix(ft8): repair FT8CN locale string resources for AGP9 strictness"
This reverts commit 2c26272e0b.
2026-08-05 07:47:17 +00:00
mckero 4d5a413fa8 Revert "fix(ft8): remove application tag from module manifest (merge conflict)"
This reverts commit f4a22d9779.
2026-08-05 07:47:17 +00:00
mckero 25078b3121 Revert "fix(ft8): remove application tag from module manifest (merge conflict)"
This reverts commit 84d11f289f.
2026-08-05 07:47:17 +00:00
mckero 96a4d15424 Revert "fix(ft8): restore Look4Sat Pro label/icon, add AppCompat theme"
This reverts commit bf4cd5c07c.
2026-08-05 07:47:17 +00:00
mckero bf4cd5c07c fix(ft8): restore Look4Sat Pro label/icon, add AppCompat theme
User reported the app installed as "FT8CN" with the FT8CN icon and
FT8 not opening. Root causes (verified by aapt badging on the release
APK):
1. module values/strings.xml carried <string name="app_name">FT8CN
   which won the resource merge -> application-label became FT8CN.
   Removed it; dialogs that referenced R.string.app_name now use a
   module-local ft8cn_app_name (HelpDialog/ClearCacheDataDialog).
2. module shipped its own mipmap ic_launcher* (8 files) which
   overrode our launcher icon -> deleted all of them.
3. MainActivity extends AppCompatActivity but the module's
   <application> lost android:theme when the conflicting attributes
   were stripped -> startup crash. Re-added android:theme=Theme.Ft8CN
   (MaterialComponents) on the module application element.
Verified: processReleaseManifest/Resources + ft8 javac + app kotlin
BUILD SUCCESSFUL. Plan: .hermes/plans/2026-08-05_150000-v455-fix-plan.md
2026-08-05 06:52:13 +00:00
mckero 84d11f289f fix(ft8): remove application tag from module manifest (merge conflict)
feature/ft8 module manifest carried an application element from the
FT8CN standalone app (allowBackup/icon/label/usesCleartextTraffic),
clashing with the main app manifest at merge (allowBackup false vs
true, icon, label). Removed the tag - components (activities, service,
permissions) remain and merge cleanly. Verified: processRelease-
MainManifest + mergeReleaseResources BUILD SUCCESSFUL.
2026-08-05 06:33:54 +00:00
mckero f4a22d9779 fix(ft8): remove application tag from module manifest (merge conflict)
feature/ft8 module manifest carried an application element from the
FT8CN standalone app (allowBackup/icon/label/usesCleartextTraffic),
clashing with the main app manifest at merge (allowBackup false vs
true, icon, label). Removed the tag - components (activities, service,
permissions) remain and merge cleanly. Verified: processRelease-
MainManifest + mergeReleaseResources BUILD SUCCESSFUL.
2026-08-05 06:29:21 +00:00
mckero 0e767f4250 feat(wavelog): QRZ grid lookup, LoTW satellite list refresh, RST 59
User-prioritized WaveLog fixes (4.5.5, commit-only per instruction):

- QRZ 对方网格爬虫: QrzGridClient (domain, pure JVM) fetches
  https://www.qrz.com/db/{call} with user-supplied cookies (parses
  EditThisCookie JSON or raw "k=v; k=v"), extracts Grid Square from
  the Detail table. Cookies NEVER built in - entered in settings.
- Settings: WaveLog card top-right gear opens QRZ cookie dialog with
  test query button (detects logged-in callsign from cookie, looks up
  its grid, shows result or failure in the dialog).
- LogTab: on Enter, async lookup of the other station's grid ->
  queue.updateGridsquare -> uploaded with QSO (postQso gridsquare).
- LoTW satellite list: 112 names embedded (lotw.arrl.org config.tq6),
  normalizeSatName maps Celestrak TLE names to LoTW names (SAUDISAT-1C
  -> SO-50, FUNCUBE-1 -> AO-73, DIWATA-2B -> PO-101, ZARYA/ARISS ->
  ARISS); "Update sats" button in WaveLog card downloads the live list
  (LotwSatellitesRepo, SharedPreferences persisted, never in build).
- RST: rst_sent/rst_rcvd = 59/59 in both v1 ADIF and v2 JSON.
- Grid mismatch dialog kept (cloud station grid vs station QTH);
  QSO gridsquare no longer uses station grid.
- New strings in 5 locales; check_strings 9 files OK.
Verified: core:domain/data + feature:settings/radar + app
compileDebugKotlin BUILD SUCCESSFUL.
2026-08-05 06:23:47 +00:00
mckero 2c26272e0b fix(ft8): repair FT8CN locale string resources for AGP9 strictness
FT8CN 0.93 shipped malformed strings that old AGP tolerated but AGP9
rejects: 148 unclosed tags (</string>>), unclosed XML comments,
trailing garbage after tags, and non-positional multi-placeholder
formats. Fixed across all 8 locale files: closed tags/comments,
removed trailing garbage, numbered placeholders in argument order
(%1$s %2$d %.3$1f style). aapt2 compile of every values-* dir now
passes 0 errors; :feature:ft8 + :app compileDebugKotlin SUCCESSFUL.
2026-08-05 05:23:57 +00:00
mckero fc1bec1915 fix(ft8): unify osmdroid version to fix duplicate classes
CI build failed at :app:checkReleaseDuplicateClasses: local
osmdroid-android-6.1.14.aar (copied from FT8CN) clashed with the
project's osmdroid 6.1.20 (feature/map remote dependency). Switched
feature/ft8 to libs.other.osmdroid (6.1.20), removed the local aar.
Verified: compileDebugJavaWithJavac + :app:compileDebugKotlin
BUILD SUCCESSFUL.
2026-08-05 05:08:51 +00:00
mckero 4d7952d293 fix(ft8): register FT8 in settings page-order list (UI settings)
User spot-check found FT8 missing from the settings screens list
(SettingsScreen.kt:703) - page order drag/hide UI could not show or
reorder it. Added "nav_ft8 to FT8" after AMSAT (name must match
Screen.screenId). Verified: :feature:settings + :app compileDebugKotlin
BUILD SUCCESSFUL.
2026-08-05 04:53:37 +00:00
mckero 8a2b0a9aa3 feat(ft8): 4.5.5 integrate FT8CN 0.93 (full port, original UI)
Full FT8 mode integration (user chose scope C: complete port, keep
original visual style):

- feature/ft8 module: 213 Java classes from FT8CN 0.93 source
  (com.bg7yoz.ft8cn namespace preserved so code needs zero changes),
  DataBinding enabled, BuildConfig fields (APPLICATION_ID/VERSION_NAME/
  apkBuildTime) restored for AGP9
- libft8cn.so from the user's FT8CN_0.93 APK (armeabi-v7a, 1.53MB;
  differs from repo copy, APK version used)
- 237 res files + 58 assets + 4 local libs (MPAndroidChart spectrum,
  commons-net, nanohttpd, osmdroid map) copied verbatim
- Proguard keep rules for JNI classes (RegisterNatives)
- Entry: More menu "FT8" (default sub menu, after AMSAT) launches
  MainActivity via Intent (external activity, LAUNCHER filter removed
  to avoid double launcher)
- Manifest merged via module (RECORD_AUDIO/BT/location perms)
- Version 4.5.5/455 + What's new 5 languages (REPLACED per rule)
Verified: :feature:ft8:compileDebugJavaWithJavac + :app:compileDebugKotlin
BUILD SUCCESSFUL; check_strings 9 files OK.
2026-08-05 04:49:35 +00:00
mckero c9d87be289 fix(aprs): upload feedback, manual report reliability, station position
User testing round 3 (4.5.4): no success feedback on upload, aprs.fi
shows nothing, passcode calculator OK, notification present.

- Manual report now works even when service not running: ACTION_REPORT_NOW
  starts the service first (Toast "not configured" if missing callsign)
- Upload result feedback guaranteed: Toast always shows (short OK /
  long fail+reason), last result persisted (time/ok/detail) and shown
  in the settings card "Last report: HH:mm:ss OK/failed - detail"
- Position source: station position from settingsRepo (user decision)
  with live GPS last-known as fallback
- sendPacket reads the server confirmation line (short 3s timeout);
  server error text (Invalid/error) surfaces in Toast + card
- Strings EN/ZH/TR/IN/ID +4 keys
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
2026-08-05 03:29:36 +00:00
mckero 012ea1eeb6 fix(aprs): passcode compute button, crash log, notif permission, login verify
User feedback round 2 (4.5.4): no notification shown, report not
sending, no error visibility on crash. Diagnosis: APRS-IS port 14580
reachable (verified with real login test), 24580 SSL refused; login
format OK. Fixes:

- Settings dialog: "Compute passcode" button - fills passcode from
  callsign via the ported 0x73E2 algorithm (user can see the result)
- Global crash handler in MainApplication: stack trace appended to
  files/crash_log.txt so crashes are diagnosable (user: no crash logs
  were available before)
- POST_NOTIFICATIONS runtime permission requested when enabling APRS
  (Android 13+ otherwise silently hides the service notification)
- AprsIsClient: read the login response (aprsc "# logresp ...
  unverified"/"Invalid") and surface the server message as the error
- AprsForegroundService: Toast on manual report result (OK / failure
  with server reason)
- Strings EN/ZH/TR/IN/ID +3 keys
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
2026-08-05 03:06:09 +00:00
mckero 920af7bde2 fix(aprs): crash when enabling APRS with callsign set (foreground service type)
Root cause: user reported crash on enabling APRS with a callsign set
(empty callsign worked because the service stops early and never
reaches startForeground). Android 14+ requires the service to declare
foregroundServiceType when startForeground passes a type; the service
had none -> process died on toggle.

- Manifest: add android:foregroundServiceType="dataSync"
- AprsCard: use startForegroundService() for start/report actions
  (Android 8+ standard for foreground services)
- AprsForegroundService: try-catch around startForeground, fallback
  stopSelf instead of killing the process
- AprsReporter: passcode "-1" (APRSdroid "no auth" convention) also
  auto-computes from callsign
- Merged upstream 418a05e3 (zh wording fix, no conflicts)
Verified: compileDebugKotlin all modules BUILD SUCCESSFUL,
check_strings 9 files OK.
2026-08-05 02:50:40 +00:00
mckero 44ae5cef5d fix(aprs): settings crash from format arg type mismatch + service class name
- prefs_aprs_summary uses %2$d but AprsCard passed port.toString()
  (String) -> String.format threw IllegalFormatConversionException
  on every Settings screen open (crash). Pass Int now.
- Manifest service name was ".AprsForegroundService" (resolves under
  applicationId) but the class lives in com.rtbishop.look4sat.app ->
  ClassNotFoundException when toggling. Use full class name.
Verified: :feature:settings:compileDebugKotlin + :app:compileDebugKotlin
BUILD SUCCESSFUL.
2026-08-05 02:39:14 +00:00
mckero 518d9a4d22 feat(aprs): 4.5.4 APRS-IS network reporting (ported from APRSdroid 1.6.3d)
Core logic extracted from APRSdroid 1.6.3d (jadx reverse-engineered,
verified against APRS spec):
- core/domain/aprs/AprsPacket.kt: passcode algorithm (0x73E2 XOR),
  login line, uncompressed/compressed position encoding (ab0oo)
- core/data/aprs/AprsIsClient.kt: APRS-IS TCP client (login + packet
  lines, 30s reconnect strategy)
- core/data/aprs/AprsReporter.kt: periodic reporting scheduler
  (default 5 min, manual trigger support)
- core/data/aprs/AprsStore.kt: SharedPreferences persistence
  (fill once, saved)
- app/AprsForegroundService.kt: foreground service with notification
  (keeps APRS alive across pages), START_STICKY, last-known position
  provider
- feature/settings/AprsCard.kt: APRS card between DataCard and
  OutputCard, gear button opens settings dialog (server/port/
  callsign/ssid/passcode/interval/status/symbol), manual report button
- Manifest: service registration + FOREGROUND_SERVICE(_DATA_SYNC) +
  POST_NOTIFICATIONS
- Strings in EN/ZH/TR/IN/ID (23 keys x 5)
- Version 4.5.4/454 + What's new 5 languages
2026-08-05 02:07:11 +00:00
Arty Bishop 602b1553a2 v4.4.4 - Icom CAT, components, filters and sources tweaks 2026-08-04 20:51:22 +02:00
atsunatsuandatsunatsu bd0881fb4b Added linear transponder Doppler calculator (#232)
Co-authored-by: atsunatsu <atsunatsu@users.noreply.github.com>
2026-08-04 18:45:12 +02:00
mckero 72f507163a fix(status): shrink day cells so long satellite names stay visible
User feedback: on narrow screens long names (e.g. AO-123_[FM]) were
truncated before the mode tag. Scaled the 6 day cells down
proportionally (weight 1f -> 0.8f, height 26dp -> 24dp) and widened
the name column (weight 1.4f -> 2f) in both header and rows.
Verified: :feature:status:compileDebugKotlin BUILD SUCCESSFUL.
2026-08-04 16:41:41 +00:00
mckero 2777607ab5 fix(i18n): drop values-b+in (duplicates values-in under aapt2)
aapt2 treats in/b+in as the same locale config as id -> Duplicate
resources build failure. in and id are equivalent in Android's
resource matcher (both normalize to id), so values-in + values-id
is sufficient coverage for Indonesian devices.
2026-08-04 16:18:50 +00:00