Skip to content

GT-2783 Convert the login flow to a Circuit LoginScreen - #4654

Open
smgoss wants to merge 7 commits into
developfrom
fix/issue-4610
Open

smgoss wants to merge 7 commits into
developfrom
fix/issue-4610

Conversation

@smgoss

@smgoss smgoss commented Sep 28, 2026

Copy link
Copy Markdown

Resolves #4610 (GT-2783)

Login was still hosted by its own Activity (LoginActivity hosting a stateful LoginLayout). This converts it to a Circuit screen launched with startCircuitActivity, following the same structure as the Delete Account conversion (DeleteAccountScreen/DeleteAccountPresenter/DeleteAccountLayout).

Changes

  • LoginScreen: @Parcelize data class LoginScreen(val createAccount: Boolean = false) : ParcelableScreen.
  • LoginPresenter: nested UiState (createAccount, loginError) and UiEvent (Login(AccountType), ClearError, Close), plain @AssistedFactory @CircuitInject factory. It owns the login error state (still rememberSaveable) and a small CloseWhenAuthenticated() helper that pops the navigator once GodToolsAccountManager.isAuthenticatedFlow reports an authenticated account. That covers both a successful login and opening the screen while already logged in. Close pops the navigator.
  • LoginLauncherProducer: the login launcher registers Activity result callbacks from a composable, so it can't be mocked with plain mockk. Following the ToolFiltersStateProducer pattern, it is an interface with DefaultLoginLauncherProducer (a thin wrapper around the existing rememberLoginLauncher, so Google and Facebook still go through the account-manager abstraction), bound in a new LoginModule, plus a hand-written FakeLoginLauncherProducer for tests.
  • LoginLayout: now a stateless @CircuitInject UI taking (state: UiState, modifier: Modifier = Modifier) that sends UiEvents. TEST_TAG_* constants were added. The visuals are unchanged.
  • DrawerMenuLayout: the Login and Create Account actions call context.startCircuitActivity(LoginScreen(...)).
  • Removed LoginActivity, startLoginActivity() and the .ui.login.LoginActivity manifest entry in the switch commit. Removed LoginLayoutEvent and the temporary stateful LoginLayout overload (kept only so commit 1 compiles) in a separate orphan commit.

Commits: 1) add the Circuit screen with tests, 2) switch callers and delete LoginActivity, 3) remove orphaned code.

Behavior change to be aware of: LoginResponse.Success no longer closes the screen directly. The screen closes when the account manager reports the authenticated account. Both providers store the user id on success, so isAuthenticatedFlow flips right away. This avoids popping twice (the old Activity called finish() twice, which did nothing extra, but a double pop() would also pop the previous screen if LoginScreen is ever pushed onto an existing back stack).

Intentionally not done:

Tests

  • LoginPresenterTest (src/test): createAccount is passed through to state and to the launcher; a login error shows in loginError; a successful login shows no error; the screen pops once authenticated and when already authenticated; Login launches Google and Facebook; ClearError dismisses the error; Close pops.
  • LoginLayoutTest (testDebug, v2.runComposeUiTest): heading for login vs create account, close icon, Google and Facebook buttons, and the error dialog (hidden, shown, confirm fires ClearError).
  • LoginLayoutPaparazziTest: Login, Create Account and Error scenarios. Goldens are not recorded yet; run the Record Snapshots workflow on this branch and fold the result into the first commit.
  • Not compiled or run locally: Gradle can't reach its Maven repositories in the environment where this was written. Imports, signatures and test helpers were checked by hand against sibling code. Please run ./gradlew :build-logic:ktlintCheck ktlintCheck, ./gradlew :app:testProductionDebugUnitTest and verifyPaparazzi (after recording) in CI.

Review

Checked against the project conventions for Circuit, migration and testing, a correctness review, a security review and a simplification pass. Nothing needed changing.

  • Architecture: the full LoginScreen/LoginPresenter/LoginLayout file set is present. The screen is @Parcelize data class ... : ParcelableScreen. The presenter has a plain nested @AssistedFactory @CircuitInject Factory, and present() stays short. LoginLauncherProducer follows the interface + Default* + @Binds + Fake* pattern, and LoginModule sits in the feature package. The drawer uses startCircuitActivity. The switch commit and the orphan-removal commit are separate. LoginLayoutPaparazziTest is present.
  • No references remain to LoginActivity, startLoginActivity or LoginLayoutEvent in code or manifests. rememberLoginLauncher falls back to the Hilt entry point, so it works inside CircuitActivity.
  • Security: no findings. CircuitActivity was already exported, and LoginScreen gives no new privilege.
  • Left as is on purpose: Success doesn't pop directly, to avoid a double pop. Both providers write the user id before reporting Success. The close icon's null contentDescription existed before and is kept to avoid visual changes. Using rememberUpdatedState(state.eventSink) matches the owners' pattern.
  • Still open: Paparazzi goldens must be recorded by the CI workflow. Nothing has been compiled yet.
  • Merges cleanly with Migrate Google authentication to Android Credential Manager #4612: no overlapping files and no merge-tree conflicts.

Generated by Claude Code

Stephen Goss and others added 7 commits September 26, 2026 19:16
LoginPresenter now owns the login error state and closes the screen
once GodToolsAccountManager reports an authenticated account, which
covers both a successful login and opening the screen while already
logged in. LoginLayout becomes a stateless @CircuitInject UI that sends
UiEvents for close, login and error dismissal.

The login launcher registers Activity result callbacks from a
composable, so it is wrapped in a LoginLauncherProducer interface with
a DefaultLoginLauncherProducer backed by rememberLoginLauncher and a
FakeLoginLauncherProducer for presenter tests.

The existing stateful LoginLayout overload is kept as a thin wrapper
so LoginActivity keeps compiling until callers are switched over.
The Login and Create Account drawer actions now open LoginScreen with
startCircuitActivity, which already provides the theme, edge-to-edge
and back handling. LoginActivity, its startLoginActivity() helper and
its manifest entry are no longer reachable, so they are removed.
The stateful LoginLayout overload and LoginLayoutEvent only existed for
LoginActivity, which was replaced by the Circuit-hosted LoginScreen.
ktlint wants a blank line between every when-condition once one of them
spans several lines, and the Success branch has a comment above it.
Keying the effect on the collected isAuthenticated value let it pop the
navigator more than once when the account was already signed in, which
could also close the screen below LoginScreen. Wait for the first
authenticated value in a single effect instead, like LoginActivity's
finishWhenAuthenticated() did.
@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.60656% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.59%. Comparing base (88bef9f) to head (8f1e3c8).

Files with missing lines Patch % Lines
...org/cru/godtools/ui/login/LoginLauncherProducer.kt 0.00% 4 Missing ⚠️
...lin/org/cru/godtools/ui/drawer/DrawerMenuLayout.kt 0.00% 2 Missing ⚠️
...in/kotlin/org/cru/godtools/ui/login/LoginLayout.kt 90.90% 0 Missing and 2 partials ⚠️
...in/kotlin/org/cru/godtools/ui/login/LoginModule.kt 0.00% 1 Missing ⚠️
...kotlin/org/cru/godtools/ui/login/LoginPresenter.kt 96.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #4654      +/-   ##
===========================================
+ Coverage    53.56%   54.59%   +1.02%     
===========================================
  Files          440      442       +2     
  Lines        11572    11590      +18     
  Branches      1944     1946       +2     
===========================================
+ Hits          6198     6327     +129     
+ Misses        4790     4675     -115     
- Partials       584      588       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@smgoss
smgoss marked this pull request as ready for review September 29, 2026 14:11
@smgoss
smgoss requested a review from a team September 29, 2026 14:11
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.

Convert the login flow to Circuit

1 participant