Skip to content

Commit a5d0482

Browse files
eriedclaude
andcommitted
CI: rolling pre-release for wear-os-watch-ultra
Cherry-pick the branch-apk workflow already used on external-gps and multi-wheel-support so every push to this branch produces a fresh debug APK at the rolling `branch-wear-os-watch-ultra` GitHub release. Add a BRANCH.md describing what the watch app does and what testers should poke at, since the workflow inlines that file into the release body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 97ccb9e commit a5d0482

2 files changed

Lines changed: 222 additions & 0 deletions

File tree

.github/workflows/branch-apk.yml

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: Branch APK
2+
3+
# On every push to a non-main branch, build a debug APK and replace the
4+
# rolling pre-release tagged `branch-<name>` with the freshly built artifact.
5+
# Testers always get the latest commit by visiting the same release URL.
6+
#
7+
# Pull-request runs build the APK as a workflow artifact (downloadable from
8+
# the Actions tab for ~90 days) but do not touch the release list, so PRs
9+
# from forks can't push tags.
10+
11+
on:
12+
push:
13+
branches-ignore:
14+
- main
15+
tags-ignore:
16+
- "**"
17+
pull_request:
18+
branches:
19+
- "**"
20+
workflow_dispatch:
21+
22+
permissions:
23+
contents: write
24+
25+
jobs:
26+
build:
27+
runs-on: ubuntu-latest
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0 # full history so we can produce a "since main" log
34+
35+
- name: Set up JDK 17
36+
uses: actions/setup-java@v4
37+
with:
38+
distribution: temurin
39+
java-version: "17"
40+
41+
- name: Set up Gradle
42+
uses: gradle/actions/setup-gradle@v3
43+
44+
- name: Make gradlew executable
45+
run: chmod +x ./gradlew
46+
47+
- name: Build debug APK
48+
run: ./gradlew :app:assembleDebug --no-daemon --stacktrace
49+
env:
50+
GRADLE_OPTS: -Xmx4g -Dorg.gradle.jvmargs=-Xmx4g
51+
52+
- name: Compute build metadata
53+
id: meta
54+
if: github.event_name != 'pull_request'
55+
run: |
56+
BRANCH="${GITHUB_REF##*/}"
57+
SHORT_SHA="$(git rev-parse --short HEAD)"
58+
TAG="branch-${BRANCH}"
59+
APK_NAME="EUC-Planet-${BRANCH}-${SHORT_SHA}.apk"
60+
61+
# Pull the per-branch description if BRANCH.md exists, otherwise
62+
# fall back to a generic notice. This becomes the release body header.
63+
if [ -f BRANCH.md ]; then
64+
cp BRANCH.md /tmp/branch-notes.md
65+
else
66+
cat > /tmp/branch-notes.md <<EOF
67+
# ${BRANCH}
68+
69+
No \`BRANCH.md\` found at repo root. Add one to describe what this
70+
branch is for and who should test it.
71+
EOF
72+
fi
73+
74+
# Recent commits since branching from main, capped to keep the
75+
# release body readable on big branches.
76+
git log --pretty=format:'- %h %s' main..HEAD | head -50 > /tmp/commit-log.md || true
77+
78+
{
79+
echo "**Pre-release from branch \`${BRANCH}\`** — automatic build at \`${SHORT_SHA}\`."
80+
echo
81+
echo "This is a CI-built debug APK. It can't update an existing Play Store install in place — uninstall the Play version first to install this build."
82+
echo
83+
cat /tmp/branch-notes.md
84+
echo
85+
echo "## Recent commits"
86+
echo
87+
cat /tmp/commit-log.md
88+
} > /tmp/release-body.md
89+
90+
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
91+
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
92+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
93+
echo "apk_name=${APK_NAME}" >> "$GITHUB_OUTPUT"
94+
95+
- name: Rename APK for release
96+
if: github.event_name != 'pull_request'
97+
run: |
98+
cp app/build/outputs/apk/debug/app-debug.apk \
99+
"/tmp/${{ steps.meta.outputs.apk_name }}"
100+
101+
- name: Upload APK as workflow artifact
102+
uses: actions/upload-artifact@v4
103+
with:
104+
name: app-debug-${{ github.run_id }}
105+
path: app/build/outputs/apk/debug/app-debug.apk
106+
retention-days: 90
107+
108+
- name: Strip prior APKs from rolling release
109+
if: github.event_name != 'pull_request'
110+
env:
111+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
run: |
113+
# action-gh-release attaches new files but doesn't remove the previous
114+
# commit's APK; without this step the rolling pre-release accumulates
115+
# one APK per push. Delete every existing .apk on the tag before the
116+
# next step uploads the freshly built one. Tolerate the tag not
117+
# existing yet (first run on a brand-new branch).
118+
tag="${{ steps.meta.outputs.tag }}"
119+
if gh release view "$tag" >/dev/null 2>&1; then
120+
gh release view "$tag" --json assets \
121+
--jq '.assets[] | select(.name | endswith(".apk")) | .name' \
122+
| while read -r asset; do
123+
[ -n "$asset" ] && gh release delete-asset "$tag" "$asset" --yes || true
124+
done
125+
fi
126+
127+
- name: Replace rolling pre-release with new APK
128+
if: github.event_name != 'pull_request'
129+
uses: softprops/action-gh-release@v2
130+
with:
131+
tag_name: ${{ steps.meta.outputs.tag }}
132+
name: ${{ steps.meta.outputs.tag }} (${{ steps.meta.outputs.short_sha }})
133+
body_path: /tmp/release-body.md
134+
prerelease: true
135+
# Replace the existing tag/release on every push so testers can
136+
# always find the latest at the same URL.
137+
make_latest: "false"
138+
files: /tmp/${{ steps.meta.outputs.apk_name }}

BRANCH.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# wear-os-watch-ultra
2+
3+
## What this branch adds
4+
5+
Companion app for Wear OS 5+ watches (built and tuned on the Galaxy Watch
6+
Ultra). The phone holds the BLE link to the wheel and pushes a compact
7+
telemetry snapshot to the watch over the Wearable Data Layer; the watch is a
8+
thin client and never talks to the wheel directly.
9+
10+
Concretely shipped here:
11+
12+
- **Full-bleed speed dial** that wraps the entire watch face. Same arc
13+
geometry as the phone dashboard (260° sweep, accent-tinted safe band,
14+
orange/red danger wedges, ticks). Speed number, units, batteries and
15+
buttons live inside the dial.
16+
- **Three batteries at a glance** above the speed number: wheel, phone, and
17+
watch, each colour-graded by the same red/amber/green thresholds used on
18+
the phone dashboard.
19+
- **Accent colour follows the phone.** The accent the user picked in app
20+
settings travels through the wire format and tints the dial safe band,
21+
the wheel-name header on page 2, and the horn / light buttons.
22+
- **Imperial units follow the phone.** When `imperialUnits` is on, the
23+
watch shows mph, miles, and °F; flipping the setting takes effect within
24+
one publish cycle (≤200 ms).
25+
- **Page 2 — at-a-glance details.** Wheel name in accent, live speed, then
26+
a tabular column of voltage / current / power (V × A) / PWM / temp /
27+
torque / trip. Values align vertically across rows so you can scan down a
28+
column.
29+
- **Buttons follow the phone iconography.** Horn = `Icons.Filled.Campaign`,
30+
Light = `Icons.Filled.FlashOn` — same glyphs as the phone dashboard.
31+
- **Disconnected state** shows a phone glyph and a two-line "Open EUC
32+
Planet on your phone" message. No more red dot that read as an error.
33+
- **Resolution-clean.** All sizes derive from `BoxWithConstraints.maxWidth`
34+
so the layout looks right on small round watches (~390 dp) and on Watch
35+
Ultra (~454 dp) without separate code paths.
36+
37+
## Architecture
38+
39+
- `WearBridge` (phone, `app/`) subscribes to `WheelRepository` flows and
40+
`SettingsRepository.settings`, samples to 5 Hz, packs a `DataMap` and
41+
publishes at `/euc/state`. Reads phone battery via `BatteryManager`.
42+
- `WatchBridgeService` (watch, `wear/`) decodes the DataMap into a
43+
`WatchState` and updates a singleton `WatchStateRepository`.
44+
- `WatchApp` (Compose) collects from the repo and renders.
45+
- Control flow (horn / light) is the existing reverse channel: watch sends
46+
short Messages on `/euc/control`, phone routes them through
47+
`WheelRepository`.
48+
49+
## Who should test this
50+
51+
- **Watch Ultra owners** with a paired phone running the matching debug or
52+
pre-release APK from the same branch: confirm the dial reads correctly,
53+
battery percentages match Settings/Battery on each device, accent and
54+
imperial follow the phone, and horn/light controls work.
55+
- **Other Wear OS 5+ watches** (round and rectangular): the layout should
56+
scale; please report clipping or overlap. Square watches use the same
57+
dial with the corners falling outside the arc — intentional.
58+
- **Anyone curious about the UI without hardware**: debug builds expose an
59+
ADB demo broadcast. With the watch app open:
60+
```
61+
adb shell am broadcast -p com.eried.eucplanet \
62+
-a com.eried.eucplanet.wear.DEMO \
63+
--ef speed 32 --ei battery 78 --ei phone 64 \
64+
--es accent teal --ef maxSpeed 70 \
65+
--ez imperial false --es name "InMotion V14"
66+
```
67+
Speed/battery/accent/imperial extras are all optional.
68+
69+
## Known limits
70+
71+
- **Pairing must be done via the Wear OS by Google companion app** the
72+
first time. Without pairing, the watch shows the disconnected
73+
placeholder forever; this branch does not change that.
74+
- **Tile and complication** (carousel and watch-face quick-glance) are not
75+
here yet. The companion launches as an app you open from the launcher.
76+
- **No standalone (watch-only) BLE.** The watch never connects to the
77+
wheel directly; if the phone's app process is killed, telemetry stops.
78+
- **No on-watch settings.** Imperial / accent / max-speed cap are read
79+
from the phone — change them there.
80+
81+
## Feedback
82+
83+
File issues at https://github.com/eried/eucplanet/issues. Tag with the
84+
watch model and Wear OS version if you can.

0 commit comments

Comments
 (0)