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 8d684c839..72089a168 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 @@ -868,7 +868,14 @@ private fun ColumnScope.ForwardedEmailAliasTypeContent( } is ServiceType.FastMail -> { - // TODO: FastMail Service Implementation (BIT-712) + BitwardenPasswordField( + label = stringResource(id = R.string.api_key_required_parenthesis), + value = usernameTypeState.selectedServiceType.apiKey, + onValueChange = forwardedEmailAliasHandlers.onFastMailApiKeyTextChange, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + ) } is ServiceType.FirefoxRelay -> { @@ -1207,6 +1214,7 @@ private class ForwardedEmailAliasHandlers( val onAddyIoAccessTokenTextChange: (String) -> Unit, val onAddyIoDomainNameTextChange: (String) -> Unit, val onDuckDuckGoApiKeyTextChange: (String) -> Unit, + val onFastMailApiKeyTextChange: (String) -> Unit, val onFirefoxRelayAccessTokenTextChange: (String) -> Unit, ) { companion object { @@ -1264,6 +1272,19 @@ private class ForwardedEmailAliasHandlers( ), ) }, + onFastMailApiKeyTextChange = { newApiKey -> + viewModel.trySendAction( + GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FastMail + .ApiKeyTextChange( + apiKey = newApiKey, + ), + ) + }, onFirefoxRelayAccessTokenTextChange = { newAccessToken -> viewModel.trySendAction( GeneratorAction 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 5f78fd407..c74399dda 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 @@ -126,6 +126,10 @@ class GeneratorViewModel @Inject constructor( handleDuckDuckGoTextInputChange(action) } + is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.FastMail.ApiKeyTextChange -> { + handleFastMailTextInputChange(action) + } + is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.FirefoxRelay.AccessTokenTextChange -> { handleFirefoxRelayTextInputChange(action) } @@ -692,6 +696,25 @@ class GeneratorViewModel @Inject constructor( //endregion DuckDuckGo Service Specific Handlers + //region FastMail Service Specific Handlers + + private fun handleFastMailTextInputChange( + action: GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FastMail + .ApiKeyTextChange, + ) { + updateFastMailServiceType { fastMailServiceType -> + val newApiKey = action.apiKey + fastMailServiceType.copy(apiKey = newApiKey) + } + } + + //endregion FastMail Service Specific Handlers + //region FirefoxRelay Service Specific Handlers private fun handleFirefoxRelayTextInputChange( @@ -917,6 +940,30 @@ class GeneratorViewModel @Inject constructor( } } + private inline fun updateFastMailServiceType( + crossinline block: (FastMail) -> FastMail, + ) { + updateGeneratorMainTypeUsername { currentUsernameType -> + if (currentUsernameType.selectedType !is ForwardedEmailAlias) { + return@updateGeneratorMainTypeUsername currentUsernameType + } + + val currentServiceType = (currentUsernameType.selectedType).selectedServiceType + if (currentServiceType !is FastMail) { + return@updateGeneratorMainTypeUsername currentUsernameType + } + + val updatedServiceType = block(currentServiceType) + + currentUsernameType.copy( + selectedType = ForwardedEmailAlias( + selectedServiceType = updatedServiceType, + obfuscatedText = currentUsernameType.selectedType.obfuscatedText, + ), + ) + } + } + private inline fun updateFirefoxRelayServiceType( crossinline block: (FirefoxRelay) -> FirefoxRelay, ) { @@ -1624,6 +1671,19 @@ sealed class GeneratorAction { data class ApiKeyTextChange(val apiKey: String) : DuckDuckGo() } + /** + * Represents actions specifically related to the FastMail service. + */ + sealed class FastMail : ForwardedEmailAlias() { + + /** + * Fired when the api key input text is changed. + * + * @property apiKey The new api key text. + */ + data class ApiKeyTextChange(val apiKey: String) : FastMail() + } + /** * Represents actions specifically related to the FirefoxRelay service. */ 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 a5edecf9a..24bdbaee1 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 @@ -1055,6 +1055,46 @@ class GeneratorScreenTest : BaseComposeTest() { //endregion DuckDuckGo Service Type Tests + //region FastMail Service Type Tests + + @Suppress("MaxLineLength") + @Test + fun `in Username_ForwardedEmailAlias_FastMail state, updating api key text input should send ApiKeyTextChange action`() { + updateState( + GeneratorState( + generatedText = "Placeholder", + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FastMail(), + ), + ), + ), + ) + + val newApiKey = "apiKey" + + composeTestRule + .onNodeWithText("API key (required)") + .performScrollTo() + .performTextInput(newApiKey) + + verify { + viewModel.trySendAction( + GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.FastMail.ApiKeyTextChange( + apiKey = newApiKey, + ), + ) + } + } + + //endregion FastMail Service Type Tests + //region FirefoxRelay Service Type 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 4f248b95d..17020e03a 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 @@ -37,6 +37,9 @@ class GeneratorViewModelTest : BaseViewModelTest() { private val initialDuckDuckGoState = createDuckDuckGoState() private val duckDuckGoSavedStateHandle = createSavedStateHandleWithState(initialDuckDuckGoState) + private val initialFastMailState = createFastMailState() + private val fastMailSavedStateHandle = createSavedStateHandleWithState(initialFastMailState) + private val initialFirefoxRelay = createFirefoxRelayState() private val firefoxRelaySavedStateHandle = createSavedStateHandleWithState(initialFirefoxRelay) @@ -1083,6 +1086,50 @@ class GeneratorViewModelTest : BaseViewModelTest() { } } + @Nested + inner class FastMailActions { + private val defaultFastMailState = createFastMailState() + private lateinit var viewModel: GeneratorViewModel + + @BeforeEach + fun setup() { + viewModel = GeneratorViewModel(fastMailSavedStateHandle, fakeGeneratorRepository) + } + + @Test + fun `ApiKeyTextChange should update api key text correctly`() = runTest { + val newApiKey = "newApiKey" + val action = GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .FastMail.ApiKeyTextChange( + apiKey = newApiKey, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = defaultFastMailState.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FastMail( + apiKey = newApiKey, + ), + ), + ), + ) + + assertEquals(expectedState, viewModel.stateFlow.value) + } + } + @Nested inner class FirefoxRelayActions { private val defaultFirefoxRelayState = createFirefoxRelayState() @@ -1371,6 +1418,24 @@ class GeneratorViewModelTest : BaseViewModelTest() { ), ) + private fun createFastMailState( + generatedText: String = "defaultFastMail", + ): GeneratorState = + GeneratorState( + generatedText = generatedText, + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .FastMail(), + ), + ), + ) + private fun createFirefoxRelayState( generatedText: String = "defaultFirefoxRelay", ): GeneratorState =