diff --git a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt index 44f788834a..4a02854a49 100644 --- a/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/MainViewModel.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden import android.content.Intent import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance @@ -16,6 +17,8 @@ import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize import javax.inject.Inject +private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance" + /** * A view model that helps launch actions for the [MainActivity]. */ @@ -24,12 +27,27 @@ class MainViewModel @Inject constructor( private val specialCircumstanceManager: SpecialCircumstanceManager, private val intentManager: IntentManager, settingsRepository: SettingsRepository, + private val savedStateHandle: SavedStateHandle, ) : BaseViewModel( MainState( theme = settingsRepository.appTheme, ), ) { + private var specialCircumstance: SpecialCircumstance? + get() = savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY] + set(value) { + savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY] = value + } + init { + // Immediately restore the special circumstance if we have one and then listen for changes + specialCircumstanceManager.specialCircumstance = specialCircumstance + + specialCircumstanceManager + .specialCircumstanceStateFlow + .onEach { specialCircumstance = it } + .launchIn(viewModelScope) + settingsRepository .appThemeStateFlow .onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt index 3031e566ea..38e22d019c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/model/SpecialCircumstance.kt @@ -1,15 +1,18 @@ package com.x8bit.bitwarden.data.platform.manager.model +import android.os.Parcelable import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager +import kotlinx.parcelize.Parcelize /** * Represents a special circumstance the app may be in. These circumstances could require some kind * of navigation that is counter to what otherwise may happen based on the state of the app. */ -sealed class SpecialCircumstance { +sealed class SpecialCircumstance : Parcelable { /** * The app was launched in order to create/share a new Send using the given [data]. */ + @Parcelize data class ShareNewSend( val data: IntentManager.ShareData, val shouldFinishWhenComplete: Boolean, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt index 12f271325f..dbabe3330a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/intent/IntentManager.kt @@ -2,9 +2,11 @@ package com.x8bit.bitwarden.ui.platform.manager.intent import android.content.Intent import android.net.Uri +import android.os.Parcelable import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.compose.runtime.Composable +import kotlinx.parcelize.Parcelize /** * A manager class for simplifying the handling of Android Intents within a given context. @@ -74,19 +76,21 @@ interface IntentManager { /** * Represents file information. */ + @Parcelize data class FileData( val fileName: String, val uri: Uri, val sizeBytes: Long, - ) + ) : Parcelable /** * Represents data for a share request coming from outside the app. */ - sealed class ShareData { + sealed class ShareData : Parcelable { /** * The data required to create a new Text Send. */ + @Parcelize data class TextSend( val subject: String?, val text: String, @@ -95,6 +99,7 @@ interface IntentManager { /** * The data required to create a new File Send. */ + @Parcelize data class FileSend( val fileData: FileData, ) : ShareData() diff --git a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt index d8215d24d7..439f273266 100644 --- a/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/MainViewModelTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden import android.content.Intent +import androidx.lifecycle.SavedStateHandle import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult @@ -16,6 +17,7 @@ import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test class MainViewModelTest : BaseViewModelTest() { @@ -34,6 +36,38 @@ class MainViewModelTest : BaseViewModelTest() { private val intentManager: IntentManager = mockk { every { getShareDataFromIntent(any()) } returns null } + private val savedStateHandle = SavedStateHandle() + + @Suppress("MaxLineLength") + @Test + fun `initialization should set a saved SpecialCircumstance to the SpecialCircumstanceManager if present`() { + assertNull(specialCircumstanceManager.specialCircumstance) + + val specialCircumstance = mockk() + createViewModel( + initialSpecialCircumstance = specialCircumstance, + ) + + assertEquals( + specialCircumstance, + specialCircumstanceManager.specialCircumstance, + ) + } + + @Test + fun `SpecialCircumstance updates should update the SavedStateHandle`() { + createViewModel() + + assertNull(savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY]) + + val specialCircumstance = mockk() + specialCircumstanceManager.specialCircumstance = specialCircumstance + + assertEquals( + specialCircumstance, + savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY], + ) + } @Test fun `on AppThemeChanged should update state`() { @@ -109,13 +143,19 @@ class MainViewModelTest : BaseViewModelTest() { ) } - private fun createViewModel() = MainViewModel( + private fun createViewModel( + initialSpecialCircumstance: SpecialCircumstance? = null, + ) = MainViewModel( specialCircumstanceManager = specialCircumstanceManager, settingsRepository = settingsRepository, intentManager = intentManager, + savedStateHandle = savedStateHandle.apply { + set(SPECIAL_CIRCUMSTANCE_KEY, initialSpecialCircumstance) + }, ) companion object { + private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance" private const val USER_ID = "userID" private val DEFAULT_USER_STATE = UserState( activeUserId = USER_ID,