Skip to content

Refactor Sign-in state to explicit loading/error fields and rename ActivityProvider to BiometricPlatformWrapper#719

Merged
softartdev merged 5 commits intoclaude/biometric-authentication-mzvIQfrom
codex/find-comments-in-pull-request
Apr 30, 2026
Merged

Refactor Sign-in state to explicit loading/error fields and rename ActivityProvider to BiometricPlatformWrapper#719
softartdev merged 5 commits intoclaude/biometric-authentication-mzvIQfrom
codex/find-comments-in-pull-request

Conversation

@softartdev
Copy link
Copy Markdown
Owner

Motivation

  • Simplify and clarify the sign-in UI state by replacing the sealed State machine with explicit fields for loading and error to make state updates more straightforward.
  • Provide a typed ErrorType enum to represent form validation errors instead of sealed error objects.
  • Introduce a concise internal result type for sign-in logic to centralize mapping from business outcome to UI state.
  • Rename the platform wrapper used for biometric prompts from ActivityProvider to BiometricPlatformWrapper to reflect broader platform intent and the rememberBiometricPlatformWrapper() API.

Description

  • Replaced SignInResult.State sealed interface with properties loading: Boolean, errorType: ErrorType?, and kept biometricVisible: Boolean in SignInResult and added enum class ErrorType { EMPTY_PASSWORD, INCORRECT_PASSWORD }.
  • Updated SignInViewModel to set loading and errorType directly, added a private SignInInternalResult sealed interface and an extension updateFromSignInInternal to map internal results to SignInResult updates.
  • Modified biometric flows to clear/set loading and errorType appropriately and to use the new biometricPlatformWrapper parameter when calling biometric interactor methods.
  • Updated UI in SignInScreen to read loading and errorType instead of the old sealed State, and imported ErrorType where needed.
  • Updated documentation (feature/biometric/domain/README.md) to rename ActivityProvider to BiometricPlatformWrapper and to document the rememberBiometricPlatformWrapper() usage.

Testing

  • Built the project with ./gradlew build, which completed successfully.
  • Ran the project's unit tests with ./gradlew test, and all tests passed successfully.

Codex Task

@softartdev softartdev marked this pull request as ready for review April 30, 2026 12:07
- Update `SignInViewModelTest` to use the `errorType` property for verifying error states.
- Replace subclass-based assertions for `EmptyPass` and `IncorrectPass` with `ErrorType.EMPTY_PASSWORD` and `ErrorType.INCORRECT_PASSWORD` equality checks.
- Nest the `ErrorType` enum within the `SignInResult` class.
- Add helper functions to `SignInResult` for common state transformations, such as `showLoading`, `hideErrors`, and specific error states.
- Refactor `SignInViewModel` to use these helper functions for state updates, improving readability.
- Remove the internal `SignInInternalResult` sealed interface and its associated mapping logic.
- Update `SignInScreen` and `SignInViewModelTest` to reference the relocated `SignInResult.ErrorType`.
- Implement `BiometricDisableConfirmationDialog` and its corresponding `BiometricDisableViewModel` to handle user confirmation.
- Introduce `DisableBiometricUseCase` to encapsulate the logic for clearing stored biometric credentials.
- Refactor `SettingsViewModel` to trigger the confirmation dialog and await the result via a `Channel` before clearing stored passwords.
- Register the new dialog in the navigation graph and update dependency injection modules.
- Add localized string resources for the confirmation dialog in English and Russian.
- Expand test coverage with UI tests for the new dialog and unit tests in `SettingsViewModelTest` to verify the confirmation flow.
- Update test infrastructure with new test tags and helper methods for the biometric disable screen.
- Convert `BiometricInteractor` from an `expect` class to an interface and provide platform-specific implementations (`AndroidBiometricInteractor`, `IosBiometricInteractor`, etc.).
- Remove `DisableBiometricUseCase` and move its functionality and the biometric disable dialog channel into `BiometricInteractor`.
- Implement `TestBiometricInteractor` and a corresponding Koin module to support mocked biometric states during UI tests.
- Add `BiometricSignInTestCase` and `BiometricSettingsTestCase` to provide reusable test logic for biometric flows.
- Introduce `BiometricEnrollDialog` semantics wrapper and update `SettingsTestScreen` and `SignInScreen` with new biometric test tags.
- Update `SettingsViewModel` and dependency injection modules to reflect the new `BiometricInteractor` structure and the removal of the use case.
- Add Android instrumentation tests (`BiometricSignInTest`, `BiometricSettingsTest`) to verify biometric integration on the platform.
@softartdev softartdev merged commit e0136b9 into claude/biometric-authentication-mzvIQ Apr 30, 2026
12 of 30 checks passed
@softartdev softartdev deleted the codex/find-comments-in-pull-request branch April 30, 2026 22:18
softartdev added a commit that referenced this pull request Apr 30, 2026
* feat: add biometric authentication for SignIn and Settings

Adds biometric sign-in (Touch ID / Face ID on iOS, BiometricPrompt on
Android) for unlocking the SQLCipher-encrypted database. Users enable it
from Settings; on next launch the SignIn screen offers a "Use biometric"
button that retrieves the password from OS-secured storage after a
successful biometric prompt.

Architecture:
- Common: expect class BiometricInteractor with BiometricResult /
  DecryptedPasswordResult sealed types (mirrors LocaleInteractor's
  expect/actual pattern in core/presentation).
- Android: BiometricPrompt + Android Keystore. Password is encrypted
  with an AES-GCM key marked setUserAuthenticationRequired(true) and
  setInvalidatedByBiometricEnrollment(true), then stored in plain
  SharedPreferences. Cipher is unlocked via BiometricPrompt.CryptoObject
  so decryption literally requires a fresh biometric scan. MainActivity
  attaches itself to a small BiometricActivityHolder for prompt access.
- iOS: LAContext + Keychain Services with kSecAccessControlBiometryCurrentSet
  on a generic-password Keychain item. Item invalidates on biometric
  re-enrollment.
- JVM/Web get no-op stubs so the multiplatform build stays green.

Settings flow: a ChangeBiometric(true) action navigates to the new
BiometricEnrollDialog where the user re-types the password, then
encryptAndStorePassword runs the biometric prompt. Disabling clears the
stored ciphertext. Whenever the DB password changes via Change/Enter/
Confirm ViewModels, any stored biometric password is cleared and a
snackbar tells the user to re-enable in Settings.

Tests: SignInViewModelTest gets two new biometric paths; existing
ViewModel tests are updated to pass the new mock BiometricInteractor.

https://claude.ai/code/session_01AHoV8HEQJ86WRccDv4i6hK

* refactor: enhance biometric authentication logic and platform implementations

- Refactor `BiometricInteractor` on iOS to improve type safety with explicit `CFTypeRef` and `OSStatus` handling, and simplify `memScoped` logic.
- Update `BiometricInteractor` on Android to utilize `SharedPreferences.edit` KTX extension, add detailed logging for Keystore failures, and refactor cipher initialization.
- Optimize `BiometricEnrollViewModel` state updates to use single `update` blocks and named arguments for better readability.
- Simplify `SignInScreen` and `BiometricEnrollDialog` by resolving string resources directly within the UI components and streamlining action passing.
- Switch iOS project configuration from manual to automatic code signing and remove platform-specific development team overrides.
- Refactor Android Koin dependency injection to use `singleOf` for `BiometricInteractor`.
- Add documentation/TODOs regarding the removal of Compose-specific `AutofillManager` dependencies from presentation ViewModels.
- Clean up unused methods, such as `BiometricEnrollResult.hideError()`, and improve formatting across the presentation and UI modules.
- Update iOS user interface state and workspace configuration.

* refactor: improve biometric authentication and unify sign-in state

- Refactor `BiometricInteractor` on Android to use `Application.ActivityLifecycleCallbacks` for automatic activity tracking, eliminating the manual `BiometricActivityHolder`.
- Consolidate `SignInViewModel` UI state into a single `SignInResult` data class, replacing separate flows for result and visibility with atomic state updates.
- Update `SignInScreen` and related tests to utilize the unified state model.
- Downgrade `androidx.biometric` to `1.1.0` for stability and set `minSdk` to 23 in `CONTRIBUTING.md`.
- Add `USE_FINGERPRINT` permission to `AndroidManifest.xml` and remove manual `configChanges` handling for `MainActivity`.
- Expand `CONTRIBUTING.md` with detailed coding standards for named arguments, state management, and Composable patterns.
- Switch iOS project configuration to use automatic code signing.
- Ensure `BiometricInteractor` operations are explicitly dispatched to the main thread when interacting with UI components.

* refactor: improve sign-in state model and biometric activity tracking

- Refactor `SignInResult` from a data class with an enum-based state to a sealed interface hierarchy for improved type safety and state handling.
- Update `SignInViewModel` and `SignInScreen` to utilize the new sealed structure and implement a `setState` helper for atomic updates.
- Extract `CurrentActivityProvider` into a standalone internal class, utilizing `WeakReference` to prevent potential memory leaks of the host `FragmentActivity`.
- Enhance error handling in `BiometricEnrollViewModel` to extract and display specific error messages from `BiometricResult`.
- Remove the deprecated `USE_FINGERPRINT` permission from the Android manifest.
- Introduce a `dispose()` method in `BiometricInteractor` for explicit lifecycle management and cleaner test execution.
- Update `SignInViewModelTest` to align assertions with the new sealed state model.

* refactor: improve sign-in state model and activity lifecycle tracking

- Refactor `SignInResult` from a sealed interface to a data class containing a nested `State` interface. This separates the primary UI state (Form, Progress, Error) from the `biometricVisible` flag.
- Update `SignInViewModel` to use `MutableStateFlow.update()` for state transitions, ensuring atomic updates to the sign-in state and biometric visibility.
- Adjust `SignInScreen` and `SignInViewModelTest` to access the nested state property within the new `SignInResult` model.
- Enhance `CurrentActivityProvider` to capture `FragmentActivity` references during `onActivityCreated` and `onActivityStarted`, in addition to `onActivityResumed`.
- Modify `CurrentActivityProvider` to only clear the activity reference during `onActivityDestroyed`, ensuring a stable host for components like `BiometricPrompt` during transient lifecycle changes.
- Remove the internal helper `setState` function in `SignInViewModel` in favor of standard `StateFlow` update patterns.

* refactor: extract biometric feature into dedicated feature modules

Move BiometricInteractor (expect/actuals), BiometricResult, DecryptedPasswordResult,
and CurrentActivityProvider to feature:biometric:domain; move BiometricEnrollViewModel,
BiometricEnrollResult, and BiometricEnrollAction to feature:biometric:presentation.
Update core:presentation and core:ui build files and module READMEs accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: migrate biometric storage to DataStore and update API to suspend

- Replace `SharedPreferences` with `androidx.datastore.preferences` in `BiometricInteractor.android.kt` for more robust data handling.
- Update `hasStoredPassword()` and `clearStoredPassword()` to `suspend` functions across common and platform-specific implementations.
- Add `androidx.datastore` dependency to `libs.versions.toml` and include it in the `biometric:domain` module.
- Update `SettingsViewModelTest`, `ChangeViewModelTest`, `ConfirmViewModelTest`, and `EnterViewModelTest` to handle the new `suspend` method signatures.
- Enhance error logging in `SignInViewModel` to capture specific `BiometricResult.Error` messages.
- Update `SignInScreen` Compose preview to include the `biometricVisible` parameter.

* refactor: improve biometric architecture and activity handling

- Replace `CurrentActivityProvider` with a Compose-friendly `BiometricPlatformWrapper` pattern to provide the Android host `FragmentActivity`.
- Introduce `rememberBiometricPlatformWrapper()` expect/actual function to capture the platform host context from the UI layer.
- Refactor `BiometricInteractor` to accept `BiometricPlatformWrapper` in encryption and decryption methods.
- Introduce `BiometricCredentialsStore` on Android to encapsulate DataStore Preferences logic for storing encrypted credentials.
- Refine `DecryptedPasswordResult` domain model to explicitly distinguish between `Cancelled`, `Unavailable`, and `Failure` states.
- Update `SignInViewModel` and `BiometricEnrollViewModel` to handle the new platform wrapper and result states.
- Refactor `SignInViewModel` to utilize `SnackbarInteractor` for displaying biometric authentication errors.
- Update project documentation to reflect the new two-layer protection architecture (Android Keystore + DataStore) and the activity provider pattern.
- Update unit tests for `SignInViewModel` to reflect dependency and action changes.

* refactor: make BiometricPlatformWrapper non-nullable and simplify action handling

- Update `SignInAction.OnBiometricClick` and `BiometricEnrollAction.OnEnrollClick` to require a non-nullable `BiometricPlatformWrapper`.
- Remove redundant null checks and logging for `BiometricPlatformWrapper` in `SignInViewModel` and `BiometricEnrollViewModel`.
- Refactor `SignInScreen` and `BiometricEnrollDialog` to instantiate the biometric wrapper locally and pass it directly through the UI action flow.
- Simplify the Android implementation of `rememberBiometricPlatformWrapper` using a direct cast to `FragmentActivity`.
- Clean up `onAction` lambda references in the UI layer by using function references where possible.

* fix: wrap BiometricPlatformWrapper in remember block

- Update `rememberBiometricPlatformWrapper` to use the `remember` composable across all platforms (Android, JVM, iOS, and WasmJs).
- Prevent unnecessary re-instantiation of `BiometricPlatformWrapper` during recompositions.
- Key the `remember` block on Android using the current `FragmentActivity` to ensure the wrapper stays in sync with the host activity.

* docs: update feature support matrix in README

- Add biometric authentication status to the platform feature compatibility table.

* build: update iOS code signing and project paths

- Update `.gitignore` to reflect the relocated `iosApp` directory under the `/app` path.
- Switch iOS code signing from automatic to manual and configure SDK-specific identities and provisioning profiles.
- Define SDK-specific development team settings in the Xcode project configuration.
- Add a TODO in `SignInViewModel` to extract `AutofillManager` into an interactor, aimed at removing `androidx.compose` dependencies from the presentation module.

* Refactor Sign-in state to explicit loading/error fields and rename ActivityProvider to BiometricPlatformWrapper (#719)

* core/signin: simplify state and update biometric docs

* refactor: update SignInViewModel error assertions in tests

- Update `SignInViewModelTest` to use the `errorType` property for verifying error states.
- Replace subclass-based assertions for `EmptyPass` and `IncorrectPass` with `ErrorType.EMPTY_PASSWORD` and `ErrorType.INCORRECT_PASSWORD` equality checks.

* refactor: improve sign-in state handling and simplify ViewModel logic

- Nest the `ErrorType` enum within the `SignInResult` class.
- Add helper functions to `SignInResult` for common state transformations, such as `showLoading`, `hideErrors`, and specific error states.
- Refactor `SignInViewModel` to use these helper functions for state updates, improving readability.
- Remove the internal `SignInInternalResult` sealed interface and its associated mapping logic.
- Update `SignInScreen` and `SignInViewModelTest` to reference the relocated `SignInResult.ErrorType`.

* feat: add confirmation dialog for disabling biometric sign-in

- Implement `BiometricDisableConfirmationDialog` and its corresponding `BiometricDisableViewModel` to handle user confirmation.
- Introduce `DisableBiometricUseCase` to encapsulate the logic for clearing stored biometric credentials.
- Refactor `SettingsViewModel` to trigger the confirmation dialog and await the result via a `Channel` before clearing stored passwords.
- Register the new dialog in the navigation graph and update dependency injection modules.
- Add localized string resources for the confirmation dialog in English and Russian.
- Expand test coverage with UI tests for the new dialog and unit tests in `SettingsViewModelTest` to verify the confirmation flow.
- Update test infrastructure with new test tags and helper methods for the biometric disable screen.

* refactor: biometric interactor and enhance UI test coverage

- Convert `BiometricInteractor` from an `expect` class to an interface and provide platform-specific implementations (`AndroidBiometricInteractor`, `IosBiometricInteractor`, etc.).
- Remove `DisableBiometricUseCase` and move its functionality and the biometric disable dialog channel into `BiometricInteractor`.
- Implement `TestBiometricInteractor` and a corresponding Koin module to support mocked biometric states during UI tests.
- Add `BiometricSignInTestCase` and `BiometricSettingsTestCase` to provide reusable test logic for biometric flows.
- Introduce `BiometricEnrollDialog` semantics wrapper and update `SettingsTestScreen` and `SignInScreen` with new biometric test tags.
- Update `SettingsViewModel` and dependency injection modules to reflect the new `BiometricInteractor` structure and the removal of the use case.
- Add Android instrumentation tests (`BiometricSignInTest`, `BiometricSettingsTest`) to verify biometric integration on the platform.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant