# Kegel Pelvic floor trainer for sexual performance — a free, local-only take on what Stamina was before it went paid. Expo / React Native, Android + iOS. ## How it works A session is four guided blocks driven by a step timer with haptic cues: 1. **Holds** — 8 × squeeze-and-hold. This is the progressive-overload driver: holds start at 5s and grow **+1s every 3 completed sessions** (capped at 30s). Manual ±1s adjustment in Settings. 2. **Pushes** — 5 × reverse kegel (gently bear down). Scales at half the rate of holds, capped at 12s. 3. **Quick squeezes** — 10 × 1s burst contractions with 1s release. 4. **Quick pushes** — 10 × 1s burst pushes with 1s release. Progress (session count, difficulty adjustment, completion history) lives in AsyncStorage — no server, no account. The home screen shows your **day streak** (alive until a full day is missed), best streak, and a two-week activity strip. ## Layout - `src/app/` — expo-router screens: `index` (home), `session` (timer), `settings` - `src/lib/workout.ts` — pure session-plan + progression logic - `src/lib/streak.ts` — pure streak / calendar-day logic (tested) - `src/lib/store.ts` — tiny local store persisted to AsyncStorage - `android/` — committed prebuild output; CI builds it directly ## Development ```sh npm install npm start # Expo dev server npm run typecheck npm test # node --test over src/lib/*.test.ts ``` ## Release ### Android APK (Gitea / Obtainium) Push to `main` → Gitea Actions builds a signed APK and publishes a Gitea release — install via Obtainium. `android/app/build.gradle` keeps literal `versionCode`/`versionName`; CI stamps the run number in with `sed` before building. Keystore: `~/.android-keys/kegel-release.jks` (repo secrets `KEYSTORE_B64`, `KEYSTORE_PASSWORD`; alias `kegel`). Beware: `expo prebuild -p android` (used to regenerate launcher icons from `store/icons/make.js`) rewrites `build.gradle` and drops the release `signingConfigs` block — restore it from git. ### App Store / Google Play (EAS) Store builds run on EAS (project `@mrmen23/kegel`, profile `production`, version source remote with auto-increment). The EAS-managed Android keystore differs from the Gitea one, so Play and Obtainium installs can't update over each other. ```sh # Apple: needs an App Store Connect API key for non-interactive auth export EXPO_ASC_API_KEY_PATH=~/Downloads/AuthKey_L9Z6F5H5B7.p8 EXPO_ASC_KEY_ID=L9Z6F5H5B7 \ EXPO_ASC_ISSUER_ID= EXPO_APPLE_TEAM_ID=HZ6XCKM95R EXPO_APPLE_TEAM_TYPE=INDIVIDUAL eas build -p ios --profile production --non-interactive eas submit -p ios --profile production --latest --non-interactive # creates the ASC record on first run # Android: needs a Play service-account JSON with release access eas build -p android --profile production --non-interactive eas submit -p android --profile production --latest --key --track internal ``` Listing copy, keyword rationale and screenshot generation live in `store/` (`node store/screenshots/make.js` renders `store/screenshots/raw/*.png` into store-sized slides). Privacy policy is served from https://rehbock.xyz/kegel/privacy.html (source in `store/web/`). iOS dev runs via `npx expo run:ios` from a Mac.