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 72089a168..82a9bef1e 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 @@ -890,7 +890,14 @@ private fun ColumnScope.ForwardedEmailAliasTypeContent( } is ServiceType.SimpleLogin -> { - // TODO: SimpleLogin Service Implementation (BIT-713) + BitwardenPasswordField( + label = stringResource(id = R.string.api_key_required_parenthesis), + value = usernameTypeState.selectedServiceType.apiKey, + onValueChange = forwardedEmailAliasHandlers.onSimpleLoginApiKeyTextChange, + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + ) } null -> { @@ -1209,6 +1216,7 @@ private class PassphraseHandlers( * Each lambda corresponds to a specific user action, allowing for easy delegation of * logic when user input is detected. */ +@Suppress("LongParameterList") private class ForwardedEmailAliasHandlers( val onServiceChange: (ServiceTypeOption) -> Unit, val onAddyIoAccessTokenTextChange: (String) -> Unit, @@ -1216,6 +1224,7 @@ private class ForwardedEmailAliasHandlers( val onDuckDuckGoApiKeyTextChange: (String) -> Unit, val onFastMailApiKeyTextChange: (String) -> Unit, val onFirefoxRelayAccessTokenTextChange: (String) -> Unit, + val onSimpleLoginApiKeyTextChange: (String) -> Unit, ) { companion object { @Suppress("LongMethod") @@ -1298,6 +1307,19 @@ private class ForwardedEmailAliasHandlers( ), ) }, + onSimpleLoginApiKeyTextChange = { newApiKey -> + viewModel.trySendAction( + GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .ApiKeyTextChange( + apiKey = newApiKey, + ), + ) + }, ) } } 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 c74399dda..c62f6f034 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 @@ -71,7 +71,7 @@ class GeneratorViewModel @Inject constructor( } } - @Suppress("MaxLineLength") + @Suppress("MaxLineLength", "LongMethod") override fun handleAction(action: GeneratorAction) { when (action) { is GeneratorAction.PasswordHistoryClick -> { @@ -134,6 +134,10 @@ class GeneratorViewModel @Inject constructor( handleFirefoxRelayTextInputChange(action) } + is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.SimpleLogin.ApiKeyTextChange -> { + handleSimpleLoginTextInputChange(action) + } + is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange -> { handlePlusAddressedEmailTextInputChange(action) } @@ -734,6 +738,25 @@ class GeneratorViewModel @Inject constructor( //endregion FirefoxRelay Service Specific Handlers + //region SimpleLogin Service Specific Handlers + + private fun handleSimpleLoginTextInputChange( + action: GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .ApiKeyTextChange, + ) { + updateSimpleLoginServiceType { simpleLoginServiceType -> + val newApiKey = action.apiKey + simpleLoginServiceType.copy(apiKey = newApiKey) + } + } + + //endregion SimpleLogin Service Specific Handlers + //region Plus Addressed Email Specific Handlers private fun handlePlusAddressedEmailTextInputChange( @@ -988,6 +1011,30 @@ class GeneratorViewModel @Inject constructor( } } + private inline fun updateSimpleLoginServiceType( + crossinline block: (SimpleLogin) -> SimpleLogin, + ) { + updateGeneratorMainTypeUsername { currentUsernameType -> + if (currentUsernameType.selectedType !is ForwardedEmailAlias) { + return@updateGeneratorMainTypeUsername currentUsernameType + } + + val currentServiceType = (currentUsernameType.selectedType).selectedServiceType + if (currentServiceType !is SimpleLogin) { + 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, ) { @@ -1696,6 +1743,19 @@ sealed class GeneratorAction { */ data class AccessTokenTextChange(val accessToken: String) : FirefoxRelay() } + + /** + * Represents actions specifically related to the SimpleLogin service. + */ + sealed class SimpleLogin : ForwardedEmailAlias() { + + /** + * Fired when the api key input text is changed. + * + * @property apiKey The new api key text. + */ + data class ApiKeyTextChange(val apiKey: String) : SimpleLogin() + } } /** 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 24bdbaee1..09db7f3d5 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 @@ -1141,6 +1141,46 @@ class GeneratorScreenTest : BaseComposeTest() { //endregion FirefoxRelay Service Type Tests + //region SimpleLogin Service Type Tests + + @Suppress("MaxLineLength") + @Test + fun `in Username_ForwardedEmailAlias_SimpleLogin 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 + .SimpleLogin(), + ), + ), + ), + ) + + val newApiKey = "apiKey" + + composeTestRule + .onNodeWithText("API key (required)") + .performScrollTo() + .performTextInput(newApiKey) + + verify { + viewModel.trySendAction( + GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.SimpleLogin.ApiKeyTextChange( + apiKey = newApiKey, + ), + ) + } + } + + //endregion SimpleLogin 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 17020e03a..fa30076a4 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 @@ -43,6 +43,9 @@ class GeneratorViewModelTest : BaseViewModelTest() { private val initialFirefoxRelay = createFirefoxRelayState() private val firefoxRelaySavedStateHandle = createSavedStateHandleWithState(initialFirefoxRelay) + private val initialSimpleLogin = createSimpleLoginState() + private val simpleLoginSavedStateHandle = createSavedStateHandleWithState(initialSimpleLogin) + private val initialCatchAllEmailState = createCatchAllEmailState() private val catchAllEmailSavedStateHandle = createSavedStateHandleWithState(initialCatchAllEmailState) @@ -1175,6 +1178,51 @@ class GeneratorViewModelTest : BaseViewModelTest() { } } + @Nested + inner class SimpleLoginActions { + private val defaultSimpleLoginState = createSimpleLoginState() + private lateinit var viewModel: GeneratorViewModel + + @BeforeEach + fun setup() { + viewModel = GeneratorViewModel(simpleLoginSavedStateHandle, fakeGeneratorRepository) + } + + @Test + fun `ApiKeyTextChange should update api key text correctly`() = runTest { + val newApiKey = "newApiKey" + val action = GeneratorAction + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .SimpleLogin + .ApiKeyTextChange( + apiKey = newApiKey, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = defaultSimpleLoginState.copy( + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .SimpleLogin( + apiKey = newApiKey, + ), + ), + ), + ) + + assertEquals(expectedState, viewModel.stateFlow.value) + } + } + @Nested inner class PlusAddressedEmailActions { private val defaultPlusAddressedEmailState = createPlusAddressedEmailState() @@ -1454,6 +1502,24 @@ class GeneratorViewModelTest : BaseViewModelTest() { ), ) + private fun createSimpleLoginState( + generatedText: String = "defaultSimpleLogin", + ): GeneratorState = + GeneratorState( + generatedText = generatedText, + selectedType = GeneratorState.MainType.Username( + GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias( + selectedServiceType = GeneratorState + .MainType + .Username + .UsernameType + .ForwardedEmailAlias + .ServiceType + .SimpleLogin(), + ), + ), + ) + private fun createPlusAddressedEmailState( generatedText: String = "defaultPlusAddressedEmail", email: String = "defaultEmail",