A daily weather-insight app for Android. Each morning, at a time you set, ClothesCast posts a one-sentence comparative summary — “4°C warmer than yesterday, leave the sweater at home” or “Rain likely around 3pm.” — derived on-device from yesterday’s actual weather, today’s forecast, and the clothes thresholds you’ve configured.
It can also speak the insight aloud — through the platform TTS engine, or through an online voice (Gemini) if you’d rather a more natural read.
See PRIVACY.md for what data leaves the device and when.
Working v1: full ClothesCast pipeline (Open-Meteo → on-device rendering → notification, phone TTS, Cast, MQTT, widgets, and cached Today refreshes), Compose Settings UI for location, schedule, delivery toggles, units, clothes rules, voice, API keys, calendar extras, smart-home outputs, privacy, and telemetry, runtime permission UX, boot/timezone/locale alarm re-arm, and debug / manual actions for testing without waiting until the scheduled time.
Distribution: a push to main that changes something shippable builds and
signs a release AAB, publishes it as a GitHub prerelease, and ships it to the
Play Store internal track for testers. For anything faster, build and install
a debug APK locally (see below).
commonMain later
without a rewrite.USE_EXACT_ALARM) → BroadcastReceiver → WorkManager
for daily scheduling that survives Doze, reboot, timezone and locale
changes.TextToSpeech engine
(default, fully on-device), or an online voice via the Gemini API.
Online engines are BYOK; keys are encrypted on-device via Tink +
Android Keystore + DataStore Preferences.READ_CALENDAR granted, calendar
events can gate event-relevant weather advice without turning event titles
into notification, voice, MQTT, or Cast content.ACCESS_COARSE_LOCATION granted, the
worker uses the phone’s coarse location at notify-time instead of the
city you picked.| Module | Status |
|---|---|
:core:domain |
Pure-Kotlin models, use cases (insight rendering, clothes rules), repository interfaces |
:core:data |
Open-Meteo forecast + geocoding clients, Gemini TTS client, parser tests |
:app |
Compose UI, manifest, receivers, worker, alarm scheduler, DI, platform TTS, calendar reader, encrypted key store, widgets, Cast, MQTT, telemetry |
Two options, in roughly increasing order of friction:
main build automatically. A push to main that changes
something shippable ships a signed AAB to this track. (The same bundle is
attached to a GitHub prerelease, but an AAB is not installable — that copy
is a record, not an install path.)./gradlew :app:installDebug with a device attached over ADB, or
./gradlew :app:assembleDebug and transfer
app/build/outputs/apk/debug/app-debug.apk to the phone by hand
(Drive, USB, etc.).Your machine’s ~/.android/debug.keystore is stable, so successive
builds upgrade in place and keep their app data. The debug build also
carries its own package id (app.clothescast.debug), so it installs
alongside a Play build rather than over it.
POST_NOTIFICATIONS. Without it the daily insight has nowhere
to go.sweater, jacket,
coat, gloves, shorts) are a sensible starting set. Add your own or
tune the thresholds to match how you dress.READ_CALENDAR to let calendar presence
gate event-relevant advice without putting event titles in rendered output.
See docs/calendar-holidays.md for how holidays
are used and what to do if one shows on the wrong date.Requires JDK 21. Pure-Kotlin modules build without an Android SDK:
./gradlew :core:domain:test :core:data:test
The :app module needs the standard Android SDK (compileSdk 35,
build-tools;35.0.0) on the local Android Studio install path. The CI
workflow shows the exact setup.
Some Android OEMs (Xiaomi, Oppo, OnePlus, aggressive Samsung profiles) kill
backgrounded apps and prevent the alarm from firing on schedule. See
dontkillmyapp.com for per-OEM workarounds —
typically “exclude from battery optimisations” and “allow autostart”. The
underlying scheduling primitive (setExactAndAllowWhileIdle with
USE_EXACT_ALARM) is the strictest the platform offers; anything that
suppresses it is a vendor-side override.