From d6de6dedf1b461a89e9ac851e3929a07def18dea Mon Sep 17 00:00:00 2001 From: Marcus Rehbock Date: Fri, 7 Aug 2026 16:40:54 -0700 Subject: [PATCH] Add release-signing config plugin, README, memory of PWA parity Co-Authored-By: Claude Fable 5 --- README.md | 83 +++++++++++++++++---------------- app.json | 1 + package.json | 4 +- plugins/with-release-signing.js | 37 +++++++++++++++ 4 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 plugins/with-release-signing.js diff --git a/README.md b/README.md index 4d67aec..636df79 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,61 @@ -# Welcome to your Expo app 👋 +# health-app -This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). +Cross-platform (iOS + Android) Expo app — native front end for the private +health dashboard at **https://health.rehbock.xyz**. -## Get started +The app is a pure client: all data lives on the server and is fetched live over +HTTPS with HTTP Basic auth. Nothing is stored on the device except the auth +token (in the platform keychain via `expo-secure-store`). -1. Install dependencies +## Screens - ```bash - npm install - ``` +| Screen | Data source | +| --- | --- | +| Today | overview cards + clinical priorities (`health-meta.json`) | +| Blood | `/api/health/v1/biomarkers` + `/draws` — search, flags, sparklines | +| Sleep | `/api/health/v1/sleep` + `sleep-data.json` — stacked stage chart | +| Train | `/api/health/v1/workouts` — weekly volume chart, expandable sessions | +| Body | `dexa-scans.json` — DXA summary with deltas, regions, indices | +| Mind | `/api/meditation/v1/stats` + `/sessions` | +| DNA | `ancestry-traits.json` — 99 AncestryDNA traits | -2. Start the app +Ported from the PWA at `health.rehbock.xyz/app/` +(repo `marcus/health.rehbock.xyz`, `app/index.html`); charts are +react-native-svg, tap a bar for the night/week detail. - ```bash - npx expo start - ``` +## Development -In the output, you'll find options to open the app in a - -- [development build](https://docs.expo.dev/develop/development-builds/introduction/) -- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) -- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) -- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo - -You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). - -## Get a fresh project - -When you're ready, run: - -```bash -npm run reset-project +```sh +npm install +npx expo start # scan the QR with Expo Go (iOS or Android) ``` -This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. +Log in with the same Basic-auth credentials as the website. -### Other setup steps +## Android release APK -- To set up ESLint for linting, run `npx expo lint`, or follow our guide on ["Using ESLint and Prettier"](https://docs.expo.dev/guides/using-eslint/) -- If you'd like to set up unit testing, follow our guide on ["Unit Testing with Jest"](https://docs.expo.dev/develop/unit-testing/) -- Learn more about the TypeScript setup in this template in our guide on ["Using TypeScript"](https://docs.expo.dev/guides/typescript/) +Signing expects (auto-skipped if absent, falling back to the debug key): -## Learn more +- keystore `~/.android-keys/health-app-release.jks` (alias `health`), or `$HEALTH_KEYSTORE` +- password in `~/.android-keys/health-app-release.password`, or `$KEYSTORE_PASSWORD` -To learn more about developing your project with Expo, look at the following resources: +The signing config is injected by `plugins/with-release-signing.js` at prebuild. -- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). -- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. +```sh +npx expo prebuild --platform android +cd android && ./gradlew assembleRelease +# → android/app/build/outputs/apk/release/app-release.apk +``` -## Join the community +Install on GrapheneOS by copying the APK over (or via Obtainium once a +Gitea release exists). -Join our community of developers creating universal apps. +## iOS -- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. -- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. +No Mac is available in this setup, so there is no local iOS build. Options: + +- **Expo Go** (zero setup): `npx expo start`, scan the QR from the iPhone. +- **EAS Build** (real install): `npx eas build -p ios` — needs an Apple + Developer account; produces a TestFlight/ad-hoc build in the cloud. + +The `ios.bundleIdentifier` is already set to `xyz.rehbock.health`. diff --git a/app.json b/app.json index 0a7fd92..38da843 100644 --- a/app.json +++ b/app.json @@ -30,6 +30,7 @@ "plugins": [ "expo-router", "expo-secure-store", + "./plugins/with-release-signing.js", [ "expo-splash-screen", { diff --git a/package.json b/package.json index 2864822..ce5d6fe 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "lint": "expo lint" }, diff --git a/plugins/with-release-signing.js b/plugins/with-release-signing.js new file mode 100644 index 0000000..5270aa5 --- /dev/null +++ b/plugins/with-release-signing.js @@ -0,0 +1,37 @@ +/* Injects a release signing config into the prebuilt android project. + Keystore: $HEALTH_KEYSTORE or ~/.android-keys/health-app-release.jks + Password: $KEYSTORE_PASSWORD or ~/.android-keys/health-app-release.password + Falls back to the debug keystore when neither exists. */ +const { withAppBuildGradle } = require('expo/config-plugins'); + +const SIGNING = ` release { + // keystore lives outside the repo; password from env or ~/.android-keys + def ksPath = System.getenv("HEALTH_KEYSTORE") ?: "\${System.properties['user.home']}/.android-keys/health-app-release.jks" + def passFile = new File("\${System.properties['user.home']}/.android-keys/health-app-release.password") + def ksPass = System.getenv("KEYSTORE_PASSWORD") ?: (passFile.exists() ? passFile.text.trim() : null) + if (new File(ksPath).exists() && ksPass != null) { + storeFile file(ksPath) + storePassword ksPass + keyAlias System.getenv("KEY_ALIAS") ?: "health" + keyPassword ksPass + } + } + }`; + +module.exports = function withReleaseSigning(config) { + return withAppBuildGradle(config, (cfg) => { + let g = cfg.modResults.contents; + if (!g.includes('def ksPath')) { + g = g.replace( + /(signingConfigs \{[\s\S]*?keyPassword 'android'\n {8}\}\n)( {4}\})/, + `$1${SIGNING}`, + ); + g = g.replace( + /\/\/ Caution![^\n]*\n\s*\/\/ see https:\/\/reactnative\.dev[^\n]*\n\s*signingConfig signingConfigs\.debug/, + 'signingConfig signingConfigs.release.storeFile ? signingConfigs.release : signingConfigs.debug', + ); + cfg.modResults.contents = g; + } + return cfg; + }); +};