Skip to content

New App: Line Dash - A swipeable analog clock and stats dashboard #4272

Merged
bobrippling merged 114 commits into
espruino:masterfrom
pagnotta:master
Jul 15, 2026
Merged

New App: Line Dash - A swipeable analog clock and stats dashboard #4272
bobrippling merged 114 commits into
espruino:masterfrom
pagnotta:master

Conversation

@pagnotta

@pagnotta pagnotta commented Jun 27, 2026

Copy link
Copy Markdown

This PR introduces Line Dash, a new clock face based on the beautiful Line Clock by deepDiverPaul. The original minimalist line aesthetic was expanded into a swipeable suite of interactive dashboard gauges.

Features of the new app:

  • Analog Clock: Clean face that automatically inherits the system's 12/24h preference. Swipe up/down to briefly show the date (weekday and day/month, following the system locale).
  • Swipeable Dashboards: Swipe left/right (or tap) to cycle between the gauges; every screen can be disabled individually in the settings. The app remembers the active screen across reloads — get a message while checking your heart rate mid-run and you return straight to the HR gauge, with the sensor re-enabled.
  • Steps Gauge: A sweeping dial tracking your daily steps.
  • Distance Meter: Distance covered today, based on steps and a configurable stride length, plus a persistent trip meter: swipe up to start a trip ("count from here"), swipe down to return to the day total — the trip keeps running in the background. Resetting a trip requires a confirmation (RESET? popup); trips survive reloads and end automatically at midnight.
  • Heart Rate Gauge: Dynamic, color-coded tick marks indicating your current HR zone (based on an age-derived max HR). The sensor only runs while this screen is shown; optional live updates with a configurable interval to save power.
  • Battery Gauge: A 180-degree fuel gauge with fixed color zones on the dial (green above 30%, a yellow warning band down to 15%, and a red reserve below — native 3-bit colors, no dithering). Automatically switches to this dashboard when the charger is connected and returns to the clock when unplugged.
  • Barometer: A 300-degree 950-1050 hPa dial with 1 hPa subticks. Swipe up/down for the exact reading and an estimated altitude. The reading can be calibrated to sea level in the settings, so it matches local weather reports. The sensor is only powered while the screen is visible and unlocked (a single reading per minute while locked).

The app is intentionally full-screen and does not display widgets. An attribution to the original author has been placed in the LICENSE and README.md files.

app-screenshot-clock app-screenshot app-screenshot-step app-screenshot-bat

Patrick Heeren added 30 commits June 27, 2026 12:28
Patrick Heeren and others added 26 commits July 5, 2026 18:12
The Vector font's space glyph is a full character wide, which made the
gap between day and month look oversized. drawOverlayPill now accepts
the big line as two parts drawn with a narrow fixed gap (6px); the
RESET? popup keeps using the plain string form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Vector font advance width includes trailing spacing after the last
glyph, so values centered via setFontAlign sat about 2px left of the
circle center (vertically they were already exact). Nudge the text
right proportionally to the font size; verified against watch
screenshots and emulator renders (offset now 0px for 09/14 on the
barometer, clock and battery checked visually).
New swipeable screen showing air pressure on a 950-1050 hPa dial in
10 hPa steps, using the Bangle.js 2 pressure sensor. The sensor is
only powered while the screen is shown (same pattern as the HRM,
without the debounce since the barometer starts cheaply); readings
redraw at most every 2 seconds, and a -- placeholder is shown until
the first reading arrives. Toggleable via a Show Barometer setting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lay the pressure scale out like the battery fuel gauge (270-450
degrees, full 950-1050 hPa range always visible, labels on every
second tick to avoid collisions). Add an optional centerSize gauge
option so the four-digit hPa reading renders in the same font size
as the two-digit battery value instead of being shrunk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Revert the enlarged center circle; instead drawNumber accepts an array
of two strings rendered as stacked lines, so 1014 shows as 10 over 14
in the standard-size circle. Sub-1000 readings stay on one line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
100 hPa now spread over 300 degrees instead of 180, so small pressure
changes move the dial noticeably. Every 10 hPa tick gets a label since
the wider spacing leaves room for four-digit values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dial and its tick labels already give the hundreds, so the center
circle only shows the last two digits (leading zero, full font size)
with a small hPa label below - reusing drawNumber's label support.
This replaces the stacked two-line rendering for four-digit values.
The last-two-digits value now truncates like the steps/distance
decimals instead of rounding, so it matches the needle position.
Swiping up or down on the barometer screen shows the exact reading
(e.g. 1010.6 with an hPa label) in the same pill overlay style as the
date on the clock face; the date overlay timeout was generalized into
an info overlay shared by both screens.
10 subdivisions per 10 hPa interval instead of 5, matching the tick
density of the steps/distance gauges; the 5 hPa mark gets the medium
tick automatically.
Staying on the baro screen overnight kept the sensor polling every
second and redrawing the gauge every 2s, draining the battery. The
barometer now runs continuously only while the screen is shown AND the
watch is unlocked; while locked, the once-a-minute redraw takes a
single Bangle.getPressure() one-shot reading instead, so the display
keeps updating without the standby cost. All power decisions go
through one updateBaroPower() helper (screen change, lock/unlock,
charging, startup).
New 'Sea level (hPa)' settings entry: the menu takes a one-shot raw
reading when opened and the user dials in the value their weather
report gives; the stored factor (QNH / raw) corrects altitude and
sensor offset in one constant. Applied to every reading, so the dial,
circle digits and exact-reading overlay all show sea-level pressure.
Default factor 1 keeps the previous behavior.
Vertical swipes on the baro screen now cycle: exact sea-level reading
-> altitude -> hidden. The altitude comes from the international
barometric formula using the raw pressure and the sea-level value
entered at calibration (default 1013.25), so the calibration doubles
as the altimeter reference.
The continuous green-to-red gradient produced dithered in-between
colors on the 3-bit display at almost every charge level. The dial now
shows fixed fuel-gauge zones in native colors instead - green above
30%, yellow warning band down to 15%, red reserve below - and the
needle and center circle take the color of the current zone.
Plugging in felt sluggish and briefly showed a locked state: the
backlight was raised before the unlock, and the resulting lcdPower and
lock events each repainted a screen the charge handler had just drawn
(three full redraws instead of one). Unlock now happens before the
backlight wake, the lock listener only repaints when the lock state on
screen actually changed, and the lcdPower listener skips repainting a
screen drawn less than 500ms ago.
Plugging in now only wakes the backlight, and only after the battery
dashboard has been drawn, so the light reveals the finished screen.
The watch stays locked; the lcdPower listener's freshness check keeps
the wake from repainting the screen a second time.
The minute redraw loop could die after plugging in: the lock listener
cleared the draw timeout and, since the repaint-skip optimization, did
not always redraw (which is what re-armed it). And with the display
counted as off, queueDraw/draw stopped entirely - but the transflective
screen stays readable on the charger, so the gauge visibly froze.

The lock and lcdPower listeners now re-arm the minute tick whenever
they skip a repaint, and queueDraw/draw keep running while charging
regardless of the LCD state.
Tap no longer advances to the next gauge: it now shows the info overlay
of the current dashboard (date on the clock, exact pressure/altitude
cycle on the barometer) and drives the trip reset confirmation on the
distance screen. Vertical swipes are reserved for navigating sub-views
(day/trip on the distance gauge); horizontal swipes keep cycling the
dashboards.
Swipe up on the barometer switches to an altimeter dial (marked ALT),
swipe down returns to the pressure dial. One full revolution covers
100m with ticks every 10m and subticks every meter; the center circle
shows the hundreds of meters. Tap shows the exact altitude as a pill,
replacing the old three-step overlay cycle on the pressure dial (each
view now has its own single-value overlay). The active sub-view is
persisted across app reloads like the trip view.
Put 0 at the 12 o'clock position of the altimeter dial instead of the
7 o'clock position the other gauges start at, so the needle behaves
like an aircraft altimeter (25m points right, 50m down). The dial also
wraps below zero, showing the end of the 90m segment left of the 0.
The unit on the labels already tells the altimeter apart from the
pressure dial, matching how the distance gauge marks its ticks.
New 'Altitude Unit' setting switches the altimeter between meters and
feet: same dial, one revolution per 100 units, with 2ft subticks in
feet mode since 1ft is below the sensor resolution. The needle now
follows an exponentially smoothed altitude so sensor noise (~0.5m)
does not make it flutter on the fine dial; after a gap in readings
(one-shot polling while locked) the value snaps instead of lagging.
The tap overlay keeps showing the exact unsmoothed altitude.
Tap now shows the exact step count on the steps gauge and the exact
distance of the current view (day total or trip) on the distance
gauge, so every dashboard answers a tap with its precise value. The
trip reset moves back to its original gesture (swipe up in the trip
view, confirmed by another swipe up) and taps are ignored while the
RESET? popup shows; vertical swipes dismiss a visible pill.

@bobrippling bobrippling left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great - just one question

Comment thread apps.json
Patrick Heeren added 2 commits July 15, 2026 18:53
apps.json had an accidentally committed generated copy instead of the
upstream placeholder, and jimp (local screenshot tooling) had slipped
into package.json/package-lock.json. None of this belongs in the PR.
The previous reset used current upstream, which includes upstream's own
js-yaml bump and so still showed up in the PR diff.
@bobrippling bobrippling merged commit e7d1d0d into espruino:master Jul 15, 2026
2 checks 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