Merge branch 'main' into QA-1126b/adding-native-sanity-test

This commit is contained in:
ifernandezdiaz
2025-06-27 11:36:08 -03:00
8 changed files with 171 additions and 93 deletions

View File

@@ -199,31 +199,29 @@ private fun AutoFillScreenContent(
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(height = 8.dp))
if (state.showInlineAutofillOption) {
BitwardenSwitch(
label = stringResource(id = R.string.inline_autofill),
supportingText = stringResource(
id = R.string.use_inline_autofill_explanation_long,
),
isChecked = state.isUseInlineAutoFillEnabled,
onCheckedChange = autoFillHandlers.onUseInlineAutofillClick,
enabled = state.canInteractWithInlineAutofillToggle,
cardStyle = CardStyle.Full,
modifier = Modifier
.fillMaxWidth()
.testTag("InlineAutofillSwitch")
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(height = 8.dp))
AnimatedVisibility(visible = state.showInlineAutofill) {
Column {
FillStyleSelector(
selectedStyle = state.autofillStyle,
onStyleChange = autoFillHandlers.onAutofillStyleChange,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(height = 8.dp))
}
}
if (state.browserAutofillSettingsOptions.isNotEmpty()) {
BrowserAutofillSettingsCard(
options = state.browserAutofillSettingsOptions,
onOptionClicked = autoFillHandlers.onBrowserAutofillSelected,
enabled = state.isAutoFillServicesEnabled,
)
Spacer(modifier = Modifier.height(8.dp))
AnimatedVisibility(visible = state.showBrowserSettingOptions) {
Column {
BrowserAutofillSettingsCard(
options = state.browserAutofillSettingsOptions,
onOptionClicked = autoFillHandlers.onBrowserAutofillSelected,
enabled = state.isAutoFillServicesEnabled,
)
Spacer(modifier = Modifier.height(8.dp))
}
}
if (state.showPasskeyManagementRow) {
@@ -329,6 +327,26 @@ private fun AutoFillScreenContent(
}
}
@Composable
private fun FillStyleSelector(
selectedStyle: AutofillStyle,
onStyleChange: (AutofillStyle) -> Unit,
modifier: Modifier = Modifier,
resources: Resources = LocalContext.current.resources,
) {
BitwardenMultiSelectButton(
label = stringResource(id = R.string.display_autofill_suggestions),
supportingText = stringResource(id = R.string.use_inline_autofill_explanation_long),
options = AutofillStyle.entries.map { it.label() }.toImmutableList(),
selectedOption = selectedStyle.label(),
onOptionSelected = {
onStyleChange(AutofillStyle.entries.first { style -> style.label(resources) == it })
},
cardStyle = CardStyle.Full,
modifier = modifier.testTag(tag = "InlineAutofillSelector"),
)
}
@Composable
private fun AccessibilityAutofillSwitch(
isAccessibilityAutoFillEnabled: Boolean,

View File

@@ -8,6 +8,8 @@ import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.core.util.persistentListOfNotNull
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
@@ -52,7 +54,11 @@ class AutoFillViewModel @Inject constructor(
.value,
isAutoFillServicesEnabled = settingsRepository.isAutofillEnabledStateFlow.value,
isCopyTotpAutomaticallyEnabled = !settingsRepository.isAutoCopyTotpDisabled,
isUseInlineAutoFillEnabled = settingsRepository.isInlineAutofillEnabled,
autofillStyle = if (settingsRepository.isInlineAutofillEnabled) {
AutofillStyle.INLINE
} else {
AutofillStyle.POPUP
},
showInlineAutofillOption = isBuildVersionAtLeast(Build.VERSION_CODES.R),
showPasskeyManagementRow = isBuildVersionAtLeast(
Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
@@ -117,7 +123,7 @@ class AutoFillViewModel @Inject constructor(
is AutoFillAction.DefaultUriMatchTypeSelect -> handleDefaultUriMatchTypeSelect(action)
AutoFillAction.BlockAutoFillClick -> handleBlockAutoFillClick()
AutoFillAction.UseAccessibilityAutofillClick -> handleUseAccessibilityAutofillClick()
is AutoFillAction.UseInlineAutofillClick -> handleUseInlineAutofillClick(action)
is AutoFillAction.AutofillStyleSelected -> handleAutofillStyleSelected(action)
AutoFillAction.PasskeyManagementClick -> handlePasskeyManagementClick()
is AutoFillAction.Internal -> handleInternalAction(action)
AutoFillAction.AutofillActionCardCtaClick -> handleAutofillActionCardCtaClick()
@@ -228,9 +234,9 @@ class AutoFillViewModel @Inject constructor(
sendEvent(AutoFillEvent.NavigateToAccessibilitySettings)
}
private fun handleUseInlineAutofillClick(action: AutoFillAction.UseInlineAutofillClick) {
settingsRepository.isInlineAutofillEnabled = action.isEnabled
mutableStateFlow.update { it.copy(isUseInlineAutoFillEnabled = action.isEnabled) }
private fun handleAutofillStyleSelected(action: AutoFillAction.AutofillStyleSelected) {
settingsRepository.isInlineAutofillEnabled = action.style == AutofillStyle.INLINE
mutableStateFlow.update { it.copy(autofillStyle = action.style) }
}
private fun handlePasskeyManagementClick() {
@@ -281,7 +287,7 @@ data class AutoFillState(
val isAccessibilityAutofillEnabled: Boolean,
val isAutoFillServicesEnabled: Boolean,
val isCopyTotpAutomaticallyEnabled: Boolean,
val isUseInlineAutoFillEnabled: Boolean,
val autofillStyle: AutofillStyle,
val showInlineAutofillOption: Boolean,
val showPasskeyManagementRow: Boolean,
val defaultUriMatchType: UriMatchType,
@@ -290,13 +296,31 @@ data class AutoFillState(
val browserAutofillSettingsOptions: ImmutableList<BrowserAutofillSettingsOption>,
val isUserManagedPrivilegedAppsEnabled: Boolean,
) : Parcelable {
/**
* Whether or not the dropdown controlling the [autofillStyle] value is displayed.
*/
val showInlineAutofill: Boolean get() = isAutoFillServicesEnabled && showInlineAutofillOption
/**
* Whether or not the toggle controlling the [isUseInlineAutoFillEnabled] value can be
* interacted with.
* Whether or not the toggles for enabling 3rd-party autofill support should be displayed.
*/
val canInteractWithInlineAutofillToggle: Boolean
get() = isAutoFillServicesEnabled
val showBrowserSettingOptions: Boolean
get() = isAutoFillServicesEnabled && browserAutofillSettingsOptions.isNotEmpty()
}
/**
* The visual style of autofill that should be used.
*/
enum class AutofillStyle(val label: Text) {
/**
* Displays the autofill data in the keyboard.
*/
INLINE(label = R.string.autofill_suggestions_inline.asText()),
/**
* Displays the autofill data as a popup attached to the field you are filling.
*/
POPUP(label = R.string.autofill_suggestions_popup.asText()),
}
@Suppress("MaxLineLength")
@@ -425,8 +449,8 @@ sealed class AutoFillAction {
/**
* User clicked use inline autofill button.
*/
data class UseInlineAutofillClick(
val isEnabled: Boolean,
data class AutofillStyleSelected(
val style: AutofillStyle,
) : AutoFillAction()
/**

View File

@@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.autofill.model.browser.BrowserPackage
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.AutoFillAction
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.AutoFillViewModel
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.AutofillStyle
/**
* Handlers for the AutoFill screen.
@@ -14,7 +15,7 @@ class AutoFillHandlers(
val onAutofillActionCardClick: () -> Unit,
val onAutofillActionCardDismissClick: () -> Unit,
val onAutofillServicesClick: (isEnabled: Boolean) -> Unit,
val onUseInlineAutofillClick: (isEnabled: Boolean) -> Unit,
val onAutofillStyleChange: (style: AutofillStyle) -> Unit,
val onBrowserAutofillSelected: (browserPackage: BrowserPackage) -> Unit,
val onPasskeyManagementClick: () -> Unit,
val onPrivilegedAppsClick: () -> Unit,
@@ -47,12 +48,8 @@ class AutoFillHandlers(
),
)
},
onUseInlineAutofillClick = {
viewModel.trySendAction(
AutoFillAction.UseInlineAutofillClick(
it,
),
)
onAutofillStyleChange = {
viewModel.trySendAction(AutoFillAction.AutofillStyleSelected(it))
},
onBrowserAutofillSelected = {
viewModel.trySendAction(AutoFillAction.BrowserAutofillSelected(it))

View File

@@ -425,7 +425,9 @@ Scanning will happen automatically.</string>
<string name="privacy_policy">Privacy Policy</string>
<string name="passkey_management">Passkey management</string>
<string name="autofill_services">Autofill services</string>
<string name="inline_autofill">Use inline autofill</string>
<string name="display_autofill_suggestions">Display autofill suggestions</string>
<string name="autofill_suggestions_inline">Inline (shows in keyboard)</string>
<string name="autofill_suggestions_popup">Popup (shows over input field)</string>
<string name="accessibility">Use accessibility</string>
<string name="accessibility_description5">Required to use the Autofill Quick-Action Tile.</string>
<string name="personal_ownership_policy_in_effect">An organization policy is affecting your ownership options.</string>
@@ -660,8 +662,8 @@ Do you want to switch to this account?</string>
<string name="session_timeout_action">Session timeout action</string>
<string name="account_fingerprint_phrase">Account fingerprint phrase</string>
<string name="passkey_management_explanation_long">Use Bitwarden to save new passkeys and log in with passkeys stored in your vault.</string>
<string name="autofill_services_explanation_long">The Android Autofill Framework is used to assist in filling login information into other apps on your device.</string>
<string name="use_inline_autofill_explanation_long">Use inline autofill if your selected keyboard supports it. Otherwise, use the default overlay.</string>
<string name="autofill_services_explanation_long">Allow Bitwarden to use your saved login information to sign in to other apps on your device.</string>
<string name="use_inline_autofill_explanation_long">Choose how your autofill suggestions will appear when you sign in to other apps on your device.</string>
<string name="additional_options">Additional options</string>
<string name="continue_to_web_app">Continue to web app?</string>
<string name="continue_to_x">Continue to %1$s?</string>

View File

@@ -9,9 +9,8 @@
Authenticator App release variant: -->
<item>B06B54566AF2FBCC762700C8844B84EC410C230EACC3878FCF0248C0D9772A95</item>
<!-- These are the SHA-256 digest for one-off builds created from branches by the Github
Action of the Authenticator App release variant. Uncomment this to one off builds generated
from the Authenticator App repo. -->
<!-- <item>52f393fb529fbf2ab5bb018bf17bf0f829d2fbebce099915aba42deab5a2fbb8</item>-->
<!-- These are the SHA-256 digest for APK signing key of the Authenticator App release
variant. -->
<item>52f393fb529fbf2ab5bb018bf17bf0f829d2fbebce099915aba42deab5a2fbb8</item>
</string-array>
</resources>

View File

@@ -2,8 +2,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.filterToOne
@@ -233,60 +231,85 @@ class AutoFillScreenTest : BitwardenComposeTest() {
}
@Test
fun `on use inline auto fill toggle should send UseInlineAutofillClick`() {
fun `on inline autofill style selected should send AutofillStyleSelected`() {
mutableStateFlow.update {
it.copy(
isAutoFillServicesEnabled = true,
isUseInlineAutoFillEnabled = false,
autofillStyle = AutofillStyle.POPUP,
)
}
composeTestRule
.onNodeWithText("Use inline autofill")
.onNodeWithContentDescription(
label = "Popup (shows over input field). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.performScrollTo()
.performClick()
verify { viewModel.trySendAction(AutoFillAction.UseInlineAutofillClick(true)) }
composeTestRule
.onNodeWithText(text = "Inline (shows in keyboard)")
.assert(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(AutoFillAction.AutofillStyleSelected(AutofillStyle.INLINE))
}
}
@Test
fun `use inline autofill should be toggled on or off according to state`() {
composeTestRule
.onNodeWithText("Use inline autofill")
.performScrollTo()
.assertIsOff()
mutableStateFlow.update { it.copy(isUseInlineAutoFillEnabled = true) }
composeTestRule
.onNodeWithText("Use inline autofill")
.performScrollTo()
.assertIsOn()
}
@Test
fun `use inline autofill should be disabled or enabled according to state`() {
fun `autofill style should be display selection according to state`() {
mutableStateFlow.update {
it.copy(
isAutoFillServicesEnabled = true,
isUseInlineAutoFillEnabled = true,
autofillStyle = AutofillStyle.INLINE,
)
}
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.performScrollTo()
.assertIsDisplayed()
mutableStateFlow.update { it.copy(autofillStyle = AutofillStyle.POPUP) }
composeTestRule
.onNodeWithContentDescription(
label = "Popup (shows over input field). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.performScrollTo()
.assertIsDisplayed()
}
@Test
fun `use display autofill suggestions should be visible or enabled according to state`() {
mutableStateFlow.update {
it.copy(isAutoFillServicesEnabled = true)
}
composeTestRule
.onNodeWithText("Use inline autofill")
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.performScrollTo()
.assertIsOn()
.assertIsEnabled()
.assertIsDisplayed()
mutableStateFlow.update {
it.copy(
isAutoFillServicesEnabled = false,
isUseInlineAutoFillEnabled = true,
)
it.copy(isAutoFillServicesEnabled = false)
}
composeTestRule
.onNodeWithText("Use inline autofill")
.performScrollTo()
.assertIsOn()
.assertIsNotEnabled()
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.assertDoesNotExist()
}
@Suppress("MaxLineLength")
@@ -319,11 +342,18 @@ class AutoFillScreenTest : BitwardenComposeTest() {
@Test
fun `use inline autofill should be displayed according to state`() {
mutableStateFlow.update {
it.copy(showInlineAutofillOption = true)
it.copy(
isAutoFillServicesEnabled = true,
showInlineAutofillOption = true,
)
}
composeTestRule
.onNodeWithText(text = "Use inline autofill")
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.performScrollTo()
.assertIsDisplayed()
@@ -331,7 +361,13 @@ class AutoFillScreenTest : BitwardenComposeTest() {
it.copy(showInlineAutofillOption = false)
}
composeTestRule.onNodeWithText(text = "Use inline autofill").assertDoesNotExist()
composeTestRule
.onNodeWithContentDescription(
label = "Inline (shows in keyboard). Display autofill suggestions. " +
"Choose how your autofill suggestions will appear when you sign in " +
"to other apps on your device.",
)
.assertDoesNotExist()
}
@Test
@@ -521,6 +557,7 @@ class AutoFillScreenTest : BitwardenComposeTest() {
@Test
fun `BrowserAutofillSettingsCard is only displayed when there are options in the list`() {
mutableStateFlow.update { it.copy(isAutoFillServicesEnabled = true) }
val browserAutofillSupportingText =
"Improves login filling for supported websites on selected browsers. " +
"Once enabled, youll be directed to browser settings to enable " +
@@ -656,7 +693,7 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
isAccessibilityAutofillEnabled = false,
isAutoFillServicesEnabled = false,
isCopyTotpAutomaticallyEnabled = false,
isUseInlineAutoFillEnabled = false,
autofillStyle = AutofillStyle.INLINE,
showInlineAutofillOption = true,
showPasskeyManagementRow = true,
defaultUriMatchType = UriMatchType.DOMAIN,

View File

@@ -288,11 +288,12 @@ class AutoFillViewModelTest : BaseViewModelTest() {
}
@Test
fun `on UseInlineAutofillClick should update the state and save the new value to settings`() {
fun `on AutofillStyleSelected should update the state and save the new value to settings`() {
val viewModel = createViewModel()
viewModel.trySendAction(AutoFillAction.UseInlineAutofillClick(false))
val autofillStyle = AutofillStyle.POPUP
viewModel.trySendAction(AutoFillAction.AutofillStyleSelected(style = autofillStyle))
assertEquals(
DEFAULT_STATE.copy(isUseInlineAutoFillEnabled = false),
DEFAULT_STATE.copy(autofillStyle = autofillStyle),
viewModel.stateFlow.value,
)
verify { settingsRepository.isInlineAutofillEnabled = false }
@@ -466,7 +467,7 @@ private val DEFAULT_STATE: AutoFillState = AutoFillState(
isAccessibilityAutofillEnabled = false,
isAutoFillServicesEnabled = false,
isCopyTotpAutomaticallyEnabled = false,
isUseInlineAutoFillEnabled = true,
autofillStyle = AutofillStyle.INLINE,
showInlineAutofillOption = false,
showPasskeyManagementRow = true,
defaultUriMatchType = UriMatchType.DOMAIN,

View File

@@ -15,16 +15,16 @@ androdixAutofill = "1.3.0"
androidxBiometrics = "1.2.0-alpha05"
androidxBrowser = "1.8.0"
androidxCamera = "1.4.2"
androidxComposeBom = "2025.05.01"
androidxComposeBom = "2025.06.01"
androidxCore = "1.16.0"
androidxCredentials = "1.5.0"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.9.0"
androidxNavigation = "2.9.0"
androidxRoom = "2.7.1"
androidxRoom = "2.7.2"
androidxSecurityCrypto = "1.1.0-alpha06"
androidxSplash = "1.1.0-rc01"
androidxWork = "2.10.1"
androidxWork = "2.10.2"
bitwardenSdk = "1.0.0-20250623.141835-223"
crashlytics = "3.0.4"
detekt = "1.23.8"
@@ -48,7 +48,7 @@ ksp = "2.2.0-2.0.2"
mockk = "1.14.2"
okhttp = "4.12.0"
retrofitBom = "3.0.0"
robolectric = "4.14.1"
robolectric = "4.15.1"
sonarqube = "6.2.0.5505"
testng = "7.11.0"
timber = "5.0.1"