- Kotlin 85.5%
- HTML 14%
- Python 0.5%
|
|
||
|---|---|---|
| .claude/commands | ||
| .github/workflows | ||
| .idea | ||
| app | ||
| build/reports/problems | ||
| docs | ||
| google-play | ||
| gradle | ||
| Plans | ||
| screenshots | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| AUDIT_REPORT.md | ||
| AUDIT_REPORT_ROUND4.md | ||
| AUDIT_REPORT_ROUND5.md | ||
| build.gradle.kts | ||
| CHANGELOG.md | ||
| CHANGELOG_AGENT.md | ||
| CLAUDE.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| ISA.md | ||
| LICENSE | ||
| README.md | ||
| settings.gradle.kts | ||
| UX_RECOMMENDATIONS.md | ||
| UX_RECOMMENDATIONS_ROUND4.md | ||
| UX_RECOMMENDATIONS_ROUND5.md | ||
SunZones
Track sunrise, sunset and daylight across multiple locations around the world. A minimal, single-screen Android app with time-aware gradient backgrounds, animated sun/moon arcs, and a yearly daylight chart.
Screenshots
Features
- Multi-location tracking -- swipe between saved locations to see real-time sun and moon data; tap page indicator to manage cities
- Sunrise & sunset times with day/night length and countdown to next sunrise
- Animated sun/moon arc -- a visual progress indicator showing where the sun (or moon) is in its path across the sky
- Moon phase & illumination -- current lunar phase emoji and illumination percentage
- Local time, labelled -- each card and city-list row says whose clock it is showing ("19:00 local time · 7 h ahead of you") and flags when the date over there is already different
- Time-of-day gradients -- card backgrounds shift dynamically through sunrise, morning, midday, afternoon, sunset, twilight and night palettes
- Resizable home-screen widget -- three designs from a 2x1 strip to a 4x4 block, each with a full-width sun/moon arc as its horizon line
- Timezone-aware -- each location stores its own IANA timezone, so times are always correct regardless of where your device is
- Search or GPS -- add locations by city name search or use your current position
- Sunrise/sunset reminders -- optional local notifications, scheduled entirely on-device
- 20 languages -- English, Polish, Chinese, Hindi, Spanish, French, Arabic, Bengali, Portuguese, Russian, Urdu, Indonesian, German, Japanese, Nigerian Pidgin, Marathi, Telugu, Turkish, Tamil, Vietnamese
SunZones Pro
A single one-time purchase (premium_lifetime, Google Play Billing -- no subscription, no ads, no account). The free tier holds three locations and every core sun feature above.
- Unlimited locations
- Twilight & light -- golden hour, blue hour and the full civil/nautical/astronomical twilight timeline
- Night & Moon -- moonrise and moonset, the astronomical-night window, how much of it is moonless, the next new moon, and whether the Milky Way core clears the horizon after dark
- Yearly daylight chart -- month-by-month day length, tap a month to jump to it
- Time travel -- step the date back and forward, or pick any day between 1900 and 2100, and every reading on the card follows
- Golden hour reminders
Privacy
No account, no backend, no analytics SDK, no ads. Sun and moon maths run entirely on-device; saved places never leave it.
One exception, stated up front: crash reports are sent to Google's Firebase Crashlytics, on by default, and can be turned off in Settings -> Diagnostics. Coordinates, saved place names and device identifiers are stripped from the message, breadcrumbs and custom keys before anything is sent; the stack trace and Crashlytics' own device/installation metadata are what remain.
The published privacy policy, the Data Safety form and the store listing still describe the pre-1.7.0 state and are being rewritten -- see the "Store-facing documents Crashlytics makes false" section of
Plans/ROADMAP.md.
Architecture
Clean MVVM, single-activity Jetpack Compose app with Hilt dependency injection.
Room DB (Flow)
-> LocationDao -> LocationRepository -> GetLocationsUseCase(date, includeAstro)
-> CalculateSunTimesUseCase (+ CalculateAstroNightUseCase) -> SunLocation
-> MainViewModel (StateFlow) -> Compose UI
-> WidgetRenderData -> Glance widget
| Layer | Responsibility |
|---|---|
data/local |
Room entity, DAO, database |
data/repository |
Thin DAO wrapper, current-location replacement logic |
data/preferences |
DataStore: notification settings, crash-reporting opt-out |
domain/usecase |
Sun/moon calculations via commons-suncalc, astronomical night and Milky Way season, yearly daylight aggregation |
domain/model |
SunLocation (computed fields), AstroNight, ArcState (the one day/night decision), MonthDaylight |
domain/util |
Timezone-id hardening, administrative-suffix stripping for geocoded names |
diagnostics |
Crash-report scrubbing (pure JVM) + the Crashlytics seam behind it |
billing |
EntitlementRepository, Play Billing data source, monetization config/killswitch |
ui/main |
HorizontalPager of SunCard composables, date bar, 60-second auto-refresh; SelectedDatePolicy validates the restored/intent-borne date |
ui/widget |
Glance widget; layout breakpoints, element gating and bitmap arcs decided in pure functions outside the Glance tree |
ui/addlocation |
Geocoder search + GPS, timezone resolution via Android ICU |
di |
Hilt modules providing Room, DAO, Geocoder, FusedLocationProviderClient, billing and diagnostics |
Tech stack
| Language | Kotlin |
| UI | Jetpack Compose, Material 3 |
| DI | Hilt |
| Database | Room (schema exported, migrations tested) |
| Preferences | DataStore |
| Widget | Jetpack Glance 1.1.1 |
| Background work | WorkManager |
| Billing | Google Play Billing 9.1.0 |
| Crash reporting | Firebase Crashlytics (opt-out; no Firebase Analytics) |
| Sun/moon math | commons-suncalc 3.11 |
| Location | Google Play Services FusedLocationProviderClient |
| Geocoding | Android Geocoder |
| Timezone lookup | android.icu.util.TimeZone (built-in) |
| Tests | JUnit 5, MockK -- 391 JVM unit tests |
| Min SDK | 26 (Android 8.0) |
| Target SDK | 36 (Android 16) |
Build
Requires Java 17 and the Android SDK.
A google-services.json for the Firebase project must be present at app/google-services.json -- it is committed, since this repository is private.
export JAVA_HOME="/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home"
./gradlew assembleDebug
Full gate:
./gradlew lint testDebugUnitTest assembleDebug
assembleRelease now uploads the R8 mapping file to Firebase, so a release build needs network access. Debug builds upload nothing.
License
MIT -- Pawel Orzech