Skip to content

Add the password import step UI to the onboarding - #9623

Merged
catalinradoiu merged 11 commits into
developfrom
feature/cradoiu/onboarding-passwords-import-step
Aug 31, 2026
Merged

catalinradoiu merged 11 commits into
developfrom
feature/cradoiu/onboarding-passwords-import-step

Conversation

@catalinradoiu

@catalinradoiu catalinradoiu commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/task/1217265967787212
Tech Design URL (if applicable): https://app.asana.com/1/137249556945/project/481882893211075/task/1217143698674642
API Proposals URL(s) (if applicable): None

Description

Adds the onboarding cards for the password import experiment (UI only).

Two cards land here:

  • Import passwords — the prompt card, offering the import with a Skip alternative.
  • Import complete — the outcome card, with three content states: Parsing (a shimmer skeleton of the result row), Finished (the imported/skipped counts), and Failed.

Supporting changes:

  • StepIndicatorMode replaces the previous showsStepIndicator boolean, adding CONTINUES_PREVIOUS. The prompt and outcome cards are two engineering steps but one product step, so they show the same "N of M" and the outcome card adds nothing to the total.
  • The outcome card is a StatefulDialogBinder: the import keeps counting after the web flow returns, so the card is entered immediately and shows the wait rather than parking the user on a card-less step. Its state transition is held until the entrance animations finish, so the loading state is actually seen.
  • ContentValueStore gains a step-id-only overload so a step's content state can be seeded before that step is current.
  • Card decoration for the new step: a right-wing embellishment and its background.
  • New pixel onboarding_password-import (shown / clicked / confirmed), following the onboarding instrumentation standard. Bounded enums only, no counts and no identifiers.

Steps to test this PR

Apply the following patch:

Diff: fake password import onboarding flow
Index: app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt b/app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt
--- a/app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt	(revision 6564d10b993437ee7945211924655fc8c358613f)
+++ b/app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt	(date 1787834953511)
@@ -196,7 +196,7 @@
                     add(widgetPromptStep(ctx))
                     add(addWidgetStep(ctx))
                 }
-                if (showPasswordImport) {
+                if (true) {
                     add(passwordImportStep(ctx))
                     add(passwordImportLaunchStep(ctx))
                     add(passwordImportCompleteStep(ctx))
Index: app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ConfigDrivenOnboardingPageViewModel.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ConfigDrivenOnboardingPageViewModel.kt b/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ConfigDrivenOnboardingPageViewModel.kt
--- a/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ConfigDrivenOnboardingPageViewModel.kt	(revision 6564d10b993437ee7945211924655fc8c358613f)
+++ b/app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ConfigDrivenOnboardingPageViewModel.kt	(date 1787840253879)
@@ -33,6 +33,8 @@
 import com.duckduckgo.app.onboarding.orchestrator.NewUserOnboardingPlanBootstrapper
 import com.duckduckgo.app.onboarding.orchestrator.NewUserOnboardingPlanProvider
 import com.duckduckgo.app.onboarding.orchestrator.NewUserOnboardingResult
+import com.duckduckgo.app.onboarding.orchestrator.NewUserOnboardingStepIds
+import com.duckduckgo.app.onboarding.orchestrator.PasswordImportOutcome
 import com.duckduckgo.app.onboarding.orchestrator.stepIndicatorProgress
 import com.duckduckgo.app.pixels.AppPixelName
 import com.duckduckgo.app.statistics.pixels.Pixel
@@ -58,6 +60,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
 
 @SuppressLint("StaticFieldLeak")
@@ -145,6 +148,8 @@
 
     private var quickSetupDefaultBrowserDialogShown = false
 
+    private var passwordImportFlowStarted = false
+
     init {
         start()
     }
@@ -460,9 +465,32 @@
             }
 
             NewUserOnboardingActivityDialog.ImportPasswordsLaunch -> {
-                // TODO: launch the Google password import flow and report its outcome back as
-                //  PasswordImportWebFlowFinished / PasswordImportParsed. Until that lands, this step has no
-                //  side effect and the outcome card stays on its parsing state.
+                if (!passwordImportFlowStarted) {
+                    passwordImportFlowStarted = true
+                    viewModelScope.launch {
+                        emit(NewUserOnboardingEvent.PasswordImportWebFlowFinished(FAKE_IMPORT_WEB_FLOW_OUTCOME))
+                        if (FAKE_IMPORT_WEB_FLOW_OUTCOME != PasswordImportOutcome.SUCCESS) {
+                            // CANCELLED sends the flow back to the import card, ERROR keeps it here; either way let it re-run.
+                            passwordImportFlowStarted = false
+                            return@launch
+                        }
+
+                        delay(FAKE_IMPORT_PARSE_DELAY)
+                        contentValues.contentState<ImportCompleteContentState>(NewUserOnboardingStepIds.PASSWORD_IMPORT_COMPLETE) {
+                            ImportCompleteContentState.Parsing
+                        }.value = when (FAKE_IMPORT_PARSE_OUTCOME) {
+                            PasswordImportOutcome.SUCCESS -> ImportCompleteContentState.Finished(
+                                imported = FAKE_IMPORTED_COUNT,
+                                skipped = FAKE_SKIPPED_COUNT,
+                            )
+
+                            PasswordImportOutcome.CANCELLED,
+                            PasswordImportOutcome.ERROR,
+                            -> ImportCompleteContentState.Failed
+                        }
+                        emit(NewUserOnboardingEvent.PasswordImportParsed(FAKE_IMPORT_PARSE_OUTCOME))
+                    }
+                }
             }
 
             NewUserOnboardingActivityDialog.SyncRestore,
@@ -492,4 +520,13 @@
     private fun emit(event: NewUserOnboardingEvent) {
         viewModelScope.launch { orchestrator.onEvent(event) }
     }
+
+    private companion object {
+        /** Knobs for the faked password import; SUCCESS then SUCCESS is the happy path. */
+        val FAKE_IMPORT_WEB_FLOW_OUTCOME = PasswordImportOutcome.SUCCESS
+        val FAKE_IMPORT_PARSE_OUTCOME = PasswordImportOutcome.SUCCESS
+        val FAKE_IMPORT_PARSE_DELAY = 500.milliseconds
+        const val FAKE_IMPORTED_COUNT = 42
+        const val FAKE_SKIPPED_COUNT = 3
+    }
 }

Prompt card

  • Step matches Figma design
  • Run onboarding to the import step and confirm the card renders: pictogram, title, body, "Import From Google" and "Skip"
  • Confirm the step indicator reads the expected "N of M"
  • In the ConfigDrivenOnboardingPageViewModel change the value of FAKE_IMPORT_PARSE_OUTCOME to ERROR to check the error cahse

UI changes

Phone Tablet
import_passwords_phone.mp4
import_passwords_tablet.mp4

Note

Medium Risk
Touches core new-user onboarding plan ordering and step-indicator math; password data handling is UI-only here but the feature path is security-sensitive once the web import is wired.

Overview
Adds config-driven onboarding cards for importing passwords from Google when the password-import experiment is in treatment: a prompt step (Import / Skip) and a follow-on outcome card with parsing, finished (imported/skipped counts), and failed states. The linear plan wires three orchestrator steps (prompt → launch → complete); launching the real Google import web flow is still a TODO in ConfigDrivenOnboardingPageViewModel, so the outcome card can remain on parsing until that lands.

Progress indicator logic moves from showsStepIndicator to StepIndicatorMode (COUNTED vs CONTINUES_PREVIOUS) so the outcome screen shares the prompt’s “N of M” without increasing the total. Plan context tracks skip/success to gate launch and complete steps; cancel from the web step uses GoBack.

Design-system support for these screens: mirrored DAX bubble tail (mirrorFraction on edge treatment and card arrow animation), right-wing embellishment, island horizon background, success icon theming, shimmer skeleton on the outcome card, and pixel onboarding_password-import (shown / clicked / confirmed).

Reviewed by Cursor Bugbot for commit 0185599. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds the onboarding cards for the password import experiment: the prompt card,
and the outcome card with its parsing, finished and failed states. The step
indicator gains a CONTINUES_PREVIOUS mode so the prompt and outcome cards read
as one product step.

The import flow itself is not wired up yet: the launch step is a no-op with a
TODO, and the outcome card stays on its parsing state until the follow-up lands.

Task: https://app.asana.com/1/137249556945/project/72649045549333/task/1216462243759588

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingEvent.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/BrandDesignUpdatePageViewModel.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ContentConfig.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/DialogConfigResolver.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/OnboardingDialogShownPixels.kt
#	app/src/main/res/layout/pre_onboarding_dax_dialog_cta_brand_design_update.xml
#	app/src/test/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProviderTest.kt

catalinradoiu commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@catalinradoiu catalinradoiu changed the title Add the password import step UI to the new-user onboarding Add the password import step UI to the onboarding Aug 26, 2026
// TODO: launch the Google password import flow and report its outcome back as
// PasswordImportWebFlowFinished / PasswordImportParsed. Until that lands, this step has no
// side effect and the outcome card stays on its parsing state.
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import CTA leaves onboarding stuck

High Severity

Tapping Import advances to ImportPasswordsLaunch, but handleCommandOnlyDialog does nothing for that step. The prior Import card stays on screen while its CTAs now target the launch step, which ignores PasswordImportRequested / PasswordImportSkipped, so the user cannot proceed or skip. The same trap applies for ERRORStay once a real web flow exists.


Please tell me if this was useful or not with a 👍 or 👎.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b51ba9c. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be added later in a future PR, not a real issue

…rt-step

# Conflicts:
#	app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingEvent.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProvider.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/ContentConfig.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/DialogConfigResolver.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/OnboardingDialogShownPixels.kt
#	app/src/main/java/com/duckduckgo/app/onboarding/ui/page/configdriven/engine/ContentController.kt
#	app/src/main/res/layout/pre_onboarding_dax_dialog_cta_brand_design_update.xml
#	app/src/test/java/com/duckduckgo/app/onboarding/orchestrator/NewUserOnboardingPlanProviderTest.kt
#	app/src/test/java/com/duckduckgo/app/onboarding/ui/page/configdriven/DialogConfigResolverTest.kt

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 20b0935. Configure here.

@LukasPaczos LukasPaczos self-assigned this Aug 27, 2026

@LukasPaczos LukasPaczos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wing is offset slightly too much towards bottom-end side of the screen on my Pixel 9 Pro. I think we should try getting it closer to the Figma design.

Figma Actual
Image Image
  1. Just a note: when we integrate the web flow, we should make sure there are the right precautions and timeouts, so that users don't get stuck on the loading state. I was

Comment thread app/src/main/res/drawable/ic_check_onboarding_success_24.xml Outdated
@catalinradoiu

catalinradoiu commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

@LukasPaczos I updated this PR after your code review. Regarding the wing position I have followed the same approach that we have on the address bar positon step for consistency:
Screenshot 2026-08-31 at 11 12 11

Regarding the timeout I will add that as part of the next PR. However in that case the operation that we are waiting for it a local one, to finish saving the passwords and that should remain stuck, but will add the timeout for safety.

@github-actions

Copy link
Copy Markdown
Contributor

Privacy Review task: https://app.asana.com/0/69071770703008/1218005294664889

@LukasPaczos LukasPaczos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

catalinradoiu and others added 2 commits August 31, 2026 18:51
…dingBackgroundAnimator.kt

Co-authored-by: Łukasz Paczos <lpaczos@duckduckgo.com>
@catalinradoiu
catalinradoiu added this pull request to the merge queue Aug 31, 2026
Merged via the queue into develop with commit 2a6fb41 Aug 31, 2026
21 checks passed
@catalinradoiu
catalinradoiu deleted the feature/cradoiu/onboarding-passwords-import-step branch August 31, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants