EigenPlan is a third-party mobile client for the Untis timetable.
EigenPlan reads the timetable from the Untis API and displays it to you - customizable.
- Filter by class - No need to look endlessly for your classes, as only your classes are displayed.
- Change color of classes - Capture everything with a quick glance - Due to customizable colors.
![]() |
![]() |
![]() |
|---|---|---|
![]() |
![]() |
![]() |
I documented the whole process in my high school research paper, which you can find on my website. Note that this document is in German. Basically, I reverse-engineered the Untis API with HTTP-Toolkit and wrote a Flutter app to display the data.
If your school uses Untis and doesn't provide individual credentials, I'd be glad to support you to get the app running for you school as well! It should, in theory, already work, but you never know. If you need credentials to test the app yourself, please contact me.
Pull requests and issues are always welcome.
Before opening a PR, this project expects:
- Static analysis —
flutter analyzemust pass cleanly. - Code generation —
dart run build_runner build --delete-conflicting-outputsmust succeed. - Build —
flutter build apk --debug --target-platform android-arm64must succeed. (Release builds need a localkey.propertiesthis repo doesn't ship with, so verification always targets debug.) - Tests, in two tiers:
flutter test— offline unit tests against fixtures derived fromdocs/api/captures/, no network or credentials needed.flutter test --tags=live --run-skipped— exercises the real Untis API with real school credentials to check everything still parses. Never run in CI. Seetest/live/README.mdfor how to provide credentials.
Tiers 1–3 and the offline test tier run in CI on every PR
(.github/workflows/ci.yml). Locally, the Claude Code Stop hook
(.claude/hooks/verify-before-stop.sh) runs all of the
above, including the live-API tier, at its default full verification level — see CLAUDE.md.
Generate the nessessary code:
dart run build_runner buildRelease APKs (the ones published to GitHub Releases and mirrored by IzzyOnDroid) are built
with tool/build_release_apk.sh. The script refuses to build
unless the Flutter SDK matches what pubspec.yaml declares, because the Flutter version,
channel and remote URL are compiled into the APK and a mismatch silently breaks reproducible
builds.
EigenPlan aims to be reproducible; docs/reproducible-builds.md
documents the exact build environment a rebuilder has to match and explains why each part of it
matters. tool/rb_compare.sh builds the APK twice and reports which zip
entries differ, so reproducibility can be checked locally instead of via a release:
tool/rb_compare.sh # two builds, identical environment
tool/rb_compare.sh --second 'taskset -c 0-3' # vary one thing: the CPU count







