Skip to content

Repository files navigation

Shul-Widget

Multi-tenant Android app + home-screen widget for any Luach-powered shul. One APK serves every shul on the registry — the user picks which one when they install. Built around Jetpack Glance for the widget, Jetpack Compose for the full app shell, Supabase for data, and KosherJava for halachic times.

Current release: v3.1.2 — install from Shul-Widget-Published-App releases. Supports Android 7.1.1 (API 25) and up.

What's in the box

Widgets

A lineup of home-screen widgets, each responsive across size brackets via Glance SizeMode.Responsive:

  • Full Day — the full chronological day with a zmanim strip. Full Screen — edge-to-edge day view.
  • Next Up — the next item on the schedule. Next Zman — the upcoming zman with a live countdown.
  • Shabbos — candle lighting, parsha, and motzei. Weather — current conditions, hourly strip, 5-day outlook.
  • Zmanim — the user's selected zmanim set. Custom — per-instance configurable content.
  • Always-light cream surface with navy + gold text — readable against any home-screen wallpaper.
  • Refreshes every 30 minutes via WorkManager, plus instantly on boot / screen-on / date change.

App shell

Tap the widget (or the launcher icon) to open the full app. Seven tabs:

Tab What
Today Chronological list of today's minyanim, shiurim, and events with resolved times. Pull-to-refresh. Tap the calendar to view any future date.
Schedule Recurring weekly view per tefilla (Shacharis / Mincha / Maariv).
Zmanim All zmanim selected in Settings — Alos, Misheyakir, sunrise, sunset, plag, tzais, candle lighting, etc.
Weather Open-Meteo forecast — current conditions, 5-day outlook, tap a day for hourly breakdown + sunrise/sunset.
Admin Sign in with Google or email/password to manage minyanim, shiurim, events, exceptions, and settings for your shul. The login form is always visible; signed-in users see the CRUD shell.
Chat (admins only) Per-shul team chat with OS notifications via a background poll worker.
Settings Switch shul, refresh schedule, check for updates, customize zmanim, toggle item-type icons, links to kiosk preview + web admin + GitHub + contact + bug report.

Data + auth

  • Supabase REST direct from the device — every shul has its own Supabase project; the registry table in the master project hands the URL + anon key + table prefix to the picker, then the rest of the app talks to the selected shul.
  • RLS-gated writesshul_<prefix>_users table with role owner / admin decides who can edit; the bearer token comes from either email/password sign-in or the Google OAuth flow (v1.1.0).
  • Stale-while-revalidate caching (v1.0.7) — Today, Zmanim, and Schedule paint the cached snapshot instantly on tab-switch while the network refresh runs in the background.
  • KosherJava on-device for sunrise / sunset / candle lighting / tzais / plag / mincha gedola / etc. — driven by the chosen shul's lat / lon / timezone.

Self-update

  • Polls https://api.github.com/repos/Shalom-Karr/Shul-Widget-Published-App/releases every 6 hours, or on demand from Settings → Check for updates.
  • ABI-aware: the in-app updater picks the matching APK slice (armeabi-v7a / arm64-v8a / x86_64 / universal) so a TracFone-class device doesn't download more than it needs.
  • Self-installs via ACTION_VIEW on Android < 8 (reliable confirm dialog) and PackageInstaller.Session on Android 8+ (catches STATUS_PENDING_USER_ACTION to fire the install prompt).

Repo layout

Shul-Widget/                              ← this repo
├── app/
│   ├── build.gradle.kts                  ← deps + ABI splits + R8 + signing
│   ├── proguard-rules.pro
│   └── src/main/
│       ├── AndroidManifest.xml           ← receivers, FileProvider, OAuth deep link
│       ├── kotlin/com/luach/widget/
│       │   ├── ShulWidget.kt             ← Glance widget composable
│       │   ├── ShulWidgetReceiver.kt
│       │   ├── ShulWidgetApp.kt          ← Application class
│       │   ├── app/                      ← Compose full-app shell
│       │   │   ├── MainActivity.kt       ← single-activity host + OAuth deep-link handler
│       │   │   ├── AppNavHost.kt         ← bottom-nav routes + Admin tab visibility
│       │   │   └── screens/{today,schedule,zmanim,weather,admin,chat,settings}/
│       │   ├── auth/
│       │   │   ├── SupabaseAuthClient.kt ← password + OAuth (authorize URL + /user)
│       │   │   ├── OAuthLauncher.kt      ← Chrome Custom Tabs handoff
│       │   │   ├── AdminSession.kt
│       │   │   └── RbacChecker.kt        ← shul_<prefix>_users gate
│       │   ├── chat/
│       │   │   └── ChatPollWorker.kt     ← periodic poll + OS notifications
│       │   ├── config/
│       │   │   ├── ShulPickerActivity.kt ← picker + widget configure activity
│       │   │   └── ShulPrefs.kt
│       │   ├── data/
│       │   │   ├── Models.kt
│       │   │   ├── RegistryClient.kt
│       │   │   ├── SupabaseClient.kt     ← per-shul REST + admin CRUD
│       │   │   ├── ScheduleResolver.kt   ← day_pattern + anchor logic, SWR flow
│       │   │   └── LocalCache.kt
│       │   ├── kiosk/
│       │   │   └── KioskPreviewActivity.kt
│       │   ├── refresh/                  ← BootReceiver, TimeTickReceiver, WorkManager
│       │   ├── update/                   ← UpdateChecker, ApkInstaller, InstallStatusReceiver
│       │   ├── weather/                  ← OpenMeteoClient, WeatherCache, models
│       │   ├── widget/                   ← WeatherWidget + WeatherWidgetReceiver
│       │   └── zmanim/                   ← ZmanimProvider (KosherJava wrapper), ZmanimKey
│       └── res/                          ← layouts, drawables, mipmaps, strings, xml configs
├── supabase/                             ← run in the master project (registry) — migrations
│   ├── 01_shuls_registry.sql
│   ├── 02_seed_shuls.sql
│   ├── 03_add_kiosk_url.sql
│   ├── 04_add_admin_url.sql
│   ├── 05_install_telemetry.sql
│   └── registry_add_latlon.sql
├── .github/workflows/
│   ├── android-apk.yml                   ← debug APK on every push, uploads artifact
│   └── release-to-public.yml             ← on tag: waits for build, attaches APKs to public release
├── SIGNING.md
└── README.md

How it works

Two-repo split

  • Shul-Widget (this repo) — source code, CI workflows, migrations.
  • Shul-Widget-Published-App (public) — release page, CHANGELOG, landing site, APK downloads. The in-app updater only ever talks to this public repo.

The release-to-public.yml workflow builds the APKs in Shul-Widget on tag push, waits for the Android build to finish (lewagon/wait-on-check-action), then pushes a release + CHANGELOG entry to Shul-Widget-Published-App.

First-install flow

  1. Drop the widget on the home screen → Android opens ShulPickerActivity.
  2. Picker calls RegistryClient.fetchShuls() against the master Supabase project.
  3. User taps a shul → {slug, supabaseUrl, supabaseAnonKey, tablePrefix, kioskUrl, adminUrl, logoUrl} saved to DataStore.
  4. WidgetRefreshWorker is scheduled (30 min periodic) and fires immediately.

Refresh + render

chosen shul (DataStore)
    │
    ▼
SupabaseClient ──parallel──▶ getSettings / getMinyanim / getShiurim
    │                        / getMinyanExceptions(today) / getShiurExceptions(today)
    │                        / getEvents / getEventExceptions(today)
    │
    ▼
LocalCache.save(snapshot)   ←── stale-while-revalidate: cache emit first, then fresh
    │
    ▼
ScheduleResolver: match day_pattern(today, jCal) → resolve anchor + offset_minutes via ZmanimProvider
    │
    ▼
FullDaySchedule { minyanim, shiurim, events, zmanim, hebrewDate }
    │
    ▼
Glance widget render  +  Today / Schedule / Zmanim screens

Auto-update

UpdateChecker (in the periodic worker, plus manual from Settings):

  • GETs /releases?per_page=1 on the public repo (the /releases/latest endpoint silently 404s if everything is a pre-release, which once stranded the v0.0.7 → v1.0.0 transition).
  • Compares the tag against PackageManager.getPackageInfo(packageName).versionName (v1.0.9+: was previously reflecting on BuildConfig.VERSION_NAME, which broke with applicationIdSuffix = ".debug").
  • Picks the asset whose name contains the device's primary ABI; falls back to the universal APK.
  • Downloads to app-private storage → ApkInstaller shows the system install confirmation.

Local builds

Requires JDK 17 + Android SDK (build-tools 34.0.0, platform 34, platform-tools). No Android Studio.

# One-time
gradle wrapper --gradle-version 8.7

# Every build
./gradlew :app:assembleDebug
# Output:
#   app/build/outputs/apk/debug/app-armeabi-v7a-debug.apk
#   app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
#   app/build/outputs/apk/debug/app-x86_64-debug.apk
#   app/build/outputs/apk/debug/app-universal-debug.apk

Sideload to a connected device:

adb install -r app/build/outputs/apk/debug/app-armeabi-v7a-debug.apk

CI on push is usually faster (~3–5 min) — see the Actions tab.

Releasing

Push a tag matching v*:

git tag v1.1.1
git push origin main
git push origin v1.1.1

The pipeline:

  1. android-apk.yml builds the four ABI APKs on push to main.
  2. release-to-public.yml fires on the tag, waits for the matching build (lewagon/wait-on-check-action polls Checks API for this github.sha), then:
    • Renames app-<abi>-debug.apkshul-widget-<tag>-<abi>.apk (the universal APK keeps the legacy shul-widget-<tag>.apk filename so older clients with naïve endsWith(".apk") matchers still upgrade safely).
    • Prepends a CHANGELOG entry in the public repo and stamps the index.html download CTA.
    • Creates the GitHub release on the public repo and attaches all four APKs.

Keystore: debug signing right now (committed debug.keystore for stable signature across CI runs). See SIGNING.md for release-keystore setup if/when we switch.

Google OAuth setup

v1.1.0 added a "Sign in with Google" button to the Admin tab. The flow is Supabase-mediated (no Google Play Services dependency).

Per Supabase project (each shul):

  1. Supabase Studio → Authentication → Providers → Google → enable.
  2. Paste in a Web OAuth Client ID + secret from Google Cloud Console.
  3. Authorized redirect URI on the Google client must be the Supabase callback shown in Studio: https://<project-ref>.supabase.co/auth/v1/callback.
  4. In Supabase Studio → Authentication → URL Configuration → Additional Redirect URLs, add: luach://auth/callback. The Android intent-filter on MainActivity catches this scheme.

On Android:

  • App opens a Chrome Custom Tab at <supabaseUrl>/auth/v1/authorize?provider=google&redirect_to=luach://auth/callback.
  • After Google consent, Supabase 302s the browser to luach://auth/callback#access_token=…&refresh_token=…&expires_in=….
  • MainActivity.onNewIntent parses the URL fragment, hands the tokens to SettingsViewModel.handleOAuthCallback, which calls /auth/v1/user for the userId, does the RBAC check, and persists the session.

Email/password sign-in is still available as a fallback below the Google button.

Supabase setup (one time per master project)

Run in the registry-holder Supabase project (currently CMH/Khal: qvoxpfigbukidlmshiei.supabase.co):

  1. supabase/01_shuls_registry.sql — registry table + public-read RLS.
  2. supabase/02_seed_shuls.sql — TLK, Khal Yereim, CMH rows.

Per-shul Supabase projects need their own schema (the shul_<prefix>_* tables) plus the shul_<prefix>_users RLS gate that decides who can edit.

Configuration knobs

File What
gradle.properties shulWidget.versionCode / shulWidget.versionName — single source of truth, baked into the APK manifest.
app/build.gradle.kts splits.abi (ABIs to ship), buildTypes.debug (R8 settings), signing configs.
app/proguard-rules.pro Keep rules — receivers, activities, kotlinx.serialization, BuildConfig.
app/src/main/res/xml/*_widget_info.xml Per-widget min/max resize, configure activity, target cell size (fullscreen_, nextup_, nextzman_, shabbos_, weather_, zmanim_).
app/src/main/AndroidManifest.xml OAuth deep-link intent filter (luach://auth/callback), permissions, receivers.

Known issues

  • The "debug" build type is also debuggable=true, which silently disables R8. APK is ~8 MB instead of ~5 MB it could be. Switching to a true release build is a future change — it requires CI keystore secrets and breaks the in-place upgrade path because applicationIdSuffix=".debug" would go away.

License

No license has been chosen yet, so the default applies: all rights reserved. The source is public for reference; add a LICENSE file to grant reuse rights.

About

Multi-tenant Android app + home-screen widgets for any shul: minyanim, shiurim, zmanim, Shabbos times, and weather. One APK serves every shul on the registry. Jetpack Glance + Compose, Supabase, KosherJava.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages