Skip to content

Flashlight extensions#18

Draft
moWerk wants to merge 6 commits intoAsteroidOS:masterfrom
moWerk:multipage-features
Draft

Flashlight extensions#18
moWerk wants to merge 6 commits intoAsteroidOS:masterfrom
moWerk:multipage-features

Conversation

@moWerk
Copy link
Copy Markdown
Member

@moWerk moWerk commented Mar 22, 2026

Extend asteroid-flashlight into a full light-tool suite

What this PR does

This started as a simple question: what else can a flashlight do? Three days and several hundred iterations later, the answer is: quite a lot — and all of it without touching the existing flashlight experience.

The app still launches white in under a second. It still closes with a tap. The first page is unchanged. The only visible addition to the core experience is four small dots at the bottom of the screen, invisible while the flashlight is active, that hint at what lies beyond.

The pages

Flashlight carries forward upstream improvements from the 2022 and 2024 polish passes: brightness management via DisplaySettings, flatTire offset, hasRoundScreen radius, and upstream easing curves. A PageHeader appears when the light is off so the page is identifiable during navigation. Everything else is identical to what shipped.

Beacon is where the flashlight becomes a signaling tool. A single ValueCycler selects the mode: a calm breathing Pulse for marking your location, an urgent double-flash Emergency mode for critical situations, and the international distress codes SOS, CQD, XXX, and MAYDAY in full Morse. The beacon circle is always live — users see the selected pattern playing in the button before they activate it, which makes the Morse codes immediately understandable without documentation. An IntSelector at the bottom selects the beacon color by hue from white through the full spectrum to red, updating the live animation instantly. A horizontal drag overlay adjusts signal speed in real time.

Strobe extends the light-as-tool concept to calibrated frequency. 1–25 Hz with an RPM readout that makes the page genuinely useful for checking motor and fan rotation speeds — the classic stroboscope application. Hz can be adjusted before activation via IntSelector or during activation via full-screen drag anywhere on the screen, giving precise hands-free control while the flash is running.

Message is the unexpected one. A full-screen left-scrolling text banner across six categories: Emergency, Navigation, Social, Fun, Emoji, and Kaomoji. The trigger button shows a live scrolling preview of the selected message. On activation it expands to fill the screen and the content rotates to stay parallel to the ground — the watch reads the accelerometer at 30Hz and keeps the text horizon-locked regardless of wrist angle, so a bystander across a crowded room or concert hall sees correctly oriented text however you are holding your arm. A horizontal drag adjusts scroll speed continuously without stopping the scroll, giving the feel of pushing or braking a spinning record. The Emoji category blinks instead of scrolls, with drag controlling the blink rate from 40 bpm down to static.

On the technical side

The startup architecture was a rabbit hole worth documenting. main.qml is now genuinely minimal — a bare white Rectangle and an inactive Loader. The Rectangle renders in frame 1, before LayerStack, Component, ListView, or any Loader is evaluated. AppShell.qml loads in Component.onCompleted so its parse cost and the DisplaySettings D-Bus round-trip fall after the first Wayland frame commit. Pages 2–4 are deferred by a 1500ms timer, loading silently while the user is still looking at the white screen. The result measured on catfish hardware is parity with the upstream startup time despite shipping four additional pages.

On stock inclusion

The argument against shipping this as stock rather than community is that not every user will use every feature, and each added feature costs the user some mental overhead in their model of the device. That argument is correct as a general principle and harder to apply consistently to AsteroidOS as it actually exists today.

We ship an agenda app whose primary purpose at the time of addition was to demonstrate a handwriting keyboard — it now occupies the first launcher slot and is in the way for users who do not use it. We ship a game that was added because it was the only new app available to fill the launcher. We ship a compass and an HRM that are closer to proofs of concept than finished tools. In that context, an extended flashlight that is regression-free, startup-time-matched, and keeps its additions behind a swipe that most users will never take is not a worse offender than what we already ship — it is one of the better-justified additions in the stock set.

There is also the fun aspect of the project to consider. The message page is an easter egg and a tech demo. The horizon-locked banner scroll, the hue selector, the accelerometer-driven rotation, swipe input scrubber — these are techniques I hope other developers see and get inspired by, the same way I have tried to do with watchfaces and other contributions. Code that lives in an opkg repository that nobody browses does not inspire anyone. If an appstore existed it would be easier to accept four days of work ending up undiscoverable. We are not there yet, and until we are, stock inclusion is the only path to the visibility that makes a contribution meaningful and justifies polishing.

The user who only wants a flashlight gets exactly that. Same startup, same page 0, same gesture to close, four dots at the bottom that disappear the moment the light comes on. The mental overhead of those dots is as close to zero as interface design can get. And if somewhere out there a user stranded on a dark road after a breakdown finds a red pulsing beacon in their flashlight app and is seen before a car hits them — that moment justifies every other user who never scrolled past page one.

Translations

29 new translation IDs. en_GB, de_DE, fr, es, and nl_NL ship complete. Emoji, Kaomoji, and the international distress codes SOS/CQD/XXX/MAYDAY are intentionally left as literals — these are universally recognised signals whose meaning must not be altered by translation. toUpperCase() is enforced at render time so translators write natural sentence case in their .ts files and the UI handles the rest.

Thanks

To Kido for the architecture guidance and the mapplauncherd discovery that explained why the startup floor exists and what it actually costs. To the community for the reception that made three days of iteration feel worthwhile. Cambionn for the swift NL translation. And to Dodo for pushing back hard enough that every design decision in this PR now has a reason behind.

moWerk added 6 commits March 22, 2026 01:44
Restructure the app into a two-file startup pattern for minimum
first-frame latency. main.qml contains only a bare white Rectangle
and an inactive AppShell Loader — this renders in frame 1 before
any Loader or LayerStack is evaluated, giving the user a white
screen before QML finishes parsing.

AppShell.qml holds the LayerStack, horizontal ListView, PageDot,
and DisplaySettings. It is activated in Component.onCompleted so
its parse cost and the DisplaySettings D-Bus round-trip to the
system settings daemon fall after the first Wayland frame commit.

Pages 1-3 are deferred via Loader active: pagesWarmed, activated
1500ms after launch while the user is still on the flashlight page.
cacheBuffer keeps all delegates alive so delegate destroy/recreate
cannot reset property defaults and break the anyFeatureActive relay.

PageDot visible only while no feature is active. ListView.interactive
blocked while any feature is active to prevent accidental navigation.
DisplayBlanking.preventBlanking gated on anyFeatureActive so the
screen sleeps normally on idle menu pages.
Carry forward upstream brightness management via DisplaySettings,
DeviceSpecs.flatTireHeight vertical offset, hasRoundScreen radius,
and upstream easing curves and ColorAnimation on color change.

Add PageHeader visible while flashlight is off so the page is
identifiable when navigating. Add threshold-checked onPressed/
onReleased pattern matching QuickPanelToggle fix to prevent
accidental toggle on horizontal swipes.

Expose flashOn property relayed to app.flashlightOn so PageDot
and ListView.interactive react to the flashlight state.
Single page combining what would have been separate beacon and
morse pages. ValueCycler above the trigger selects the signal
mode: Pulse, Emergency, SOS, CQD, XXX, MAYDAY.

All modes share a single signalLevel property (0.0-1.0) driven
by a unified animation and timer system. beaconOn changes only
the opacity floor — idle: 0.4+signalLevel*0.6, active: signalLevel
— so the button always shows a live preview of the selected pattern
regardless of activation state.

A dark backing Rectangle matches the flashlight idle button style.
IntSelector below the trigger selects beacon color by hue value
0-360 degrees where 0 is white and 360 is red. beaconColor is a
pure binding never broken by imperative sets so hue changes update
the live animation instantly.

Horizontal drag overlay active while beaconOn adjusts the speed
multiplier 0.25-4.0x with a fading multiplier indicator label.
SmoothedAnimation on width/height/radius handles rapid toggle
correctly by reversing from current value rather than snapping.
Full-screen flash at 1-25 Hz, expanding from a button circle on
activation. RPM readout above the trigger converts Hz to rotations
per minute making the page useful as a stroboscope for checking
motor and fan rotation speeds.

IntSelector below the trigger provides precise Hz adjustment with
the built-in drag scrubber. A full-screen drag overlay active only
while strobeOn adjusts Hz with delta-based finger movement and
shows a fading Hz feedback label. The overlay is disabled while
inactive so horizontal ListView swipes pass through freely.

SmoothedAnimation on width/height/radius prevents snap on rapid
toggle. hzFeedbackLabel visibility bound to strobeOn so it cuts
instantly on deactivation rather than fading over idle UI elements.
Full-screen left-scrolling text banner across six categories:
Emergency, Navigation, Social, Fun, Emoji, Kaomoji. Two ValueCycler
controls above and below the trigger select category and message.
The trigger button shows a live scrolling preview of the selected
message before activation.

On activation the trigger expands to full screen. The content
rotates to stay parallel to the ground using a smoothed accelerometer
reading via Qt.Sensors Accelerometer at 30Hz — the text remains
readable to bystanders regardless of wrist angle.

Horizontal drag while active adjusts scroll speed in px/s for text
categories and blink cycle in ms for the Emoji category. The drag
axis is projected onto the current rotation angle at press time so
the gesture is correctly interpreted at any wrist orientation.

Emoji category uses a pulsing opacity animation instead of scrolling
since a single emoji does not benefit from horizontal movement.
Drag adjusts blink rate 40-200 bpm. Static mode at 0 bpm available.

BannerScroll: Timer-driven scroll at real elapsed time so speed
changes take effect mid-scroll without restarting. Starts off-screen
right so no blink at x=0 before first tick. toUpperCase enforced
at render time so translators write natural case.

ValueCycler: minimal centered cycler with HighlightBar for touch
feedback. toUpperCase display enforced. Used by beacon and message.
All strings internationalised with qsTrId.
29 new translation IDs covering page headers, beacon mode names,
and all message page strings across six categories.

Emoji, Kaomoji, and international distress codes SOS/CQD/XXX/MAYDAY
are left as literals — universally recognised, translation would
reduce clarity. toUpperCase enforced at render time so translators
write natural case in .ts files.

en_GB: Torch for Flashlight, 999 for emergency number.
de_DE: native speaker verified. 112 for emergency number.
fr: native speaker reviewed by kido. 112 for emergency number.
    Une autre for encore, Ennuyeeeux matches drawn-out source energy.
es: native speaker verified. 112 for Spain.
nl_NL: native speaker contribution. Beacon left untranslated as
    internationally recognised nautical term. BRU-U-U for burp.
@moWerk moWerk marked this pull request as draft March 22, 2026 18:25
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.

1 participant