mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1147: Adding navigation to block auto fill screen and skeleton UI (#671)
This commit is contained in:
parent
3500f90812
commit
8c64d6b01b
10 changed files with 282 additions and 2 deletions
|
@ -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() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
)
|
Loading…
Reference in a new issue