Skip to content

[WatchfacePage] Add on-device watchface store with direct install from unofficial-watchfaces#123

Open
moWerk wants to merge 5 commits intoAsteroidOS:masterfrom
moWerk:watchface-store
Open

[WatchfacePage] Add on-device watchface store with direct install from unofficial-watchfaces#123
moWerk wants to merge 5 commits intoAsteroidOS:masterfrom
moWerk:watchface-store

Conversation

@moWerk
Copy link
Copy Markdown
Member

@moWerk moWerk commented Mar 31, 2026

This PR introduces the first iteration of an on-device watchface store
for AsteroidOS, allowing users to browse, install and remove community
watchfaces directly from the watch without requiring a computer.

Todo to before undraft:
- Add translations.
- Polish layout

watchface-store.mp4

What's new

Watchface Store — a new page accessible from the bottom of the
watchface selector gallery. When an IP connection is detected, the
store loads the full catalog from the unofficial-watchfaces repository
and displays all community watchfaces with their preview thumbnails.
Tapping an uninstalled watchface downloads and activates it
immediately. Long pressing an installed watchface removes it after a
remorse timer.

User-space installs — all community watchfaces install into the
XDG user data directory (~/.local/share/asteroid-launcher/) so no
root access is required. Fonts land in ~/.fonts/ and are picked up by
fontconfig without a full system restart. A launcher restart button
with a remorse timer is provided for cases where new fonts or QML
paths need to be picked up by the compositor.

Extended watchface selector — the selector now shows both system
and user-installed watchfaces in a unified gallery. Long pressing a
user-installed watchface in the selector removes it directly. The
active watchface is indicated by a subtle dark circle that doubles as
a pressure-sensitive gesture indicator — the circle deepens during
press and reaches full depth at the longpress threshold, giving
continuous visual feedback proportional to intent.

WatchfaceHelper C++ singleton — a new singleton handles all
network downloads, file writes and system calls from QML. Write access
is enforced against a path whitelist. Arbitrary filesystem reads from
QML are blocked.

Notes for reviewers

  • Community watchfaces must follow the unofficial-watchfaces file
    structure spec for all assets to install correctly
  • The catalog is cached on first load and only refreshed on explicit
    user request to avoid GitHub API rate limits
  • Preview thumbnails are cached permanently at the device-appropriate
    resolution and never re-downloaded unless the cache is cleared
  • Stock watchfaces are not affected and remain in their system paths
  • Font-delivering watchfaces require a launcher restart after install;
    the store page makes this available with a safety remorse timer

moWerk added 2 commits March 31, 2026 20:15
Introduces WatchfaceHelper as a QML singleton registered under
org.asteroid.settings 1.0. Provides network-backed file downloads
via QNetworkAccessManager, directory creation, watchface removal,
font cache rebuilding and launcher restart.

All filesystem writes are enforced against a whitelist of
user-writable paths mirroring what the ./watchface deploy script
writes: the XDG GenericDataLocation asteroid-launcher subtree,
~/.fonts/ and the QStandardPaths cache location. Any write outside
these paths is blocked and logged. readFile() is additionally
restricted to the cache location only, preventing arbitrary
filesystem reads from QML.

Path helpers userWatchfacePath(), userAssetPath(), userFontsPath()
and cachePath() expose the resolved paths to QML consumers.
restartSession() runs fc-cache -f synchronously before issuing
systemctl --user restart asteroid-launcher so newly installed fonts
are visible immediately after the compositor restarts.

Adds Qt5::Network to CMakeLists target_link_libraries.
WatchfaceSelector gains a second FolderListModel watching the XDG
user watchface path alongside the system path. Both are merged into
a unified ListModel via a debounced Timer to prevent double-clear
during simultaneous count changes. A HEAD probe to api.github.com
fires on Component.onCompleted and gates a persistent store footer
button that is greyed out with an alternative label when offline.

WatchfaceStorePage fetches the unofficial-watchfaces repo catalog
via the GitHub Contents API and caches the response JSON to disk.
Subsequent page opens load from cache and only hit the network on
explicit refresh. Thumbnail previews are downloaded lazily at the
device-appropriate resolution and cached permanently. Watchfaces
whose preview returns a 404 are pruned from the model silently.
Directory listings recurse into subdirectories to handle watchfaces
with nested asset structures.

Installs land in the XDG user data tree so no root access is
required. The QML file, single-resolution preview PNG, watchfaces-img
assets and fonts are fetched in parallel. The downloaded watchface is
auto-activated on install complete. A persistent footer offers a
RemorseTimer-guarded launcher restart to pick up new fonts and QML
paths — the page fades to black when the remorse timer fires and
holds until the process is killed, preventing a flash back to page
content.

Interaction model on both pages uses manual onPressed tracking with
a Dims.l(2) scroll detection threshold, matching the IntSelector
scrubber pattern. Press immediately starts an opacity animation
giving frame-one feedback. Scroll cancels and reverses the animation.
Release within threshold confirms the action. Long press triggers
deletion via RemorseTimer on both pages. Install state is conveyed
through color and opacity transitions on a per-tile circle with no
discrete timer: green animates in from press, bumps on completion,
flashes red on network failure. The selector page uses the same
circle in black to indicate the active watchface at opacity 0.2
with the press animation fading toward 0.5 over the full longpress
budget as a continuous gesture discriminator.
@moWerk moWerk marked this pull request as draft March 31, 2026 19:32
Community watchfaces that ship a custom wallpaper now have it
fetched into the XDG user wallpapers folder during install.
The existing _fetchDirectory call pattern handles all file types
in the wallpapers/full/ folder including jpg, png and svg.
moWerk added 2 commits April 1, 2026 00:09
Footer buttons on WatchfaceSelector and WatchfaceStorePage were
clipped at the bottom edge on round watches. Adds a conditional
spacer below the button stack sized to Dims.l(8) on round screens
and zero on square screens.
@moWerk moWerk marked this pull request as ready for review March 31, 2026 22:26
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