- Dart 86.5%
- Python 5%
- C++ 2.6%
- CMake 1.9%
- Shell 1.6%
- Other 2.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| android | ||
| assets | ||
| communication/2026/08/search-for-testers | ||
| docs | ||
| fastlane | ||
| fdroid/metadata | ||
| fonts | ||
| ios | ||
| lib | ||
| linux | ||
| macos | ||
| test | ||
| tool | ||
| web | ||
| windows | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| firebase.json | ||
| justfile | ||
| l10n.yaml | ||
| LICENSE | ||
| PRIVACY_POLICY.md | ||
| pubspec.google.yaml | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
| RELEASE_NOTES.md | ||
| RELEASE_VERSION.md | ||
SoftWakeup
A morning alarm for people who cannot wake up easily — gentle to wake to, impossible to sleep through.
Most alarms start the day with a small heart attack: a brutal ring, a rushed hand hunting the snooze button, guilt, repeat. SoftWakeup is built for heavy sleepers and serial snoozers, and takes another path: instead of dragging you out of sleep, it carries you out of it.
Screenshots
| Home — your mornings at a glance | Better alarm — the soft wake-up editor |
|---|---|
![]() |
![]() |
The idea: a cocoon, not a siren
Well before the real alarm, SoftWakeup starts a soft wake-up: gentle audio — birdsong, breeze, soft chimes, or a sound of your own — played quietly, without repeating, requiring nothing from you. No button to press, no decision to make half-asleep. You hear it, you drift, you surface. Your body learns that morning is approaching, and when the firm alarm finally rings, you are already halfway awake.
And even your snooze is part of the plan instead of a defeat: the wake-up returns at a gentle cadence, a little firmer each time, until you are up.
A "Better alarm", at a glance
- Wake-up time — the deadline. The firm alarm that always rings.
- Soft wake-up — begins as many minutes before as you choose, with gentle audio that plays on its own. No user intervention needed.
- Snooze cadence — the soft sequence repeats at that interval with a crescendo across iterations, up to the normal alarm.
- Repeats — once, on chosen weekdays, or every day.
Everything is resolved when the alarm is armed, and the operating system itself owns every step of the morning — the sequence plays even if the app is not running.
Prefer it plain? A Simple alarm — one time, flat identical snoozes — is always available.
Your morning, your sounds
- A bundled ringtone library for the firm alarm and the soft wake-up, with tap-to-preview.
- Import your own audio: any sound on your device can become a wake-up voice, kept in the app's private library.
- Per-alarm and per-step volume, gentle ramps, and a cross-iteration crescendo.
- Labels, an on/off switch per alarm, and a "next ring" hint on every card.
Languages
SoftWakeup is fully localised and follows the device/system language. It currently ships in eight European languages:
| Language | Locale |
|---|---|
| English | en |
| German | de |
| French | fr |
| Spanish | es |
| Italian | it |
| Portuguese | pt |
| Dutch | nl |
| Polish | pl |
If the device language is not among these, the app falls back to English.
Translations live in lib/l10n/*.arb and are compiled with flutter gen-l10n
(see l10n.yaml). Two gates keep the app fully translated: a CI check
(just l10n-check) and a completeness test
(test/l10n/completeness_test.dart) both fail the build if any string is
missing a translation — so no untranslated text can ever ship.
Reliable by design
SoftWakeup has one rule that outranks all others: the alarm rings, always.
- The OS schedules everything. On Android and iOS the wake-up chain is handed to the operating system's own scheduler — never to an in-app timer. Android rings with the app killed, the screen locked and under Doze, and re-arms after reboot.
- The ring path needs nothing. Every sound, volume and step is resolved at arm time and travels with the alarm; there is nothing to look up — and therefore nothing to fail — at ring time.
- Never silent. If an imported audio file is missing, the alarm falls back to a bundled tone. A missing file is never a silent morning.
- Stopping is deliberate. Swiping the app away while it rings does not silence it; only a conscious action — the ring screen or the notification's Stop/Snooze — ends the ringing.
- Self-healing. On start, SoftWakeup reconciles itself with the OS scheduler and re-arms anything the OS lost.
Platform honesty: iOS may refuse to ring a force-killed app — an OS constraint, not a bug. In that state SoftWakeup still rings with a bundled notification tone. Android has no such constraint.
Platforms
- Android — the full experience; rings even when the app is not running.
- iOS — Not yet tested, I do not have a mac. Full experience while the app is alive; best-effort bundled notification tone when force-killed (OS constraint).
- Linux — a desktop companion for development; rings only while the app is open.
Under the hood
Built with Flutter and organised by domain-driven design: a pure-Dart domain (alarms, wake-up chains, repeat rules), one use case per file in the application layer, and platform adapters (OS scheduler, storage, audio) behind ports. The ring path is deliberately boring: everything an alarm needs is resolved at arm time, and the test suite doubles as the executable specification of the behaviour described above.
Localisation uses the standard Flutter stack — flutter_localizations +
intl + ARB files compiled by flutter gen-l10n — with a context.l10n
extension for ergonomic access and a completeness gate that keeps every
locale in sync.
Build it yourself
SoftWakeup is a standard Flutter project (in soft_wakeup/):
cd soft_wakeup
flutter pub get
flutter run # any connected device or desktop target
Release builds:
flutter build apk # Android
flutter build ios # iOS (requires macOS + Xcode)
flutter build linux # Linux desktop companion
Prerequisites: the Flutter stable channel plus the usual toolchain for each
target (Android SDK, Xcode, or a Linux desktop toolchain). The Linux target
additionally needs the GStreamer development headers
(libgstreamer1.0-dev, libgstreamer-plugins-base1.0-dev).
Quality gate: flutter test — the tests are the specification — and
flutter analyze.
To run the Linux desktop companion in a specific language (for testing localisation):
just run-linux-lang fr_FR.UTF-8 # French
# also: de_DE, es_ES, it_IT, pt_PT, nl_NL, pl_PL
(The Flutter Linux embedder resolves the locale via GLib, which gives the
LANGUAGE environment variable priority over LANG; the recipe sets both,
so a leftover LANGUAGE=en_US cannot force English.)
Distribution
SoftWakeup ships in two builds from one codebase:
- FOSS (F-Droid) — the default build. No Firebase, no analytics, no crash reporting: the analytics/crashlytics ports are wired to no-op implementations. This is what F-Droid builds directly from the source repo.
- Play (Google Play) — the same app plus Firebase Analytics, Crashlytics,
and Remote Config. Built by our CI, which swaps in
pubspec.google.yamland setsPLAY_BUILD=true.
The split is pinned by test/fdroid_flavor_test.dart so Firebase can never
leak back into the FOSS build. Build either flavour with:
just apk-foss # FOSS APK (F-Droid)
just apk-play # Play APK (Firebase)
just aab-foss # FOSS App Bundle
just aab-play # Play App Bundle (Google Play)
F-Droid store metadata (icon, feature graphic, and phoneScreenshots/1.png
… 6.png) lives in fastlane/metadata/android/en-US/ and is committed to the
repo — F-Droid reads it from source. The screenshots are downscaled to 720 px
wide to keep the repository small.
Contributing
This project is not currently set up for collaboration — it is a personal project, and external contributions are not being accepted at this time. This may change in the future; if and when it does, this section will be updated with the contribution guidelines.
License
GPL-3.0 — see LICENSE.

