Skip to content

Monitor Part 2 of 5 - Simplify Monitor Controls UI - #91

Merged
punk-kaos merged 2 commits into
JS8Call-improved:masterfrom
tekstrand:monitor-status-strip
Sep 7, 2026
Merged

Monitor Part 2 of 5 - Simplify Monitor Controls UI#91
punk-kaos merged 2 commits into
JS8Call-improved:masterfrom
tekstrand:monitor-status-strip

Conversation

@tekstrand

@tekstrand tekstrand commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Parts

  1. AudioDevices extraction (Monitor status bar 1/5: Extract audio device handling into AudioDevices #90, merged)
  2. The status strip itself, with the audio picker behind an overflow menu (this PR)
  3. Rig link indicator on the strip, fed by a new rig status broadcast
  4. Error state display (might combine with 3)
  5. The same audio device selection in Settings

What

  1. Replaces the status card with a two row strip under the waterfall: state dot and word, frequency button and engine switch, then power, TX offset and time drift on one line
  2. Moves the audio device picker, clock sync and drift reset behind an overflow menu
  3. Replaces the frequency dropdown with a text button that opens a single choice dialog
  4. Shows Transmitting on the dot and state word while a send is on the air
  5. Moves the release version into a new About category in Settings

Why

  1. Inneficient use of screen real estate
  2. Sync, reset, and the device picker are less common actions, move to a menu instead of permanent screen space
  3. The exposed dropdown had a filter on restore bug that emptied the frequency list after a tab switch
  4. Transmit is not a mode you switch. Messages go out per frame, so it shows as state, not as a control.
  5. Not important for the home page

I specifically left out selectFrequency being part of the service because the code is tied closely with part 3 and it makes more sense to combine into that, IMO.

Test

  1. ./gradlew :app:testDebugUnitTest
  2. ./gradlew :app:assembleDebug on the Fire HD 10 and Pixel 7 profile emulators: the switch starts and stops the engine with the dot and state word tracking it, the overflow shows its three actions, the audio picker and frequency dialog both open and select, the strip fits phone landscape next to the nav rail, and with rig_type=qmx_serial the band profile still filters the preset list.

Screenshots

Tablet

Before

status-strip-tablet-portrait-before status-strip-tablet-landscape-before

After

status-strip-tablet-portrait-after status-strip-tablet-landscape-after

Phone

Before

status-strip-phone-landscape-before status-strip-phone-portrait-before

After

status-strip-phone-portrait-after status-strip-phone-landscape-after

The card took the lower half of the screen while stopped and said the same thing twice: a header band held the word STOPPED and a full width button below it held the word Start. The strip spans the width under the waterfall as its control bar, two rows tall. A colored dot and a state word sit on the left, and a switch replaces the Start button, because the engine is the only thing on this screen that can be switched: transmit is not a mode, since messages go out per frame, and the rig connects when the engine starts, so both appear as state rather than as controls. The dot and the word read Transmitting while a send is on the air, fed by the transmit state the queue already publishes. The second row carries power, transmit offset and time drift on one line, with the audio device picker, sync to next decode, and drift reset behind an overflow menu.

Frequency became a compact text button that opens a single choice dialog. No exposed dropdown remains on this screen, so the filter on restore bug that emptied the frequency list after a tab switch is gone by construction rather than suppressed.

The release version moved into a new About category in Settings, the SNR tile is gone, and the strings and the status text style that nothing references any more went with them. The audio picker is AudioDevices.showPicker, added beside the list and selection that landed with the extraction.
@punk-kaos

Copy link
Copy Markdown
Contributor

I found one UI-state edge case in renderState().

Right now it returns early when the label/color haven’t changed, but the power switch sync happens after that return. That means the switch can stay visually ON while the engine is actually OFF — for example, if the user turns it on, gets the mic permission prompt, then denies permission. The denial handler calls renderState(), but because the state is still STOPPED, the cached label/color match and it returns before resetting the switch.

I’d keep the label/dot repaint optimization, but move the powerSwitch.isChecked = shouldBeOn sync outside the early return so it always reflects the actual engine state.

… Denying the microphone permission leaves the engine state where it was, so the label and dot are already right and the repaint skip returns early, but the switch itself moved on the tap that opened the permission dialog and was never put back, showing on next to an engine that is off. The label and dot keep the skip, since the per-tick paint cost is what the skip exists for; the switch write is a no-op when the value is unchanged. Verified on the emulator with the microphone permission revoked: flipping the switch and denying the prompt returns the switch to off.
@tekstrand

Copy link
Copy Markdown
Contributor Author

@punk-kaos ♻️

@punk-kaos
punk-kaos merged commit 89c4341 into JS8Call-improved:master Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants