Skip to content

Add home navigation drawer, FHIR sync, and OIDC/PKCE login - #67

Open
LZRS wants to merge 73 commits into
ohs-foundation:mainfrom
LZRS:feature/home-navigation-drawer
Open

Add home navigation drawer, FHIR sync, and OIDC/PKCE login#67
LZRS wants to merge 73 commits into
ohs-foundation:mainfrom
LZRS:feature/home-navigation-drawer

Conversation

@LZRS

@LZRS LZRS commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Home screen with adaptive navigation drawer, replacing the direct-to-household-list start screen
  • One-time FHIR sync wired to a "Sync now" drawer action, backed by a real FhirSyncTask
  • Full OIDC/PKCE login (ported from nawitech/opensrp) gating the app on all platforms (Android, iOS, JVM desktop, JS, Wasm/JS), wired through Koin, with FHIR sync authenticated via the session's Bearer token and a "Sign out" drawer entry

Test plan

  • ./gradlew jvmTest passes across both modules
  • All 6 KMP targets compile (Android, iOS arm64/simulator, JVM, JS, WasmJS)
  • Manual sign-in/sign-out verification on device/emulator once an identity provider is registered (see local.properties.sample)

@LZRS LZRS changed the title Feature/home navigation drawer Add sync from the side drawer Jul 27, 2026
@LZRS
LZRS requested a review from ellykits July 27, 2026 11:19
LZRS added 18 commits July 28, 2026 07:10
Also fixes pre-existing dev.ohs.fhir.* imports (should be dev.ohs.fhir.engine.*)
in jvmMain/main.kt and several jvmTest/commonTest files that were blocking the
jvmTest source set from compiling at all, unrelated to this feature but needed
to unblock test verification for it.
Also fixes the same pre-existing dev.ohs.fhir.* import issue in
OhsPlayerApplication.kt (Android) and MainViewController.kt (iOS),
which was blocking those two targets from compiling at all.
Also fixes the pre-existing HomeScreenTest.kt/HomeViewModelTest.kt
dev.ohs.fhir.sync.* import issue (should be dev.ohs.fhir.engine.sync.*)
from the earlier jvmTest-unblock, so the full jvmTest suite is green.
Reformats the auth module and sync/home files to project style, and
updates the FHIR engine dependency off the local snapshot version.
@LZRS LZRS changed the title Add sync from the side drawer Add home navigation drawer, FHIR sync, and OIDC/PKCE login Jul 28, 2026
LZRS and others added 29 commits July 29, 2026 12:08
The two request guards used && where || is required, so a message with a
null `data` bypassed validation and then dereferenced null. Correct both
guards, and document the worker's origin (the @sqlite.org/sqlite-wasm
package) and why it must run off the main thread for OPFS.
Explain that the client is OIDC-discovery based and therefore provider
agnostic: switching providers is a local.properties change, not a code
change. Add a config-key reference table, issuer examples for Keycloak,
Okta, and Zitadel, and the per-platform redirect URIs to register.
Per review, fold the property comment on `Default` into the class-level
documentation instead of leaving it inline.
The FHIR database, KSafe, and the sync-timestamp DataStore each derived
their own path under ~/.ohs-player-reference-app. Route all three through
a single desktopStorageDirectory rooted at ~/.player-reference, per review.
Collapse the per-verb SyncNowUseCase and PeriodicSyncUseCase interfaces
and their six platform implementations into one injected SyncManager
(syncNow / cancelSyncNow / startPeriodicSync / cancelPeriodicSync) with
one implementation per platform. This keeps the ViewModels unit-testable
against a fake while dropping the domain-use-case ceremony flagged in
review. Drop two low-value tests that only exercised the old wiring.
A WorkManager relaunch after the process died, or an iOS BGProcessingTask,
never runs the UI bootstrap that hydrates SessionRepository, so the sync
was sending an empty Bearer token. Load and refresh the session first via
a Koin-free ensureFreshSessionForSync(), called from AppFhirSyncWorker and
the iOS scheduler. Move AndroidAppContext.init to Application.onCreate so
KSafe exists in a headless process that never touches MainActivity.
Inject SyncManager into AuthViewModel and cancel both the in-flight and
periodic sync on logout and on a server-side revoke, so neither keeps
firing with a stale token. Logout now clears the session but leaves local
FHIR data on disk. Lift consumeRedirectCallback into AuthorizationLauncherApi
so the bootstrap test seam runs the real redirect-completion branch instead
of diverging from production, and add coverage for it.
secureRandomBytes ignored SecRandomCopyBytes' status and would return an
all-zero buffer on failure, yielding a predictable PKCE verifier and state;
check the status and fail loudly instead. Pin the KSafe key to
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (the default) explicitly,
so the background BGProcessingTask sync can read the session while the
device is locked.
Move hardcoded UI strings across the screens into composeResources so the
app can be localized. Locale resolution stays out of the ViewModels:
HomeViewModel exposes a semantic SyncError the screen maps to a string, and
the initial-sync gate's failure message moves to the screen. Also inline
the Home drawer width breakpoint into its only caller and drop its trivial
boundary test.
The blocking initial-sync gate keyed off hasAnyData(), so an account that
synced successfully but held no resources would look un-synced forever.
Persist an "initial sync completed" flag set only on a successful sync and
gate on that instead; a failed first sync still shows the gate. Remove the
now-unused hasAnyData() and its tests.
Set the ohs_logo mark as the application icon on every platform — Android
adaptive + legacy mipmaps, the iOS AppIcon, the desktop window and installer
icons, and a web SVG favicon — and show it as the login brand mark. Rename
the user-facing application to "Player Reference" across the desktop window
title, Android app_name, iOS CFBundleDisplayName, the login strings, and the
web title. Internal identifiers (Gradle project, module, package) are left
unchanged.
Screens observe FhirRepository.revision, which only ticked on app-side
upserts — sync downloads write straight to the engine database, so a register
stayed stale until the next local edit. Route the revision through a
process-wide DataChangeSignal that both upserts and completed syncs bump
(one-time and periodic, on every platform), so the list re-queries as soon as
new data lands.
Re-key the Material 3 light and dark color schemes off the logo's four tiles
— indigo primary, blue secondary, light-purple tertiary — replacing the
generic blue palette so the app reads as one brand.
composeResources renders Android's `\'` escape literally, so the backslash
showed on screen. Use plain apostrophes.
Cap the questionnaire content at 720dp and center it, so the form no longer
stretches edge to edge on desktop and large windows.
Adopt the Material 3 adaptive window size class in HomeScreen: on expanded
widths the household register sits in a list pane beside a detail pane (the
selected household's profile, or a placeholder), with the drawer permanent;
compact widths keep the single-pane push navigation and hamburger drawer.
Wire member and add-member actions from the detail pane through to the host.
Extend the adaptive home: on expanded widths a tapped member now opens the
patient profile in the detail pane (household → member drill-down in place),
and a navigation rail carries the medium breakpoint between the compact modal
drawer and the expanded permanent drawer. Give the drawer/rail a distinct
tinted surface with a divider so it reads apart from the content, tighten its
width, make the register FAB an extended "Add household" button, switch the
questionnaire dismiss control to a close icon, and render the shared cards as
elevated cards.
Switch the shared card to an outlined (flat) card, no elevation. Give the
navigation drawer and rail the brand primary surface with light content, a
signed-in user header (avatar + name), and a slightly narrower width. Strip
the login hero's tagline and subtitle — this is a reference app meant to be
customized, so it carries only the wordmark and the sign-in action.
Drop the outline/elevation from the shared list card: each row is now a
borderless rounded, padded, clickable row separated by a full-width divider.
Register rows dropped their filled cards. Each household now sits flat on
the surface with no border or divider; in the two-pane layout the open
household carries a rounded tonal highlight. Selection reaches the row
through a composition local, so the library list scaffold is untouched.
CardView is gone and the patient row matches.
The sign-in side is now plain surface with real hierarchy instead of an
elevated card: a solid brand panel with a monochrome logo mark sits
beside it on wide screens and stacks above a rounded sheet on narrow
ones. Copy is trimmed to a title, one redirect line, and the action.
The back arrow returns to the start of the bar and close moves to the
end. On wide screens the bar's contents track the centered form column,
so the controls sit at the form's edges rather than the screen's.
Detail sections lose their cards: each is now an overline label with a
divider and flat rows, collapsible via a plus/minus toggle. The patient
hero matches the household one, and the shared clinical row shows a
leading severity dot instead of an accent bar. User-facing strings in
these renderers move to Compose resources.
The register list nested its own Scaffold inside the home Scaffold, which
re-applied the status-bar inset and left a gap under the title. Zero the
inner Scaffold's window insets since the parent already handles them.
The adaptive foreground filled too much of the canvas, so the circular
mask clipped the logo's corners. Rescale it to about 62% centered and
refresh the legacy square and round icons to match.
The patient header sat in an elevated Card while the household header
renders straight into the list. Drop the card so the two profiles share
one flat header on the surface.
The register listed groups in storage order flipped by a blanket
reverse. Sort the Group search results by meta.lastUpdated descending in
the data source instead, so the most recently touched household leads
and the view model no longer second-guesses the order.
@LZRS
LZRS marked this pull request as ready for review July 29, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants