refactor(security): app-lock UI plumbing cleanup (snackbar, LifecycleEventEffect, LocalActivity, dead availability()) #104

Closed
opened 2026-07-02 02:56:12 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 02:56:12 +00:00 (Migrated from github.com)

Origin: code review of PR #45 (screen-lock app gate). Simplification/reuse cleanup in the app-lock UI plumbing.

Problem

Several small divergences from established codebase patterns, bundled:

  • SettingsScreen uses Toast (the app's only Toast) for the app-lock rejection message, instead of the SnackbarHostState + Scaffold(snackbarHost=…) + consume pattern every other screen uses (canonical: MailboxScreen). It also pre-resolves the string via LocalResources where the Toast.makeText(Context, @StringRes Int, …) overload takes the resId directly. The Toast is invisible to Compose test semantics, so this path can't be covered by the existing SettingsScreenTest style.
  • AppLockGateHost hand-rolls DisposableEffect + LifecycleEventObserver where androidx.lifecycle.compose.LifecycleEventEffect is already the codebase idiom (SettingsScreen, BatteryOptimizationScreen); and hand-rolls a findFragmentActivity() ContextWrapper walk where androidx.activity.compose.LocalActivity (activity-compose 1.12.4 is already on the classpath) provides it. The authenticate lambda and the activity lookup are also rebuilt on every recomposition (not remembered).
  • Dead API: AppLockManager.availability() and the four-value AppLockAvailability enum have no production caller (only the interface, its impl, and a test fake) — and the BIOMETRIC_STRONG or DEVICE_CREDENTIAL canAuthenticate combination they wrap is unsupported on API 29 (returns BIOMETRIC_ERROR_UNSUPPORTED), so the mapping is wrong for the minSdk if it were ever used.
  • _uiState is hand-mirrored from gate.state at every mutation site (they can silently disagree on PROCEED/DISABLE paths); it is derivable as f(gate.state, error).

Suggested fix

Switch the message to the snackbar pattern (add a snackbarHost to Settings' existing Scaffold); replace the hand-rolled lifecycle observer with LifecycleEventEffect and the context walk with LocalActivity, remembering the derived activity/lambda; delete availability()/AppLockAvailability (keep isDeviceSecure() and AUTHENTICATORS); derive AppLockUiState from the gate via a single publish helper.

Origin: code review of PR #45 (screen-lock app gate). Simplification/reuse cleanup in the app-lock UI plumbing. ## Problem Several small divergences from established codebase patterns, bundled: - **`SettingsScreen` uses `Toast`** (the app's only `Toast`) for the app-lock rejection message, instead of the `SnackbarHostState` + `Scaffold(snackbarHost=…)` + consume pattern every other screen uses (canonical: `MailboxScreen`). It also pre-resolves the string via `LocalResources` where the `Toast.makeText(Context, @StringRes Int, …)` overload takes the resId directly. The Toast is invisible to Compose test semantics, so this path can't be covered by the existing `SettingsScreenTest` style. - **`AppLockGateHost` hand-rolls** `DisposableEffect` + `LifecycleEventObserver` where `androidx.lifecycle.compose.LifecycleEventEffect` is already the codebase idiom (`SettingsScreen`, `BatteryOptimizationScreen`); and hand-rolls a `findFragmentActivity()` `ContextWrapper` walk where `androidx.activity.compose.LocalActivity` (activity-compose 1.12.4 is already on the classpath) provides it. The `authenticate` lambda and the activity lookup are also rebuilt on every recomposition (not `remember`ed). - **Dead API:** `AppLockManager.availability()` and the four-value `AppLockAvailability` enum have no production caller (only the interface, its impl, and a test fake) — and the `BIOMETRIC_STRONG or DEVICE_CREDENTIAL` `canAuthenticate` combination they wrap is unsupported on API 29 (returns `BIOMETRIC_ERROR_UNSUPPORTED`), so the mapping is wrong for the minSdk if it were ever used. - **`_uiState` is hand-mirrored** from `gate.state` at every mutation site (they can silently disagree on PROCEED/DISABLE paths); it is derivable as `f(gate.state, error)`. ## Suggested fix Switch the message to the snackbar pattern (add a `snackbarHost` to Settings' existing `Scaffold`); replace the hand-rolled lifecycle observer with `LifecycleEventEffect` and the context walk with `LocalActivity`, `remember`ing the derived activity/lambda; delete `availability()`/`AppLockAvailability` (keep `isDeviceSecure()` and `AUTHENTICATORS`); derive `AppLockUiState` from the gate via a single publish helper.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#104