diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt index aa014a9bf..2b3236500 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt @@ -853,7 +853,14 @@ private fun ColumnScope.ForwardedEmailAliasTypeContent( } is ServiceType.FirefoxRelay -> { - // TODO: FirefoxRelay Service Implementation (BIT-1196) + BitwardenPasswordField( + label = stringResource(id = R.string.api_access_token), + value = usernameTypeState.selectedServiceType.apiAccessToken, + onValueChange = forwardedEmailAliasHandlers.onFirefoxRelayAccessTokenTextChange, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + ) } is ServiceType.SimpleLogin -> { @@ -1179,6 +1186,7 @@ private class PassphraseHandlers( private class ForwardedEmailAliasHandlers( val onServiceChange: (ServiceTypeOption) -> Unit, val onDuckDuckGoApiKeyTextChange: (String) -> Unit, + val onFirefoxRelayAccessTokenTextChange: (String) -> Unit, ) { companion object { fun create(viewModel: GeneratorViewModel): ForwardedEmailAliasHandlers { @@ -1208,6 +1216,20 @@ private class ForwardedEmailAliasHandlers( ), ) }, + onFirefoxRelayAccessTokenTextChange = { newAccessToken -> + viewModel.trySendAction( + GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FirefoxRelay + .AccessTokenTextChange( + accessToken = newAccessToken, + ), + + ) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index f404390e8..ae63a3c4f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -23,6 +23,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Us import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.CatchAllEmail import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.DuckDuckGo +import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.FirefoxRelay import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.RandomWord @@ -118,6 +119,10 @@ class GeneratorViewModel @Inject constructor( handleDuckDuckGoTextInputChange(action) } + is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.FirefoxRelay.AccessTokenTextChange -> { + handleFirefoxRelayTextInputChange(action) + } + is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange -> { handlePlusAddressedEmailTextInputChange(action) } @@ -599,6 +604,8 @@ class GeneratorViewModel @Inject constructor( //endregion Forwarded Email Alias Specific Handlers + //region DuckDuckGo Service Specific Handlers + private fun handleDuckDuckGoTextInputChange( action: GeneratorAction .MainType @@ -614,6 +621,27 @@ class GeneratorViewModel @Inject constructor( } } + //endregion DuckDuckGo Service Specific Handlers + + //region FirefoxRelay Service Specific Handlers + + private fun handleFirefoxRelayTextInputChange( + action: GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FirefoxRelay + .AccessTokenTextChange, + ) { + updateFirefoxRelayServiceType { firefoxRelayServiceType -> + val newAccessToken = action.accessToken + firefoxRelayServiceType.copy(apiAccessToken = newAccessToken) + } + } + + //endregion FirefoxRelay Service Specific Handlers + //region Plus Addressed Email Specific Handlers private fun handlePlusAddressedEmailTextInputChange( @@ -796,6 +824,30 @@ class GeneratorViewModel @Inject constructor( } } + private inline fun updateFirefoxRelayServiceType( + crossinline block: (FirefoxRelay) -> FirefoxRelay, + ) { + updateGeneratorMainTypeUsername { currentUsernameType -> + if (currentUsernameType.selectedType !is ForwardedEmailAlias) { + return@updateGeneratorMainTypeUsername currentUsernameType + } + + val currentServiceType = (currentUsernameType.selectedType).selectedServiceType + if (currentServiceType !is FirefoxRelay) { + return@updateGeneratorMainTypeUsername currentUsernameType + } + + val updatedServiceType = block(currentServiceType) + + currentUsernameType.copy( + selectedType = ForwardedEmailAlias( + selectedServiceType = updatedServiceType, + obfuscatedText = currentUsernameType.selectedType.obfuscatedText, + ), + ) + } + } + private inline fun updatePlusAddressedEmailType( crossinline block: (PlusAddressedEmail) -> PlusAddressedEmail, ) { @@ -1458,6 +1510,19 @@ sealed class GeneratorAction { */ data class ApiKeyTextChange(val apiKey: String) : DuckDuckGo() } + + /** + * Represents actions specifically related to the FirefoxRelay service. + */ + sealed class FirefoxRelay : ForwardedEmailAlias() { + + /** + * Fired when the access token input text is changed. + * + * @property accessToken The new access token text. + */ + data class AccessTokenTextChange(val accessToken: String) : FirefoxRelay() + } } /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index 653db91dd..4ba0d72e0 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -967,6 +967,52 @@ class GeneratorScreenTest : BaseComposeTest() { //endregion DuckDuckGo Service Type Tests + //region FirefoxRelay Service Type Tests + + @Suppress("MaxLineLength") + @Test + fun `in Username_ForwardedEmailAlias_FirefoxRelay state, updating access token text input should send AccessTokenTextChange action`() { + updateState( + GeneratorState( + generatedText = "Placeholder", + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FirefoxRelay(), + ), + ), + ), + ) + + val newAccessToken = "accessToken" + + composeTestRule + .onNodeWithText("API access token") + .performScrollTo() + .performTextInput(newAccessToken) + + verify { + viewModel.trySendAction( + GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FirefoxRelay + .AccessTokenTextChange( + accessToken = newAccessToken, + ), + ) + } + } + + //endregion FirefoxRelay Service Type Tests + //region Username Plus Addressed Email Tests @Suppress("MaxLineLength") diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt index a49216bf5..4d93f7860 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModelTest.kt @@ -34,6 +34,9 @@ class GeneratorViewModelTest : BaseViewModelTest() { private val initialDuckDuckGoState = createDuckDuckGoState() private val duckDuckGoSavedStateHandle = createSavedStateHandleWithState(initialDuckDuckGoState) + private val initialFirefoxRelay = createFirefoxRelayState() + private val firefoxRelaySavedStateHandle = createSavedStateHandleWithState(initialFirefoxRelay) + private val initialCatchAllEmailState = createCatchAllEmailState() private val catchAllEmailSavedStateHandle = createSavedStateHandleWithState(initialCatchAllEmailState) @@ -998,6 +1001,51 @@ class GeneratorViewModelTest : BaseViewModelTest() { } } + @Nested + inner class FirefoxRelayActions { + private val defaultFirefoxRelayState = createFirefoxRelayState() + private lateinit var viewModel: GeneratorViewModel + + @BeforeEach + fun setup() { + viewModel = GeneratorViewModel(firefoxRelaySavedStateHandle, fakeGeneratorRepository) + } + + @Test + fun `AccessTokenTextChange should update access token text correctly`() = runTest { + val newAccessToken = "newAccessToken" + val action = GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FirefoxRelay + .AccessTokenTextChange( + accessToken = newAccessToken, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = defaultFirefoxRelayState.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FirefoxRelay( + apiAccessToken = newAccessToken, + ), + ), + ), + ) + + assertEquals(expectedState, viewModel.stateFlow.value) + } + } + @Nested inner class PlusAddressedEmailActions { private val defaultPlusAddressedEmailState = createPlusAddressedEmailState() @@ -1223,6 +1271,24 @@ class GeneratorViewModelTest : BaseViewModelTest() { ), ) + private fun createFirefoxRelayState( + generatedText: String = "defaultFirefoxRelay", + ): GeneratorState = + GeneratorState( + generatedText = generatedText, + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FirefoxRelay(), + ), + ), + ) + private fun createPlusAddressedEmailState( generatedText: String = "defaultPlusAddressedEmail", email: String = "defaultEmail",