mirror of
https://github.com/bitwarden/android.git
synced 2026-06-14 09:24:30 -05:00
Compare commits
2 Commits
crowdin-pu
...
v2026.5.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b283af983 | ||
|
|
d0880d31f2 |
@@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
@@ -25,6 +26,8 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.bitwarden.ui.platform.util.setHorizonOSAppLayout
|
||||
import com.bitwarden.ui.platform.util.setupEdgeToEdge
|
||||
@@ -93,6 +96,7 @@ class MainActivity : AppCompatActivity() {
|
||||
mainViewModel.trySendAction(MainAction.PremiumCheckoutResult(it))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
intent = intent.validate()
|
||||
var shouldShowSplashScreen = true
|
||||
@@ -132,6 +136,14 @@ class MainActivity : AppCompatActivity() {
|
||||
theme = state.theme,
|
||||
dynamicColor = state.isDynamicColorsEnabled,
|
||||
) {
|
||||
MainActivityDialogs(
|
||||
dialogState = state.dialogState,
|
||||
onAccessibilityDisclaimerDismiss = {
|
||||
mainViewModel.trySendAction(
|
||||
MainAction.DismissAccessibilityDisclaimerDialog,
|
||||
)
|
||||
},
|
||||
)
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = RootNavigationRoute,
|
||||
@@ -223,6 +235,26 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainActivityDialogs(
|
||||
dialogState: MainState.DialogState?,
|
||||
onAccessibilityDisclaimerDismiss: () -> Unit,
|
||||
) {
|
||||
when (dialogState) {
|
||||
MainState.DialogState.AccessibilityDisclosure -> {
|
||||
BitwardenBasicDialog(
|
||||
title = stringResource(id = BitwardenString.accessibility_service_disclosure),
|
||||
message = stringResource(
|
||||
id = BitwardenString.accessibility_disclosure_start_up_text,
|
||||
),
|
||||
onDismissRequest = onAccessibilityDisclaimerDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetupEventsEffect(navController: NavController) {
|
||||
EventsEffect(viewModel = mainViewModel) { event ->
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.x8bit.bitwarden.ui.platform.model.FeatureFlagsState
|
||||
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
|
||||
@@ -99,6 +100,7 @@ class MainViewModel @Inject constructor(
|
||||
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
|
||||
isDynamicColorsEnabled = settingsRepository.isDynamicColorsEnabled,
|
||||
hasResizeBeenRequested = false,
|
||||
dialogState = null,
|
||||
),
|
||||
) {
|
||||
private var specialCircumstance: SpecialCircumstance?
|
||||
@@ -149,6 +151,12 @@ class MainViewModel @Inject constructor(
|
||||
.onEach(::trySendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.hasShownAccessibilityDisclaimerFlow
|
||||
.map { MainAction.Internal.HasShownAccessibilityDisclaimerUpdate(it) }
|
||||
.onEach(::trySendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
merge(
|
||||
authRepository
|
||||
.userStateFlow
|
||||
@@ -201,6 +209,10 @@ class MainViewModel @Inject constructor(
|
||||
is MainAction.WebAuthnResult -> handleWebAuthnResult(action)
|
||||
is MainAction.CookieAcquisitionResult -> handleCookieAcquisitionResult(action)
|
||||
is MainAction.PremiumCheckoutResult -> handlePremiumCheckoutResult(action)
|
||||
is MainAction.DismissAccessibilityDisclaimerDialog -> {
|
||||
handleDismissAccessibilityDisclaimerDialog()
|
||||
}
|
||||
|
||||
is MainAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
@@ -224,6 +236,20 @@ class MainViewModel @Inject constructor(
|
||||
is MainAction.Internal.DynamicColorsUpdate -> handleDynamicColorsUpdate(action)
|
||||
is MainAction.Internal.CookieAcquisitionReady -> handleCookieAcquisitionReady()
|
||||
is MainAction.Internal.ResizeHasBeenRequested -> handleResizeHasBeenRequested()
|
||||
is MainAction.Internal.HasShownAccessibilityDisclaimerUpdate -> {
|
||||
handleHasShownAccessibilityDisclaimerUpdate(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleHasShownAccessibilityDisclaimerUpdate(
|
||||
action: MainAction.Internal.HasShownAccessibilityDisclaimerUpdate,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = MainState.DialogState.AccessibilityDisclosure
|
||||
.takeUnless { action.hasBeenShown },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +283,10 @@ class MainViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleDismissAccessibilityDisclaimerDialog() {
|
||||
settingsRepository.accessibilityDisclaimerHasBeenShown()
|
||||
}
|
||||
|
||||
private fun handleAppResumeDataUpdated(action: MainAction.ResumeScreenDataReceived) {
|
||||
when (val data = action.screenResumeData) {
|
||||
null -> appResumeManager.clearResumeScreen()
|
||||
@@ -538,12 +568,25 @@ data class MainState(
|
||||
val isScreenCaptureAllowed: Boolean,
|
||||
val isDynamicColorsEnabled: Boolean,
|
||||
val hasResizeBeenRequested: Boolean,
|
||||
val dialogState: DialogState?,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Contains all feature flags that are available to the UI.
|
||||
*/
|
||||
val featureFlagsState: FeatureFlagsState
|
||||
get() = FeatureFlagsState
|
||||
|
||||
/**
|
||||
* Representation of all dialogs displayed from the [MainActivity].
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class DialogState : Parcelable {
|
||||
/**
|
||||
* Displays an accessibility disclosure to users explaining how we utilize the
|
||||
* AccessibilityService.
|
||||
*/
|
||||
data object AccessibilityDisclosure : DialogState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -605,6 +648,11 @@ sealed class MainAction {
|
||||
*/
|
||||
data class AppSpecificLanguageUpdate(val appLanguage: AppLanguage) : MainAction()
|
||||
|
||||
/**
|
||||
* Received if the user dismisses the accessibility disclaimer dialog.
|
||||
*/
|
||||
data object DismissAccessibilityDisclaimerDialog : MainAction()
|
||||
|
||||
/**
|
||||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
@@ -660,6 +708,11 @@ sealed class MainAction {
|
||||
* Indicates that resize has been requested on the Activity
|
||||
*/
|
||||
data object ResizeHasBeenRequested : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the accessibility disclaimer has been displayed.
|
||||
*/
|
||||
data class HasShownAccessibilityDisclaimerUpdate(val hasBeenShown: Boolean) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,17 @@ interface SettingsDiskSource : FlightRecorderDiskSource {
|
||||
*/
|
||||
var initialAutofillDialogShown: Boolean?
|
||||
|
||||
/**
|
||||
* Indicates if the accessibility disclaimer has been displayed to the user.
|
||||
*/
|
||||
var hasShownAccessibilityDisclaimer: Boolean?
|
||||
|
||||
/**
|
||||
* Emits up-to-date values indicating if the accessibility disclaimer has been displayed to
|
||||
* the user.
|
||||
*/
|
||||
val hasShownAccessibilityDisclaimerFlow: Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* The currently persisted app theme (or `null` if not set).
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,7 @@ private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "accountBiometricInteg
|
||||
private const val CRASH_LOGGING_ENABLED_KEY = "crashLoggingEnabled"
|
||||
private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "clearClipboard"
|
||||
private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "addSitePromptShown"
|
||||
private const val HAS_SHOWN_ACCESSIBILITY_DISCLAIMER_KEY = "hasShownAccessibilityDisclaimer"
|
||||
private const val HAS_USER_LOGGED_IN_OR_CREATED_AN_ACCOUNT_KEY = "hasUserLoggedInOrCreatedAccount"
|
||||
private const val SHOW_AUTOFILL_SETTING_BADGE = "showAutofillSettingBadge"
|
||||
private const val SHOW_BROWSER_AUTOFILL_SETTING_BADGE = "showBrowserAutofillSettingBadge"
|
||||
@@ -123,6 +124,8 @@ class SettingsDiskSourceImpl(
|
||||
|
||||
private val mutableIsDynamicColorsEnabledFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableHasShownAccessibilityDisclaimerFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
init {
|
||||
migrateScreenCaptureSetting()
|
||||
}
|
||||
@@ -162,6 +165,17 @@ class SettingsDiskSourceImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override var hasShownAccessibilityDisclaimer: Boolean?
|
||||
set(value) {
|
||||
putBoolean(HAS_SHOWN_ACCESSIBILITY_DISCLAIMER_KEY, value)
|
||||
mutableHasShownAccessibilityDisclaimerFlow.tryEmit(value)
|
||||
}
|
||||
get() = getBoolean(HAS_SHOWN_ACCESSIBILITY_DISCLAIMER_KEY)
|
||||
|
||||
override val hasShownAccessibilityDisclaimerFlow: Flow<Boolean?>
|
||||
get() = mutableHasShownAccessibilityDisclaimerFlow
|
||||
.onSubscription { emit(hasShownAccessibilityDisclaimer) }
|
||||
|
||||
override var systemBiometricIntegritySource: String?
|
||||
get() = getString(key = SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY)
|
||||
set(value) {
|
||||
@@ -264,6 +278,7 @@ class SettingsDiskSourceImpl(
|
||||
// - Premium upgrade banner dismissed
|
||||
// - Upgraded to Premium action card consumed
|
||||
// - Upgraded to Premium action card pending
|
||||
// - Has shown accessibility disclaimer dialog
|
||||
}
|
||||
|
||||
override fun getIntroducingArchiveActionCardDismissed(userId: String): Boolean? =
|
||||
|
||||
@@ -187,6 +187,16 @@ interface SettingsRepository : FlightRecorderManager {
|
||||
*/
|
||||
val isScreenCaptureAllowedStateFlow: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Whether the accessibility disclaimer has been displayed to the user.
|
||||
*/
|
||||
val hasShownAccessibilityDisclaimerFlow: StateFlow<Boolean>
|
||||
|
||||
/**
|
||||
* Stores that the accessibility disclaimer has been displayed to the user.
|
||||
*/
|
||||
fun accessibilityDisclaimerHasBeenShown()
|
||||
|
||||
/**
|
||||
* Disables autofill if it is currently enabled.
|
||||
*/
|
||||
|
||||
@@ -372,6 +372,16 @@ class SettingsRepositoryImpl(
|
||||
initialValue = isScreenCaptureAllowed,
|
||||
)
|
||||
|
||||
override val hasShownAccessibilityDisclaimerFlow: StateFlow<Boolean>
|
||||
get() = settingsDiskSource
|
||||
.hasShownAccessibilityDisclaimerFlow
|
||||
.map { it ?: false }
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = settingsDiskSource.hasShownAccessibilityDisclaimer ?: false,
|
||||
)
|
||||
|
||||
init {
|
||||
policyManager
|
||||
.getActivePoliciesFlow(type = PolicyTypeJson.MAXIMUM_VAULT_TIMEOUT)
|
||||
@@ -379,6 +389,10 @@ class SettingsRepositoryImpl(
|
||||
.launchIn(unconfinedScope)
|
||||
}
|
||||
|
||||
override fun accessibilityDisclaimerHasBeenShown() {
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimer = true
|
||||
}
|
||||
|
||||
override fun disableAutofill() {
|
||||
autofillManager.disableAutofillServices()
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
private val mutableAppLanguageFlow = MutableStateFlow(AppLanguage.DEFAULT)
|
||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||
private val mutableIsDynamicColorsEnabledFlow = MutableStateFlow(false)
|
||||
private val mutableHasShownAccessibilityDisclaimerFlow = MutableStateFlow(true)
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
every { appTheme } returns AppTheme.DEFAULT
|
||||
every { appThemeStateFlow } returns mutableAppThemeFlow
|
||||
@@ -123,6 +124,10 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
every { appLanguage = any() } just runs
|
||||
every { isDynamicColorsEnabled } returns false
|
||||
every { isDynamicColorsEnabledFlow } returns mutableIsDynamicColorsEnabledFlow
|
||||
every {
|
||||
hasShownAccessibilityDisclaimerFlow
|
||||
} returns mutableHasShownAccessibilityDisclaimerFlow
|
||||
every { accessibilityDisclaimerHasBeenShown() } just runs
|
||||
}
|
||||
private val authRepository = mockk<AuthRepository> {
|
||||
every { activeUserId } returns DEFAULT_USER_STATE.activeUserId
|
||||
@@ -1333,6 +1338,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
|
||||
isDynamicColorsEnabled = settingsRepository.isDynamicColorsEnabled,
|
||||
hasResizeBeenRequested = false,
|
||||
dialogState = null,
|
||||
)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
@@ -1349,6 +1355,49 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on HasShownAccessibilityDisclaimerUpdate with false should show accessibility dialog`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
mutableHasShownAccessibilityDisclaimerFlow.value = false
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = MainState.DialogState.AccessibilityDisclosure,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on HasShownAccessibilityDisclaimerUpdate with true should clear accessibility dialog`() =
|
||||
runTest {
|
||||
mutableHasShownAccessibilityDisclaimerFlow.value = false
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = MainState.DialogState.AccessibilityDisclosure,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
mutableHasShownAccessibilityDisclaimerFlow.value = true
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on DismissAccessibilityDisclaimerDialog should store that the accessibility disclaimer has been shown`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(MainAction.DismissAccessibilityDisclaimerDialog)
|
||||
verify(exactly = 1) {
|
||||
settingsRepository.accessibilityDisclaimerHasBeenShown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
initialSpecialCircumstance: SpecialCircumstance? = null,
|
||||
) = MainViewModel(
|
||||
@@ -1378,6 +1427,7 @@ private val DEFAULT_STATE: MainState = MainState(
|
||||
isScreenCaptureAllowed = true,
|
||||
isDynamicColorsEnabled = false,
|
||||
hasResizeBeenRequested = false,
|
||||
dialogState = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_FIRST_TIME_STATE = FirstTimeState(
|
||||
|
||||
@@ -466,6 +466,42 @@ class SettingsDiskSourceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hasShownAccessibilityDisclaimer should pull from and update SharedPreferences`() {
|
||||
val hasShownAccessibilityDisclaimerKey =
|
||||
"bwPreferencesStorage:hasShownAccessibilityDisclaimer"
|
||||
val expected = true
|
||||
|
||||
assertNull(settingsDiskSource.hasShownAccessibilityDisclaimer)
|
||||
|
||||
fakeSharedPreferences.edit {
|
||||
putBoolean(hasShownAccessibilityDisclaimerKey, expected)
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
expected,
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimer,
|
||||
)
|
||||
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimer = false
|
||||
assertFalse(fakeSharedPreferences.getBoolean(hasShownAccessibilityDisclaimerKey, true))
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `hasShownAccessibilityDisclaimerFlow should react to changes in hasShownAccessibilityDisclaimer`() =
|
||||
runTest {
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimerFlow.test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(settingsDiskSource.hasShownAccessibilityDisclaimer)
|
||||
assertNull(awaitItem())
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimer = true
|
||||
assertEquals(true, awaitItem())
|
||||
settingsDiskSource.hasShownAccessibilityDisclaimer = false
|
||||
assertEquals(false, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultTimeoutInMinutes when values are present should pull from SharedPreferences`() {
|
||||
val vaultTimeoutBaseKey = "bwPreferencesStorage:vaultTimeout"
|
||||
|
||||
@@ -92,6 +92,7 @@ class FakeSettingsDiskSource(
|
||||
private var hasSeenAddLoginCoachMark: Boolean? = null
|
||||
private var hasSeenGeneratorCoachMark: Boolean? = null
|
||||
private var storedIsDynamicColorsEnabled: Boolean? = null
|
||||
private var storedHasShownAccessibilityDisclaimer: Boolean? = null
|
||||
private var storedBrowserAutofillDialogReshowTime: Instant? = null
|
||||
|
||||
private val mutableShowAutoFillSettingBadgeFlowMap =
|
||||
@@ -109,6 +110,8 @@ class FakeSettingsDiskSource(
|
||||
private val mutableIsDynamicColorsEnabled =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableHasShownAccessibilityDisclaimerFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableVaultRegisteredForExportFlow =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
@@ -158,6 +161,18 @@ class FakeSettingsDiskSource(
|
||||
emit(isDynamicColorsEnabled)
|
||||
}
|
||||
|
||||
override var hasShownAccessibilityDisclaimer: Boolean?
|
||||
get() = storedHasShownAccessibilityDisclaimer
|
||||
set(value) {
|
||||
storedHasShownAccessibilityDisclaimer = value
|
||||
mutableHasShownAccessibilityDisclaimerFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val hasShownAccessibilityDisclaimerFlow: Flow<Boolean?>
|
||||
get() = mutableHasShownAccessibilityDisclaimerFlow.onSubscription {
|
||||
emit(hasShownAccessibilityDisclaimer)
|
||||
}
|
||||
|
||||
override var screenCaptureAllowed: Boolean?
|
||||
get() = storedScreenCaptureAllowed
|
||||
set(value) {
|
||||
|
||||
@@ -1119,6 +1119,30 @@ class SettingsRepositoryTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hasShownAccessibilityDisclaimerFlow should emit changes from SettingsDiskSource`() =
|
||||
runTest {
|
||||
fakeSettingsDiskSource.hasShownAccessibilityDisclaimer = null
|
||||
settingsRepository.hasShownAccessibilityDisclaimerFlow.test {
|
||||
assertFalse(awaitItem())
|
||||
|
||||
fakeSettingsDiskSource.hasShownAccessibilityDisclaimer = true
|
||||
assertTrue(awaitItem())
|
||||
|
||||
fakeSettingsDiskSource.hasShownAccessibilityDisclaimer = false
|
||||
assertFalse(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accessibilityDisclaimerHasBeenShown should update SettingsDiskSource`() {
|
||||
assertNull(fakeSettingsDiskSource.hasShownAccessibilityDisclaimer)
|
||||
|
||||
settingsRepository.accessibilityDisclaimerHasBeenShown()
|
||||
|
||||
assertTrue(fakeSettingsDiskSource.hasShownAccessibilityDisclaimer == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clearClipboardFrequency should pull from and update SettingsDiskSource`() = runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
|
||||
@@ -118,7 +118,7 @@ sealed class FlagKey<out T : Any> {
|
||||
* Data object holding the feature flag key for the mobile Premium upgrade feature.
|
||||
*/
|
||||
data object MobilePremiumUpgrade : FlagKey<Boolean>() {
|
||||
override val keyName: String = "PM-31697-premium-upgrade-path"
|
||||
override val keyName: String = "pm-31697-premium-upgrade-path"
|
||||
override val defaultValue: Boolean = false
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class FlagKeyTest {
|
||||
)
|
||||
assertEquals(
|
||||
FlagKey.MobilePremiumUpgrade.keyName,
|
||||
"PM-31697-premium-upgrade-path",
|
||||
"pm-31697-premium-upgrade-path",
|
||||
)
|
||||
assertEquals(
|
||||
FlagKey.AttachmentUpdates.keyName,
|
||||
|
||||
@@ -615,6 +615,7 @@ select Add TOTP to store the key safely</string>
|
||||
<string name="forwarded_email_description">Generate an email alias with an external forwarding service.</string>
|
||||
<string name="accessibility_service_disclosure">Accessibility Service Disclosure</string>
|
||||
<string name="accessibility_disclosure_text">Bitwarden uses the Accessibility Service to search for login fields in apps and websites, then establish the appropriate field IDs for entering a username & password when a match for the app or site is found. We do not store any of the information presented to us by the service, nor do we make any attempt to control any on-screen elements beyond text entry of credentials.</string>
|
||||
<string name="accessibility_disclosure_start_up_text">Bitwarden offers an optional autofill method that uses Android’s Accessibility Service to detect login fields in apps and websites. If you choose to enable it, Bitwarden will identify the appropriate fields and enter your credentials when a match is found. We do not store any information observed by the service, nor do we control any on-screen elements beyond credential entry.</string>
|
||||
<string name="accept">Accept</string>
|
||||
<string name="decline">Decline</string>
|
||||
<string name="login_request_has_already_expired">Login request has already expired.</string>
|
||||
|
||||
Reference in New Issue
Block a user