feat(train): setting up and keeping the Polar Loop (ADR 0030, D34–D36)

On Android, the Loop through Polar's BLE SDK 8.3.0 (from JitPack, asked
for Polar's group alone, its files pinned by SHA-256 in Gradle's
verification metadata): LoopBridge.kt over app.halkyon.train/loop, the
first-time use's figures mapped and checked in LoopSetup.kt, tested on
the JVM. PolarLoop per person remembers the band in loop.json, connects
while the app is open, sets its clock at each connection, keeps what it
tells (battery, charging, firmware), offers a changed profile and sends
it only when asked (D35: first-time use writes a new user identifier
each time), and lets the band go by a factory reset after asking. The
setup sheet finds Loops nearest first and asks only for what the
profile lacks; Polar's defaults (60, 220 less the age, a VO2max from
Polar's fitness tables) go to the band alone.

Polar's SDK is not free software (D36, correcting D32): the app's AGPL
gains a section 7 permission for linking it, LICENSE.md names it, and
its licence and bundled notices join the licence list on Android.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 18:58:02 +01:00
commit cd72cf6ccc
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -239,5 +239,42 @@
"profileSleepGoal": "Sleep goal",
"profileSave": "Save",
"profileSaved": "Saved",
"profileInvalid": "Check the figures marked."
"profileInvalid": "Check the figures marked.",
"profileNeeded": "Needed for your band",
"loopTitle": "Polar Loop",
"loopHint": "Your band: its setup, battery and clock",
"@loopHint": {"description": "Under Polar Loop in the account sheet."},
"loopNone": "Train sets up a Polar Loop and keeps it itself, without Polar Flow or a Polar account. A band used with Polar Flow needs a factory reset first.",
"loopSetUp": "Set up a Loop",
"loopSetUpAgain": "Set up again",
"loopLetGo": "Let the band go",
"loopLetGoTitle": "Let the band go?",
"loopLetGoText": "The band is reset to the factory's state, and what it holds that hasn't reached Train is lost. Afterwards it can be paired with another phone, or with Polar Flow.",
"loopLetGoConfirm": "Reset the band",
"loopConnected": "Connected",
"loopLastSeen": "Last seen {when}",
"@loopLastSeen": {"placeholders": {"when": {"type": "String", "example": "28 Oct 2026, 18:30"}}},
"loopNotInReach": "Not in reach",
"loopBattery": "Battery {level} %",
"@loopBattery": {"placeholders": {"level": {"type": "int"}}},
"loopCharging": "Battery {level} %, charging",
"@loopCharging": {"placeholders": {"level": {"type": "int"}}},
"loopFirmware": "Firmware {version}",
"@loopFirmware": {"placeholders": {"version": {"type": "String", "example": "1.2.3"}}},
"loopProfileChanged": "Your body changed since the band was set up",
"loopProfileChangedHint": "Sending sets the band up again with your figures as they are now.",
"loopSend": "Send to the band",
"loopSent": "Sent to the band.",
"loopFailed": "The band didn't answer. Keep it close, and try again.",
"loopSearching": "Looking for Loops nearby",
"loopSearchingHint": "Keep the band close; tap it to wake it up.",
"loopRefused": "Train can't look for your band without nearby devices. Allow them in Android's settings.",
"loopUnnamed": "Polar Loop",
"loopSettingUp": "Setting up the band…",
"loopSettingUpHint": "Android may ask you to pair it.",
"loopSetUpDone": "The band is set up.",
"loopFormTitle": "For your band",
"loopFormIntro": "Your band needs these. They're kept in Your body, sealed with your key. For a resting or highest heart rate or a VO2max you haven't given, the band takes Polar's defaults, and Your body stays as it is.",
"loopFormSave": "Set up the band"
}