diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreen.kt index 2c8bf73178..12ffd905dc 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -14,6 +15,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.pluralStringResource @@ -21,6 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bitwarden.ui.platform.base.util.EventsEffect @@ -31,6 +34,7 @@ import com.bitwarden.ui.platform.components.button.BitwardenFilledButton import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.bitwarden.ui.platform.components.text.BitwardenClickableText import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.manager.IntentManager @@ -64,6 +68,12 @@ fun SetupBrowserAutofillScreen( browserPackage = event.browserPackage, ) } + + SetupBrowserAutofillEvent.NavigateToBrowserIntegrationsInfo -> { + intentManager.launchUri( + "https://bitwarden.com/help/auto-fill-android/#browser-integrations/".toUri(), + ) + } } } SetupBrowserAutofillDialogs( @@ -106,6 +116,9 @@ fun SetupBrowserAutofillScreen( ) { SetupBrowserAutofillContent( state = state, + onWhyIsThisStepRequiredClick = remember(viewModel) { + { viewModel.trySendAction(SetupBrowserAutofillAction.WhyIsThisStepRequiredClick) } + }, onBrowserClick = remember(viewModel) { { viewModel.trySendAction(SetupBrowserAutofillAction.BrowserIntegrationClick(it)) } }, @@ -120,9 +133,11 @@ fun SetupBrowserAutofillScreen( } } +@Suppress("LongMethod") @Composable private fun SetupBrowserAutofillContent( state: SetupBrowserAutofillState, + onWhyIsThisStepRequiredClick: () -> Unit, onBrowserClick: (BrowserPackage) -> Unit, onContinueClick: () -> Unit, onTurnOnLaterClick: () -> Unit, @@ -154,7 +169,16 @@ private fun SetupBrowserAutofillContent( .fillMaxWidth() .standardHorizontalMargin(), ) - Spacer(modifier = Modifier.height(height = 24.dp)) + BitwardenClickableText( + label = stringResource(id = BitwardenString.why_is_this_step_required), + style = BitwardenTheme.typography.labelMedium, + onClick = onWhyIsThisStepRequiredClick, + modifier = Modifier + .wrapContentWidth() + .align(alignment = Alignment.CenterHorizontally) + .standardHorizontalMargin(), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) BrowserAutofillSettingsCard( options = state.browserAutofillSettingsOptions, onOptionClicked = onBrowserClick, @@ -221,6 +245,7 @@ private fun SetupBrowserAutofillContent_preview() { BrowserAutofillSettingsOption.ChromeBeta(enabled = true), ), ), + onWhyIsThisStepRequiredClick = { }, onBrowserClick = { }, onContinueClick = { }, onTurnOnLaterClick = { }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModel.kt index 5a7807f4bb..a9d27fe955 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModel.kt @@ -56,6 +56,10 @@ class SetupBrowserAutofillViewModel @Inject constructor( handleBrowserIntegrationClick(action) } + SetupBrowserAutofillAction.WhyIsThisStepRequiredClick -> { + handleWhyIsThisStepRequiredClick() + } + SetupBrowserAutofillAction.CloseClick -> handleCloseClick() SetupBrowserAutofillAction.DismissDialog -> handleDismissDialog() SetupBrowserAutofillAction.ContinueClick -> handleContinueClick() @@ -81,6 +85,10 @@ class SetupBrowserAutofillViewModel @Inject constructor( ) } + private fun handleWhyIsThisStepRequiredClick() { + sendEvent(SetupBrowserAutofillEvent.NavigateToBrowserIntegrationsInfo) + } + private fun handleCloseClick() { sendEvent(SetupBrowserAutofillEvent.NavigateBack) } @@ -167,6 +175,11 @@ sealed class SetupBrowserAutofillEvent { data class NavigateToBrowserAutofillSettings( val browserPackage: BrowserPackage, ) : SetupBrowserAutofillEvent() + + /** + * Navigates to the browser integrations info page. + */ + data object NavigateToBrowserIntegrationsInfo : SetupBrowserAutofillEvent() } /** @@ -205,6 +218,11 @@ sealed class SetupBrowserAutofillAction { */ data object TurnOnLaterConfirmClick : SetupBrowserAutofillAction() + /** + * Indicates that the "Why is this step required?" button was clicked. + */ + data object WhyIsThisStepRequiredClick : SetupBrowserAutofillAction() + /** * Models actions the [SetupBrowserAutofillViewModel] itself may send. */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreenTest.kt index a69e87c3ce..d22c68f461 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillScreenTest.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo +import androidx.core.net.toUri import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.ui.platform.manager.IntentManager import com.bitwarden.ui.util.assertNoDialogExists @@ -34,7 +35,9 @@ import org.junit.Test class SetupBrowserAutofillScreenTest : BitwardenComposeTest() { private var onNavigateBackCalled = false - private val intentManager = mockk() + private val intentManager = mockk { + every { launchUri(uri = any()) } just runs + } private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) @@ -68,6 +71,16 @@ class SetupBrowserAutofillScreenTest : BitwardenComposeTest() { assertTrue(onNavigateBackCalled) } + @Test + fun `NavigateToBrowserIntegrationsInfo should call onNavigateBack`() { + mutableEventFlow.tryEmit(SetupBrowserAutofillEvent.NavigateToBrowserIntegrationsInfo) + verify(exactly = 1) { + intentManager.launchUri( + uri = "https://bitwarden.com/help/auto-fill-android/#browser-integrations/".toUri(), + ) + } + } + @Test fun `NavigateToBrowserAutofillSettings should start system autofill settings activity`() { val browserPackage = BrowserPackage.CHROME_STABLE @@ -112,6 +125,18 @@ class SetupBrowserAutofillScreenTest : BitwardenComposeTest() { composeTestRule.onNodeWithContentDescription(label = "Close").assertExists() } + @Test + fun `why is this step required button click should emit WhyIsThisStepRequiredClick`() { + mutableStateFlow.update { it.copy(isInitialSetup = false) } + composeTestRule + .onNodeWithText(text = "Why is this step required?") + .performScrollTo() + .performClick() + verify(exactly = 1) { + viewModel.trySendAction(SetupBrowserAutofillAction.WhyIsThisStepRequiredClick) + } + } + @Test fun `close button click should emit CloseClick`() { mutableStateFlow.update { it.copy(isInitialSetup = false) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModelTest.kt index d0eb65d773..7ff2814e83 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/accountsetup/SetupBrowserAutofillViewModelTest.kt @@ -101,6 +101,19 @@ class SetupBrowserAutofillViewModelTest { } } + @Test + fun `WhyIsThisStepRequiredClick should send NavigateToBrowserIntegrationsInfo event`() = + runTest { + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.trySendAction(SetupBrowserAutofillAction.WhyIsThisStepRequiredClick) + assertEquals( + SetupBrowserAutofillEvent.NavigateToBrowserIntegrationsInfo, + awaitItem(), + ) + } + } + @Test fun `BrowserIntegrationClick should send NavigateToBrowserAutofillSettings event`() = runTest { val browserPackage = BrowserPackage.BRAVE_RELEASE diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 63e117e76a..5c3283ba4f 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -1130,4 +1130,5 @@ Do you want to switch to this account? Passwords Passkeys Import + Why is this step required?