mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 12:00:19 +03:00
BIT-1147: Adding UI for empty and content view states in BlockAutoFillScreen (#688)
This commit is contained in:
parent
9779cb9cf2
commit
6dd4a31a57
10 changed files with 394 additions and 15 deletions
|
@ -1,12 +1,30 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
|
@ -18,16 +36,21 @@ 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.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
|
||||
/**
|
||||
* Displays the block auto-fill screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BlockAutoFillScreen(
|
||||
|
@ -49,7 +72,7 @@ fun BlockAutoFillScreen(
|
|||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
BitwardenTopAppBar(
|
||||
title = stringResource(id = R.string.autofill),
|
||||
title = stringResource(id = R.string.block_auto_fill),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = painterResource(id = R.drawable.ic_back),
|
||||
navigationIconContentDescription = stringResource(id = R.string.back),
|
||||
|
@ -58,16 +81,172 @@ fun BlockAutoFillScreen(
|
|||
},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
visible = true,
|
||||
enter = scaleIn(),
|
||||
exit = scaleOut(),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = stringResource(id = R.string.add_item),
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Text(text = "Not yet implemented")
|
||||
when (val viewState = state.viewState) {
|
||||
is BlockAutoFillState.ViewState.Content -> {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 8.dp, horizontal = 20.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.auto_fill_will_not_be_offered_for_these_ur_is,
|
||||
),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items(viewState.blockedUris) { uri ->
|
||||
BlockAutoFillListItem(
|
||||
label = uri,
|
||||
onClick = {},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is BlockAutoFillState.ViewState.Empty -> {
|
||||
item {
|
||||
BlockAutoFillNoItems(
|
||||
addItemClickAction = {},
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No items view for the [BlockAutoFillScreen].
|
||||
*/
|
||||
@Composable
|
||||
private fun BlockAutoFillNoItems(
|
||||
addItemClickAction: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = R.drawable.ic_blocked_uri_background,
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.surfaceVariant,
|
||||
)
|
||||
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
id = R.drawable.ic_blocked_uri_foreground,
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.outline,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
text = stringResource(id = R.string.auto_fill_will_not_be_offered_for_these_ur_is),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
onClick = addItemClickAction,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.new_blocked_uri),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BlockAutoFillListItem(
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(color = MaterialTheme.colorScheme.primary),
|
||||
onClick = onClick,
|
||||
)
|
||||
.bottomDivider(paddingStart = 16.dp)
|
||||
.defaultMinSize(minHeight = 56.dp)
|
||||
.padding(end = 8.dp, top = 16.dp, bottom = 16.dp)
|
||||
.then(modifier),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
.weight(1f),
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_edit_alt),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
|
|||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -13,15 +15,32 @@ private const val KEY_STATE = "state"
|
|||
* View model for the blocked autofill URIs screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
class BlockAutoFillViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<BlockAutoFillState, BlockAutoFillEvent, BlockAutoFillAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: BlockAutoFillState(
|
||||
viewState = BlockAutoFillState.ViewState.Empty,
|
||||
),
|
||||
?: BlockAutoFillState(viewState = BlockAutoFillState.ViewState.Empty),
|
||||
) {
|
||||
init {
|
||||
updateContentWithUris(
|
||||
uris = settingsRepository.blockedAutofillUris,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateContentWithUris(uris: List<String>) {
|
||||
mutableStateFlow.update { currentState ->
|
||||
if (uris.isNotEmpty()) {
|
||||
currentState.copy(
|
||||
viewState = BlockAutoFillState.ViewState.Content(uris.map { it }),
|
||||
)
|
||||
} else {
|
||||
currentState.copy(
|
||||
viewState = BlockAutoFillState.ViewState.Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: BlockAutoFillAction) {
|
||||
when (action) {
|
||||
|
@ -51,6 +70,16 @@ data class BlockAutoFillState(
|
|||
*/
|
||||
sealed class ViewState : Parcelable {
|
||||
|
||||
/**
|
||||
* Represents a content state for the [BlockAutoFillScreen].
|
||||
*
|
||||
* @property blockedUris The list of blocked URIs.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Content(
|
||||
val blockedUris: List<String> = emptyList(),
|
||||
) : ViewState()
|
||||
|
||||
/**
|
||||
* Represents an empty content state for the [BlockAutoFillScreen].
|
||||
*/
|
||||
|
|
|
@ -113,6 +113,7 @@ private fun darkColorScheme(context: Context): ColorScheme =
|
|||
surfaceContainerHighest = R.color.dark_surface_container_highest.toColor(context),
|
||||
surfaceContainerLow = R.color.dark_surface_container_low.toColor(context),
|
||||
surfaceContainerLowest = R.color.dark_surface_container_lowest.toColor(context),
|
||||
surfaceVariant = R.color.dark_surface_variant.toColor(context),
|
||||
surfaceDim = R.color.dark_surface_dim.toColor(context),
|
||||
onSurface = R.color.dark_on_surface.toColor(context),
|
||||
onSurfaceVariant = R.color.dark_on_surface_variant.toColor(context),
|
||||
|
@ -149,6 +150,7 @@ private fun lightColorScheme(context: Context): ColorScheme =
|
|||
surfaceContainerHighest = R.color.surface_container_highest.toColor(context),
|
||||
surfaceContainerLow = R.color.surface_container_low.toColor(context),
|
||||
surfaceContainerLowest = R.color.surface_container_lowest.toColor(context),
|
||||
surfaceVariant = R.color.surface_variant.toColor(context),
|
||||
surfaceDim = R.color.surface_dim.toColor(context),
|
||||
onSurface = R.color.on_surface.toColor(context),
|
||||
onSurfaceVariant = R.color.on_surface_variant.toColor(context),
|
||||
|
|
10
app/src/main/res/drawable/ic_blocked_uri_background.xml
Normal file
10
app/src/main/res/drawable/ic_blocked_uri_background.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="159dp"
|
||||
android:height="159dp"
|
||||
android:viewportWidth="159"
|
||||
android:viewportHeight="159">
|
||||
<path
|
||||
android:pathData="M79.48,158.95C123.37,158.95 158.95,123.37 158.95,79.48C158.95,35.58 123.37,0 79.48,0C35.58,0 0,35.58 0,79.48C0,123.37 35.58,158.95 79.48,158.95Z"
|
||||
android:fillColor="#DDE3EA"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
31
app/src/main/res/drawable/ic_blocked_uri_foreground.xml
Normal file
31
app/src/main/res/drawable/ic_blocked_uri_foreground.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="159dp"
|
||||
android:height="159dp"
|
||||
android:viewportWidth="159"
|
||||
android:viewportHeight="159">
|
||||
<path
|
||||
android:pathData="M79.47,155.05C121.21,155.05 155.05,121.21 155.05,79.47C155.05,37.74 121.21,3.9 79.47,3.9C37.74,3.9 3.9,37.74 3.9,79.47C3.9,121.21 37.74,155.05 79.47,155.05ZM79.47,158.95C123.37,158.95 158.95,123.37 158.95,79.47C158.95,35.58 123.37,0 79.47,0C35.58,0 0,35.58 0,79.47C0,123.37 35.58,158.95 79.47,158.95Z"
|
||||
android:fillColor="#757780"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M101.75,64.02C103.68,39.69 93.87,18.9 76.45,4.23C76.04,3.88 75.99,3.26 76.33,2.85C76.68,2.44 77.3,2.39 77.71,2.73C95.57,17.79 105.68,39.18 103.7,64.17C100.42,105.42 64.15,136.18 22.69,132.89C22.15,132.85 21.75,132.38 21.79,131.84C21.84,131.31 22.31,130.91 22.84,130.95C63.24,134.15 98.57,104.18 101.75,64.02Z"
|
||||
android:fillColor="#757780"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M86.99,128.14C89,128.29 111.17,129.79 129.89,118.99C136.53,115.16 140.45,112.5 143.48,109.9C146.51,107.3 148.67,104.75 151.79,101.06L151.82,101.02C152.16,100.61 152.78,100.56 153.19,100.91C153.6,101.26 153.65,101.88 153.31,102.29L153.25,102.35C150.16,106.01 147.91,108.68 144.75,111.38C141.59,114.09 137.55,116.82 130.87,120.68C111.6,131.8 88.91,130.24 86.86,130.09C42.89,126.84 9.06,87.36 12.16,43.9C12.2,43.37 12.67,42.96 13.21,43C13.74,43.04 14.15,43.51 14.11,44.05C11.08,86.43 44.1,124.97 86.99,128.14Z"
|
||||
android:fillColor="#757780"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M48.59,55.04C27.23,71.21 14.31,94.73 13.28,117.81C13.25,118.36 12.8,118.78 12.26,118.75C11.73,118.73 11.32,118.28 11.34,117.73C12.41,93.98 25.68,69.93 47.44,53.46C83.37,26.27 133.01,29.77 156.71,62.17C157.03,62.61 156.94,63.22 156.51,63.55C156.08,63.87 155.48,63.79 155.16,63.35C132.23,32.01 83.86,28.34 48.59,55.04Z"
|
||||
android:fillColor="#757780"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M105.32,37.06C105.32,40.29 102.7,42.91 99.47,42.91C96.23,42.91 93.61,40.29 93.61,37.06C93.61,33.82 96.23,31.2 99.47,31.2C102.7,31.2 105.32,33.82 105.32,37.06Z"
|
||||
android:fillColor="#757780"/>
|
||||
<path
|
||||
android:pathData="M27.79,84.84C27.79,88.07 25.06,90.69 21.7,90.69C18.33,90.69 15.6,88.07 15.6,84.84C15.6,81.61 18.33,78.99 21.7,78.99C25.06,78.99 27.79,81.61 27.79,84.84Z"
|
||||
android:fillColor="#757780"/>
|
||||
<path
|
||||
android:pathData="M70.21,123.36C70.21,126.86 67.37,129.7 63.87,129.7C60.37,129.7 57.53,126.86 57.53,123.36C57.53,119.86 60.37,117.02 63.87,117.02C67.37,117.02 70.21,119.86 70.21,123.36Z"
|
||||
android:fillColor="#757780"/>
|
||||
</vector>
|
13
app/src/main/res/drawable/ic_edit_alt.xml
Normal file
13
app/src/main/res/drawable/ic_edit_alt.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M19.962,1.704C20.048,2.148 19.961,2.642 19.592,3.042C18.794,3.906 13.324,9.578 12.46,10.474C12.299,10.641 12.102,10.752 11.889,10.809L9.248,11.519C9.019,11.58 8.775,11.507 8.618,11.329C8.461,11.152 8.418,10.901 8.506,10.681L9.614,7.938C9.674,7.787 9.763,7.649 9.883,7.528C10.628,6.777 15.982,1.387 16.879,0.481C17.268,0.087 17.758,-0.042 18.222,0.025C18.664,0.088 19.057,0.321 19.349,0.607C19.642,0.893 19.879,1.277 19.962,1.704ZM18.045,1.262C17.945,1.248 17.86,1.267 17.767,1.36C16.87,2.266 11.528,7.645 10.773,8.406L10.135,9.986L11.564,9.602C12.45,8.684 17.889,3.044 18.673,2.194C18.734,2.128 18.757,2.054 18.735,1.941C18.709,1.809 18.622,1.645 18.475,1.501C18.328,1.357 18.168,1.28 18.045,1.262Z"
|
||||
android:fillColor="#1B1B1F"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M10,1.25H3.125C2.089,1.25 1.25,2.089 1.25,3.125V16.875C1.25,17.91 2.089,18.75 3.125,18.75H16.875C17.91,18.75 18.75,17.91 18.75,16.875V10C18.75,9.655 18.47,9.375 18.125,9.375C17.78,9.375 17.5,9.655 17.5,10V16.875C17.5,17.22 17.22,17.5 16.875,17.5H3.125C2.78,17.5 2.5,17.22 2.5,16.875V3.125C2.5,2.78 2.78,2.5 3.125,2.5H10C10.345,2.5 10.625,2.22 10.625,1.875C10.625,1.53 10.345,1.25 10,1.25Z"
|
||||
android:fillColor="#1B1B1F"/>
|
||||
</vector>
|
|
@ -33,6 +33,7 @@
|
|||
<color name="on_error_container">@color/red_410002</color>
|
||||
<color name="surface_dim">@color/grey_DBD9DD</color>
|
||||
<color name="surface">@color/grey_FBF8FD</color>
|
||||
<color name="surface_variant">@color/grey_DDE3EA</color>
|
||||
<color name="surface_bright">@color/grey_FBF8FD</color>
|
||||
<color name="surface_container_lowest">@color/white_FFFFFF</color>
|
||||
<color name="surface_container_low">@color/white_F5F3F7</color>
|
||||
|
@ -82,6 +83,7 @@
|
|||
<color name="dark_on_error_container">@color/red_FFDAD6</color>
|
||||
<color name="dark_surface_dim">@color/grey_131316</color>
|
||||
<color name="dark_surface">@color/grey_131316</color>
|
||||
<color name="dark_surface_variant">@color/grey_45464F</color>
|
||||
<color name="dark_surface_bright">@color/grey_39393C</color>
|
||||
<color name="dark_surface_container_lowest">@color/grey_0D0E11</color>
|
||||
<color name="dark_surface_container_low">@color/grey_1B1B1F</color>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<!-- Greys -->
|
||||
<color name="grey_FBF8FD">#FBF8FD</color>
|
||||
<color name="grey_DDE3EA">#DDE3EA</color>
|
||||
<color name="grey_DDE2F9">#DDE2F9</color>
|
||||
<color name="grey_EFEFF4">#EFEFF4</color>
|
||||
<color name="grey_F2F0F4">#F2F0F4</color>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class BlockAutoFillScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<BlockAutoFillEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<BlockAutoFillViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
BlockAutoFillScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(BlockAutoFillAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(BlockAutoFillEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Screen should display empty state view when in ViewState Empty`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Auto-fill will not be offered for these URIs.")
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("New blocked URI")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Screen should display content state view when in ViewState Content`() {
|
||||
mutableStateFlow.value = BlockAutoFillState(
|
||||
viewState = BlockAutoFillState.ViewState.Content(listOf("uri1", "uri2")),
|
||||
)
|
||||
|
||||
listOf("uri1", "uri2").forEach { uri ->
|
||||
composeTestRule
|
||||
.onNodeWithText(uri)
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: BlockAutoFillState = BlockAutoFillState(
|
||||
BlockAutoFillState.ViewState.Empty,
|
||||
)
|
|
@ -2,13 +2,47 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.blockautofill
|
|||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class BlockAutoFillViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { blockedAutofillUris } returns listOf("blockedUri")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `initial state with blocked URIs updates state to ViewState Content`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
val expectedState = BlockAutoFillState(
|
||||
viewState = BlockAutoFillState.ViewState.Content(
|
||||
blockedUris = listOf("blockedUri"),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `initial state with empty blocked URIs maintains state as ViewState Empty`() =
|
||||
runTest {
|
||||
every { settingsRepository.blockedAutofillUris } returns emptyList()
|
||||
val viewModel = createViewModel()
|
||||
val expectedState = BlockAutoFillState(
|
||||
viewState = BlockAutoFillState.ViewState.Empty,
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -22,6 +56,7 @@ class BlockAutoFillViewModelTest : BaseViewModelTest() {
|
|||
state: BlockAutoFillState? = DEFAULT_STATE,
|
||||
): BlockAutoFillViewModel = BlockAutoFillViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue