Fix a screen capture bug that clears the setting when the app language changes (#3372)

This commit is contained in:
David Perez 2024-06-27 13:56:55 -05:00 committed by GitHub
parent 1bc348fa1a
commit 4f5454b4b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 52 deletions

View file

@ -66,6 +66,7 @@ class MainActivity : AppCompatActivity() {
} }
setContent { setContent {
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
LocalManagerProvider { LocalManagerProvider {
BitwardenTheme(theme = state.theme) { BitwardenTheme(theme = state.theme) {
RootNavScreen( RootNavScreen(
@ -97,15 +98,8 @@ class MainActivity : AppCompatActivity() {
.eventFlow .eventFlow
.onEach { event -> .onEach { event ->
when (event) { when (event) {
is MainEvent.CompleteAutofill -> { is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
handleCompleteAutofill(event)
}
MainEvent.Recreate -> handleRecreate() MainEvent.Recreate -> handleRecreate()
is MainEvent.ScreenCaptureSettingChange -> {
handleScreenCaptureSettingChange(event)
}
} }
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
@ -118,15 +112,15 @@ class MainActivity : AppCompatActivity() {
) )
} }
private fun handleScreenCaptureSettingChange(event: MainEvent.ScreenCaptureSettingChange) { private fun handleRecreate() {
if (event.isAllowed) { recreate()
}
private fun updateScreenCapture(isScreenCaptureAllowed: Boolean) {
if (isScreenCaptureAllowed) {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else { } else {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} }
} }
private fun handleRecreate() {
recreate()
}
} }

View file

@ -50,8 +50,9 @@ class MainViewModel @Inject constructor(
private val authRepository: AuthRepository, private val authRepository: AuthRepository,
private val savedStateHandle: SavedStateHandle, private val savedStateHandle: SavedStateHandle,
) : BaseViewModel<MainState, MainEvent, MainAction>( ) : BaseViewModel<MainState, MainEvent, MainAction>(
MainState( initialState = MainState(
theme = settingsRepository.appTheme, theme = settingsRepository.appTheme,
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
), ),
) { ) {
private var specialCircumstance: SpecialCircumstance? private var specialCircumstance: SpecialCircumstance?
@ -81,9 +82,8 @@ class MainViewModel @Inject constructor(
settingsRepository settingsRepository
.isScreenCaptureAllowedStateFlow .isScreenCaptureAllowedStateFlow
.onEach { isAllowed -> .map { MainAction.Internal.ScreenCaptureUpdate(it) }
sendEvent(MainEvent.ScreenCaptureSettingChange(isAllowed)) .onEach(::trySendAction)
}
.launchIn(viewModelScope) .launchIn(viewModelScope)
authRepository authRepository
@ -124,6 +124,7 @@ class MainViewModel @Inject constructor(
} }
is MainAction.Internal.CurrentUserStateChange -> handleCurrentUserStateChange() is MainAction.Internal.CurrentUserStateChange -> handleCurrentUserStateChange()
is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate(action)
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action) is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange() is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange()
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action) is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
@ -141,6 +142,10 @@ class MainViewModel @Inject constructor(
recreateUiAndGarbageCollect() recreateUiAndGarbageCollect()
} }
private fun handleScreenCaptureUpdate(action: MainAction.Internal.ScreenCaptureUpdate) {
mutableStateFlow.update { it.copy(isScreenCaptureAllowed = action.isScreenCaptureEnabled) }
}
private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) { private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
mutableStateFlow.update { it.copy(theme = action.theme) } mutableStateFlow.update { it.copy(theme = action.theme) }
} }
@ -249,6 +254,7 @@ class MainViewModel @Inject constructor(
@Parcelize @Parcelize
data class MainState( data class MainState(
val theme: AppTheme, val theme: AppTheme,
val isScreenCaptureAllowed: Boolean,
) : Parcelable ) : Parcelable
/** /**
@ -281,6 +287,13 @@ sealed class MainAction {
*/ */
data object CurrentUserStateChange : Internal() data object CurrentUserStateChange : Internal()
/**
* Indicates that the screen capture state has changed.
*/
data class ScreenCaptureUpdate(
val isScreenCaptureEnabled: Boolean,
) : Internal()
/** /**
* Indicates that the app theme has changed. * Indicates that the app theme has changed.
*/ */
@ -305,11 +318,6 @@ sealed class MainEvent {
*/ */
data class CompleteAutofill(val cipherView: CipherView) : MainEvent() data class CompleteAutofill(val cipherView: CipherView) : MainEvent()
/**
* Event indicating a change in the screen capture setting.
*/
data class ScreenCaptureSettingChange(val isAllowed: Boolean) : MainEvent()
/** /**
* Event indicating that the UI should recreate itself. * Event indicating that the UI should recreate itself.
*/ */

View file

@ -59,6 +59,7 @@ class MainViewModelTest : BaseViewModelTest() {
private val settingsRepository = mockk<SettingsRepository> { private val settingsRepository = mockk<SettingsRepository> {
every { appTheme } returns AppTheme.DEFAULT every { appTheme } returns AppTheme.DEFAULT
every { appThemeStateFlow } returns mutableAppThemeFlow every { appThemeStateFlow } returns mutableAppThemeFlow
every { isScreenCaptureAllowed } returns true
every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow
} }
private val authRepository = mockk<AuthRepository> { private val authRepository = mockk<AuthRepository> {
@ -129,9 +130,6 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.eventFlow.test { viewModel.eventFlow.test {
// Ignore initial screen capture event
awaitItem()
mutableUserStateFlow.value = UserState( mutableUserStateFlow.value = UserState(
activeUserId = userId1, activeUserId = userId1,
accounts = listOf( accounts = listOf(
@ -179,9 +177,6 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.eventFlow.test { viewModel.eventFlow.test {
// Ignore initial screen capture event
awaitItem()
mutableVaultStateEventFlow.tryEmit(VaultStateEvent.Unlocked(userId = "userId")) mutableVaultStateEventFlow.tryEmit(VaultStateEvent.Unlocked(userId = "userId"))
expectNoEvents() expectNoEvents()
@ -198,9 +193,6 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val cipherView = mockk<CipherView>() val cipherView = mockk<CipherView>()
viewModel.eventFlow.test { viewModel.eventFlow.test {
// Ignore initial screen capture event
awaitItem()
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView) autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
assertEquals( assertEquals(
MainEvent.CompleteAutofill(cipherView = cipherView), MainEvent.CompleteAutofill(cipherView = cipherView),
@ -229,9 +221,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
assertEquals( assertEquals(
MainState( DEFAULT_STATE,
theme = AppTheme.DEFAULT,
),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
viewModel.trySendAction( viewModel.trySendAction(
@ -240,7 +230,7 @@ class MainViewModelTest : BaseViewModelTest() {
), ),
) )
assertEquals( assertEquals(
MainState( DEFAULT_STATE.copy(
theme = AppTheme.DARK, theme = AppTheme.DARK,
), ),
viewModel.stateFlow.value, viewModel.stateFlow.value,
@ -623,25 +613,19 @@ class MainViewModelTest : BaseViewModelTest() {
) )
} }
@Suppress("MaxLineLength")
@Test @Test
fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() = fun `changes in the allowed screen capture value should update the state`() {
runTest { val viewModel = createViewModel()
val viewModel = createViewModel()
viewModel.eventFlow.test { assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
assertEquals(
MainEvent.ScreenCaptureSettingChange(isAllowed = true),
awaitItem(),
)
mutableScreenCaptureAllowedFlow.value = false mutableScreenCaptureAllowedFlow.value = false
assertEquals(
MainEvent.ScreenCaptureSettingChange(isAllowed = false), assertEquals(
awaitItem(), DEFAULT_STATE.copy(isScreenCaptureAllowed = false),
) viewModel.stateFlow.value,
} )
} }
private fun createViewModel( private fun createViewModel(
initialSpecialCircumstance: SpecialCircumstance? = null, initialSpecialCircumstance: SpecialCircumstance? = null,
@ -659,6 +643,11 @@ class MainViewModelTest : BaseViewModelTest() {
) )
} }
private val DEFAULT_STATE: MainState = MainState(
theme = AppTheme.DEFAULT,
isScreenCaptureAllowed = true,
)
private const val SPECIAL_CIRCUMSTANCE_KEY: String = "special-circumstance" private const val SPECIAL_CIRCUMSTANCE_KEY: String = "special-circumstance"
private val DEFAULT_ACCOUNT = UserState.Account( private val DEFAULT_ACCOUNT = UserState.Account(
userId = "activeUserId", userId = "activeUserId",