mirror of
https://github.com/bitwarden/android.git
synced 2026-03-21 22:00:42 -05:00
PM-18315 add UI when 3pa is available for each chrome channel which s… (#4758)
This commit is contained in:
@@ -47,6 +47,7 @@ import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
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.feature.settings.autofill.chrome.ChromeAutofillSettingsCard
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.util.displayLabel
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -95,6 +96,11 @@ fun AutoFillScreen(
|
||||
}
|
||||
|
||||
AutoFillEvent.NavigateToSetupAutofill -> onNavigateToSetupAutofill()
|
||||
is AutoFillEvent.NavigateToChromeAutofillSettings -> {
|
||||
intentManager.startChromeAutofillSettingsActivity(
|
||||
releaseChannel = event.releaseChannel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +145,7 @@ fun AutoFillScreen(
|
||||
actionText = stringResource(R.string.get_started),
|
||||
onActionClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
|
||||
viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick)
|
||||
}
|
||||
},
|
||||
onDismissClick = remember(viewModel) {
|
||||
@@ -196,6 +202,20 @@ fun AutoFillScreen(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
|
||||
if (state.chromeAutofillSettingsOptions.isNotEmpty()) {
|
||||
ChromeAutofillSettingsCard(
|
||||
options = state.chromeAutofillSettingsOptions,
|
||||
onOptionClicked = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(AutoFillAction.ChromeAutofillSelected(it))
|
||||
}
|
||||
},
|
||||
enabled = state.isAutoFillServicesEnabled,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
if (state.showPasskeyManagementRow) {
|
||||
BitwardenExternalLinkRow(
|
||||
text = stringResource(id = R.string.passkey_management),
|
||||
|
||||
@@ -5,13 +5,20 @@ import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -28,6 +35,7 @@ private const val KEY_STATE = "state"
|
||||
@HiltViewModel
|
||||
class AutoFillViewModel @Inject constructor(
|
||||
authRepository: AuthRepository,
|
||||
chromeThirdPartyAutofillEnabledManager: ChromeThirdPartyAutofillEnabledManager,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val firstTimeActionManager: FirstTimeActionManager,
|
||||
@@ -50,6 +58,7 @@ class AutoFillViewModel @Inject constructor(
|
||||
defaultUriMatchType = settingsRepository.defaultUriMatchType,
|
||||
showAutofillActionCard = false,
|
||||
activeUserId = userId,
|
||||
chromeAutofillSettingsOptions = persistentListOf(),
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -81,6 +90,12 @@ class AutoFillViewModel @Inject constructor(
|
||||
.map { AutoFillAction.Internal.UpdateShowAutofillActionCard(it.showSetupAutofillCard) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
chromeThirdPartyAutofillEnabledManager
|
||||
.chromeThirdPartyAutofillStatusFlow
|
||||
.map { AutoFillAction.Internal.ChromeAutofillStatusReceive(status = it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: AutoFillAction) = when (action) {
|
||||
@@ -94,8 +109,9 @@ class AutoFillViewModel @Inject constructor(
|
||||
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
|
||||
AutoFillAction.PasskeyManagementClick -> handlePasskeyManagementClick()
|
||||
is AutoFillAction.Internal -> handleInternalAction(action)
|
||||
AutoFillAction.AutoFillActionCardCtaClick -> handleAutoFillActionCardCtClick()
|
||||
AutoFillAction.AutofillActionCardCtaClick -> handleAutofillActionCardCtaClick()
|
||||
AutoFillAction.DismissShowAutofillActionCard -> handleDismissShowAutofillActionCard()
|
||||
is AutoFillAction.ChromeAutofillSelected -> handleChromeAutofillSelected(action)
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: AutoFillAction.Internal) {
|
||||
@@ -111,14 +127,34 @@ class AutoFillViewModel @Inject constructor(
|
||||
is AutoFillAction.Internal.UpdateShowAutofillActionCard -> {
|
||||
handleUpdateShowAutofillActionCard(action)
|
||||
}
|
||||
|
||||
is AutoFillAction.Internal.ChromeAutofillStatusReceive -> {
|
||||
handleChromeAutofillStatusReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChromeAutofillStatusReceive(
|
||||
action: AutoFillAction.Internal.ChromeAutofillStatusReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
chromeAutofillSettingsOptions = action
|
||||
.status
|
||||
.toChromeAutoFillSettingsOptions(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChromeAutofillSelected(action: AutoFillAction.ChromeAutofillSelected) {
|
||||
sendEvent(AutoFillEvent.NavigateToChromeAutofillSettings(action.releaseChannel))
|
||||
}
|
||||
|
||||
private fun handleDismissShowAutofillActionCard() {
|
||||
dismissShowAutofillActionCard()
|
||||
}
|
||||
|
||||
private fun handleAutoFillActionCardCtClick() {
|
||||
private fun handleAutofillActionCardCtaClick() {
|
||||
sendEvent(AutoFillEvent.NavigateToSetupAutofill)
|
||||
}
|
||||
|
||||
@@ -216,6 +252,7 @@ data class AutoFillState(
|
||||
val defaultUriMatchType: UriMatchType,
|
||||
val showAutofillActionCard: Boolean,
|
||||
val activeUserId: String,
|
||||
val chromeAutofillSettingsOptions: ImmutableList<ChromeAutofillSettingsOption>,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
@@ -226,6 +263,19 @@ data class AutoFillState(
|
||||
get() = isAutoFillServicesEnabled
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun ChromeThirdPartyAutofillStatus.toChromeAutoFillSettingsOptions(): ImmutableList<ChromeAutofillSettingsOption> =
|
||||
persistentListOfNotNull(
|
||||
ChromeAutofillSettingsOption.Stable(
|
||||
enabled = this.stableStatusData.isThirdPartyEnabled,
|
||||
)
|
||||
.takeIf { this.stableStatusData.isAvailable },
|
||||
ChromeAutofillSettingsOption.Beta(
|
||||
enabled = this.betaChannelStatusData.isThirdPartyEnabled,
|
||||
)
|
||||
.takeIf { this.betaChannelStatusData.isAvailable },
|
||||
)
|
||||
|
||||
/**
|
||||
* Models events for the auto-fill screen.
|
||||
*/
|
||||
@@ -262,6 +312,13 @@ sealed class AutoFillEvent {
|
||||
val text: Text,
|
||||
) : AutoFillEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the Autofill settings of the specified [releaseChannel].
|
||||
*/
|
||||
data class NavigateToChromeAutofillSettings(
|
||||
val releaseChannel: ChromeReleaseChannel,
|
||||
) : AutoFillEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the setup autofill screen.
|
||||
*/
|
||||
@@ -335,7 +392,12 @@ sealed class AutoFillAction {
|
||||
/**
|
||||
* User has clicked the CTA on the autofill action card.
|
||||
*/
|
||||
data object AutoFillActionCardCtaClick : AutoFillAction()
|
||||
data object AutofillActionCardCtaClick : AutoFillAction()
|
||||
|
||||
/**
|
||||
* User has clicked one of the chrome autofill options.
|
||||
*/
|
||||
data class ChromeAutofillSelected(val releaseChannel: ChromeReleaseChannel) : AutoFillAction()
|
||||
|
||||
/**
|
||||
* Internal actions.
|
||||
@@ -359,5 +421,12 @@ sealed class AutoFillAction {
|
||||
* An update for changes in the [showAutofillActionCard] value from the settings repository.
|
||||
*/
|
||||
data class UpdateShowAutofillActionCard(val showAutofillActionCard: Boolean) : Internal()
|
||||
|
||||
/**
|
||||
* Received updated [ChromeThirdPartyAutofillStatus] data.
|
||||
*/
|
||||
data class ChromeAutofillStatusReceive(
|
||||
val status: ChromeThirdPartyAutofillStatus,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Card for displaying a list of [ChromeAutofillSettingsOption]s and whether they are
|
||||
* currently enabled.
|
||||
*
|
||||
* @param options List of data to display in the card, if the list is empty nothing will be drawn.
|
||||
* @param onOptionClicked Lambda that is invoked when an option row is clicked and passes back the
|
||||
* [ChromeReleaseChannel] for that option.
|
||||
* @param enabled Whether to show the switches for each option as enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun ChromeAutofillSettingsCard(
|
||||
options: ImmutableList<ChromeAutofillSettingsOption>,
|
||||
onOptionClicked: (ChromeReleaseChannel) -> Unit,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (options.isEmpty()) return
|
||||
Column(modifier = modifier) {
|
||||
options.forEachIndexed { index, option ->
|
||||
BitwardenSwitch(
|
||||
label = option.optionText(),
|
||||
isChecked = option.isEnabled,
|
||||
onCheckedChange = {
|
||||
onOptionClicked(option.chromeReleaseChannel)
|
||||
},
|
||||
cardStyle = if (index == 0) {
|
||||
CardStyle.Top(
|
||||
dividerPadding = 16.dp,
|
||||
)
|
||||
} else {
|
||||
CardStyle.Middle(
|
||||
dividerPadding = 16.dp,
|
||||
)
|
||||
},
|
||||
enabled = enabled,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.improves_login_filling_for_supported_websites_on_chrome,
|
||||
),
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
color = BitwardenTheme.colorScheme.text.secondary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.cardStyle(
|
||||
cardStyle = CardStyle.Bottom,
|
||||
paddingHorizontal = 16.dp,
|
||||
)
|
||||
.defaultMinSize(minHeight = 48.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ChromeAutofillSettingsCard_preview() {
|
||||
BitwardenTheme {
|
||||
ChromeAutofillSettingsCard(
|
||||
options = persistentListOf(
|
||||
ChromeAutofillSettingsOption.Stable(enabled = false),
|
||||
ChromeAutofillSettingsOption.Beta(enabled = true),
|
||||
),
|
||||
enabled = true,
|
||||
onOptionClicked = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Models an option for an option for each type of supported version of Chrome to enable
|
||||
* third party autofill. Each [ChromeAutofillSettingsOption] contains the associated
|
||||
* [ChromeReleaseChannel], the [optionText] to display in any UI component, and
|
||||
* whether or not the third party autofill [isEnabled].
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class ChromeAutofillSettingsOption(val isEnabled: Boolean) : Parcelable {
|
||||
abstract val chromeReleaseChannel: ChromeReleaseChannel
|
||||
abstract val optionText: Text
|
||||
|
||||
/**
|
||||
* Represents the stable Chrome release channel.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Stable(val enabled: Boolean) : ChromeAutofillSettingsOption(isEnabled = enabled) {
|
||||
override val chromeReleaseChannel: ChromeReleaseChannel
|
||||
get() = ChromeReleaseChannel.STABLE
|
||||
override val optionText: Text
|
||||
get() = R.string.use_chrome_autofill_integration.asText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the beta Chrome release channel.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Beta(val enabled: Boolean) : ChromeAutofillSettingsOption(isEnabled = enabled) {
|
||||
override val chromeReleaseChannel: ChromeReleaseChannel
|
||||
get() = ChromeReleaseChannel.BETA
|
||||
override val optionText: Text
|
||||
get() = R.string.use_chrome_beta_autofill_integration.asText()
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,11 @@ interface IntentManager {
|
||||
*/
|
||||
fun startCredentialManagerSettings(context: Context)
|
||||
|
||||
/**
|
||||
* Starts the Chrome autofill settings activity for the provided [ChromeReleaseChannel].
|
||||
*/
|
||||
fun startChromeAutofillSettingsActivity(releaseChannel: ChromeReleaseChannel): Boolean
|
||||
|
||||
/**
|
||||
* Start an activity to view the given [uri] in an external browser.
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.credentials.CredentialManager
|
||||
import com.x8bit.bitwarden.BuildConfig
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
@@ -133,6 +134,22 @@ class IntentManagerImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun startChromeAutofillSettingsActivity(
|
||||
releaseChannel: ChromeReleaseChannel,
|
||||
): Boolean = try {
|
||||
val intent = Intent(Intent.ACTION_APPLICATION_PREFERENCES)
|
||||
.apply {
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addCategory(Intent.CATEGORY_APP_BROWSER)
|
||||
addCategory(Intent.CATEGORY_PREFERENCE)
|
||||
setPackage(releaseChannel.packageName)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
|
||||
override fun launchUri(uri: Uri) {
|
||||
if (uri.scheme.equals(other = "androidapp", ignoreCase = true)) {
|
||||
val packageName = uri.toString().removePrefix(prefix = "androidapp://")
|
||||
|
||||
@@ -1221,4 +1221,7 @@ Do you want to switch to this account?</string>
|
||||
<string name="passkey_operation_failed_because_user_verification_attempts_exceeded">Passkey operation failed because user verification attempts exceeded.</string>
|
||||
<string name="passkey_operation_failed_because_no_item_was_selected">Passkey operation failed because no item was selected.</string>
|
||||
<string name="self_host_server_url">Self-host server URL</string>
|
||||
<string name="use_chrome_autofill_integration">Use Chrome autofill integration</string>
|
||||
<string name="use_chrome_beta_autofill_integration">Use Chrome autofill integration (Beta)</string>
|
||||
<string name="improves_login_filling_for_supported_websites_on_chrome">Improves login filling for supported websites on Chrome. Once enabled, you’ll be directed to Chrome settings to enable third-party autofill.</string>
|
||||
</resources>
|
||||
|
||||
@@ -14,9 +14,11 @@ import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import io.mockk.every
|
||||
@@ -24,6 +26,7 @@ import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -47,6 +50,7 @@ class AutoFillScreenTest : BaseComposeTest() {
|
||||
every { startSystemAutofillSettingsActivity() } answers { isSystemSettingsRequestSuccess }
|
||||
every { startCredentialManagerSettings(any()) } just runs
|
||||
every { startSystemAccessibilitySettingsActivity() } just runs
|
||||
every { startChromeAutofillSettingsActivity(any()) } returns true
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -487,7 +491,7 @@ class AutoFillScreenTest : BaseComposeTest() {
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify { viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick) }
|
||||
verify { viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -505,6 +509,83 @@ class AutoFillScreenTest : BaseComposeTest() {
|
||||
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToSetupAutofill)
|
||||
assertTrue(onNavigateToSetupAutoFillScreenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ChromeAutofillSettingsCard is only displayed when there are options in the list`() {
|
||||
val chromeAutofillSupportingText =
|
||||
"Improves login filling for supported websites on Chrome. " +
|
||||
"Once enabled, you’ll be directed to Chrome settings to enable " +
|
||||
"third-party autofill."
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(chromeAutofillSupportingText)
|
||||
.assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
chromeAutofillSettingsOptions = persistentListOf(
|
||||
ChromeAutofillSettingsOption.Stable(enabled = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(chromeAutofillSupportingText)
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Chrome autofill options are clicked the correct action is sent`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
isAutoFillServicesEnabled = true,
|
||||
chromeAutofillSettingsOptions = persistentListOf(
|
||||
ChromeAutofillSettingsOption.Stable(enabled = true),
|
||||
ChromeAutofillSettingsOption.Beta(enabled = false),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Use Chrome autofill integration")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Use Chrome autofill integration (Beta)")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(
|
||||
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.BETA),
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.STABLE),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when NavigateToChromeAutofillSettings events are sent they invoke the intent manager with the correct release channel`() {
|
||||
mutableEventFlow.tryEmit(
|
||||
AutoFillEvent.NavigateToChromeAutofillSettings(
|
||||
ChromeReleaseChannel.STABLE,
|
||||
),
|
||||
)
|
||||
mutableEventFlow.tryEmit(
|
||||
AutoFillEvent.NavigateToChromeAutofillSettings(
|
||||
ChromeReleaseChannel.BETA,
|
||||
),
|
||||
)
|
||||
|
||||
verify(exactly = 1) {
|
||||
intentManager.startChromeAutofillSettingsActivity(ChromeReleaseChannel.BETA)
|
||||
intentManager.startChromeAutofillSettingsActivity(ChromeReleaseChannel.STABLE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||
@@ -518,4 +599,5 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||
defaultUriMatchType = UriMatchType.DOMAIN,
|
||||
showAutofillActionCard = false,
|
||||
activeUserId = "activeUserId",
|
||||
chromeAutofillSettingsOptions = persistentListOf(),
|
||||
)
|
||||
|
||||
@@ -4,12 +4,17 @@ import android.os.Build
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.manager.chrome.ChromeThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeReleaseChannel
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutoFillData
|
||||
import com.x8bit.bitwarden.data.autofill.model.chrome.ChromeThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.chrome.model.ChromeAutofillSettingsOption
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -17,6 +22,7 @@ import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -40,6 +46,12 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||
every { storeShowAutoFillSettingBadge(any()) } just runs
|
||||
}
|
||||
|
||||
private val mutableChromeAutofillStatusFlow = MutableStateFlow(DEFAULT_AUTOFILL_STATUS)
|
||||
private val chromeThirdPartyAutofillEnabledManager =
|
||||
mockk<ChromeThirdPartyAutofillEnabledManager> {
|
||||
every { chromeThirdPartyAutofillStatusFlow } returns mutableChromeAutofillStatusFlow
|
||||
}
|
||||
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { isInlineAutofillEnabled } returns true
|
||||
every { isInlineAutofillEnabled = any() } just runs
|
||||
@@ -330,7 +342,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||
mutableFirstTimeStateFlow.update { it.copy(showSetupAutofillCard = true) }
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.AutoFillActionCardCtaClick)
|
||||
viewModel.trySendAction(AutoFillAction.AutofillActionCardCtaClick)
|
||||
assertEquals(
|
||||
AutoFillEvent.NavigateToSetupAutofill,
|
||||
awaitItem(),
|
||||
@@ -354,6 +366,55 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when ChromeAutofillStatusReceive with updated information is processed state updates as expected`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
mutableChromeAutofillStatusFlow.update {
|
||||
it.copy(
|
||||
stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA.copy(isAvailable = true),
|
||||
)
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
chromeAutofillSettingsOptions = persistentListOf(
|
||||
ChromeAutofillSettingsOption.Stable(enabled = false),
|
||||
),
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when ChromeAutofillSelected action is handled the correct NavigateToChromeAutofillSettings event is sent`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(
|
||||
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.STABLE),
|
||||
)
|
||||
assertEquals(
|
||||
AutoFillEvent.NavigateToChromeAutofillSettings(ChromeReleaseChannel.STABLE),
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
AutoFillAction.ChromeAutofillSelected(ChromeReleaseChannel.BETA),
|
||||
)
|
||||
assertEquals(
|
||||
AutoFillEvent.NavigateToChromeAutofillSettings(ChromeReleaseChannel.BETA),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: AutoFillState? = DEFAULT_STATE,
|
||||
): AutoFillViewModel = AutoFillViewModel(
|
||||
@@ -361,6 +422,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
|
||||
settingsRepository = settingsRepository,
|
||||
authRepository = authRepository,
|
||||
firstTimeActionManager = firstTimeActionManager,
|
||||
chromeThirdPartyAutofillEnabledManager = chromeThirdPartyAutofillEnabledManager,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -375,4 +437,15 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||
defaultUriMatchType = UriMatchType.DOMAIN,
|
||||
showAutofillActionCard = false,
|
||||
activeUserId = "activeUserId",
|
||||
chromeAutofillSettingsOptions = persistentListOf(),
|
||||
)
|
||||
|
||||
private val DEFAULT_CHROME_AUTOFILL_DATA = ChromeThirdPartyAutoFillData(
|
||||
isAvailable = false,
|
||||
isThirdPartyEnabled = false,
|
||||
)
|
||||
|
||||
private val DEFAULT_AUTOFILL_STATUS = ChromeThirdPartyAutofillStatus(
|
||||
stableStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
|
||||
betaChannelStatusData = DEFAULT_CHROME_AUTOFILL_DATA,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user