diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreen.kt index 5967772744..3fb2f5cae4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreen.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.newdevicenotice import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height @@ -24,19 +25,24 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.unit.dp +import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.ContinueClick import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.EmailAccessToggle +import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.LearnMoreClick import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.x8bit.bitwarden.ui.platform.base.util.toAnnotatedString import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter +import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme /** @@ -47,12 +53,19 @@ fun NewDeviceNoticeEmailAccessScreen( onNavigateBackToVault: () -> Unit, onNavigateToTwoFactorOptions: () -> Unit, viewModel: NewDeviceNoticeEmailAccessViewModel = hiltViewModel(), + intentManager: IntentManager = LocalIntentManager.current, ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() EventsEffect(viewModel = viewModel) { event -> when (event) { NavigateToTwoFactorOptions -> onNavigateToTwoFactorOptions() NewDeviceNoticeEmailAccessEvent.NavigateBackToVault -> onNavigateBackToVault() + is NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore -> { + intentManager.launchUri( + "https://bitwarden.com/help/new-device-verification/" + .toUri(), + ) + } } } @@ -65,7 +78,12 @@ fun NewDeviceNoticeEmailAccessScreen( viewModel.trySendAction(EmailAccessToggle(isEnabled = newState)) } }, - onContinueClick = { viewModel.trySendAction(ContinueClick) }, + onContinueClick = remember(viewModel) { + { viewModel.trySendAction(ContinueClick) } + }, + onLearnMoreClick = remember(viewModel) { + { viewModel.trySendAction(LearnMoreClick) } + }, ) } } @@ -76,6 +94,7 @@ private fun NewDeviceNoticeEmailAccessContent( isEmailAccessEnabled: Boolean, onEmailAccessToggleChanged: (Boolean) -> Unit, onContinueClick: () -> Unit, + onLearnMoreClick: () -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -86,7 +105,7 @@ private fun NewDeviceNoticeEmailAccessContent( .verticalScroll(state = rememberScrollState()), ) { Spacer(modifier = Modifier.height(104.dp)) - HeaderContent() + HeaderContent(onLearnMoreClick = onLearnMoreClick) Spacer(modifier = Modifier.height(24.dp)) MainContent( email = email, @@ -110,7 +129,9 @@ private fun NewDeviceNoticeEmailAccessContent( */ @Suppress("MaxLineLength") @Composable -private fun ColumnScope.HeaderContent() { +private fun ColumnScope.HeaderContent( + onLearnMoreClick: () -> Unit, +) { Image( painter = rememberVectorPainter(id = R.drawable.warning), contentDescription = null, @@ -132,6 +153,13 @@ private fun ColumnScope.HeaderContent() { color = BitwardenTheme.colorScheme.text.primary, textAlign = TextAlign.Center, ) + BitwardenClickableText( + label = stringResource(id = R.string.learn_more), + onClick = onLearnMoreClick, + style = BitwardenTheme.typography.labelLarge, + innerPadding = PaddingValues(vertical = 8.dp, horizontal = 16.dp), + modifier = Modifier.testTag("LearnMoreLabel"), + ) } /** @@ -183,6 +211,7 @@ private fun NewDeviceNoticeEmailAccessScreen_preview() { isEmailAccessEnabled = true, onEmailAccessToggleChanged = {}, onContinueClick = {}, + onLearnMoreClick = {}, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModel.kt index 192f75bc6a..d704a79543 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModel.kt @@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager import com.x8bit.bitwarden.data.platform.manager.model.FlagKey import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.ContinueClick import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.EmailAccessToggle +import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessAction.LearnMoreClick import com.x8bit.bitwarden.ui.auth.feature.newdevicenotice.NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import dagger.hilt.android.lifecycle.HiltViewModel @@ -42,6 +43,7 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor( when (action) { ContinueClick -> handleContinueClick() is EmailAccessToggle -> handleEmailAccessToggle(action) + LearnMoreClick -> handleLearnMoreClick() } } @@ -71,6 +73,10 @@ class NewDeviceNoticeEmailAccessViewModel @Inject constructor( it.copy(isEmailAccessEnabled = action.isEnabled) } } + + private fun handleLearnMoreClick() { + sendEvent(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore) + } } /** @@ -95,6 +101,11 @@ sealed class NewDeviceNoticeEmailAccessEvent { * Navigates back. */ data object NavigateBackToVault : NewDeviceNoticeEmailAccessEvent() + + /** + * Navigates to learn more about New Device Login Protection + */ + data object NavigateToLearnMore : NewDeviceNoticeEmailAccessEvent() } /** @@ -110,4 +121,9 @@ sealed class NewDeviceNoticeEmailAccessAction { * User tapped the email access toggle. */ data class EmailAccessToggle(val isEnabled: Boolean) : NewDeviceNoticeEmailAccessAction() + + /** + * User tapped the learn more button. + */ + data object LearnMoreClick : NewDeviceNoticeEmailAccessAction() } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreenTest.kt index 2418d96958..ab5ee23522 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessScreenTest.kt @@ -5,10 +5,14 @@ import androidx.compose.ui.test.assertIsOn 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.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest +import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import io.mockk.every +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import io.mockk.verify import junit.framework.TestCase.assertTrue import kotlinx.coroutines.flow.MutableStateFlow @@ -27,6 +31,10 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() { every { eventFlow } returns mutableEventFlow } + private val intentManager: IntentManager = mockk { + every { launchUri(any()) } just runs + } + @Before fun setUp() { composeTestRule.setContent { @@ -34,6 +42,7 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() { onNavigateBackToVault = { onNavigateBackToVaultCalled = true }, onNavigateToTwoFactorOptions = { onNavigateToTwoFactorOptionsCalled = true }, viewModel = viewModel, + intentManager = intentManager, ) } } @@ -91,6 +100,14 @@ class NewDeviceNoticeEmailAccessScreenTest : BaseComposeTest() { mutableEventFlow.tryEmit(NewDeviceNoticeEmailAccessEvent.NavigateToTwoFactorOptions) assertTrue(onNavigateToTwoFactorOptionsCalled) } + + @Test + fun `on NavigateToLearnMore should call launchUri on IntentManager`() { + mutableEventFlow.tryEmit(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore) + verify { + intentManager.launchUri("https://bitwarden.com/help/new-device-verification/".toUri()) + } + } } private const val EMAIL = "active@bitwarden.com" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModelTest.kt index 73756004cd..23134a86cb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/newdevicenotice/NewDeviceNoticeEmailAccessViewModelTest.kt @@ -110,6 +110,16 @@ class NewDeviceNoticeEmailAccessViewModelTest : BaseViewModelTest() { } } + @Test + fun `LearnMoreClick should emit NavigateToLearnMore`() = + runTest { + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.trySendAction(NewDeviceNoticeEmailAccessAction.LearnMoreClick) + assertEquals(NewDeviceNoticeEmailAccessEvent.NavigateToLearnMore, awaitItem()) + } + } + private fun createViewModel( savedStateHandle: SavedStateHandle = SavedStateHandle().also { it["email_address"] = EMAIL