BIT-1147: Adding navigation to block auto fill screen and skeleton UI (#671)

This commit is contained in:
Joshua Queen 2024-01-18 17:44:12 -05:00 committed by Álison Fernandes
parent 3500f90812
commit 8c64d6b01b
10 changed files with 282 additions and 2 deletions

View file

@ -12,6 +12,8 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.navigate
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.appearanceDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.navigateToAppearance
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.autoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.blockAutoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill.navigateToBlockAutoFillScreen
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
import com.x8bit.bitwarden.ui.platform.feature.settings.other.navigateToOther
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
@ -51,12 +53,16 @@ fun NavGraphBuilder.settingsGraph(
onNavigateToDeleteAccount = onNavigateToDeleteAccount,
)
appearanceDestination(onNavigateBack = { navController.popBackStack() })
autoFillDestination(onNavigateBack = { navController.popBackStack() })
autoFillDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToBlockAutoFillScreen = { navController.navigateToBlockAutoFillScreen() },
)
otherDestination(onNavigateBack = { navController.popBackStack() })
vaultSettingsDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToFolders = onNavigateToFolders,
)
blockAutoFillDestination(onNavigateBack = { navController.popBackStack() })
}
}

View file

@ -12,11 +12,15 @@ private const val AUTO_FILL_ROUTE = "settings_auto_fill"
*/
fun NavGraphBuilder.autoFillDestination(
onNavigateBack: () -> Unit,
onNavigateToBlockAutoFillScreen: () -> Unit,
) {
composableWithPushTransitions(
route = AUTO_FILL_ROUTE,
) {
AutoFillScreen(onNavigateBack = onNavigateBack)
AutoFillScreen(
onNavigateBack = onNavigateBack,
onNavigateToBlockAutoFillScreen = onNavigateToBlockAutoFillScreen,
)
}
}

View file

@ -53,6 +53,7 @@ fun AutoFillScreen(
onNavigateBack: () -> Unit,
viewModel: AutoFillViewModel = hiltViewModel(),
intentManager: IntentManager = LocalIntentManager.current,
onNavigateToBlockAutoFillScreen: () -> Unit,
) {
val state by viewModel.stateFlow.collectAsState()
val context = LocalContext.current
@ -71,6 +72,9 @@ fun AutoFillScreen(
is AutoFillEvent.ShowToast -> {
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
}
AutoFillEvent.NavigateToBlockAutoFill -> {
onNavigateToBlockAutoFillScreen()
}
}
}
@ -170,6 +174,16 @@ fun AutoFillScreen(
{ viewModel.trySendAction(AutoFillAction.UriDetectionMethodSelect(it)) }
},
)
BitwardenTextRow(
text = stringResource(id = R.string.block_auto_fill),
description = stringResource(
id = R.string.auto_fill_will_not_be_offered_for_these_ur_is,
),
onClick = remember(viewModel) {
{ viewModel.trySendAction(AutoFillAction.BlockAutoFillClick) }
},
modifier = Modifier.fillMaxWidth(),
)
Spacer(modifier = Modifier.height(16.dp))
}
}

View file

@ -57,6 +57,7 @@ class AutoFillViewModel @Inject constructor(
AutoFillAction.BackClick -> handleBackClick()
is AutoFillAction.CopyTotpAutomaticallyClick -> handleCopyTotpAutomaticallyClick(action)
is AutoFillAction.UriDetectionMethodSelect -> handleUriDetectionMethodSelect(action)
AutoFillAction.BlockAutoFillClick -> handleBlockAutoFillClick()
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
is AutoFillAction.Internal.AutofillEnabledUpdateReceive -> {
handleAutofillEnabledUpdateReceive(action)
@ -109,6 +110,10 @@ class AutoFillViewModel @Inject constructor(
it.copy(isAutoFillServicesEnabled = action.isAutofillEnabled)
}
}
private fun handleBlockAutoFillClick() {
sendEvent(AutoFillEvent.NavigateToBlockAutoFill)
}
}
/**
@ -150,6 +155,11 @@ sealed class AutoFillEvent {
*/
data object NavigateToAutofillSettings : AutoFillEvent()
/**
* Navigate to block auto fill screen.
*/
data object NavigateToBlockAutoFill : AutoFillEvent()
/**
* Displays a toast with the given [Text].
*/
@ -195,6 +205,11 @@ sealed class AutoFillAction {
val uriDetectionMethod: AutoFillState.UriDetectionMethod,
) : AutoFillAction()
/**
* User clicked block auto fill button.
*/
data object BlockAutoFillClick : AutoFillAction()
/**
* User clicked use inline autofill button.
*/

View file

@ -0,0 +1,28 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import com.x8bit.bitwarden.ui.platform.base.util.composableWithPushTransitions
private const val BLOCK_AUTO_FILL_ROUTE = "settings_block_auto_fill"
/**
* Add block auto-fill destination to the nav graph.
*/
fun NavGraphBuilder.blockAutoFillDestination(
onNavigateBack: () -> Unit,
) {
composableWithPushTransitions(
route = BLOCK_AUTO_FILL_ROUTE,
) {
BlockAutoFillScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the block auto-fill screen.
*/
fun NavController.navigateToBlockAutoFillScreen(navOptions: NavOptions? = null) {
navigate(BLOCK_AUTO_FILL_ROUTE, navOptions)
}

View file

@ -0,0 +1,73 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
/**
* Displays the block auto-fill screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BlockAutoFillScreen(
onNavigateBack: () -> Unit,
viewModel: BlockAutoFillViewModel = hiltViewModel(),
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
BlockAutoFillEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.autofill),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(BlockAutoFillAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "Not yet implemented")
}
}
}

View file

@ -0,0 +1,84 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.android.parcel.Parcelize
import javax.inject.Inject
private const val KEY_STATE = "state"
/**
* View model for the blocked autofill URIs screen.
*/
@HiltViewModel
@Suppress("TooManyFunctions", "LargeClass")
class BlockAutoFillViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : BaseViewModel<BlockAutoFillState, BlockAutoFillEvent, BlockAutoFillAction>(
initialState = savedStateHandle[KEY_STATE]
?: BlockAutoFillState(
viewState = BlockAutoFillState.ViewState.Empty,
),
) {
override fun handleAction(action: BlockAutoFillAction) {
when (action) {
BlockAutoFillAction.BackClick -> handleCloseClick()
}
}
private fun handleCloseClick() {
sendEvent(
event = BlockAutoFillEvent.NavigateBack,
)
}
}
/**
* Represents the state for block auto fill.
*
* @property viewState indicates what view state the screen is in.
*/
@Parcelize
data class BlockAutoFillState(
val viewState: ViewState,
) : Parcelable {
/**
* Represents the specific view states for the [BlockAutoFillScreen].
*/
sealed class ViewState : Parcelable {
/**
* Represents an empty content state for the [BlockAutoFillScreen].
*/
@Parcelize
data object Empty : ViewState()
}
}
/**
* Represents a set of events that can be emitted for the block auto fill screen.
* Each subclass of this sealed class denotes a distinct event that can occur.
*/
sealed class BlockAutoFillEvent {
/**
* Navigate back to previous screen.
*/
data object NavigateBack : BlockAutoFillEvent()
}
/**
* Represents a set of actions related to the block auto fill screen.
* Each subclass of this sealed class denotes a distinct action that can be taken.
*/
sealed class BlockAutoFillAction {
/**
* User clicked close.
*/
data object BackClick : BlockAutoFillAction()
}

View file

@ -29,6 +29,7 @@ class AutoFillScreenTest : BaseComposeTest() {
private var isSystemSettingsRequestSuccess = false
private var onNavigateBackCalled = false
private var onNavigateToBlockAutoFillScreenCalled = false
private val mutableEventFlow = bufferedMutableSharedFlow<AutoFillEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
@ -45,6 +46,7 @@ class AutoFillScreenTest : BaseComposeTest() {
composeTestRule.setContent {
AutoFillScreen(
onNavigateBack = { onNavigateBackCalled = true },
onNavigateToBlockAutoFillScreen = { onNavigateToBlockAutoFillScreenCalled = true },
viewModel = viewModel,
intentManager = intentManager,
)
@ -234,6 +236,21 @@ class AutoFillScreenTest : BaseComposeTest() {
mutableEventFlow.tryEmit(AutoFillEvent.NavigateBack)
assertTrue(onNavigateBackCalled)
}
@Test
fun `on block auto fill click should send BlockAutoFillClick`() {
composeTestRule
.onNodeWithText("Block auto-fill")
.performScrollTo()
.performClick()
verify { viewModel.trySendAction(AutoFillAction.BlockAutoFillClick) }
}
@Test
fun `on NavigateToBlockAutoFill should call onNavigateToBlockAutoFillScreen`() {
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToBlockAutoFill)
assertTrue(onNavigateToBlockAutoFillScreenCalled)
}
}
private val DEFAULT_STATE: AutoFillState = AutoFillState(

View file

@ -153,6 +153,15 @@ class AutoFillViewModelTest : BaseViewModelTest() {
)
}
@Test
fun `on BlockAutoFillClick should emit NavigateToBlockAutoFill`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AutoFillAction.BlockAutoFillClick)
assertEquals(AutoFillEvent.NavigateToBlockAutoFill, awaitItem())
}
}
private fun createViewModel(
state: AutoFillState? = DEFAULT_STATE,
): AutoFillViewModel = AutoFillViewModel(

View file

@ -0,0 +1,30 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.jupiter.api.Test
class BlockAutoFillViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(BlockAutoFillAction.BackClick)
assertEquals(BlockAutoFillEvent.NavigateBack, awaitItem())
}
}
private fun createViewModel(
state: BlockAutoFillState? = DEFAULT_STATE,
): BlockAutoFillViewModel = BlockAutoFillViewModel(
savedStateHandle = SavedStateHandle().apply { set("state", state) },
)
}
private val DEFAULT_STATE: BlockAutoFillState = BlockAutoFillState(
BlockAutoFillState.ViewState.Empty,
)