Skip to content

About

A calm bubble level for Android that calibrates on any firm surface. No ads, no permissions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Bubble Level – Spot On

A calm bubble level for Android: flat colour, hairlines, one accent, a circle motif, a centred instrument, and named themes chosen in Settings. No ads, no tracking, no internet permission.

Surface mode, Aurum theme, tilted 6.9° Edge mode, Ambra theme, tube 4.2° off level Surface mode, Celeste theme, level at 0.0° Calibration step 2 of 2, Salvia theme Settings page, Rosa theme

Android 7.0 (API 24) and later. Download the APK from Releases; privacy policy.

Build

Gradle-free: aapt2 → javac → R8 → bundletool. Needs the Android SDK (build-tools;36.0.0, platforms;android-36, cmdline-tools;latest), JDK 17+, bundletool, python3 with Pillow, and zip. The design system lives in hairline/ (its own repository, added with git subtree): the build compiles hairline/src and hairline/android with the app and takes the fonts from hairline/assets.

./build.sh                     # build/Level.aab (Play), build/Level.apk (universal), build/mapping.txt
NEW_KEYSTORE=1 ./build.sh      # first build on a machine without the upload key: creates one

The upload key lives outside the repository at ~/.local/share/bubble-level-android/upload.jks (KEYSTORE overrides it). Its password comes from KEYSTORE_PASS or the desktop keyring (secret-tool lookup service bubble-level-android key upload-keystore). VERSION_CODE / VERSION_NAME override the version; every Play upload needs a higher VERSION_CODE.

Publishing on Google Play, with every Play Console answer: docs/play-console.md. Store text and graphics live in fastlane/metadata/android/en-US/; tools/store_screenshots.sh recaptures the screenshots from an emulator, tools/make_feature_graphic.py redraws the feature graphic.

Layout

src/me/saatvik/level/
  LevelEngine.java   sensor math, adaptive filter, mode hysteresis, two-step reversal calibration (no Android deps)
  Renderer.java      the instrument and readout, and the settings rows; android.graphics only
  LevelView.java     the level on hairline's Stage: spring bubble, mode and page transitions, calibration
                     flow, actions, what each control says to accessibility services
  Settings.java      preferences; calibration in a no-backup file
  Ui.java            per-frame state (extends hairline's Frame) LevelView writes and Renderer reads
  MainActivity.java  sensors, window, lifecycle
hairline/            the design system: palette and themes, drawing primitives, the settings page, the
                     Stage view (frame loop, input, accessibility, keyboard, back, haptics, the system's
                     text scale, contrast and animation settings), window chrome, fonts, preview stubs
                     (hairline/README.md)
tools/preview/       renders Renderer on the JVM with hairline's AWT-backed stubs; EngineTest

javac -d /tmp/pv $(find tools/preview hairline/preview hairline/src -name '*.java') src/me/saatvik/level/{Ui,Renderer,LevelEngine}.java
java -cp /tmp/pv me.saatvik.level.EngineTest
java -Djava.awt.headless=true -cp /tmp/pv me.saatvik.level.Preview hairline/assets/fonts /tmp/shots

Behaviour

  • Surface mode (bullseye) when the screen is within 40° of horizontal, edge mode (tube) beyond 50°. Edge mode rotates the instrument to the nearest 90° so the tube stays world-horizontal.
  • Calibration: place, capture; turn 180° about the vertical (gyro-detected, or tap Ready); capture. The two readings cancel the surface slope and leave the sensor offset, so any firm surface works. With a gyroscope the measured net turn between the two resting poses is used, so an imprecise turn still solves exactly, however the phone was carried (picked up, held tipped, spun on its own axis). Surface and edge offsets are calibrated and applied separately (a camera bump or an edge has its own geometry). Calibration is stored outside backups: it never moves to another phone.
  • Tap the instrument or the pause icon to hold the reading.
  • Settings is one list: units, level tolerance and mode as segmented choices (every option visible), haptic feedback and keep-screen-on switches, calibration status with Reset, and the theme swatches last. Reset asks first (Keep / Erase) and says so if the change could not be saved.
  • Every control is exposed to TalkBack and Switch Access as a virtual node, and reachable with a keyboard or D-pad: arrows move a focus ring, Tab goes in reading order, Enter or Space activates, Escape goes back. Choices are groups ("2 of 4"), the reading is a polite live region, and nothing is spoken that is not also on screen.
  • Follows the system's settings: the Settings page grows with the font size up to 2× (labels wrap, choices stack into a list), the level page up to 1.3×; high contrast (Android 14+) strengthens text and outlines; "Remove animations" turns transitions off. From Android 14 a back gesture previews closing Settings.

Credits

Visual style inspired by Dustland Design's The Clean One games; not affiliated with or endorsed by Dustland Design.

Rubik is bundled under the SIL Open Font License (hairline/assets/fonts/OFL.txt).

About

A calm bubble level for Android that calibrates on any firm surface. No ads, no permissions.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages