Add home navigation drawer, FHIR sync, and OIDC/PKCE login - #67
Open
LZRS wants to merge 73 commits into
Open
Conversation
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.
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.
Fix sync login
LZRS
marked this pull request as ready for review
July 29, 2026 18:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FhirSyncTasknawitech/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 entryTest plan
./gradlew jvmTestpasses across both moduleslocal.properties.sample)