mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
Fix a screen capture bug that clears the setting when the app language changes (#3372)
This commit is contained in:
parent
1bc348fa1a
commit
4f5454b4b7
3 changed files with 43 additions and 52 deletions
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue