mirror of
https://github.com/bitwarden/android.git
synced 2024-11-24 02:15:53 +03:00
PM-12773 show autofill card when user skipped this step in onboarding (#4021)
This commit is contained in:
parent
a5cf4f49d7
commit
83652c9699
6 changed files with 237 additions and 16 deletions
|
@ -1,6 +1,8 @@
|
||||||
package com.x8bit.bitwarden.ui.platform.components.card
|
package com.x8bit.bitwarden.ui.platform.components.card
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -89,6 +91,12 @@ fun BitwardenActionCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default exit animation for [BitwardenActionCard] when using an animation wrapper like
|
||||||
|
* [AnimatedVisibility].
|
||||||
|
*/
|
||||||
|
fun actionCardExitAnimation() = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Top)
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -2,8 +2,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
@ -23,7 +21,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -44,6 +41,7 @@ import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
|
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
|
||||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
|
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
|
||||||
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard
|
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.card.actionCardExitAnimation
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||||
|
@ -183,7 +181,7 @@ fun AccountSecurityScreen(
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = state.shouldShowUnlockActionCard,
|
visible = state.shouldShowUnlockActionCard,
|
||||||
label = "UnlockActionCard",
|
label = "UnlockActionCard",
|
||||||
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Top),
|
exit = actionCardExitAnimation(),
|
||||||
) {
|
) {
|
||||||
BitwardenActionCard(
|
BitwardenActionCard(
|
||||||
cardTitle = stringResource(id = R.string.set_up_unlock),
|
cardTitle = stringResource(id = R.string.set_up_unlock),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
|
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
@ -31,7 +32,11 @@ import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenActionCard
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.card.actionCardExitAnimation
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
|
||||||
|
@ -125,6 +130,32 @@ fun AutoFillScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = state.showAutofillActionCard,
|
||||||
|
label = "AutofillActionCard",
|
||||||
|
exit = actionCardExitAnimation(),
|
||||||
|
) {
|
||||||
|
BitwardenActionCard(
|
||||||
|
cardTitle = stringResource(R.string.turn_on_autofill),
|
||||||
|
actionText = stringResource(R.string.get_started),
|
||||||
|
onActionClick = remember(viewModel) {
|
||||||
|
{
|
||||||
|
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismissClick = remember(viewModel) {
|
||||||
|
{
|
||||||
|
viewModel.trySendAction(AutoFillAction.DismissShowAutofillActionCard)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
NotificationBadge(notificationCount = 1)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.standardHorizontalMargin()
|
||||||
|
.padding(top = 12.dp, bottom = 16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
BitwardenListHeaderText(
|
BitwardenListHeaderText(
|
||||||
label = stringResource(id = R.string.autofill),
|
label = stringResource(id = R.string.autofill),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
|
@ -25,22 +26,30 @@ private const val KEY_STATE = "state"
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AutoFillViewModel @Inject constructor(
|
class AutoFillViewModel @Inject constructor(
|
||||||
|
authRepository: AuthRepository,
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
) : BaseViewModel<AutoFillState, AutoFillEvent, AutoFillAction>(
|
) : BaseViewModel<AutoFillState, AutoFillEvent, AutoFillAction>(
|
||||||
initialState = savedStateHandle[KEY_STATE]
|
initialState = savedStateHandle[KEY_STATE]
|
||||||
?: AutoFillState(
|
?: run {
|
||||||
isAskToAddLoginEnabled = !settingsRepository.isAutofillSavePromptDisabled,
|
val userId = requireNotNull(authRepository.userStateFlow.value).activeUserId
|
||||||
isAccessibilityAutofillEnabled = settingsRepository
|
AutoFillState(
|
||||||
.isAccessibilityEnabledStateFlow
|
isAskToAddLoginEnabled = !settingsRepository.isAutofillSavePromptDisabled,
|
||||||
.value,
|
isAccessibilityAutofillEnabled = settingsRepository
|
||||||
isAutoFillServicesEnabled = settingsRepository.isAutofillEnabledStateFlow.value,
|
.isAccessibilityEnabledStateFlow
|
||||||
isCopyTotpAutomaticallyEnabled = !settingsRepository.isAutoCopyTotpDisabled,
|
.value,
|
||||||
isUseInlineAutoFillEnabled = settingsRepository.isInlineAutofillEnabled,
|
isAutoFillServicesEnabled = settingsRepository.isAutofillEnabledStateFlow.value,
|
||||||
showInlineAutofillOption = !isBuildVersionBelow(Build.VERSION_CODES.R),
|
isCopyTotpAutomaticallyEnabled = !settingsRepository.isAutoCopyTotpDisabled,
|
||||||
showPasskeyManagementRow = !isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE),
|
isUseInlineAutoFillEnabled = settingsRepository.isInlineAutofillEnabled,
|
||||||
defaultUriMatchType = settingsRepository.defaultUriMatchType,
|
showInlineAutofillOption = !isBuildVersionBelow(Build.VERSION_CODES.R),
|
||||||
),
|
showPasskeyManagementRow = !isBuildVersionBelow(
|
||||||
|
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
|
||||||
|
),
|
||||||
|
defaultUriMatchType = settingsRepository.defaultUriMatchType,
|
||||||
|
showAutofillActionCard = false,
|
||||||
|
activeUserId = userId,
|
||||||
|
)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -64,6 +73,12 @@ class AutoFillViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
settingsRepository
|
||||||
|
.getShowAutofillBadgeFlow(userId = state.activeUserId)
|
||||||
|
.map { AutoFillAction.Internal.UpdateShowAutofillActionCard(it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleAction(action: AutoFillAction) = when (action) {
|
override fun handleAction(action: AutoFillAction) = when (action) {
|
||||||
|
@ -77,6 +92,8 @@ class AutoFillViewModel @Inject constructor(
|
||||||
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
|
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
|
||||||
AutoFillAction.PasskeyManagementClick -> handlePasskeyManagementClick()
|
AutoFillAction.PasskeyManagementClick -> handlePasskeyManagementClick()
|
||||||
is AutoFillAction.Internal -> handleInternalAction(action)
|
is AutoFillAction.Internal -> handleInternalAction(action)
|
||||||
|
AutoFillAction.AutoFillActionCardCtaClick -> handleAutoFillActionCardCtClick()
|
||||||
|
AutoFillAction.DismissShowAutofillActionCard -> handleDismissShowAutofillActionCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInternalAction(action: AutoFillAction.Internal) {
|
private fun handleInternalAction(action: AutoFillAction.Internal) {
|
||||||
|
@ -88,9 +105,28 @@ class AutoFillViewModel @Inject constructor(
|
||||||
is AutoFillAction.Internal.AutofillEnabledUpdateReceive -> {
|
is AutoFillAction.Internal.AutofillEnabledUpdateReceive -> {
|
||||||
handleAutofillEnabledUpdateReceive(action)
|
handleAutofillEnabledUpdateReceive(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is AutoFillAction.Internal.UpdateShowAutofillActionCard -> {
|
||||||
|
handleUpdateShowAutofillActionCard(action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleDismissShowAutofillActionCard() {
|
||||||
|
dismissShowAutofillActionCard()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAutoFillActionCardCtClick() {
|
||||||
|
dismissShowAutofillActionCard()
|
||||||
|
// TODO PM-13068 navigate to auto fill setup screen
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleUpdateShowAutofillActionCard(
|
||||||
|
action: AutoFillAction.Internal.UpdateShowAutofillActionCard,
|
||||||
|
) {
|
||||||
|
mutableStateFlow.update { it.copy(showAutofillActionCard = action.showAutofillActionCard) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAskToAddLoginClick(action: AutoFillAction.AskToAddLoginClick) {
|
private fun handleAskToAddLoginClick(action: AutoFillAction.AskToAddLoginClick) {
|
||||||
settingsRepository.isAutofillSavePromptDisabled = !action.isEnabled
|
settingsRepository.isAutofillSavePromptDisabled = !action.isEnabled
|
||||||
mutableStateFlow.update { it.copy(isAskToAddLoginEnabled = action.isEnabled) }
|
mutableStateFlow.update { it.copy(isAskToAddLoginEnabled = action.isEnabled) }
|
||||||
|
@ -102,6 +138,7 @@ class AutoFillViewModel @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
settingsRepository.disableAutofill()
|
settingsRepository.disableAutofill()
|
||||||
}
|
}
|
||||||
|
dismissShowAutofillActionCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBackClick() {
|
private fun handleBackClick() {
|
||||||
|
@ -154,6 +191,14 @@ class AutoFillViewModel @Inject constructor(
|
||||||
private fun handleBlockAutoFillClick() {
|
private fun handleBlockAutoFillClick() {
|
||||||
sendEvent(AutoFillEvent.NavigateToBlockAutoFill)
|
sendEvent(AutoFillEvent.NavigateToBlockAutoFill)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dismissShowAutofillActionCard() {
|
||||||
|
if (!state.showAutofillActionCard) return
|
||||||
|
settingsRepository.storeShowAutoFillSettingBadge(
|
||||||
|
userId = state.activeUserId,
|
||||||
|
showBadge = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,6 +214,8 @@ data class AutoFillState(
|
||||||
val showInlineAutofillOption: Boolean,
|
val showInlineAutofillOption: Boolean,
|
||||||
val showPasskeyManagementRow: Boolean,
|
val showPasskeyManagementRow: Boolean,
|
||||||
val defaultUriMatchType: UriMatchType,
|
val defaultUriMatchType: UriMatchType,
|
||||||
|
val showAutofillActionCard: Boolean,
|
||||||
|
val activeUserId: String,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,6 +322,16 @@ sealed class AutoFillAction {
|
||||||
*/
|
*/
|
||||||
data object PasskeyManagementClick : AutoFillAction()
|
data object PasskeyManagementClick : AutoFillAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User has clicked the "X" to dismiss the autofill action card.
|
||||||
|
*/
|
||||||
|
data object DismissShowAutofillActionCard : AutoFillAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User has clicked the CTA on the autofill action card.
|
||||||
|
*/
|
||||||
|
data object AutoFillActionCardCtaClick : AutoFillAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal actions.
|
* Internal actions.
|
||||||
*/
|
*/
|
||||||
|
@ -292,5 +349,10 @@ sealed class AutoFillAction {
|
||||||
data class AutofillEnabledUpdateReceive(
|
data class AutofillEnabledUpdateReceive(
|
||||||
val isAutofillEnabled: Boolean,
|
val isAutofillEnabled: Boolean,
|
||||||
) : Internal()
|
) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An update for changes in the [showAutofillActionCard] value from the settings repository.
|
||||||
|
*/
|
||||||
|
data class UpdateShowAutofillActionCard(val showAutofillActionCard: Boolean) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,6 +462,42 @@ class AutoFillScreenTest : BaseComposeTest() {
|
||||||
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToBlockAutoFill)
|
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToBlockAutoFill)
|
||||||
assertTrue(onNavigateToBlockAutoFillScreenCalled)
|
assertTrue(onNavigateToBlockAutoFillScreenCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `autofill action card should show when state is true and hide when false`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Get started")
|
||||||
|
.assertDoesNotExist()
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE.copy(showAutofillActionCard = true) }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Get started")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE.copy(showAutofillActionCard = false) }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Get started")
|
||||||
|
.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when autofill card is visible clicking the cta button should send correct action`() {
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE.copy(showAutofillActionCard = true) }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Get started")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify { viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when autofill action card is visible clicking dismissing should send correct action`() {
|
||||||
|
mutableStateFlow.update { DEFAULT_STATE.copy(showAutofillActionCard = true) }
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription("Close")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
verify { viewModel.trySendAction(AutoFillAction.DismissShowAutofillActionCard) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||||
|
@ -473,4 +509,6 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||||
showInlineAutofillOption = true,
|
showInlineAutofillOption = true,
|
||||||
showPasskeyManagementRow = true,
|
showPasskeyManagementRow = true,
|
||||||
defaultUriMatchType = UriMatchType.DOMAIN,
|
defaultUriMatchType = UriMatchType.DOMAIN,
|
||||||
|
showAutofillActionCard = false,
|
||||||
|
activeUserId = "activeUserId",
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
|
@ -15,6 +16,7 @@ import io.mockk.runs
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
@ -25,6 +27,11 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(false)
|
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(false)
|
||||||
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(false)
|
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
|
private val authRepository: AuthRepository = mockk {
|
||||||
|
every { userStateFlow.value?.activeUserId } returns "activeUserId"
|
||||||
|
}
|
||||||
|
private val mutableShowAutofillActionCardFlow = MutableStateFlow(false)
|
||||||
private val settingsRepository: SettingsRepository = mockk {
|
private val settingsRepository: SettingsRepository = mockk {
|
||||||
every { isInlineAutofillEnabled } returns true
|
every { isInlineAutofillEnabled } returns true
|
||||||
every { isInlineAutofillEnabled = any() } just runs
|
every { isInlineAutofillEnabled = any() } just runs
|
||||||
|
@ -37,6 +44,8 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||||
every { isAccessibilityEnabledStateFlow } returns mutableIsAccessibilityEnabledStateFlow
|
every { isAccessibilityEnabledStateFlow } returns mutableIsAccessibilityEnabledStateFlow
|
||||||
every { isAutofillEnabledStateFlow } returns mutableIsAutofillEnabledStateFlow
|
every { isAutofillEnabledStateFlow } returns mutableIsAutofillEnabledStateFlow
|
||||||
every { disableAutofill() } just runs
|
every { disableAutofill() } just runs
|
||||||
|
every { getShowAutofillBadgeFlow(any()) } returns mutableShowAutofillActionCardFlow
|
||||||
|
every { storeShowAutoFillSettingBadge(any(), any()) } just runs
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -196,6 +205,40 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on AutoFillServicesClick should update show autofill in repository if card shown`() {
|
||||||
|
mutableShowAutofillActionCardFlow.update { true }
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(showAutofillActionCard = true),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true))
|
||||||
|
verify(exactly = 1) {
|
||||||
|
settingsRepository.storeShowAutoFillSettingBadge(
|
||||||
|
DEFAULT_STATE.activeUserId,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on AutoFillServicesClick should not update show autofill in repository if card not shown`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(showAutofillActionCard = false),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true))
|
||||||
|
verify(exactly = 0) {
|
||||||
|
settingsRepository.storeShowAutoFillSettingBadge(
|
||||||
|
DEFAULT_STATE.activeUserId,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
@ -266,11 +309,50 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when showAutofillBadgeFlow updates value, should update state`() = runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(DEFAULT_STATE, awaitItem())
|
||||||
|
mutableShowAutofillActionCardFlow.emit(true)
|
||||||
|
assertEquals(DEFAULT_STATE.copy(showAutofillActionCard = true), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when AutoFillActionCardCtaClick action is sent should update show autofill in repository`() {
|
||||||
|
mutableShowAutofillActionCardFlow.update { true }
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
|
||||||
|
verify {
|
||||||
|
settingsRepository.storeShowAutoFillSettingBadge(
|
||||||
|
DEFAULT_STATE.activeUserId,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when DismissShowAutofillActionCard action is sent should update show autofill in repository`() {
|
||||||
|
mutableShowAutofillActionCardFlow.update { true }
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.trySendAction(AutoFillAction.DismissShowAutofillActionCard)
|
||||||
|
verify {
|
||||||
|
settingsRepository.storeShowAutoFillSettingBadge(
|
||||||
|
DEFAULT_STATE.activeUserId,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
state: AutoFillState? = DEFAULT_STATE,
|
state: AutoFillState? = DEFAULT_STATE,
|
||||||
): AutoFillViewModel = AutoFillViewModel(
|
): AutoFillViewModel = AutoFillViewModel(
|
||||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
|
authRepository = authRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,4 +365,6 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||||
showInlineAutofillOption = false,
|
showInlineAutofillOption = false,
|
||||||
showPasskeyManagementRow = true,
|
showPasskeyManagementRow = true,
|
||||||
defaultUriMatchType = UriMatchType.DOMAIN,
|
defaultUriMatchType = UriMatchType.DOMAIN,
|
||||||
|
showAutofillActionCard = false,
|
||||||
|
activeUserId = "activeUserId",
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue