diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt index 7ab595aed57..1e279313d2d 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt @@ -62,6 +62,7 @@ class MainActivity : AppCompatActivity() { setupEdgeToEdge(appThemeFlow = mainViewModel.stateFlow.map { it.theme }) setContent { val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() + updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed) val navController = rememberNavController() observeViewModelEvents(navController) LocalManagerProvider { @@ -96,10 +97,6 @@ class MainActivity : AppCompatActivity() { .eventFlow .onEach { event -> when (event) { - is MainEvent.ScreenCaptureSettingChange -> { - handleScreenCaptureSettingChange(event) - } - MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen() is MainEvent.UpdateAppTheme -> { AppCompatDelegate.setDefaultNightMode(event.osTheme) @@ -123,8 +120,8 @@ class MainActivity : AppCompatActivity() { mainViewModel.trySendAction(MainAction.OpenDebugMenu) } - private fun handleScreenCaptureSettingChange(event: MainEvent.ScreenCaptureSettingChange) { - if (event.isAllowed) { + private fun updateScreenCapture(isScreenCaptureAllowed: Boolean) { + if (isScreenCaptureAllowed) { window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } else { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt index 6661affe420..2a8549c4865 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt @@ -26,6 +26,7 @@ class MainViewModel @Inject constructor( ) : BaseViewModel( MainState( theme = settingsRepository.appTheme, + isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed, isDynamicColorsEnabled = settingsRepository.isDynamicColorsEnabled, ), ) { @@ -43,9 +44,10 @@ class MainViewModel @Inject constructor( .launchIn(viewModelScope) settingsRepository .isScreenCaptureAllowedStateFlow - .map { MainEvent.ScreenCaptureSettingChange(it) } - .onEach(::sendEvent) + .map { MainAction.Internal.ScreenCaptureUpdate(it) } + .onEach(::sendAction) .launchIn(viewModelScope) + viewModelScope.launch { configRepository.getServerConfig(forceRefresh = false) } @@ -64,6 +66,10 @@ class MainViewModel @Inject constructor( when (action) { is MainAction.Internal.DynamicColorUpdate -> handleDynamicColorUpdate(action) is MainAction.Internal.ThemeUpdate -> handleThemeUpdated(action) + + is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate( + screenCaptureUpdateAction = action, + ) } } @@ -94,6 +100,16 @@ class MainViewModel @Inject constructor( ) } + private fun handleScreenCaptureUpdate( + screenCaptureUpdateAction: MainAction.Internal.ScreenCaptureUpdate, + ) { + mutableStateFlow.update { + it.copy( + isScreenCaptureAllowed = screenCaptureUpdateAction.isScreenCaptureEnabled, + ) + } + } + private fun handleIntent( intent: Intent, isFirstIntent: Boolean, @@ -109,6 +125,7 @@ class MainViewModel @Inject constructor( data class MainState( val theme: AppTheme, val isDynamicColorsEnabled: Boolean, + val isScreenCaptureAllowed: Boolean, ) : Parcelable /** @@ -147,6 +164,13 @@ sealed class MainAction { data class ThemeUpdate( val theme: AppTheme, ) : Internal() + + /** + * Indicates that the screen capture state has changed. + */ + data class ScreenCaptureUpdate( + val isScreenCaptureEnabled: Boolean, + ) : Internal() } } @@ -160,11 +184,6 @@ sealed class MainEvent { */ data object NavigateToDebugMenu : MainEvent() - /** - * Event indicating a change in the screen capture setting. - */ - data class ScreenCaptureSettingChange(val isAllowed: Boolean) : MainEvent() - /** * Indicates that the app theme has been updated. */ diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt index c2ef72fe047..d4f204dd0be 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/MainViewModelTest.kt @@ -22,6 +22,7 @@ class MainViewModelTest : BaseViewModelTest() { every { appTheme } returns AppTheme.DEFAULT every { appThemeStateFlow } returns mutableAppThemeFlow every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow + every { isScreenCaptureAllowed } returns false every { isDynamicColorsEnabled } returns false every { isDynamicColorsEnabledFlow } returns mutableIsDynamicColorsEnabledFlow } @@ -31,7 +32,7 @@ class MainViewModelTest : BaseViewModelTest() { fun `on AppThemeChanged should update state`() = runTest { val viewModel = createViewModel() viewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow -> - eventFlow.skipItems(count = 2) + eventFlow.skipItems(count = 1) assertEquals( DEFAULT_STATE, stateFlow.awaitItem(), @@ -79,12 +80,29 @@ class MainViewModelTest : BaseViewModelTest() { val viewModel = createViewModel() viewModel.eventFlow.test { // Ignore the events that are fired off by flows in the ViewModel init - skipItems(2) + skipItems(1) viewModel.trySendAction(MainAction.OpenDebugMenu) assertEquals(MainEvent.NavigateToDebugMenu, awaitItem()) } } + @Test + fun `changes in the allowed screen capture value should update the state`() { + val viewModel = createViewModel() + + assertEquals( + DEFAULT_STATE.copy(isScreenCaptureAllowed = false), + viewModel.stateFlow.value, + ) + + mutableScreenCaptureAllowedFlow.value = true + + assertEquals( + DEFAULT_STATE.copy(isScreenCaptureAllowed = true), + viewModel.stateFlow.value, + ) + } + private fun createViewModel(): MainViewModel = MainViewModel( settingsRepository = settingsRepository, @@ -95,4 +113,5 @@ class MainViewModelTest : BaseViewModelTest() { private val DEFAULT_STATE = MainState( theme = AppTheme.DEFAULT, isDynamicColorsEnabled = false, + isScreenCaptureAllowed = false, )