mirror of
https://github.com/bitwarden/android.git
synced 2025-02-17 04:19:54 +03:00
BIT-871: Adding UI for forwarded email alias (#440)
This commit is contained in:
parent
2a9cec5f8e
commit
492038f163
5 changed files with 326 additions and 9 deletions
|
@ -44,7 +44,8 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
*
|
*
|
||||||
* @param label The descriptive text label for the [OutlinedTextField].
|
* @param label The descriptive text label for the [OutlinedTextField].
|
||||||
* @param options A list of strings representing the available options in the dialog.
|
* @param options A list of strings representing the available options in the dialog.
|
||||||
* @param selectedOption The currently selected option that is displayed in the [OutlinedTextField].
|
* @param selectedOption The currently selected option that is displayed in the [OutlinedTextField]
|
||||||
|
* (or `null` if no option is selected).
|
||||||
* @param onOptionSelected A lambda that is invoked when an option
|
* @param onOptionSelected A lambda that is invoked when an option
|
||||||
* is selected from the dropdown menu.
|
* is selected from the dropdown menu.
|
||||||
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
|
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
|
||||||
|
@ -56,7 +57,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
fun BitwardenMultiSelectButton(
|
fun BitwardenMultiSelectButton(
|
||||||
label: String,
|
label: String,
|
||||||
options: ImmutableList<String>,
|
options: ImmutableList<String>,
|
||||||
selectedOption: String,
|
selectedOption: String?,
|
||||||
onOptionSelected: (String) -> Unit,
|
onOptionSelected: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
supportingText: String? = null,
|
supportingText: String? = null,
|
||||||
|
@ -104,7 +105,7 @@ fun BitwardenMultiSelectButton(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
value = selectedOption,
|
value = selectedOption ?: "",
|
||||||
onValueChange = onOptionSelected,
|
onValueChange = onOptionSelected,
|
||||||
enabled = shouldShowDialog,
|
enabled = shouldShowDialog,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
|
|
|
@ -47,7 +47,6 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.toDp
|
import com.x8bit.bitwarden.ui.platform.base.util.toDp
|
||||||
|
@ -56,6 +55,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenReadOnlyTextFieldWithActions
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenReadOnlyTextFieldWithActions
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
||||||
|
@ -67,6 +67,8 @@ import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||||
|
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.ForwardedEmailAlias.ServiceTypeOption
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MAX_NUMBER_OF_WORDS
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MAX_NUMBER_OF_WORDS
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MAX
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MAX
|
||||||
|
@ -151,6 +153,10 @@ fun GeneratorScreen(
|
||||||
PassphraseHandlers.create(viewModel = viewModel)
|
PassphraseHandlers.create(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val forwardedEmailAliasHandlers = remember(viewModel) {
|
||||||
|
ForwardedEmailAliasHandlers.create(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
val plusAddressedEmailHandlers = remember(viewModel) {
|
val plusAddressedEmailHandlers = remember(viewModel) {
|
||||||
PlusAddressedEmailHandlers.create(viewModel = viewModel)
|
PlusAddressedEmailHandlers.create(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
|
@ -203,6 +209,7 @@ fun GeneratorScreen(
|
||||||
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
|
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
|
||||||
passwordHandlers = passwordHandlers,
|
passwordHandlers = passwordHandlers,
|
||||||
passphraseHandlers = passphraseHandlers,
|
passphraseHandlers = passphraseHandlers,
|
||||||
|
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
|
||||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||||
catchAllEmailHandlers = catchAllEmailHandlers,
|
catchAllEmailHandlers = catchAllEmailHandlers,
|
||||||
randomWordHandlers = randomWordHandlers,
|
randomWordHandlers = randomWordHandlers,
|
||||||
|
@ -224,6 +231,7 @@ private fun ScrollContent(
|
||||||
onUsernameSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
onUsernameSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||||
passwordHandlers: PasswordHandlers,
|
passwordHandlers: PasswordHandlers,
|
||||||
passphraseHandlers: PassphraseHandlers,
|
passphraseHandlers: PassphraseHandlers,
|
||||||
|
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
|
||||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||||
catchAllEmailHandlers: CatchAllEmailHandlers,
|
catchAllEmailHandlers: CatchAllEmailHandlers,
|
||||||
randomWordHandlers: RandomWordHandlers,
|
randomWordHandlers: RandomWordHandlers,
|
||||||
|
@ -274,6 +282,7 @@ private fun ScrollContent(
|
||||||
UsernameTypeItems(
|
UsernameTypeItems(
|
||||||
usernameState = selectedType,
|
usernameState = selectedType,
|
||||||
onSubStateOptionClicked = onUsernameSubStateOptionClicked,
|
onSubStateOptionClicked = onUsernameSubStateOptionClicked,
|
||||||
|
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
|
||||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||||
catchAllEmailHandlers = catchAllEmailHandlers,
|
catchAllEmailHandlers = catchAllEmailHandlers,
|
||||||
randomWordHandlers = randomWordHandlers,
|
randomWordHandlers = randomWordHandlers,
|
||||||
|
@ -734,6 +743,7 @@ private fun PassphraseIncludeNumberToggleItem(
|
||||||
private fun ColumnScope.UsernameTypeItems(
|
private fun ColumnScope.UsernameTypeItems(
|
||||||
usernameState: GeneratorState.MainType.Username,
|
usernameState: GeneratorState.MainType.Username,
|
||||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||||
|
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
|
||||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||||
catchAllEmailHandlers: CatchAllEmailHandlers,
|
catchAllEmailHandlers: CatchAllEmailHandlers,
|
||||||
randomWordHandlers: RandomWordHandlers,
|
randomWordHandlers: RandomWordHandlers,
|
||||||
|
@ -749,7 +759,10 @@ private fun ColumnScope.UsernameTypeItems(
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias -> {
|
is GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias -> {
|
||||||
// TODO: Implement ForwardedEmailAlias BIT-657
|
ForwardedEmailAliasTypeContent(
|
||||||
|
usernameTypeState = selectedType,
|
||||||
|
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is GeneratorState.MainType.Username.UsernameType.CatchAllEmail -> {
|
is GeneratorState.MainType.Username.UsernameType.CatchAllEmail -> {
|
||||||
|
@ -773,7 +786,7 @@ private fun UsernameOptionsItem(
|
||||||
currentSubState: GeneratorState.MainType.Username,
|
currentSubState: GeneratorState.MainType.Username,
|
||||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||||
) {
|
) {
|
||||||
val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.values().toList()
|
val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.entries
|
||||||
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
|
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
|
||||||
|
|
||||||
BitwardenMultiSelectButton(
|
BitwardenMultiSelectButton(
|
||||||
|
@ -802,6 +815,83 @@ private fun UsernameOptionsItem(
|
||||||
|
|
||||||
//endregion UsernameType Composables
|
//endregion UsernameType Composables
|
||||||
|
|
||||||
|
//region ForwardedEmailAliasType Composables
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.ForwardedEmailAliasTypeContent(
|
||||||
|
usernameTypeState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias,
|
||||||
|
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
ServiceTypeOptionsItem(
|
||||||
|
currentSubState = usernameTypeState,
|
||||||
|
onSubStateOptionClicked = forwardedEmailAliasHandlers.onServiceChange,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
when (usernameTypeState.selectedServiceType) {
|
||||||
|
|
||||||
|
is ServiceType.AnonAddy -> {
|
||||||
|
// TODO: AnonAddy Service Implementation (BIT-711)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ServiceType.DuckDuckGo -> {
|
||||||
|
// TODO: DuckDuckGo Service Implementation (BIT-714)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ServiceType.FastMail -> {
|
||||||
|
// TODO: FastMail Service Implementation (BIT-712)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ServiceType.FirefoxRelay -> {
|
||||||
|
// TODO: FirefoxRelay Service Implementation (BIT-1196)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ServiceType.SimpleLogin -> {
|
||||||
|
// TODO: SimpleLogin Service Implementation (BIT-713)
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> {
|
||||||
|
var obfuscatedTextField by remember { mutableStateOf("") }
|
||||||
|
BitwardenPasswordField(
|
||||||
|
label = "",
|
||||||
|
value = obfuscatedTextField,
|
||||||
|
onValueChange = { obfuscatedTextField = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ServiceTypeOptionsItem(
|
||||||
|
currentSubState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias,
|
||||||
|
onSubStateOptionClicked: (ServiceTypeOption) -> Unit,
|
||||||
|
) {
|
||||||
|
val possibleSubStates = ServiceTypeOption.entries
|
||||||
|
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
|
||||||
|
|
||||||
|
BitwardenMultiSelectButton(
|
||||||
|
label = stringResource(id = R.string.service),
|
||||||
|
options = optionsWithStrings.values.toImmutableList(),
|
||||||
|
selectedOption = (currentSubState.selectedServiceType?.displayStringResId)?.let {
|
||||||
|
stringResource(id = it)
|
||||||
|
},
|
||||||
|
onOptionSelected = { selectedOption ->
|
||||||
|
val selectedOptionId =
|
||||||
|
optionsWithStrings.entries.first { it.value == selectedOption }.key
|
||||||
|
onSubStateOptionClicked(selectedOptionId)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
//region PlusAddressedEmailType Composables
|
//region PlusAddressedEmailType Composables
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -1073,6 +1163,35 @@ private class PassphraseHandlers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class dedicated to handling user interactions related to forwarded email alias
|
||||||
|
* configuration.
|
||||||
|
* Each lambda corresponds to a specific user action, allowing for easy delegation of
|
||||||
|
* logic when user input is detected.
|
||||||
|
*/
|
||||||
|
private class ForwardedEmailAliasHandlers(
|
||||||
|
val onServiceChange: (ServiceTypeOption) -> Unit,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun create(viewModel: GeneratorViewModel): ForwardedEmailAliasHandlers {
|
||||||
|
return ForwardedEmailAliasHandlers(
|
||||||
|
onServiceChange = { newServiceTypeOption ->
|
||||||
|
viewModel.trySendAction(
|
||||||
|
GeneratorAction
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOptionSelect(
|
||||||
|
serviceTypeOption = newServiceTypeOption,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class dedicated to handling user interactions related to plus addressed email
|
* A class dedicated to handling user interactions related to plus addressed email
|
||||||
* configuration.
|
* configuration.
|
||||||
|
|
|
@ -21,7 +21,8 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Pa
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeTypeOption
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeTypeOption
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username
|
||||||
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.CatchAllEmail
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.AnonAddy
|
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
|
||||||
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.PlusAddressedEmail
|
||||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.RandomWord
|
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.RandomWord
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -44,6 +45,7 @@ private const val KEY_STATE = "state"
|
||||||
*
|
*
|
||||||
* @property savedStateHandle Handles the saved state of this ViewModel.
|
* @property savedStateHandle Handles the saved state of this ViewModel.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("LargeClass")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class GeneratorViewModel @Inject constructor(
|
class GeneratorViewModel @Inject constructor(
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
@ -107,6 +109,10 @@ class GeneratorViewModel @Inject constructor(
|
||||||
handleUsernameTypeOptionSelect(action)
|
handleUsernameTypeOptionSelect(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOptionSelect -> {
|
||||||
|
handleServiceTypeOptionSelect(action)
|
||||||
|
}
|
||||||
|
|
||||||
is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange -> {
|
is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange -> {
|
||||||
handlePlusAddressedEmailTextInputChange(action)
|
handlePlusAddressedEmailTextInputChange(action)
|
||||||
}
|
}
|
||||||
|
@ -553,6 +559,41 @@ class GeneratorViewModel @Inject constructor(
|
||||||
|
|
||||||
//endregion Username Type Handlers
|
//endregion Username Type Handlers
|
||||||
|
|
||||||
|
//region Forwarded Email Alias Specific Handlers
|
||||||
|
|
||||||
|
private fun handleServiceTypeOptionSelect(
|
||||||
|
action: GeneratorAction
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOptionSelect,
|
||||||
|
) {
|
||||||
|
when (action.serviceTypeOption) {
|
||||||
|
ForwardedEmailAlias.ServiceTypeOption.ANON_ADDY -> updateForwardedEmailAliasType {
|
||||||
|
ForwardedEmailAlias(selectedServiceType = ServiceType.AnonAddy())
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardedEmailAlias.ServiceTypeOption.DUCK_DUCK_GO -> updateForwardedEmailAliasType {
|
||||||
|
ForwardedEmailAlias(selectedServiceType = ServiceType.DuckDuckGo())
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardedEmailAlias.ServiceTypeOption.FAST_MAIL -> updateForwardedEmailAliasType {
|
||||||
|
ForwardedEmailAlias(selectedServiceType = ServiceType.FastMail())
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardedEmailAlias.ServiceTypeOption.FIREFOX_RELAY -> updateForwardedEmailAliasType {
|
||||||
|
ForwardedEmailAlias(selectedServiceType = ServiceType.FirefoxRelay())
|
||||||
|
}
|
||||||
|
|
||||||
|
ForwardedEmailAlias.ServiceTypeOption.SIMPLE_LOGIN -> updateForwardedEmailAliasType {
|
||||||
|
ForwardedEmailAlias(selectedServiceType = ServiceType.SimpleLogin())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion Forwarded Email Alias Specific Handlers
|
||||||
|
|
||||||
//region Plus Addressed Email Specific Handlers
|
//region Plus Addressed Email Specific Handlers
|
||||||
|
|
||||||
private fun handlePlusAddressedEmailTextInputChange(
|
private fun handlePlusAddressedEmailTextInputChange(
|
||||||
|
@ -699,6 +740,18 @@ class GeneratorViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun updateForwardedEmailAliasType(
|
||||||
|
crossinline block: (ForwardedEmailAlias) -> ForwardedEmailAlias,
|
||||||
|
) {
|
||||||
|
updateGeneratorMainTypeUsername { currentSelectedType ->
|
||||||
|
val currentUsernameType = currentSelectedType.selectedType
|
||||||
|
if (currentUsernameType !is ForwardedEmailAlias) {
|
||||||
|
return@updateGeneratorMainTypeUsername currentSelectedType
|
||||||
|
}
|
||||||
|
currentSelectedType.copy(selectedType = block(currentUsernameType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private inline fun updatePlusAddressedEmailType(
|
private inline fun updatePlusAddressedEmailType(
|
||||||
crossinline block: (PlusAddressedEmail) -> PlusAddressedEmail,
|
crossinline block: (PlusAddressedEmail) -> PlusAddressedEmail,
|
||||||
) {
|
) {
|
||||||
|
@ -1007,7 +1060,8 @@ data class GeneratorState(
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class ForwardedEmailAlias(
|
data class ForwardedEmailAlias(
|
||||||
val selectedServiceType: ServiceType = AnonAddy(),
|
val selectedServiceType: ServiceType? = null,
|
||||||
|
val obfuscatedText: String = "",
|
||||||
) : UsernameType(), Parcelable {
|
) : UsernameType(), Parcelable {
|
||||||
override val displayStringResId: Int
|
override val displayStringResId: Int
|
||||||
get() = UsernameTypeOption.FORWARDED_EMAIL_ALIAS.labelRes
|
get() = UsernameTypeOption.FORWARDED_EMAIL_ALIAS.labelRes
|
||||||
|
@ -1042,7 +1096,7 @@ data class GeneratorState(
|
||||||
* this property to provide the appropriate string resource ID for
|
* this property to provide the appropriate string resource ID for
|
||||||
* its display string.
|
* its display string.
|
||||||
*/
|
*/
|
||||||
abstract val displayStringResId: Int
|
abstract val displayStringResId: Int?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the Anon Addy service type, with a configurable option for
|
* Represents the Anon Addy service type, with a configurable option for
|
||||||
|
@ -1329,6 +1383,26 @@ sealed class GeneratorAction {
|
||||||
*/
|
*/
|
||||||
sealed class UsernameType : Username() {
|
sealed class UsernameType : Username() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents actions specifically related to Forwarded Email Alias.
|
||||||
|
*/
|
||||||
|
sealed class ForwardedEmailAlias : UsernameType() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the action of selecting a service type option.
|
||||||
|
*
|
||||||
|
* @property serviceTypeOption The selected service type option.
|
||||||
|
*/
|
||||||
|
data class ServiceTypeOptionSelect(
|
||||||
|
val serviceTypeOption: GeneratorState
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOption,
|
||||||
|
) : ForwardedEmailAlias()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents actions specifically related to Plus Addressed Email.
|
* Represents actions specifically related to Plus Addressed Email.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -874,6 +874,59 @@ class GeneratorScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
//endregion Passcode Passphrase Tests
|
//endregion Passcode Passphrase Tests
|
||||||
|
|
||||||
|
//region Forwarded Email Alias Tests
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `in Username_ForwardedEmailAlias state, updating the service type should send ServiceTypeOptionSelect action`() {
|
||||||
|
updateState(
|
||||||
|
GeneratorState(
|
||||||
|
generatedText = "Placeholder",
|
||||||
|
selectedType = GeneratorState.MainType.Username(
|
||||||
|
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||||
|
selectedServiceType = null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val newServiceType = GeneratorState
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOption
|
||||||
|
.ANON_ADDY
|
||||||
|
|
||||||
|
// Opens the menu
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithContentDescription(label = "Service, null")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
// Choose the option from the menu
|
||||||
|
composeTestRule
|
||||||
|
.onAllNodesWithText(text = "addy.io")
|
||||||
|
.onLast()
|
||||||
|
.assert(hasAnyAncestor(isDialog()))
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(
|
||||||
|
GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOptionSelect(
|
||||||
|
newServiceType,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure dialog is hidden:
|
||||||
|
composeTestRule
|
||||||
|
.onNode(isDialog())
|
||||||
|
.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion Forwarded Email Alias Tests
|
||||||
|
|
||||||
//region Username Plus Addressed Email Tests
|
//region Username Plus Addressed Email Tests
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
|
|
|
@ -27,6 +27,10 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
||||||
private val initialUsernameState = createPlusAddressedEmailState()
|
private val initialUsernameState = createPlusAddressedEmailState()
|
||||||
private val usernameSavedStateHandle = createSavedStateHandleWithState(initialUsernameState)
|
private val usernameSavedStateHandle = createSavedStateHandleWithState(initialUsernameState)
|
||||||
|
|
||||||
|
private val initialForwardedEmailAliasState = createForwardedEmailAliasState()
|
||||||
|
private val forwardedEmailAliasSavedStateHandle =
|
||||||
|
createSavedStateHandleWithState(initialForwardedEmailAliasState)
|
||||||
|
|
||||||
private val initialCatchAllEmailState = createCatchAllEmailState()
|
private val initialCatchAllEmailState = createCatchAllEmailState()
|
||||||
private val catchAllEmailSavedStateHandle =
|
private val catchAllEmailSavedStateHandle =
|
||||||
createSavedStateHandleWithState(initialCatchAllEmailState)
|
createSavedStateHandleWithState(initialCatchAllEmailState)
|
||||||
|
@ -895,6 +899,58 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class ForwardedEmailAliasActions {
|
||||||
|
private val defaultForwardedEmailAliasState = createForwardedEmailAliasState()
|
||||||
|
private lateinit var viewModel: GeneratorViewModel
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
viewModel =
|
||||||
|
GeneratorViewModel(forwardedEmailAliasSavedStateHandle, fakeGeneratorRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ServiceTypeOptionSelect should update service type correctly`() = runTest {
|
||||||
|
val action = GeneratorAction
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOptionSelect(
|
||||||
|
serviceTypeOption = GeneratorState
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceTypeOption
|
||||||
|
.ANON_ADDY,
|
||||||
|
)
|
||||||
|
|
||||||
|
viewModel.actionChannel.trySend(action)
|
||||||
|
|
||||||
|
val expectedState = defaultForwardedEmailAliasState.copy(
|
||||||
|
selectedType = GeneratorState.MainType.Username(
|
||||||
|
selectedType = GeneratorState
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias(
|
||||||
|
selectedServiceType = GeneratorState
|
||||||
|
.MainType
|
||||||
|
.Username
|
||||||
|
.UsernameType
|
||||||
|
.ForwardedEmailAlias
|
||||||
|
.ServiceType
|
||||||
|
.AnonAddy(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class PlusAddressedEmailActions {
|
inner class PlusAddressedEmailActions {
|
||||||
private val defaultPlusAddressedEmailState = createPlusAddressedEmailState()
|
private val defaultPlusAddressedEmailState = createPlusAddressedEmailState()
|
||||||
|
@ -1088,6 +1144,20 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun createForwardedEmailAliasState(
|
||||||
|
generatedText: String = "defaultForwardedEmailAlias",
|
||||||
|
obfuscatedText: String = "defaultObfuscatedText",
|
||||||
|
): GeneratorState =
|
||||||
|
GeneratorState(
|
||||||
|
generatedText = generatedText,
|
||||||
|
selectedType = GeneratorState.MainType.Username(
|
||||||
|
GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias(
|
||||||
|
selectedServiceType = null,
|
||||||
|
obfuscatedText = obfuscatedText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private fun createPlusAddressedEmailState(
|
private fun createPlusAddressedEmailState(
|
||||||
generatedText: String = "defaultPlusAddressedEmail",
|
generatedText: String = "defaultPlusAddressedEmail",
|
||||||
email: String = "defaultEmail",
|
email: String = "defaultEmail",
|
||||||
|
|
Loading…
Add table
Reference in a new issue