Per upstream author feedback (PR #233) and tablet UX report:
- Bottom bar keeps max 5 primary destinations: Satellites/Passes/
AMSAT/Map/Settings; Radar moves to the More menu (still reachable
from Passes via item click)
- Legacy migration: persisted orders are rewritten in memory
(main menu drops Radar + appends AMSAT; More menu drops AMSAT +
appends Radar) so existing installs get the new layout
- Wide screens / tablets (width breakpoint) now use the side
navigation rail instead of the bottom bar - fixes the wasted
bottom strip ("big chin") in landscape/tablet layouts
- New AMSAT tab icon: MDI satellite-variant (Apache 2.0,
https://pictogrammers.com)
- What's new updated in 5 locales; version 4.5.6 (457)
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.
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.
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.
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.
- 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.
resourceConfigurations is rejected by AGP 9.3 ("When localeFilters
are specified, resourceConfigurations cannot include locale
qualifiers"). Switch to androidResources.localeFilters.
Verified: :app:mergeReleaseResources + :app:processReleaseResources
BUILD SUCCESSFUL.
Root cause of Indonesian not matching: release build has
isShrinkResources=true and R8 was dropping the Indonesian resource
configs entirely - aapt dump badging showed locales only
'--_--' es ru si tr uk zh (no in, no id). values-in/values-id were
merged at merge time but stripped before packaging.
Fix: explicit resourceConfigurations in app/build.gradle.kts keeping
en zh tr in id es ru si uk. Verified: :app:mergeReleaseResources
BUILD SUCCESSFUL.
New feature/status module: fetches https://amsat.org/status/ and
renders a live status grid in the official site colors:
- Parser (AmSatParser): 47 satellites x 6 days x 12 two-hour slots,
official colors (blue=Active, orange=TLM/Beacon, pink=Not Heard,
deep-orange=Conflicting, gray=none); 598+ report details extracted
from inline JS tooltips (callsign/date/time/grid)
- Three-level viz: color grid -> report count -> tap day cell opens
report list dialog
- Manual refresh with spin animation + last-updated timestamp +
legend row; loading/error states
- New "AMSAT" entry in the More menu (Screen.AmSat), integrated with
page-order / hide-page settings (SettingsScreen screens list,
defaultSubMenuOrder, allNavItems, migration for existing users)
- AmSatRepository via IRemoteSource.getStatusHtml() (UA header);
shared remoteSource promoted to a lazy class property in MainContainer
Radar page Log tab: local entries now grouped by pass session with a
thick divider + satellite label between groups (matches the log page).
What's new updated in EN/ZH/TR/IN/ID. Version bumped to 4.5.3/453.
Not released (user gates all releases).
User-test fixes (3rd overwrite, version stays 4.5.2/452):
1. Log page was invisible everywhere outside the radar tab:
- allNavItems in MainScreen.kt was missing Screen.WavelogLog ->
not in bottom nav, not in More menu
- SettingsScreen UI-order lists were missing the WavelogLog row ->
not in page order settings either
Now the Log page appears in the More menu (default sub menu tail,
migration appends it) AND in UI settings page-order lists.
2. Frequency display: formatFrequency was MHz.kHz.Hz (145.900.000);
now MHz.kHz (145.900) per request. Applies to transceiver panel
and Log page alike (shared formatter).
3. Log tab frequency: now extracts the exact numbers shown in the
transceiver panel (txBaseFrequencyHz for TX, uplinkLow/uplinkHigh
range for linear) - no re-computation, no extra decimals. Upload
uses the same tuned frequency (Hz precision kept internally).
4. Log page (More menu) table: date+time column (MM-dd HH:mm),
row separators (table lines), uploaded checkmark kept.
Verified: check_strings OK (8 files); :app:compileDebugKotlin
BUILD SUCCESSFUL (includes upstream merge 1a552417).
Background: user tested 4.5.2 and reported 4 issues. Same-version
overwrite per user (4.5.2 exists solely for the logbook system).
Fixes:
1. Upload 404 — root cause: user server URL ending in /index.php was
concatenated again (/index.php/index.php/api/v2/...) -> 404. Now:
- normalizeUrl strips trailing /index.php
- every request tries the index.php path first, falls back to the
rewritten path on 404
- 409 conflict (duplicate QSO) counts as success (moves out of queue)
- failure messages include the actual URL + HTTP code for debugging
2. Swipe-to-delete was dead: rowWidth was never measured (0) so the
75% threshold was 0 and the drag was clamped to 0. Now measured via
onSizeChanged + smooth spring/tween snap-back animation.
3. Transponder picker: long card list replaced with an
ExposedDropdownMenuBox dropdown (scrollable menu, pick one).
4. New Log page under the More menu: table view (time / frequency /
satellite / callsign / uploaded checkmark). WavelogQso gains
uploaded flag; uploader marks instead of removing; queue keeps
uploaded entries (500 cap). Old persisted subMenuOrder gets
WavelogLog appended (migration).
Verified: check_strings.py OK (8 files, 452/4.5.2);
:app:compileDebugKotlin BUILD SUCCESSFUL.
Background: 8 bottom-nav items squeeze long English labels on narrow
screens. 4.5.1 introduces the 5+N pattern: 5 main tabs plus a fixed
6th "More" button that pops a second-level menu (spring bounce) with
the remaining pages.
Changes:
- MainScreen: nav split into main (<=5, screenOrder-driven) + more
(subMenuOrder); More button with popup panel + spring animation;
BackHandler closes the menu before navigating back
- MoreMenuPopup: bottom-end card, current page highlighted, scrim
click to dismiss
- UI Settings: page order card now has two zones (main menu, max 5,
Settings locked last with no drag handle / more menu); move buttons
between zones, drag-to-reorder within zones; new subMenuOrder pref
- Defaults: main = Satellites/Passes/Radar/Map/Settings,
more = Mutual/Roaming/CwDecode; old screenOrder migrates by
classifying pages against the default sub menu
- Hard-coded UI strings localized (Tracking/Lat/Lon/Qth/Connect/
Track/Stop/CW permission prompts) into EN/ZH/TR
- NEW Indonesian locale (values-in, 181 strings + cw module strings)
- user rule: every future release must update EN/ZH/TR/IN
- Version 4.5.1 (451)
Verified: check_strings.py OK (8 files, no bare apostrophes);
:app:compileDebugKotlin BUILD SUCCESSFUL locally.
Decode page showed stats but empty waterfall and no decoded text:
the ported i3/d.k() FFT dispatch was broken by a jadx structure
misplacement - the runtime path (k=1 -> a5=0 -> cond_22 single-thread
FFT) was replaced by a hallucinated `throw null` else-branch while the
real cond_22 code sat in a dead else. Verified against smali
(7030-7263): j3.c.q forward FFT + post-processing loop + tail + small-
array branch now live in the a5==0 branch.
Also:
- UiSettingsCard now lists CwDecode (toggle + drag-reorder) between
Roaming and Map
- unknown screenIds in persisted screenOrder fall back to
defaultScreenOrder position (CwDecode lands between Roaming and Map
for existing users instead of trailing after Settings)
Verified: :feature:cw + :feature:settings + :app compileDebugKotlin
BUILD SUCCESSFUL.
Three release-breaking issues found in the v4.5.0 APK (app crashed on
launch, label showed "Morse Expert", dex shrank to 282KB vs 3.5MB):
1. app_name: the ported app_values.xml shipped a "Morse Expert"
app_name string which overrode Look4Sat Pro's label during resource
merging - removed (no other string collisions).
2. R8 stripped nearly all code: the in-app sun.misc.Unsafe/Cleaner
stubs clashed with android.jar library classes. Moved stubs to
com.rtbishop.look4sat.feature.cw.suncompat and updated k3.d/s/r
imports (k3.r keeps the reflective Class.forName("sun.misc.Unsafe")
string, which returns null on Android hidden-API limits).
3. proguard-rules.pro added (AGP 9 variant-level
CanProduceConsumerProguardFiles): keep pas.** (JNI RegisterNatives
resolves by class name) plus all ported CW classes.
4. app-level ndk abiFilters forced to armeabi-v7a: the ported
libnativedecoderjni.so is v7a-only, so a multi-ABI APK would crash
with UnsatisfiedLinkError on arm64 devices.
Verified: :feature:cw:compileDebugKotlin BUILD SUCCESSFUL.
Compose integration of the ported CW decoder engine:
- CwDecodeScreen: AndroidView embedding the ported activity_main.xml,
lifecycle delegated to the ported MainActivity controller (onCreate ->
onResume, onDispose -> onPause/onDestroy), RECORD_AUDIO runtime
permission flow (with permanent-denial -> app settings), original
options_menu actions as a top button row (pause/clear/save/record/
settings), double-back-to-exit preserved
- CwSettingsDialog: message_type (general_text/ham_radio_qso),
text_font_size (7-99), and the 9 color keys (bg_color/text_color/...)
reading/writing the same prefs keys as the original app
(getPackageName()+"_preferences"), colors sourced from I2.b tables
- Navigation: Screen.CwDecode ("CwDecode") placed between Roaming and
Map in the default order; defaultScreenOrder updated; ic_cw morse icon;
nav_cw strings (en/zh/tr); app depends on :feature:cw
Verified: :feature:cw:compileDebugKotlin + :app:compileDebugKotlin
BUILD SUCCESSFUL (first pass, no errors).
Users can now change the bottom navigation order (previously fixed):
- New "Page order" section under the Settings toggle in the UI
Settings card: vertical list of all 7 pages, each with a drag
handle (new ic_drag drawable, Material drag_indicator glyph) on
the right.
- Drag a handle up/down: the item follows the finger with live
swap + animateItem() placement animation; on release the order is
persisted (OtherSettings.screenOrder, comma-separated in prefs so
order survives; StringSet would not).
- "Reset order" button restores the default order
(Satellites/Passes/Radar/Mutual/Roaming/Map/Settings).
- MainScreen now sorts navItems by screenOrder (empty = default,
stable sort keeps the canonical order); hiddenScreens filtering
unchanged; Settings entry always visible.
- New strings prefs_ui_order_title / prefs_ui_order_reset in
en/zh/tr; What's-new updated in all three locales.
Version stays 4.4.9 (覆盖 per user). Verified: core:data tests
pass, settings + app compile.
Two changes per user request:
1. Data source toggles default OFF with legacy-URL migration
(continuation of the 4.4.8 fix, now also in the dialog): old
example.com placeholder URLs are replaced by the real defaults and
the custom toggles stay off, so the online update never points at a
dead source after upgrading.
2. New "UI Settings" card between Other Settings and Credits:
one switch per bottom-navigation page (卫星/过境/雷达/匹配/漫游/地图/设置
in fixed order). Turning a page off removes it from the nav bar and
the remaining items close up automatically; order is never
rearranged. The Settings entry is always visible (switch disabled)
so the user can never lose access to settings. Persisted via
OtherSettings.hiddenScreens (StringSet of Screen.screenId; screenId
added to the Screen sealed class - simpleName is unsafe under R8).
Version bump per fork convention: 4.4.8 -> 4.4.9, versionCode 449.
What's-new updated in en/zh/tr.
Verified: core:data + core:domain tests pass, all modules compile.
Addresses three review findings:
1. Coordinates now come straight from settingsRepo.stationPosition in the screen (collectAsStateWithLifecycle) — the exact same StateFlow the Settings page shows. Previously a separate ViewModel re-derived them, and it could lag behind the Settings page (user: '设置页更新了站位但漫游页死活不更新'). With the shared source the two pages can never disagree. RoamingViewModel removed; state derivation moved to RoamingState.fromPosition().
2. Red marker placement ported faithfully from the QTH定位器 app: it is driven by the 3rd character pair of the 8-char locator (the 'ih' in OL42ih45), mapped to a 0..1 fraction (lon a=west..x=east, lat inverted a=south..x=north), then scaled to the actual center-cell size. The grid now uses the reference proportions (columns 21.4/56.2/21.4, rows 31.5/35.9/31.7) and fills the screen, so the marker lands accurately on any device.
3. Workflow now uploads a versioned APK (look4sat-<version>.apk instead of look4sat.apk).
Also: Settings 'Other' card rows got vertical spacing (Arrangement.spacedBy) so the new roaming toggle is not glued to the night-mode row.
Ports the QTH定位器 (com.us1pm.gridsquarelocator) location panel into Look4Sat as a new 'Roaming' page, restyled with the app's own look.
UI (top to bottom):
- Info header: GPS status dot, big 8-char locator, Lat/Lon rows with DMS + 5-decimal display, and a GPS 定位 button
- 3x3 grid panel: the current 4-char Maidenhead square (e.g. OL42) centered, surrounded by its 8 neighbors (OL33..OL51), with a red position marker in the center cell
Logic:
- QthConverter gains qthNeighbors(square) building the 3x3 grid with field/square carry at boundaries (AA00 wraps to RR99, IO91 crosses into J field), and qthToSquare(locator) extracting the 4-char square
- Verified against the decompiled app algorithm and the reference screenshot (OL42 grid matches exactly); 9 unit tests cover normal, boundary and field-wrap cases
Navigation:
- New bottom-nav item 'Roaming' between Match and Settings, with a crosshair icon
- New feature:roaming module (ViewModel + Compose screen) registered in the app
Build config:
- Lowered Gradle JVM heap from -Xmx6g to 768m: the 2GB build server froze on the old value; heavy release builds stay on GitHub Actions
Conditionally applies the BG7NTA signingConfig when keystore.properties exists (gitignored). CI signs via apksigner with GitHub Secrets, so this only affects local builds.
The official app signs com.rtbishop.look4sat with its own certificate. A fork sharing that applicationId cannot be installed over the official build (signature mismatch) and users saw overwrite/install failures.
- Add applicationId version catalog entry; namespace stays com.rtbishop.look4sat so source imports are untouched, applicationId becomes com.rtbishop.look4sat.bg7nta.
- Update PROPERTY_SATELLITE_DATA_OPTIMIZED meta-data to the fork id.
- Document the fork-applicationId requirement in the version catalog.
- MutualViewModel is Activity-scoped, so returning from Radar keeps query results
- TrackSampleData gains time field for live cut-off
- Removed standalone mutual card from radar page
- RadarViewCompose draws optional dashed station-B track + current dot
- RadarScreen builds trackB from mutual data up to current time
- Fix cubic Bezier control points (proper Catmull-Rom to Bezier conversion)
- Refine AOS/LOS more robustly by walking from edge into the pass window
- Still 5s sampling for smooth curves
- Pre-fill station A grid from settingsRepo.stationPosition
- Find ALL mutual passes per satellite (not just first)
- Fix Color.hashCode() -> Color.toArgb() for native canvas paint
- Use 2min gap between pass searches to avoid duplicates
Port satlover.de dual-station pass matching feature:
- New feature/mutual module with mutual pass data model
- MutualViewModel: compute overlapping passes for two stations
- ElevationCurveChart: Canvas-based dual elevation curve with drag
- MutualScreen: input form + results with expandable cards
- Navigation: add Mutual tab to bottom navigation bar
- i18n: add Chinese/English strings for new feature