mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1087: Adding navigation for the username types tooltip in generator. (#874)
This commit is contained in:
parent
5fffd4e3e2
commit
7b6f9491b3
4 changed files with 120 additions and 4 deletions
|
@ -46,6 +46,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
|
@ -68,7 +69,9 @@ import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
|||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
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
|
||||
|
@ -92,6 +95,7 @@ fun GeneratorScreen(
|
|||
viewModel: GeneratorViewModel = hiltViewModel(),
|
||||
onNavigateToPasswordHistory: () -> Unit,
|
||||
onNavigateBack: () -> Unit,
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
|
@ -109,6 +113,12 @@ fun GeneratorScreen(
|
|||
)
|
||||
}
|
||||
|
||||
is GeneratorEvent.NavigateToTooltip -> {
|
||||
intentManager.launchUri(
|
||||
"https://bitwarden.com/help/generator/#username-types".toUri(),
|
||||
)
|
||||
}
|
||||
|
||||
GeneratorEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +165,10 @@ fun GeneratorScreen(
|
|||
PassphraseHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val usernameTypeHandlers = remember(viewModel) {
|
||||
UsernameTypeHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val forwardedEmailAliasHandlers = remember(viewModel) {
|
||||
ForwardedEmailAliasHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
@ -216,6 +230,7 @@ fun GeneratorScreen(
|
|||
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
|
||||
passwordHandlers = passwordHandlers,
|
||||
passphraseHandlers = passphraseHandlers,
|
||||
usernameTypeHandlers = usernameTypeHandlers,
|
||||
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
|
||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||
catchAllEmailHandlers = catchAllEmailHandlers,
|
||||
|
@ -287,6 +302,7 @@ private fun ScrollContent(
|
|||
onUsernameSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
passwordHandlers: PasswordHandlers,
|
||||
passphraseHandlers: PassphraseHandlers,
|
||||
usernameTypeHandlers: UsernameTypeHandlers,
|
||||
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
|
||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||
catchAllEmailHandlers: CatchAllEmailHandlers,
|
||||
|
@ -339,6 +355,7 @@ private fun ScrollContent(
|
|||
is GeneratorState.MainType.Username -> {
|
||||
UsernameTypeItems(
|
||||
usernameState = selectedType,
|
||||
usernameTypeHandlers = usernameTypeHandlers,
|
||||
onSubStateOptionClicked = onUsernameSubStateOptionClicked,
|
||||
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
|
||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||
|
@ -821,12 +838,13 @@ private fun PassphraseIncludeNumberToggleItem(
|
|||
private fun ColumnScope.UsernameTypeItems(
|
||||
usernameState: GeneratorState.MainType.Username,
|
||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
usernameTypeHandlers: UsernameTypeHandlers,
|
||||
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
|
||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||
catchAllEmailHandlers: CatchAllEmailHandlers,
|
||||
randomWordHandlers: RandomWordHandlers,
|
||||
) {
|
||||
UsernameOptionsItem(usernameState, onSubStateOptionClicked)
|
||||
UsernameOptionsItem(usernameState, onSubStateOptionClicked, usernameTypeHandlers)
|
||||
|
||||
when (val selectedType = usernameState.selectedType) {
|
||||
is GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail -> {
|
||||
|
@ -863,6 +881,7 @@ private fun ColumnScope.UsernameTypeItems(
|
|||
private fun UsernameOptionsItem(
|
||||
currentSubState: GeneratorState.MainType.Username,
|
||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
usernameTypeHandlers: UsernameTypeHandlers,
|
||||
) {
|
||||
val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.entries
|
||||
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
|
||||
|
@ -884,9 +903,7 @@ private fun UsernameOptionsItem(
|
|||
stringResource(id = it)
|
||||
},
|
||||
tooltip = TooltipData(
|
||||
onClick = {
|
||||
// TODO: "?" icon redirects user to appropriate link (BIT-1087)
|
||||
},
|
||||
onClick = usernameTypeHandlers.onUsernameTooltipClicked,
|
||||
contentDescription = stringResource(id = R.string.learn_more),
|
||||
),
|
||||
)
|
||||
|
@ -1308,6 +1325,28 @@ private data class PassphraseHandlers(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
) {
|
||||
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.
|
||||
|
|
|
@ -163,6 +163,10 @@ class GeneratorViewModel @Inject constructor(
|
|||
handleUsernameTypeOptionSelect(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameType.TooltipClick -> {
|
||||
handleTooltipClick()
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOptionSelect -> {
|
||||
handleServiceTypeOptionSelect(action)
|
||||
}
|
||||
|
@ -503,6 +507,10 @@ class GeneratorViewModel @Inject constructor(
|
|||
clipboardManager.setText(text = state.generatedText)
|
||||
}
|
||||
|
||||
private fun handleTooltipClick() {
|
||||
sendEvent(GeneratorEvent.NavigateToTooltip)
|
||||
}
|
||||
|
||||
private fun handleUpdateGeneratedPasswordResult(
|
||||
action: GeneratorAction.Internal.UpdateGeneratedPasswordResult,
|
||||
) {
|
||||
|
@ -2023,6 +2031,11 @@ sealed class GeneratorAction {
|
|||
*/
|
||||
sealed class UsernameType : Username() {
|
||||
|
||||
/**
|
||||
* Represents the action to learn more.
|
||||
*/
|
||||
data object TooltipClick : GeneratorAction()
|
||||
|
||||
/**
|
||||
* Represents actions specifically related to Forwarded Email Alias.
|
||||
*/
|
||||
|
@ -2230,6 +2243,11 @@ sealed class GeneratorEvent {
|
|||
*/
|
||||
data object NavigateBack : GeneratorEvent()
|
||||
|
||||
/**
|
||||
* Navigate back to learn more screen.
|
||||
*/
|
||||
data object NavigateToTooltip : GeneratorEvent()
|
||||
|
||||
/**
|
||||
* Displays the message in a snackbar.
|
||||
*/
|
||||
|
|
|
@ -10,10 +10,12 @@ import androidx.compose.ui.test.assertIsOn
|
|||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.hasClickAction
|
||||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.hasProgressBarRangeInfo
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onChildren
|
||||
import androidx.compose.ui.test.onLast
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
|
@ -24,12 +26,16 @@ import androidx.compose.ui.test.performTextInput
|
|||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeRight
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
|
@ -47,6 +53,9 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { launchUri(any()) } just runs
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
|
@ -55,6 +64,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
viewModel = viewModel,
|
||||
onNavigateToPasswordHistory = { onNavigateToPasswordHistoryScreenCalled = true },
|
||||
onNavigateBack = {},
|
||||
intentManager = intentManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1177,6 +1187,43 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
|
||||
//endregion SimpleLogin Service Type Tests
|
||||
|
||||
//region Username Type Tests
|
||||
|
||||
@Test
|
||||
fun `in Username state, clicking the toolitp icon should send the TooltipClick action`() {
|
||||
updateState(DEFAULT_STATE.copy(selectedType = GeneratorState.MainType.Username()))
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(
|
||||
label = "Username type, Plus addressed email",
|
||||
useUnmergedTree = true,
|
||||
)
|
||||
// Find the button
|
||||
.onChildren()
|
||||
.filterToOne(hasClickAction())
|
||||
// Find the content description
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("Learn more"))
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction.MainType.Username.UsernameType.TooltipClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToTooltip should call launchUri on IntentManager`() {
|
||||
mutableEventFlow.tryEmit(GeneratorEvent.NavigateToTooltip)
|
||||
verify {
|
||||
intentManager.launchUri("https://bitwarden.com/help/generator/#username-types".toUri())
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Username Type Tests
|
||||
|
||||
//region Username Plus Addressed Email Tests
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
|
|
@ -527,6 +527,18 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TooltipClick should emit NavigateToTooltip event`() = runTest {
|
||||
val viewModel = createViewModel(initialUsernameState)
|
||||
|
||||
viewModel.actionChannel.trySend(GeneratorAction.MainType.Username.UsernameType.TooltipClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
val event = awaitItem()
|
||||
assertEquals(GeneratorEvent.NavigateToTooltip, event)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PasswordActions {
|
||||
private val defaultPasswordState = createPasswordState()
|
||||
|
|
Loading…
Add table
Reference in a new issue