PM-16625 PM-16626 PM-16627 Coach marks 4-6 on generator screen (#4640)

This commit is contained in:
Dave Severns
2025-01-29 12:57:56 -05:00
committed by GitHub
parent 1a7ddbef7a
commit 590fc21820
8 changed files with 612 additions and 424 deletions

View File

@@ -199,8 +199,8 @@ class CoachMarkScopeInstance<T : Enum<T>>(
anchorContent: @Composable () -> Unit,
) {
TooltipBox(
positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(
spacingBetweenTooltipAndAnchor = 12.dp,
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(
spacingBetweenTooltipAndAnchor = 10.dp,
),
tooltip = {
BitwardenToolTip(
@@ -213,7 +213,7 @@ class CoachMarkScopeInstance<T : Enum<T>>(
leftAction = leftAction,
rightAction = rightAction,
modifier = Modifier
.padding(horizontal = 4.dp)
.padding(horizontal = 6.dp)
.semantics { isCoachMarkToolTip = true },
)
},

View File

@@ -28,7 +28,7 @@ class LazyListCoachMarkState<T : Enum<T>>(
}
private suspend fun LazyListState.searchForKey(keyToFind: T) {
layoutInfo
val keyFound = layoutInfo
.visibleItemsInfo
.any { it.key == keyToFind }
.takeIf { itemAlreadyVisible ->
@@ -59,6 +59,10 @@ class LazyListCoachMarkState<T : Enum<T>>(
}
?: scrollUpToKey(keyToFind).takeIf { it }
?: scrollDownToKey(keyToFind)
if (!keyFound) {
// if key not found scroll back to the top.
scrollToItem(index = 0)
}
}
private suspend fun LazyListState.scrollUpToKey(

View File

@@ -1,25 +1,31 @@
package com.x8bit.bitwarden.ui.platform.components.tooltip
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSizeIn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.RichTooltip
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.tooltip.color.bitwardenTooltipColors
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
private val MIN_TOOLTIP_WIDTH = 312.dp
/**
* Bitwarden themed rich tool-tip to show within a [TooltipScope].
*/
@@ -33,17 +39,30 @@ fun TooltipScope.BitwardenToolTip(
leftAction: (@Composable RowScope.() -> Unit)? = null,
rightAction: (@Composable RowScope.() -> Unit)? = null,
) {
RichTooltip(
modifier = modifier,
caretSize = DpSize(width = 24.dp, height = 16.dp),
PlainTooltip(
modifier = modifier.requiredSizeIn(minWidth = MIN_TOOLTIP_WIDTH),
caretSize = DpSize(width = 24.dp, height = 12.dp),
shape = BitwardenTheme.shapes.coachmark,
title = {
contentColor = BitwardenTheme.colorScheme.text.primary,
containerColor = BitwardenTheme.colorScheme.background.secondary,
) {
// PlainTooltip already applies 8.dp of horizontal padding and 4.dp of vertical to the
// content.
Column(
modifier = Modifier.padding(
bottom = 4.dp,
start = 8.dp,
end = 8.dp,
),
) {
Row(
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = title,
style = BitwardenTheme.typography.eyebrowMedium,
color = BitwardenTheme.colorScheme.text.secondary,
modifier = Modifier.align(Alignment.CenterVertically),
)
Spacer(modifier = Modifier.weight(1f))
onDismiss?.let {
@@ -51,25 +70,22 @@ fun TooltipScope.BitwardenToolTip(
painter = rememberVectorPainter(R.drawable.ic_close_small),
contentDescription = stringResource(R.string.close),
onClick = it,
modifier = Modifier.offset(x = 16.dp, y = (-16).dp),
modifier = Modifier.offset(x = 16.dp),
)
}
}
},
action = {
Text(
text = description,
style = BitwardenTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.height(4.dp))
Row(
Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
) {
leftAction?.invoke(this)
Spacer(modifier = Modifier.weight(1f))
rightAction?.invoke(this)
}
},
colors = bitwardenTooltipColors(),
) {
Text(
text = description,
style = BitwardenTheme.typography.bodyMedium,
)
}
}
}

View File

@@ -13,6 +13,10 @@ import kotlinx.coroutines.withTimeout
/**
* Default implementation of [BitwardenToolTipState]
*
* This is making use of the implementation of [TooltipState] provided via
* [androidx.compose.material3.rememberTooltipState] but overriding [dismiss] to be
* no-op.
*/
class BitwardenToolTipStateImpl(
initialIsVisible: Boolean,

View File

@@ -1,25 +0,0 @@
package com.x8bit.bitwarden.ui.platform.components.tooltip.color
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.RichTooltipColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTip
/**
* Bitwarden themed colors for the [BitwardenToolTip]
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun bitwardenTooltipColors(
contentColor: Color = BitwardenTheme.colorScheme.text.primary,
containerColor: Color = BitwardenTheme.colorScheme.background.secondary,
titleContentColor: Color = BitwardenTheme.colorScheme.text.secondary,
actionContentColor: Color = BitwardenTheme.colorScheme.text.interaction,
): RichTooltipColors = RichTooltipColors(
contentColor = contentColor,
containerColor = containerColor,
titleContentColor = titleContentColor,
actionContentColor = actionContentColor,
)

View File

@@ -1138,4 +1138,9 @@ Do you want to switch to this account?</string>
<string name="coachmark_1_of_6">1 OF 6</string>
<string name="coachmark_2_of_6">2 OF 6</string>
<string name="coachmark_3_of_6">3 OF 6</string>
<string name="coachmark_4_of_6">4 OF 6</string>
<string name="coachmark_5_of_6">5 OF 6</string>
<string name="coachmark_6_of_6">6 OF 6</string>
<string name="use_these_options_to_adjust_your_password_to_your_account_requirements">Use these options to adjust your password to your accounts requirements.</string>
<string name="after_you_save_your_new_password_to_bitwarden_don_t_forget_to_update_it_on_your_account_website">"After you save your new password to Bitwarden, dont forget to update it on your account website. "</string>
</resources>

View File

@@ -1705,6 +1705,62 @@ class GeneratorScreenTest : BaseComposeTest() {
.assertIsDisplayed()
}
@Test
fun `The full coach mark tour can be completed showing all steps`() {
mutableEventFlow.tryEmit(GeneratorEvent.StartCoachMarkTour)
composeTestRule
.onNodeWithText("1 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Next")
.performClick()
composeTestRule
.onNodeWithText("2 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Next")
.performClick()
composeTestRule
.onNodeWithText("3 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Next")
.performClick()
composeTestRule
.onNodeWithText("4 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Next")
.performClick()
composeTestRule
.onNodeWithText("5 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Next")
.performClick()
composeTestRule
.onNodeWithText("6 OF 6")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Done")
.performClick()
composeTestRule
.onNode(isCoachMarkToolTip)
.assertDoesNotExist()
}
//endregion Random Word Tests
private fun updateState(state: GeneratorState) {