Clean up the generator screen and handlers (#4270)

This commit is contained in:
David Perez 2024-11-11 10:07:16 -06:00 committed by GitHub
parent 6dd783051f
commit 16cc70f344
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 535 additions and 484 deletions

View file

@ -67,6 +67,20 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Pa
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
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.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.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOption
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.CatchAllEmailHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.ForwardedEmailAliasHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.PassphraseHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.PasswordHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.PlusAddressedEmailHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.RandomWordHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.UsernameTypeHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberCatchAllEmailHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberForwardedEmailAliasHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberPassphraseHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberPasswordHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberPlusAddressedEmailHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberRandomWordHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.handlers.rememberUsernameTypeHandlers
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -135,47 +149,26 @@ fun GeneratorScreen(
remember(viewModel) { remember(viewModel) {
{ {
viewModel.trySendAction( viewModel.trySendAction(
GeneratorAction.MainType.Username.UsernameTypeOptionSelect( GeneratorAction.MainType.Username.UsernameTypeOptionSelect(it),
it,
),
) )
} }
} }
val passwordHandlers = remember(viewModel) { val passwordHandlers = rememberPasswordHandlers(viewModel)
PasswordHandlers.create(viewModel = viewModel) val passphraseHandlers = rememberPassphraseHandlers(viewModel)
} val usernameTypeHandlers = rememberUsernameTypeHandlers(viewModel)
val forwardedEmailAliasHandlers = rememberForwardedEmailAliasHandlers(viewModel)
val passphraseHandlers = remember(viewModel) { val plusAddressedEmailHandlers = rememberPlusAddressedEmailHandlers(viewModel)
PassphraseHandlers.create(viewModel = viewModel) val catchAllEmailHandlers = rememberCatchAllEmailHandlers(viewModel)
} val randomWordHandlers = rememberRandomWordHandlers(viewModel)
val usernameTypeHandlers = remember(viewModel) {
UsernameTypeHandlers.create(viewModel = viewModel)
}
val forwardedEmailAliasHandlers = remember(viewModel) {
ForwardedEmailAliasHandlers.create(viewModel = viewModel)
}
val plusAddressedEmailHandlers = remember(viewModel) {
PlusAddressedEmailHandlers.create(viewModel = viewModel)
}
val catchAllEmailHandlers = remember(viewModel) {
CatchAllEmailHandlers.create(viewModel = viewModel)
}
val randomWordHandlers = remember(viewModel) {
RandomWordHandlers.create(viewModel = viewModel)
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold( BitwardenScaffold(
topBar = { topBar = {
when (state.generatorMode) { when (val generatorMode = state.generatorMode) {
is GeneratorMode.Modal -> { is GeneratorMode.Modal -> {
ModalAppBar( ModalAppBar(
generatorMode = generatorMode,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onCloseClick = remember(viewModel) { onCloseClick = remember(viewModel) {
{ viewModel.trySendAction(GeneratorAction.CloseClick) } { viewModel.trySendAction(GeneratorAction.CloseClick) }
@ -234,6 +227,7 @@ fun GeneratorScreen(
private fun DefaultAppBar( private fun DefaultAppBar(
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
onPasswordHistoryClick: () -> Unit, onPasswordHistoryClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenMediumTopAppBar( BitwardenMediumTopAppBar(
title = stringResource(id = R.string.generator), title = stringResource(id = R.string.generator),
@ -249,15 +243,18 @@ private fun DefaultAppBar(
), ),
) )
}, },
modifier = modifier,
) )
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ModalAppBar( private fun ModalAppBar(
generatorMode: GeneratorMode.Modal,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
onCloseClick: () -> Unit, onCloseClick: () -> Unit,
onSelectClick: () -> Unit, onSelectClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenTopAppBar( BitwardenTopAppBar(
title = stringResource(id = R.string.generator), title = stringResource(id = R.string.generator),
@ -265,6 +262,10 @@ private fun ModalAppBar(
navigationIconContentDescription = stringResource(id = R.string.close), navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = onCloseClick, onNavigationIconClick = onCloseClick,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
dividerStyle = when (generatorMode) {
GeneratorMode.Modal.Password -> TopAppBarDividerStyle.NONE
is GeneratorMode.Modal.Username -> TopAppBarDividerStyle.ON_SCROLL
},
actions = { actions = {
BitwardenTextButton( BitwardenTextButton(
label = stringResource(id = R.string.select), label = stringResource(id = R.string.select),
@ -272,6 +273,7 @@ private fun ModalAppBar(
modifier = Modifier.testTag("SelectButton"), modifier = Modifier.testTag("SelectButton"),
) )
}, },
modifier = modifier,
) )
} }
@ -365,6 +367,7 @@ private fun GeneratedStringItem(
generatedText: String, generatedText: String,
onCopyClick: () -> Unit, onCopyClick: () -> Unit,
onRegenerateClick: () -> Unit, onRegenerateClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenTextFieldWithActions( BitwardenTextFieldWithActions(
label = "", label = "",
@ -390,7 +393,7 @@ private fun GeneratedStringItem(
textStyle = BitwardenTheme.typography.sensitiveInfoSmall, textStyle = BitwardenTheme.typography.sensitiveInfoSmall,
shouldAddCustomLineBreaks = true, shouldAddCustomLineBreaks = true,
visualTransformation = nonLetterColorVisualTransformation(), visualTransformation = nonLetterColorVisualTransformation(),
modifier = Modifier.padding(horizontal = 16.dp), modifier = modifier.padding(horizontal = 16.dp),
) )
} }
@ -469,26 +472,25 @@ private fun ColumnScope.PasswordTypeContent(
PasswordCapitalLettersToggleItem( PasswordCapitalLettersToggleItem(
useCapitals = passwordTypeState.useCapitals, useCapitals = passwordTypeState.useCapitals,
onPasswordToggleCapitalLettersChange = onPasswordToggleCapitalLettersChange = passwordHandlers
passwordHandlers.onPasswordToggleCapitalLettersChange, .onPasswordToggleCapitalLettersChange,
enabled = passwordTypeState.capitalsEnabled, enabled = passwordTypeState.capitalsEnabled,
) )
PasswordLowercaseLettersToggleItem( PasswordLowercaseLettersToggleItem(
useLowercase = passwordTypeState.useLowercase, useLowercase = passwordTypeState.useLowercase,
onPasswordToggleLowercaseLettersChange = onPasswordToggleLowercaseLettersChange = passwordHandlers
passwordHandlers.onPasswordToggleLowercaseLettersChange, .onPasswordToggleLowercaseLettersChange,
enabled = passwordTypeState.lowercaseEnabled, enabled = passwordTypeState.lowercaseEnabled,
) )
PasswordNumbersToggleItem( PasswordNumbersToggleItem(
useNumbers = passwordTypeState.useNumbers, useNumbers = passwordTypeState.useNumbers,
onPasswordToggleNumbersChange = onPasswordToggleNumbersChange = passwordHandlers.onPasswordToggleNumbersChange,
passwordHandlers.onPasswordToggleNumbersChange,
enabled = passwordTypeState.numbersEnabled, enabled = passwordTypeState.numbersEnabled,
) )
PasswordSpecialCharactersToggleItem( PasswordSpecialCharactersToggleItem(
useSpecialChars = passwordTypeState.useSpecialChars, useSpecialChars = passwordTypeState.useSpecialChars,
onPasswordToggleSpecialCharactersChange = onPasswordToggleSpecialCharactersChange = passwordHandlers
passwordHandlers.onPasswordToggleSpecialCharactersChange, .onPasswordToggleSpecialCharactersChange,
enabled = passwordTypeState.specialCharsEnabled, enabled = passwordTypeState.specialCharsEnabled,
) )
} }
@ -497,8 +499,7 @@ private fun ColumnScope.PasswordTypeContent(
PasswordMinNumbersCounterItem( PasswordMinNumbersCounterItem(
minNumbers = passwordTypeState.minNumbers, minNumbers = passwordTypeState.minNumbers,
onPasswordMinNumbersCounterChange = onPasswordMinNumbersCounterChange = passwordHandlers.onPasswordMinNumbersCounterChange,
passwordHandlers.onPasswordMinNumbersCounterChange,
maxValue = max(passwordTypeState.maxNumbersAllowed, passwordTypeState.minNumbersAllowed), maxValue = max(passwordTypeState.maxNumbersAllowed, passwordTypeState.minNumbersAllowed),
minValue = passwordTypeState.minNumbersAllowed, minValue = passwordTypeState.minNumbersAllowed,
) )
@ -507,8 +508,8 @@ private fun ColumnScope.PasswordTypeContent(
PasswordMinSpecialCharactersCounterItem( PasswordMinSpecialCharactersCounterItem(
minSpecial = passwordTypeState.minSpecial, minSpecial = passwordTypeState.minSpecial,
onPasswordMinSpecialCharactersChange = onPasswordMinSpecialCharactersChange = passwordHandlers
passwordHandlers.onPasswordMinSpecialCharactersChange, .onPasswordMinSpecialCharactersChange,
maxValue = max(passwordTypeState.maxSpecialAllowed, passwordTypeState.minSpecialAllowed), maxValue = max(passwordTypeState.maxSpecialAllowed, passwordTypeState.minSpecialAllowed),
minValue = passwordTypeState.minSpecialAllowed, minValue = passwordTypeState.minSpecialAllowed,
) )
@ -517,8 +518,8 @@ private fun ColumnScope.PasswordTypeContent(
PasswordAvoidAmbiguousCharsToggleItem( PasswordAvoidAmbiguousCharsToggleItem(
avoidAmbiguousChars = passwordTypeState.avoidAmbiguousChars, avoidAmbiguousChars = passwordTypeState.avoidAmbiguousChars,
onPasswordToggleAvoidAmbiguousCharsChange = onPasswordToggleAvoidAmbiguousCharsChange = passwordHandlers
passwordHandlers.onPasswordToggleAvoidAmbiguousCharsChange, .onPasswordToggleAvoidAmbiguousCharsChange,
enabled = passwordTypeState.ambiguousCharsEnabled, enabled = passwordTypeState.ambiguousCharsEnabled,
) )
} }
@ -527,18 +528,19 @@ private fun ColumnScope.PasswordTypeContent(
private fun PasswordCapitalLettersToggleItem( private fun PasswordCapitalLettersToggleItem(
useCapitals: Boolean, useCapitals: Boolean,
onPasswordToggleCapitalLettersChange: (Boolean) -> Unit, onPasswordToggleCapitalLettersChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = "A—Z", label = "A—Z",
contentDescription = stringResource(id = R.string.uppercase_ato_z),
isChecked = useCapitals, isChecked = useCapitals,
onCheckedChange = onPasswordToggleCapitalLettersChange, onCheckedChange = onPasswordToggleCapitalLettersChange,
enabled = enabled, enabled = enabled,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("UppercaseAtoZToggle") .testTag("UppercaseAtoZToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.uppercase_ato_z),
) )
} }
@ -546,18 +548,19 @@ private fun PasswordCapitalLettersToggleItem(
private fun PasswordLowercaseLettersToggleItem( private fun PasswordLowercaseLettersToggleItem(
useLowercase: Boolean, useLowercase: Boolean,
onPasswordToggleLowercaseLettersChange: (Boolean) -> Unit, onPasswordToggleLowercaseLettersChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = "a—z", label = "a—z",
contentDescription = stringResource(id = R.string.lowercase_ato_z),
isChecked = useLowercase, isChecked = useLowercase,
onCheckedChange = onPasswordToggleLowercaseLettersChange, onCheckedChange = onPasswordToggleLowercaseLettersChange,
enabled = enabled, enabled = enabled,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("LowercaseAtoZToggle") .testTag("LowercaseAtoZToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.lowercase_ato_z),
) )
} }
@ -565,18 +568,19 @@ private fun PasswordLowercaseLettersToggleItem(
private fun PasswordNumbersToggleItem( private fun PasswordNumbersToggleItem(
useNumbers: Boolean, useNumbers: Boolean,
onPasswordToggleNumbersChange: (Boolean) -> Unit, onPasswordToggleNumbersChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = "0-9", label = "0-9",
contentDescription = stringResource(id = R.string.numbers_zero_to_nine),
isChecked = useNumbers, isChecked = useNumbers,
onCheckedChange = onPasswordToggleNumbersChange, onCheckedChange = onPasswordToggleNumbersChange,
enabled = enabled, enabled = enabled,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("NumbersZeroToNineToggle") .testTag("NumbersZeroToNineToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.numbers_zero_to_nine),
) )
} }
@ -584,18 +588,19 @@ private fun PasswordNumbersToggleItem(
private fun PasswordSpecialCharactersToggleItem( private fun PasswordSpecialCharactersToggleItem(
useSpecialChars: Boolean, useSpecialChars: Boolean,
onPasswordToggleSpecialCharactersChange: (Boolean) -> Unit, onPasswordToggleSpecialCharactersChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = "!@#$%^&*", label = "!@#$%^&*",
contentDescription = stringResource(id = R.string.special_characters),
isChecked = useSpecialChars, isChecked = useSpecialChars,
onCheckedChange = onPasswordToggleSpecialCharactersChange, onCheckedChange = onPasswordToggleSpecialCharactersChange,
enabled = enabled, enabled = enabled,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("SpecialCharactersToggle") .testTag("SpecialCharactersToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.special_characters),
) )
} }
@ -605,13 +610,14 @@ private fun PasswordMinNumbersCounterItem(
onPasswordMinNumbersCounterChange: (Int) -> Unit, onPasswordMinNumbersCounterChange: (Int) -> Unit,
minValue: Int, minValue: Int,
maxValue: Int, maxValue: Int,
modifier: Modifier = Modifier,
) { ) {
BitwardenStepper( BitwardenStepper(
label = stringResource(id = R.string.min_numbers), label = stringResource(id = R.string.min_numbers),
value = minNumbers.coerceIn(minValue, maxValue), value = minNumbers.coerceIn(minValue, maxValue),
range = minValue..maxValue, range = minValue..maxValue,
onValueChange = onPasswordMinNumbersCounterChange, onValueChange = onPasswordMinNumbersCounterChange,
modifier = Modifier modifier = modifier
.testTag("MinNumberValueLabel") .testTag("MinNumberValueLabel")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
@ -623,13 +629,14 @@ private fun PasswordMinSpecialCharactersCounterItem(
onPasswordMinSpecialCharactersChange: (Int) -> Unit, onPasswordMinSpecialCharactersChange: (Int) -> Unit,
minValue: Int, minValue: Int,
maxValue: Int, maxValue: Int,
modifier: Modifier = Modifier,
) { ) {
BitwardenStepper( BitwardenStepper(
label = stringResource(id = R.string.min_special), label = stringResource(id = R.string.min_special),
value = minSpecial.coerceIn(minValue, maxValue), value = minSpecial.coerceIn(minValue, maxValue),
range = minValue..maxValue, range = minValue..maxValue,
onValueChange = onPasswordMinSpecialCharactersChange, onValueChange = onPasswordMinSpecialCharactersChange,
modifier = Modifier modifier = modifier
.testTag("MinSpecialValueLabel") .testTag("MinSpecialValueLabel")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
@ -639,6 +646,7 @@ private fun PasswordMinSpecialCharactersCounterItem(
private fun PasswordAvoidAmbiguousCharsToggleItem( private fun PasswordAvoidAmbiguousCharsToggleItem(
avoidAmbiguousChars: Boolean, avoidAmbiguousChars: Boolean,
onPasswordToggleAvoidAmbiguousCharsChange: (Boolean) -> Unit, onPasswordToggleAvoidAmbiguousCharsChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
@ -646,7 +654,7 @@ private fun PasswordAvoidAmbiguousCharsToggleItem(
isChecked = avoidAmbiguousChars, isChecked = avoidAmbiguousChars,
enabled = enabled, enabled = enabled,
onCheckedChange = onPasswordToggleAvoidAmbiguousCharsChange, onCheckedChange = onPasswordToggleAvoidAmbiguousCharsChange,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("AvoidAmbiguousCharsToggle") .testTag("AvoidAmbiguousCharsToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -666,8 +674,7 @@ private fun ColumnScope.PassphraseTypeContent(
PassphraseNumWordsCounterItem( PassphraseNumWordsCounterItem(
numWords = passphraseTypeState.numWords, numWords = passphraseTypeState.numWords,
onPassphraseNumWordsCounterChange = onPassphraseNumWordsCounterChange = passphraseHandlers.onPassphraseNumWordsCounterChange,
passphraseHandlers.onPassphraseNumWordsCounterChange,
minValue = passphraseTypeState.minNumWords, minValue = passphraseTypeState.minNumWords,
maxValue = passphraseTypeState.maxNumWords, maxValue = passphraseTypeState.maxNumWords,
) )
@ -686,14 +693,14 @@ private fun ColumnScope.PassphraseTypeContent(
) { ) {
PassphraseCapitalizeToggleItem( PassphraseCapitalizeToggleItem(
capitalize = passphraseTypeState.capitalize, capitalize = passphraseTypeState.capitalize,
onPassphraseCapitalizeToggleChange = onPassphraseCapitalizeToggleChange = passphraseHandlers
passphraseHandlers.onPassphraseCapitalizeToggleChange, .onPassphraseCapitalizeToggleChange,
enabled = passphraseTypeState.capitalizeEnabled, enabled = passphraseTypeState.capitalizeEnabled,
) )
PassphraseIncludeNumberToggleItem( PassphraseIncludeNumberToggleItem(
includeNumber = passphraseTypeState.includeNumber, includeNumber = passphraseTypeState.includeNumber,
onPassphraseIncludeNumberToggleChange = onPassphraseIncludeNumberToggleChange = passphraseHandlers
passphraseHandlers.onPassphraseIncludeNumberToggleChange, .onPassphraseIncludeNumberToggleChange,
enabled = passphraseTypeState.includeNumberEnabled, enabled = passphraseTypeState.includeNumberEnabled,
) )
} }
@ -703,6 +710,7 @@ private fun ColumnScope.PassphraseTypeContent(
private fun PassphraseNumWordsCounterItem( private fun PassphraseNumWordsCounterItem(
numWords: Int, numWords: Int,
onPassphraseNumWordsCounterChange: (Int) -> Unit, onPassphraseNumWordsCounterChange: (Int) -> Unit,
modifier: Modifier = Modifier,
minValue: Int = PASSPHRASE_MIN_NUMBER_OF_WORDS, minValue: Int = PASSPHRASE_MIN_NUMBER_OF_WORDS,
maxValue: Int = PASSPHRASE_MAX_NUMBER_OF_WORDS, maxValue: Int = PASSPHRASE_MAX_NUMBER_OF_WORDS,
) { ) {
@ -714,7 +722,7 @@ private fun PassphraseNumWordsCounterItem(
range = minValue..maxValue, range = minValue..maxValue,
onValueChange = onPassphraseNumWordsCounterChange, onValueChange = onPassphraseNumWordsCounterChange,
stepperActionsTestTag = "NumberOfWordsStepper", stepperActionsTestTag = "NumberOfWordsStepper",
modifier = Modifier modifier = modifier
.testTag("NumberOfWordsLabel") .testTag("NumberOfWordsLabel")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
) )
@ -724,6 +732,7 @@ private fun PassphraseNumWordsCounterItem(
private fun PassphraseWordSeparatorInputItem( private fun PassphraseWordSeparatorInputItem(
wordSeparator: Char?, wordSeparator: Char?,
onPassphraseWordSeparatorChange: (wordSeparator: Char?) -> Unit, onPassphraseWordSeparatorChange: (wordSeparator: Char?) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenTextField( BitwardenTextField(
label = stringResource(id = R.string.word_separator), label = stringResource(id = R.string.word_separator),
@ -737,7 +746,7 @@ private fun PassphraseWordSeparatorInputItem(
onPassphraseWordSeparatorChange(char) onPassphraseWordSeparatorChange(char)
} }
}, },
modifier = Modifier modifier = modifier
.testTag("WordSeparatorEntry") .testTag("WordSeparatorEntry")
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -748,6 +757,7 @@ private fun PassphraseWordSeparatorInputItem(
private fun PassphraseCapitalizeToggleItem( private fun PassphraseCapitalizeToggleItem(
capitalize: Boolean, capitalize: Boolean,
onPassphraseCapitalizeToggleChange: (Boolean) -> Unit, onPassphraseCapitalizeToggleChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
BitwardenSwitch( BitwardenSwitch(
@ -755,7 +765,7 @@ private fun PassphraseCapitalizeToggleItem(
isChecked = capitalize, isChecked = capitalize,
onCheckedChange = onPassphraseCapitalizeToggleChange, onCheckedChange = onPassphraseCapitalizeToggleChange,
enabled = enabled, enabled = enabled,
modifier = Modifier modifier = modifier
.testTag("CapitalizePassphraseToggle") .testTag("CapitalizePassphraseToggle")
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -767,13 +777,14 @@ private fun PassphraseIncludeNumberToggleItem(
includeNumber: Boolean, includeNumber: Boolean,
onPassphraseIncludeNumberToggleChange: (Boolean) -> Unit, onPassphraseIncludeNumberToggleChange: (Boolean) -> Unit,
enabled: Boolean, enabled: Boolean,
modifier: Modifier = Modifier,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = stringResource(id = R.string.include_number), label = stringResource(id = R.string.include_number),
isChecked = includeNumber, isChecked = includeNumber,
enabled = enabled, enabled = enabled,
onCheckedChange = onPassphraseIncludeNumberToggleChange, onCheckedChange = onPassphraseIncludeNumberToggleChange,
modifier = Modifier modifier = modifier
.testTag("IncludeNumbersToggle") .testTag("IncludeNumbersToggle")
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -832,6 +843,7 @@ private fun UsernameOptionsItem(
currentSubState: GeneratorState.MainType.Username, currentSubState: GeneratorState.MainType.Username,
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit, onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
usernameTypeHandlers: UsernameTypeHandlers, usernameTypeHandlers: UsernameTypeHandlers,
modifier: Modifier = Modifier,
) { ) {
val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.entries val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.entries
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) } val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
@ -845,10 +857,6 @@ private fun UsernameOptionsItem(
optionsWithStrings.entries.first { it.value == selectedOption }.key optionsWithStrings.entries.first { it.value == selectedOption }.key
onSubStateOptionClicked(selectedOptionId) onSubStateOptionClicked(selectedOptionId)
}, },
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
.testTag("UsernameTypePicker"),
supportingText = currentSubState.selectedType.supportingStringResId?.let { supportingText = currentSubState.selectedType.supportingStringResId?.let {
stringResource(id = it) stringResource(id = it)
}, },
@ -856,6 +864,10 @@ private fun UsernameOptionsItem(
onClick = usernameTypeHandlers.onUsernameTooltipClicked, onClick = usernameTypeHandlers.onUsernameTooltipClicked,
contentDescription = stringResource(id = R.string.learn_more), contentDescription = stringResource(id = R.string.learn_more),
), ),
modifier = modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
.testTag("UsernameTypePicker"),
) )
} }
@ -1002,6 +1014,7 @@ private fun ColumnScope.ForwardedEmailAliasTypeContent(
private fun ServiceTypeOptionsItem( private fun ServiceTypeOptionsItem(
currentSubState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias, currentSubState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias,
onSubStateOptionClicked: (ServiceTypeOption) -> Unit, onSubStateOptionClicked: (ServiceTypeOption) -> Unit,
modifier: Modifier = Modifier,
) { ) {
val possibleSubStates = ServiceTypeOption.entries val possibleSubStates = ServiceTypeOption.entries
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) } val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
@ -1017,7 +1030,7 @@ private fun ServiceTypeOptionsItem(
optionsWithStrings.entries.first { it.value == selectedOption }.key optionsWithStrings.entries.first { it.value == selectedOption }.key
onSubStateOptionClicked(selectedOptionId) onSubStateOptionClicked(selectedOptionId)
}, },
modifier = Modifier modifier = modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.testTag("ServiceTypePicker") .testTag("ServiceTypePicker")
.fillMaxWidth(), .fillMaxWidth(),
@ -1043,14 +1056,13 @@ private fun ColumnScope.PlusAddressedEmailTypeContent(
private fun PlusAddressedEmailTextInputItem( private fun PlusAddressedEmailTextInputItem(
email: String, email: String,
onPlusAddressedEmailTextChange: (email: String) -> Unit, onPlusAddressedEmailTextChange: (email: String) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenTextField( BitwardenTextField(
label = stringResource(id = R.string.email_required_parenthesis), label = stringResource(id = R.string.email_required_parenthesis),
value = email, value = email,
onValueChange = { onValueChange = onPlusAddressedEmailTextChange,
onPlusAddressedEmailTextChange(it) modifier = modifier
},
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("PlusAddressedEmailEntry") .testTag("PlusAddressedEmailEntry")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -1078,14 +1090,13 @@ private fun ColumnScope.CatchAllEmailTypeContent(
private fun CatchAllEmailTextInputItem( private fun CatchAllEmailTextInputItem(
domain: String, domain: String,
onDomainTextChange: (domain: String) -> Unit, onDomainTextChange: (domain: String) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenTextField( BitwardenTextField(
label = stringResource(id = R.string.domain_name_required_parenthesis), label = stringResource(id = R.string.domain_name_required_parenthesis),
value = domain, value = domain,
onValueChange = { onValueChange = onDomainTextChange,
onDomainTextChange(it) modifier = modifier
},
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("CatchAllEmailDomainEntry") .testTag("CatchAllEmailDomainEntry")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -1118,12 +1129,13 @@ private fun ColumnScope.RandomWordTypeContent(
private fun RandomWordCapitalizeToggleItem( private fun RandomWordCapitalizeToggleItem(
capitalize: Boolean, capitalize: Boolean,
onRandomWordCapitalizeToggleChange: (Boolean) -> Unit, onRandomWordCapitalizeToggleChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = stringResource(id = R.string.capitalize), label = stringResource(id = R.string.capitalize),
isChecked = capitalize, isChecked = capitalize,
onCheckedChange = onRandomWordCapitalizeToggleChange, onCheckedChange = onRandomWordCapitalizeToggleChange,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("CapitalizeRandomWordUsernameToggle") .testTag("CapitalizeRandomWordUsernameToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -1134,12 +1146,13 @@ private fun RandomWordCapitalizeToggleItem(
private fun RandomWordIncludeNumberToggleItem( private fun RandomWordIncludeNumberToggleItem(
includeNumber: Boolean, includeNumber: Boolean,
onRandomWordIncludeNumberToggleChange: (Boolean) -> Unit, onRandomWordIncludeNumberToggleChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenSwitch( BitwardenSwitch(
label = stringResource(id = R.string.include_number), label = stringResource(id = R.string.include_number),
isChecked = includeNumber, isChecked = includeNumber,
onCheckedChange = onRandomWordIncludeNumberToggleChange, onCheckedChange = onRandomWordIncludeNumberToggleChange,
modifier = Modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.testTag("IncludeNumberRandomWordUsernameToggle") .testTag("IncludeNumberRandomWordUsernameToggle")
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
@ -1150,7 +1163,7 @@ private fun RandomWordIncludeNumberToggleItem(
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun GeneratorPreview() { private fun Generator_preview() {
BitwardenTheme { BitwardenTheme {
GeneratorScreen( GeneratorScreen(
onNavigateToPasswordHistory = {}, onNavigateToPasswordHistory = {},
@ -1158,404 +1171,3 @@ private fun GeneratorPreview() {
) )
} }
} }
/**
* A class dedicated to handling user interactions related to password configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
@Suppress("LongParameterList")
private data class PasswordHandlers(
val onPasswordSliderLengthChange: (Int, Boolean) -> Unit,
val onPasswordToggleCapitalLettersChange: (Boolean) -> Unit,
val onPasswordToggleLowercaseLettersChange: (Boolean) -> Unit,
val onPasswordToggleNumbersChange: (Boolean) -> Unit,
val onPasswordToggleSpecialCharactersChange: (Boolean) -> Unit,
val onPasswordMinNumbersCounterChange: (Int) -> Unit,
val onPasswordMinSpecialCharactersChange: (Int) -> Unit,
val onPasswordToggleAvoidAmbiguousCharsChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
@Suppress("LongMethod")
fun create(viewModel: GeneratorViewModel): PasswordHandlers {
return PasswordHandlers(
onPasswordSliderLengthChange = { newLength, isUserInteracting ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.SliderLengthChange(
length = newLength,
isUserInteracting = isUserInteracting,
),
)
},
onPasswordToggleCapitalLettersChange = { shouldUseCapitals ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleCapitalLettersChange(
useCapitals = shouldUseCapitals,
),
)
},
onPasswordToggleLowercaseLettersChange = { shouldUseLowercase ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleLowercaseLettersChange(
useLowercase = shouldUseLowercase,
),
)
},
onPasswordToggleNumbersChange = { shouldUseNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleNumbersChange(
useNumbers = shouldUseNumbers,
),
)
},
onPasswordToggleSpecialCharactersChange = { shouldUseSpecialChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleSpecialCharactersChange(
useSpecialChars = shouldUseSpecialChars,
),
)
},
onPasswordMinNumbersCounterChange = { newMinNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = newMinNumbers,
),
)
},
onPasswordMinSpecialCharactersChange = { newMinSpecial ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = newMinSpecial,
),
)
},
onPasswordToggleAvoidAmbiguousCharsChange = { shouldAvoidAmbiguousChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = shouldAvoidAmbiguousChars,
),
)
},
)
}
}
}
/**
* A class dedicated to handling user interactions related to passphrase configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
private data class PassphraseHandlers(
val onPassphraseNumWordsCounterChange: (Int) -> Unit,
val onPassphraseWordSeparatorChange: (Char?) -> Unit,
val onPassphraseCapitalizeToggleChange: (Boolean) -> Unit,
val onPassphraseIncludeNumberToggleChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
fun create(viewModel: GeneratorViewModel): PassphraseHandlers {
return PassphraseHandlers(
onPassphraseNumWordsCounterChange = { changeInCounter ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(
numWords = changeInCounter,
),
)
},
onPassphraseWordSeparatorChange = { newSeparator ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.WordSeparatorTextChange(
wordSeparator = newSeparator,
),
)
},
onPassphraseCapitalizeToggleChange = { shouldCapitalize ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.ToggleCapitalizeChange(
capitalize = shouldCapitalize,
),
)
},
onPassphraseIncludeNumberToggleChange = { shouldIncludeNumber ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.ToggleIncludeNumberChange(
includeNumber = shouldIncludeNumber,
),
)
},
)
}
}
}
/**
* A class dedicated to handling user interactions related to all username configurations.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
@Suppress("LongParameterList")
private data class UsernameTypeHandlers(
val onUsernameTooltipClicked: () -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
fun create(viewModel: GeneratorViewModel): UsernameTypeHandlers {
return UsernameTypeHandlers(
onUsernameTooltipClicked = {
viewModel.trySendAction(
GeneratorAction.MainType.Username.UsernameType.TooltipClick,
)
},
)
}
}
}
/**
* 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.
*/
@Suppress("LongParameterList")
private data class ForwardedEmailAliasHandlers(
val onServiceChange: (ServiceTypeOption) -> Unit,
val onAddyIoAccessTokenTextChange: (String) -> Unit,
val onAddyIoDomainNameTextChange: (String) -> Unit,
val onDuckDuckGoApiKeyTextChange: (String) -> Unit,
val onFastMailApiKeyTextChange: (String) -> Unit,
val onFirefoxRelayAccessTokenTextChange: (String) -> Unit,
val onForwardEmailApiKeyTextChange: (String) -> Unit,
val onForwardEmailDomainNameTextChange: (String) -> Unit,
val onSimpleLoginApiKeyTextChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
@Suppress("LongMethod")
fun create(viewModel: GeneratorViewModel): ForwardedEmailAliasHandlers {
return ForwardedEmailAliasHandlers(
onServiceChange = { newServiceTypeOption ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.ServiceTypeOptionSelect(
serviceTypeOption = newServiceTypeOption,
),
)
},
onAddyIoAccessTokenTextChange = { newAccessToken ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.AddyIo
.AccessTokenTextChange(
accessToken = newAccessToken,
),
)
},
onAddyIoDomainNameTextChange = { newDomainName ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.AddyIo
.DomainTextChange(
domain = newDomainName,
),
)
},
onDuckDuckGoApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.DuckDuckGo
.ApiKeyTextChange(
apiKey = newApiKey,
),
)
},
onFastMailApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.FastMail
.ApiKeyTextChange(
apiKey = newApiKey,
),
)
},
onFirefoxRelayAccessTokenTextChange = { newAccessToken ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.FirefoxRelay
.AccessTokenTextChange(
accessToken = newAccessToken,
),
)
},
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
.MainType
.Username
.UsernameType
.ForwardedEmailAlias
.SimpleLogin
.ApiKeyTextChange(
apiKey = newApiKey,
),
)
},
)
}
}
}
/**
* A class dedicated to handling user interactions related to plus addressed email
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
private data class PlusAddressedEmailHandlers(
val onEmailChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
fun create(viewModel: GeneratorViewModel): PlusAddressedEmailHandlers {
return PlusAddressedEmailHandlers(
onEmailChange = { newEmail ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.PlusAddressedEmail
.EmailTextChange(
email = newEmail,
),
)
},
)
}
}
}
/**
* A class dedicated to handling user interactions related to plus addressed email
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
private data class CatchAllEmailHandlers(
val onDomainChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
fun create(viewModel: GeneratorViewModel): CatchAllEmailHandlers {
return CatchAllEmailHandlers(
onDomainChange = { newDomain ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.CatchAllEmail
.DomainTextChange(
domain = newDomain,
),
)
},
)
}
}
}
/**
* A class dedicated to handling user interactions related to Random Word
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
private data class RandomWordHandlers(
val onCapitalizeChange: (Boolean) -> Unit,
val onIncludeNumberChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
fun create(viewModel: GeneratorViewModel): RandomWordHandlers {
return RandomWordHandlers(
onCapitalizeChange = { shouldCapitalize ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.RandomWord
.ToggleCapitalizeChange(
capitalize = shouldCapitalize,
),
)
},
onIncludeNumberChange = { shouldIncludeNumber ->
viewModel.trySendAction(
GeneratorAction
.MainType
.Username
.UsernameType
.RandomWord
.ToggleIncludeNumberChange(
includeNumber = shouldIncludeNumber,
),
)
},
)
}
}
}

View file

@ -0,0 +1,40 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.Username.UsernameType.CatchAllEmail as CatchAllEmailAction
/**
* A class dedicated to handling user interactions related to plus addressed email
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
data class CatchAllEmailHandlers(
val onDomainChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [CatchAllEmailHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): CatchAllEmailHandlers = CatchAllEmailHandlers(
onDomainChange = { newDomain ->
viewModel.trySendAction(CatchAllEmailAction.DomainTextChange(domain = newDomain))
},
)
}
}
/**
* Helper function to remember a [CatchAllEmailHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberCatchAllEmailHandlers(viewModel: GeneratorViewModel): CatchAllEmailHandlers =
remember(viewModel) {
CatchAllEmailHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,102 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias as ForwardedEmailAliasAction
/**
* 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.
*/
@Suppress("LongParameterList")
data class ForwardedEmailAliasHandlers(
val onServiceChange: (ForwardedEmailAlias.ServiceTypeOption) -> Unit,
val onAddyIoAccessTokenTextChange: (String) -> Unit,
val onAddyIoDomainNameTextChange: (String) -> Unit,
val onDuckDuckGoApiKeyTextChange: (String) -> Unit,
val onFastMailApiKeyTextChange: (String) -> Unit,
val onFirefoxRelayAccessTokenTextChange: (String) -> Unit,
val onForwardEmailApiKeyTextChange: (String) -> Unit,
val onForwardEmailDomainNameTextChange: (String) -> Unit,
val onSimpleLoginApiKeyTextChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [ForwardedEmailAliasHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): ForwardedEmailAliasHandlers = ForwardedEmailAliasHandlers(
onServiceChange = { newServiceTypeOption ->
viewModel.trySendAction(
ForwardedEmailAliasAction.ServiceTypeOptionSelect(
serviceTypeOption = newServiceTypeOption,
),
)
},
onAddyIoAccessTokenTextChange = { newAccessToken ->
viewModel.trySendAction(
ForwardedEmailAliasAction.AddyIo.AccessTokenTextChange(
accessToken = newAccessToken,
),
)
},
onAddyIoDomainNameTextChange = { newDomainName ->
viewModel.trySendAction(
ForwardedEmailAliasAction.AddyIo.DomainTextChange(domain = newDomainName),
)
},
onDuckDuckGoApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
ForwardedEmailAliasAction.DuckDuckGo.ApiKeyTextChange(apiKey = newApiKey),
)
},
onFastMailApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
ForwardedEmailAliasAction.FastMail.ApiKeyTextChange(apiKey = newApiKey),
)
},
onFirefoxRelayAccessTokenTextChange = { newAccessToken ->
viewModel.trySendAction(
ForwardedEmailAliasAction.FirefoxRelay.AccessTokenTextChange(
accessToken = newAccessToken,
),
)
},
onForwardEmailApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
ForwardedEmailAliasAction.ForwardEmail.ApiKeyTextChange(apiKey = newApiKey),
)
},
onForwardEmailDomainNameTextChange = { newDomainName ->
viewModel.trySendAction(
ForwardedEmailAliasAction.ForwardEmail.DomainNameTextChange(
domainName = newDomainName,
),
)
},
onSimpleLoginApiKeyTextChange = { newApiKey ->
viewModel.trySendAction(
ForwardedEmailAliasAction.SimpleLogin.ApiKeyTextChange(apiKey = newApiKey),
)
},
)
}
}
/**
* Helper function to remember a [ForwardedEmailAliasHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberForwardedEmailAliasHandlers(
viewModel: GeneratorViewModel,
): ForwardedEmailAliasHandlers =
remember(viewModel) {
ForwardedEmailAliasHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,67 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
/**
* A class dedicated to handling user interactions related to passphrase configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
data class PassphraseHandlers(
val onPassphraseNumWordsCounterChange: (Int) -> Unit,
val onPassphraseWordSeparatorChange: (Char?) -> Unit,
val onPassphraseCapitalizeToggleChange: (Boolean) -> Unit,
val onPassphraseIncludeNumberToggleChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [PassphraseHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): PassphraseHandlers = PassphraseHandlers(
onPassphraseNumWordsCounterChange = { changeInCounter ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(
numWords = changeInCounter,
),
)
},
onPassphraseWordSeparatorChange = { newSeparator ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.WordSeparatorTextChange(
wordSeparator = newSeparator,
),
)
},
onPassphraseCapitalizeToggleChange = { shouldCapitalize ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.ToggleCapitalizeChange(
capitalize = shouldCapitalize,
),
)
},
onPassphraseIncludeNumberToggleChange = { shouldIncludeNumber ->
viewModel.trySendAction(
GeneratorAction.MainType.Passphrase.ToggleIncludeNumberChange(
includeNumber = shouldIncludeNumber,
),
)
},
)
}
}
/**
* Helper function to remember a [PassphraseHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberPassphraseHandlers(viewModel: GeneratorViewModel): PassphraseHandlers =
remember(viewModel) {
PassphraseHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,101 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
/**
* A class dedicated to handling user interactions related to password configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
@Suppress("LongParameterList")
data class PasswordHandlers(
val onPasswordSliderLengthChange: (Int, Boolean) -> Unit,
val onPasswordToggleCapitalLettersChange: (Boolean) -> Unit,
val onPasswordToggleLowercaseLettersChange: (Boolean) -> Unit,
val onPasswordToggleNumbersChange: (Boolean) -> Unit,
val onPasswordToggleSpecialCharactersChange: (Boolean) -> Unit,
val onPasswordMinNumbersCounterChange: (Int) -> Unit,
val onPasswordMinSpecialCharactersChange: (Int) -> Unit,
val onPasswordToggleAvoidAmbiguousCharsChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [PasswordHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): PasswordHandlers = PasswordHandlers(
onPasswordSliderLengthChange = { newLength, isUserInteracting ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.SliderLengthChange(
length = newLength,
isUserInteracting = isUserInteracting,
),
)
},
onPasswordToggleCapitalLettersChange = { shouldUseCapitals ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleCapitalLettersChange(
useCapitals = shouldUseCapitals,
),
)
},
onPasswordToggleLowercaseLettersChange = { shouldUseLowercase ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleLowercaseLettersChange(
useLowercase = shouldUseLowercase,
),
)
},
onPasswordToggleNumbersChange = { shouldUseNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleNumbersChange(
useNumbers = shouldUseNumbers,
),
)
},
onPasswordToggleSpecialCharactersChange = { shouldUseSpecialChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleSpecialCharactersChange(
useSpecialChars = shouldUseSpecialChars,
),
)
},
onPasswordMinNumbersCounterChange = { newMinNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = newMinNumbers,
),
)
},
onPasswordMinSpecialCharactersChange = { newMinSpecial ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = newMinSpecial,
),
)
},
onPasswordToggleAvoidAmbiguousCharsChange = { shouldAvoidAmbiguousChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = shouldAvoidAmbiguousChars,
),
)
},
)
}
}
/**
* Helper function to remember a [PasswordHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberPasswordHandlers(viewModel: GeneratorViewModel): PasswordHandlers =
remember(viewModel) {
PasswordHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,41 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail as PlusAddressedEmailAction
/**
* A class dedicated to handling user interactions related to plus addressed email
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
data class PlusAddressedEmailHandlers(
val onEmailChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [PlusAddressedEmailHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(viewModel: GeneratorViewModel): PlusAddressedEmailHandlers =
PlusAddressedEmailHandlers(
onEmailChange = { newEmail ->
viewModel.trySendAction(
PlusAddressedEmailAction.EmailTextChange(email = newEmail),
)
},
)
}
}
/**
* Helper function to remember a [PlusAddressedEmailHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberPlusAddressedEmailHandlers(viewModel: GeneratorViewModel): PlusAddressedEmailHandlers =
remember(viewModel) {
PlusAddressedEmailHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,48 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.Username.UsernameType.RandomWord as RandomWordAction
/**
* A class dedicated to handling user interactions related to Random Word
* configuration.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
data class RandomWordHandlers(
val onCapitalizeChange: (Boolean) -> Unit,
val onIncludeNumberChange: (Boolean) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [RandomWordHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): RandomWordHandlers = RandomWordHandlers(
onCapitalizeChange = { shouldCapitalize ->
viewModel.trySendAction(
RandomWordAction.ToggleCapitalizeChange(capitalize = shouldCapitalize),
)
},
onIncludeNumberChange = { shouldIncludeNumber ->
viewModel.trySendAction(
RandomWordAction.ToggleIncludeNumberChange(includeNumber = shouldIncludeNumber),
)
},
)
}
}
/**
* Helper function to remember a [RandomWordHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberRandomWordHandlers(viewModel: GeneratorViewModel): RandomWordHandlers =
remember(viewModel) {
RandomWordHandlers.create(viewModel = viewModel)
}

View file

@ -0,0 +1,40 @@
package com.x8bit.bitwarden.ui.tools.feature.generator.handlers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorViewModel
/**
* A class dedicated to handling user interactions related to all username configurations.
* Each lambda corresponds to a specific user action, allowing for easy delegation of
* logic when user input is detected.
*/
@Suppress("LongParameterList")
data class UsernameTypeHandlers(
val onUsernameTooltipClicked: () -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [UsernameTypeHandlers] by binding actions to the provided
* [GeneratorViewModel].
*/
fun create(
viewModel: GeneratorViewModel,
): UsernameTypeHandlers = UsernameTypeHandlers(
onUsernameTooltipClicked = {
viewModel.trySendAction(GeneratorAction.MainType.Username.UsernameType.TooltipClick)
},
)
}
}
/**
* Helper function to remember a [UsernameTypeHandlers] instance in a [Composable] scope.
*/
@Composable
fun rememberUsernameTypeHandlers(viewModel: GeneratorViewModel): UsernameTypeHandlers =
remember(viewModel) {
UsernameTypeHandlers.create(viewModel = viewModel)
}