mirror of
https://github.com/bitwarden/android.git
synced 2024-11-26 03:16:10 +03:00
BIT-1957: Update Email Alias generator UI to include 'ForwardEmail' (#1082)
This commit is contained in:
parent
54f5026047
commit
35e204d9c1
6 changed files with 442 additions and 0 deletions
|
@ -1062,6 +1062,31 @@ private fun ColumnScope.ForwardedEmailAliasTypeContent(
|
|||
)
|
||||
}
|
||||
|
||||
is ServiceType.ForwardEmail -> {
|
||||
BitwardenPasswordField(
|
||||
label = stringResource(id = R.string.api_key_required_parenthesis),
|
||||
value = usernameTypeState.selectedServiceType.apiKey,
|
||||
onValueChange = forwardedEmailAliasHandlers.onForwardEmailApiKeyTextChange,
|
||||
showPasswordTestTag = "ShowForwardedEmailApiSecretButton",
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics { testTag = "ForwardedEmailApiSecretEntry" }
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.domain_name_required_parenthesis),
|
||||
value = usernameTypeState.selectedServiceType.domainName,
|
||||
onValueChange = forwardedEmailAliasHandlers.onForwardEmailDomainNameTextChange,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics { testTag = "ForwardedEmailDomainNameEntry" }
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
is ServiceType.SimpleLogin -> {
|
||||
BitwardenPasswordField(
|
||||
label = stringResource(id = R.string.api_key_required_parenthesis),
|
||||
|
@ -1429,6 +1454,8 @@ private data class ForwardedEmailAliasHandlers(
|
|||
val onDuckDuckGoApiKeyTextChange: (String) -> Unit,
|
||||
val onFastMailApiKeyTextChange: (String) -> Unit,
|
||||
val onFirefoxRelayAccessTokenTextChange: (String) -> Unit,
|
||||
val onForwardEmailApiKeyTextChange: (String) -> Unit,
|
||||
val onForwardEmailDomainNameTextChange: (String) -> Unit,
|
||||
val onSimpleLoginApiKeyTextChange: (String) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
@ -1512,6 +1539,32 @@ private data class ForwardedEmailAliasHandlers(
|
|||
),
|
||||
)
|
||||
},
|
||||
onForwardEmailApiKeyTextChange = { newApiKey ->
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.ApiKeyTextChange(
|
||||
apiKey = newApiKey,
|
||||
),
|
||||
)
|
||||
},
|
||||
onForwardEmailDomainNameTextChange = { newDomainName ->
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.DomainNameTextChange(
|
||||
domainName = newDomainName,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSimpleLoginApiKeyTextChange = { newApiKey ->
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
|
|
|
@ -41,6 +41,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Us
|
|||
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.FastMail
|
||||
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.ForwardEmail
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.SimpleLogin
|
||||
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
|
||||
|
@ -169,6 +170,10 @@ class GeneratorViewModel @Inject constructor(
|
|||
handleSimpleLoginTextInputChange(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.ForwardEmail -> {
|
||||
handleForwardEmailSpecificAction(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange -> {
|
||||
handlePlusAddressedEmailTextInputChange(action)
|
||||
}
|
||||
|
@ -971,6 +976,15 @@ class GeneratorViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
ForwardedEmailAlias.ServiceTypeOption.FORWARD_EMAIL -> updateForwardedEmailAliasType {
|
||||
ForwardedEmailAlias(
|
||||
selectedServiceType = ForwardEmail(
|
||||
apiKey = options.forwardEmailApiAccessToken.orEmpty(),
|
||||
domainName = options.forwardEmailDomainName.orEmpty(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
ForwardedEmailAlias.ServiceTypeOption.SIMPLE_LOGIN -> updateForwardedEmailAliasType {
|
||||
ForwardedEmailAlias(
|
||||
selectedServiceType = SimpleLogin(
|
||||
|
@ -1102,6 +1116,73 @@ class GeneratorViewModel @Inject constructor(
|
|||
|
||||
//endregion FirefoxRelay Service Specific Handlers
|
||||
|
||||
//region ForwardEmail Email Specific Handlers
|
||||
|
||||
private fun handleForwardEmailSpecificAction(
|
||||
action: GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail,
|
||||
) {
|
||||
when (action) {
|
||||
is GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.ApiKeyTextChange,
|
||||
-> {
|
||||
handleForwardEmailApiKeyTextChange(action)
|
||||
}
|
||||
|
||||
is GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.DomainNameTextChange,
|
||||
-> {
|
||||
handleForwardEmailDomainNameTextChange(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleForwardEmailApiKeyTextChange(
|
||||
action: GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.ApiKeyTextChange,
|
||||
) {
|
||||
updateForwardEmailServiceType { forwardEmailServiceType ->
|
||||
val newApiKey = action.apiKey
|
||||
forwardEmailServiceType.copy(apiKey = newApiKey)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleForwardEmailDomainNameTextChange(
|
||||
action: GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.DomainNameTextChange,
|
||||
) {
|
||||
updateForwardEmailServiceType { forwardEmailServiceType ->
|
||||
val newDomainName = action.domainName
|
||||
forwardEmailServiceType.copy(domainName = newDomainName)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion ForwardEmail Email Specific Handlers
|
||||
|
||||
//region SimpleLogin Service Specific Handlers
|
||||
|
||||
private fun handleSimpleLoginTextInputChange(
|
||||
|
@ -1485,6 +1566,28 @@ class GeneratorViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun updateForwardEmailServiceType(
|
||||
crossinline block: (ForwardEmail) -> ForwardEmail,
|
||||
) {
|
||||
updateGeneratorMainTypeUsername { currentUsernameType ->
|
||||
if (currentUsernameType.selectedType !is ForwardedEmailAlias) {
|
||||
return@updateGeneratorMainTypeUsername currentUsernameType
|
||||
}
|
||||
|
||||
val currentServiceType = currentUsernameType.selectedType.selectedServiceType
|
||||
if (currentServiceType !is ForwardEmail) {
|
||||
return@updateGeneratorMainTypeUsername currentUsernameType
|
||||
}
|
||||
|
||||
currentUsernameType.copy(
|
||||
selectedType = ForwardedEmailAlias(
|
||||
selectedServiceType = block(currentServiceType),
|
||||
obfuscatedText = currentUsernameType.selectedType.obfuscatedText,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun updateCatchAllEmailType(
|
||||
crossinline block: (CatchAllEmail) -> CatchAllEmail,
|
||||
) {
|
||||
|
@ -1810,6 +1913,7 @@ data class GeneratorState(
|
|||
DUCK_DUCK_GO(R.string.duck_duck_go),
|
||||
FAST_MAIL(R.string.fastmail),
|
||||
FIREFOX_RELAY(R.string.firefox_relay),
|
||||
FORWARD_EMAIL(R.string.forward_email),
|
||||
SIMPLE_LOGIN(R.string.simple_login),
|
||||
}
|
||||
|
||||
|
@ -1886,6 +1990,22 @@ data class GeneratorState(
|
|||
get() = ServiceTypeOption.FIREFOX_RELAY.labelRes
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the ForwardEmail service type, with configurable options for
|
||||
* api key and domain name.
|
||||
*
|
||||
* @property apiKey The api key used for generation.
|
||||
* @property domainName The domain name used for generation.
|
||||
*/
|
||||
@Parcelize
|
||||
data class ForwardEmail(
|
||||
val apiKey: String = "",
|
||||
val domainName: String = "",
|
||||
) : ServiceType(), Parcelable {
|
||||
override val displayStringResId: Int
|
||||
get() = ServiceTypeOption.FORWARD_EMAIL.labelRes
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the SimpleLogin service type, with a configurable option for
|
||||
* api key.
|
||||
|
@ -2206,6 +2326,26 @@ sealed class GeneratorAction {
|
|||
data class AccessTokenTextChange(val accessToken: String) : FirefoxRelay()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents actions specifically related to the ForwardEmail service.
|
||||
*/
|
||||
sealed class ForwardEmail : ForwardedEmailAlias() {
|
||||
|
||||
/**
|
||||
* Fired when the api key input text is changed.
|
||||
*
|
||||
* @property apiKey The new api key text.
|
||||
*/
|
||||
data class ApiKeyTextChange(val apiKey: String) : ForwardEmail()
|
||||
|
||||
/**
|
||||
* Fires when the domain name input text is changed.
|
||||
*
|
||||
* @property domainName The new domain name text.
|
||||
*/
|
||||
data class DomainNameTextChange(val domainName: String) : ForwardEmail()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents actions specifically related to the SimpleLogin service.
|
||||
*/
|
||||
|
|
|
@ -60,6 +60,15 @@ fun ServiceType.toUsernameGeneratorRequest(): UsernameGeneratorRequest.Forwarded
|
|||
}
|
||||
}
|
||||
|
||||
is ServiceType.ForwardEmail -> {
|
||||
val apiKey = this.apiKey.orNullIfBlank() ?: return null
|
||||
val domainName = this.domainName.orNullIfBlank() ?: return null
|
||||
UsernameGeneratorRequest.Forwarded(
|
||||
service = ForwarderServiceType.ForwardEmail(apiKey, domainName),
|
||||
website = null,
|
||||
)
|
||||
}
|
||||
|
||||
is ServiceType.SimpleLogin -> {
|
||||
this
|
||||
.apiKey
|
||||
|
|
|
@ -1429,6 +1429,92 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
|
||||
//endregion SimpleLogin Service Type Tests
|
||||
|
||||
//region ForwardEmail Service Type Tests
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Username_ForwardedEmailAlias_ForwardEmail state, updating api key text input should send ApiKeyTextChange action`() {
|
||||
updateState(
|
||||
DEFAULT_STATE.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||
selectedServiceType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ServiceType
|
||||
.ForwardEmail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val newApiKey = "apiKey"
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("API key (required)")
|
||||
.performScrollTo()
|
||||
.performTextInput(newApiKey)
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.ApiKeyTextChange(
|
||||
apiKey = newApiKey,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Username_ForwardedEmailAlias_ForwardEmail state, updating domain name text input should send DomainNameChange action`() {
|
||||
updateState(
|
||||
DEFAULT_STATE.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||
selectedServiceType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ServiceType
|
||||
.ForwardEmail(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val newDomainName = "domainName"
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Domain name (required)")
|
||||
.performScrollTo()
|
||||
.performTextInput(newDomainName)
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.DomainNameTextChange(
|
||||
domainName = newDomainName,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion ForwardEmail Service Type Tests
|
||||
|
||||
//region Username Type Tests
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1588,6 +1588,99 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ForwardEmailActions {
|
||||
private val defaultForwardEmailState = createForwardEmailState()
|
||||
private lateinit var viewModel: GeneratorViewModel
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
viewModel = createViewModel(defaultForwardEmailState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ApiKeyTextChange should update api key text correctly`() {
|
||||
val newApiKey = "newApiKey"
|
||||
val action = GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.ApiKeyTextChange(
|
||||
apiKey = newApiKey,
|
||||
)
|
||||
|
||||
fakeGeneratorRepository.setMockGenerateForwardedServiceResult(
|
||||
GeneratedForwardedServiceUsernameResult.Success(
|
||||
generatedEmailAddress = "defaultForwardEmail",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = defaultForwardEmailState.copy(
|
||||
generatedText = "-",
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||
selectedServiceType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ServiceType
|
||||
.ForwardEmail(
|
||||
apiKey = newApiKey,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DomainNameTextChange should update domain name text correctly`() {
|
||||
val newDomainName = "newDomainName"
|
||||
val action = GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ForwardEmail
|
||||
.DomainNameTextChange(
|
||||
domainName = newDomainName,
|
||||
)
|
||||
|
||||
fakeGeneratorRepository.setMockGenerateForwardedServiceResult(
|
||||
GeneratedForwardedServiceUsernameResult.Success(
|
||||
generatedEmailAddress = "defaultForwardEmail",
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = defaultForwardEmailState.copy(
|
||||
generatedText = "-",
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||
selectedServiceType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ServiceType
|
||||
.ForwardEmail(
|
||||
domainName = newDomainName,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class SimpleLoginActions {
|
||||
private val defaultSimpleLoginState = createSimpleLoginState()
|
||||
|
@ -1943,6 +2036,25 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createForwardEmailState(
|
||||
generatedText: String = "defaultForwardEmail",
|
||||
): GeneratorState =
|
||||
GeneratorState(
|
||||
generatedText = generatedText,
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||
selectedServiceType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias
|
||||
.ServiceType
|
||||
.ForwardEmail(),
|
||||
),
|
||||
),
|
||||
currentEmailAddress = "currentEmail",
|
||||
)
|
||||
|
||||
private fun createSimpleLoginState(
|
||||
generatedText: String = "defaultSimpleLogin",
|
||||
): GeneratorState =
|
||||
|
|
|
@ -121,6 +121,48 @@ internal class ServiceTypeExtensionsTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUsernameGeneratorRequest for ForwardEmail returns null when apiKey is blank`() {
|
||||
val forwardMailServiceType = ServiceType.ForwardEmail(
|
||||
apiKey = "",
|
||||
domainName = "domainName",
|
||||
)
|
||||
val request = forwardMailServiceType.toUsernameGeneratorRequest()
|
||||
|
||||
assertNull(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUsernameGeneratorRequest for ForwardEmail returns null when domainName is blank`() {
|
||||
val forwardMailServiceType = ServiceType.ForwardEmail(
|
||||
apiKey = "apiKey",
|
||||
domainName = "",
|
||||
)
|
||||
val request = forwardMailServiceType.toUsernameGeneratorRequest()
|
||||
|
||||
assertNull(request)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUsernameGeneratorRequest for ForwardEmail returns correct request`() {
|
||||
val forwardEmailServiceType = ServiceType.ForwardEmail(
|
||||
apiKey = "apiKey",
|
||||
domainName = "domainName",
|
||||
)
|
||||
val request = forwardEmailServiceType.toUsernameGeneratorRequest()
|
||||
|
||||
assertEquals(
|
||||
UsernameGeneratorRequest.Forwarded(
|
||||
service = ForwarderServiceType.ForwardEmail(
|
||||
apiToken = "apiKey",
|
||||
domain = "domainName",
|
||||
),
|
||||
website = null,
|
||||
),
|
||||
request,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUsernameGeneratorRequest for SimpleLogin returns null when apiKey is blank`() {
|
||||
val simpleLoginServiceType = ServiceType.SimpleLogin(apiKey = "")
|
||||
|
|
Loading…
Reference in a new issue