mirror of
https://github.com/bitwarden/android.git
synced 2025-02-22 16:49:13 +03:00
PM-17650 Implement custom tool tip state to prevent tool tips from dismissing. (#4637)
This commit is contained in:
parent
b2c4fbb593
commit
7a25aafc23
15 changed files with 224 additions and 25 deletions
app/src
main
java/com/x8bit/bitwarden
data/platform/repository
ui/platform
res/values
test/java/com/x8bit/bitwarden
data/platform/repository
ui/platform/feature/debugmenu
|
@ -38,6 +38,11 @@ interface DebugMenuRepository {
|
||||||
*/
|
*/
|
||||||
fun resetOnboardingStatusForCurrentUser()
|
fun resetOnboardingStatusForCurrentUser()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the value for the show coach mark statuses so their default values will be used.
|
||||||
|
*/
|
||||||
|
fun resetCoachMarkTourStatuses()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manipulates the state to force showing the onboarding carousel.
|
* Manipulates the state to force showing the onboarding carousel.
|
||||||
*
|
*
|
||||||
|
|
|
@ -56,6 +56,11 @@ class DebugMenuRepositoryImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun resetCoachMarkTourStatuses() {
|
||||||
|
settingsDiskSource.storeShouldShowGeneratorCoachMark(shouldShow = null)
|
||||||
|
settingsDiskSource.storeShouldShowAddLoginCoachMark(shouldShow = null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun modifyStateToShowOnboardingCarousel(
|
override fun modifyStateToShowOnboardingCarousel(
|
||||||
userStateUpdateTrigger: () -> Unit,
|
userStateUpdateTrigger: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -60,7 +60,6 @@ fun <T : Enum<T>> CoachMarkContainer(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable CoachMarkScope<T>.() -> Unit,
|
content: @Composable CoachMarkScope<T>.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -101,9 +100,8 @@ fun <T : Enum<T>> CoachMarkContainer(
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onTap = {
|
onTap = {
|
||||||
scope.launch {
|
// NO-OP, this consumes any touch events
|
||||||
state.showToolTipForCurrentCoachMark()
|
// while the scrim is showing.
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TooltipState
|
|
||||||
import androidx.compose.material3.rememberTooltipState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -32,6 +30,8 @@ import com.x8bit.bitwarden.ui.platform.base.util.toListItemCardStyle
|
||||||
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightShape
|
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightShape
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||||
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTip
|
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTip
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTipState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.tooltip.rememberBitwardenToolTipState
|
||||||
import okhttp3.internal.toImmutableList
|
import okhttp3.internal.toImmutableList
|
||||||
import org.jetbrains.annotations.VisibleForTesting
|
import org.jetbrains.annotations.VisibleForTesting
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class CoachMarkScopeInstance<T : Enum<T>>(
|
||||||
rightAction: @Composable() (RowScope.() -> Unit)?,
|
rightAction: @Composable() (RowScope.() -> Unit)?,
|
||||||
anchorContent: @Composable () -> Unit,
|
anchorContent: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val toolTipState = rememberTooltipState(
|
val toolTipState = rememberBitwardenToolTipState(
|
||||||
initialIsVisible = false,
|
initialIsVisible = false,
|
||||||
isPersistent = true,
|
isPersistent = true,
|
||||||
)
|
)
|
||||||
|
@ -192,7 +192,7 @@ class CoachMarkScopeInstance<T : Enum<T>>(
|
||||||
leftAction: @Composable() (RowScope.() -> Unit)?,
|
leftAction: @Composable() (RowScope.() -> Unit)?,
|
||||||
rightAction: @Composable() (RowScope.() -> Unit)?,
|
rightAction: @Composable() (RowScope.() -> Unit)?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
toolTipState: TooltipState = rememberTooltipState(
|
toolTipState: BitwardenToolTipState = rememberBitwardenToolTipState(
|
||||||
initialIsVisible = false,
|
initialIsVisible = false,
|
||||||
isPersistent = true,
|
isPersistent = true,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package com.x8bit.bitwarden.ui.platform.components.coachmark
|
package com.x8bit.bitwarden.ui.platform.components.coachmark
|
||||||
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.TooltipState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
@ -11,6 +10,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightShape
|
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightShape
|
||||||
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightState
|
import com.x8bit.bitwarden.ui.platform.components.coachmark.model.CoachMarkHighlightState
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTipState
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -27,7 +27,7 @@ import kotlin.math.min
|
||||||
* none should be highlighted at start.
|
* none should be highlighted at start.
|
||||||
* @param isCoachMarkVisible is any coach mark currently visible.
|
* @param isCoachMarkVisible is any coach mark currently visible.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@Stable
|
||||||
open class CoachMarkState<T : Enum<T>>(
|
open class CoachMarkState<T : Enum<T>>(
|
||||||
val orderedList: List<T>,
|
val orderedList: List<T>,
|
||||||
initialCoachMarkHighlight: T? = null,
|
initialCoachMarkHighlight: T? = null,
|
||||||
|
@ -58,7 +58,7 @@ open class CoachMarkState<T : Enum<T>>(
|
||||||
fun updateHighlight(
|
fun updateHighlight(
|
||||||
key: T,
|
key: T,
|
||||||
bounds: Rect?,
|
bounds: Rect?,
|
||||||
toolTipState: TooltipState,
|
toolTipState: BitwardenToolTipState,
|
||||||
shape: CoachMarkHighlightShape = CoachMarkHighlightShape.SQUARE,
|
shape: CoachMarkHighlightShape = CoachMarkHighlightShape.SQUARE,
|
||||||
) {
|
) {
|
||||||
highlights[key] = CoachMarkHighlightState(
|
highlights[key] = CoachMarkHighlightState(
|
||||||
|
@ -193,9 +193,9 @@ open class CoachMarkState<T : Enum<T>>(
|
||||||
/**
|
/**
|
||||||
* Cleans up the tooltip state by dismissing it if visible and calling onDispose.
|
* Cleans up the tooltip state by dismissing it if visible and calling onDispose.
|
||||||
*/
|
*/
|
||||||
private fun TooltipState.cleanUp() {
|
private fun BitwardenToolTipState.cleanUp() {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
dismiss()
|
dismissBitwardenToolTip()
|
||||||
}
|
}
|
||||||
onDispose()
|
onDispose()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.gestures.scrollBy
|
||||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
import androidx.compose.runtime.saveable.listSaver
|
import androidx.compose.runtime.saveable.listSaver
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
@ -13,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
* A [CoachMarkState] that depends on a [LazyListState] to automatically scroll to the current
|
* A [CoachMarkState] that depends on a [LazyListState] to automatically scroll to the current
|
||||||
* Coach Mark if not on currently on the screen.
|
* Coach Mark if not on currently on the screen.
|
||||||
*/
|
*/
|
||||||
|
@Stable
|
||||||
class LazyListCoachMarkState<T : Enum<T>>(
|
class LazyListCoachMarkState<T : Enum<T>>(
|
||||||
private val lazyListState: LazyListState,
|
private val lazyListState: LazyListState,
|
||||||
orderedList: List<T>,
|
orderedList: List<T>,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package com.x8bit.bitwarden.ui.platform.components.coachmark.model
|
package com.x8bit.bitwarden.ui.platform.components.coachmark.model
|
||||||
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.TooltipState
|
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.tooltip.BitwardenToolTipState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a highlight within a coach mark sequence.
|
* Represents a highlight within a coach mark sequence.
|
||||||
|
@ -13,10 +12,9 @@ import androidx.compose.ui.geometry.Rect
|
||||||
* @property toolTipState The state of the tooltip associated with this highlight.
|
* @property toolTipState The state of the tooltip associated with this highlight.
|
||||||
* @property shape The shape of the highlight (e.g., square, oval).
|
* @property shape The shape of the highlight (e.g., square, oval).
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
data class CoachMarkHighlightState<T : Enum<T>>(
|
data class CoachMarkHighlightState<T : Enum<T>>(
|
||||||
val key: T,
|
val key: T,
|
||||||
val highlightBounds: Rect?,
|
val highlightBounds: Rect?,
|
||||||
val toolTipState: TooltipState,
|
val toolTipState: BitwardenToolTipState,
|
||||||
val shape: CoachMarkHighlightShape,
|
val shape: CoachMarkHighlightShape,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.components.tooltip
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TooltipState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom [TooltipState] to be used for the tool tips which should not be
|
||||||
|
* dismissed automatically by clicking outside of the pop-up area.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
interface BitwardenToolTipState : TooltipState {
|
||||||
|
/**
|
||||||
|
* Call to dismiss the tool tip from the screen, should be used in
|
||||||
|
* place of [TooltipState.dismiss]
|
||||||
|
*/
|
||||||
|
fun dismissBitwardenToolTip()
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.components.tooltip
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
|
import androidx.compose.foundation.MutatePriority
|
||||||
|
import androidx.compose.foundation.MutatorMutex
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.TooltipState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of [BitwardenToolTipState]
|
||||||
|
*/
|
||||||
|
class BitwardenToolTipStateImpl(
|
||||||
|
initialIsVisible: Boolean,
|
||||||
|
override val isPersistent: Boolean,
|
||||||
|
private val mutatorMutex: MutatorMutex,
|
||||||
|
) : BitwardenToolTipState {
|
||||||
|
|
||||||
|
override fun dismissBitwardenToolTip() {
|
||||||
|
transition.targetState = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override val transition: MutableTransitionState<Boolean> =
|
||||||
|
MutableTransitionState(initialIsVisible)
|
||||||
|
|
||||||
|
override val isVisible: Boolean
|
||||||
|
get() = transition.currentState || transition.targetState
|
||||||
|
|
||||||
|
/** continuation used to clean up */
|
||||||
|
private var job: (CancellableContinuation<Unit>)? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the tooltip associated with the current [TooltipState]. When this method is called, all
|
||||||
|
* of the other tooltips associated with [mutatorMutex] will be dismissed.
|
||||||
|
*
|
||||||
|
* @param mutatePriority [MutatePriority] to be used with [mutatorMutex].
|
||||||
|
*/
|
||||||
|
override suspend fun show(mutatePriority: MutatePriority) {
|
||||||
|
val cancellableShow: suspend () -> Unit = {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
transition.targetState = true
|
||||||
|
job = continuation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show associated tooltip for [TooltipDuration] amount of time
|
||||||
|
// or until tooltip is explicitly dismissed depending on [isPersistent].
|
||||||
|
mutatorMutex.mutate(mutatePriority) {
|
||||||
|
try {
|
||||||
|
if (isPersistent) {
|
||||||
|
cancellableShow()
|
||||||
|
} else {
|
||||||
|
withTimeout(BITWARDEN_TOOL_TIP_TIMEOUT) { cancellableShow() }
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mutatePriority != MutatePriority.PreventUserInput) {
|
||||||
|
// timeout or cancellation has occurred and we close out the current tooltip.
|
||||||
|
dismissBitwardenToolTip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are overriding this specifically to make it so it is a no-op this prevents the
|
||||||
|
* tooltip from being dismissed if the user taps anywhere out of it.
|
||||||
|
*/
|
||||||
|
override fun dismiss() {
|
||||||
|
/**No-Op**/
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cleans up [mutatorMutex] when the tooltip associated with this state leaves Composition. */
|
||||||
|
override fun onDispose() {
|
||||||
|
job?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a [BitwardenToolTipState] in a composable scope remembered across compositions.
|
||||||
|
*
|
||||||
|
* @param mutatorMutex if providing your own, ensure that any tool tips that should be
|
||||||
|
* shown/hidden in the context of the one you are using this state for, make use of the same
|
||||||
|
* instance of the passed in value.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
@ExperimentalMaterial3Api
|
||||||
|
fun rememberBitwardenToolTipState(
|
||||||
|
initialIsVisible: Boolean = false,
|
||||||
|
isPersistent: Boolean = false,
|
||||||
|
mutatorMutex: MutatorMutex = BitwardenToolTipStateDefaults.GlobalMutatorMutex,
|
||||||
|
): BitwardenToolTipState =
|
||||||
|
remember(isPersistent, mutatorMutex) {
|
||||||
|
BitwardenToolTipStateImpl(
|
||||||
|
initialIsVisible = initialIsVisible,
|
||||||
|
isPersistent = isPersistent,
|
||||||
|
mutatorMutex = mutatorMutex,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a global [MutatorMutex] as a singleton to be used by default for each
|
||||||
|
* created [BitwardenToolTipState]
|
||||||
|
*/
|
||||||
|
private object BitwardenToolTipStateDefaults {
|
||||||
|
val GlobalMutatorMutex = MutatorMutex()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val BITWARDEN_TOOL_TIP_TIMEOUT = 1500L
|
|
@ -115,6 +115,19 @@ fun DebugMenuScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
BitwardenFilledButton(
|
||||||
|
label = stringResource(R.string.reset_coach_mark_tour_status),
|
||||||
|
onClick = remember(viewModel) {
|
||||||
|
{
|
||||||
|
viewModel.trySendAction(DebugMenuAction.ResetCoachMarkTourStatuses)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEnabled = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin(),
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,14 @@ class DebugMenuViewModel @Inject constructor(
|
||||||
DebugMenuAction.ResetFeatureFlagValues -> handleResetFeatureFlagValues()
|
DebugMenuAction.ResetFeatureFlagValues -> handleResetFeatureFlagValues()
|
||||||
DebugMenuAction.RestartOnboarding -> handleResetOnboardingStatus()
|
DebugMenuAction.RestartOnboarding -> handleResetOnboardingStatus()
|
||||||
DebugMenuAction.RestartOnboardingCarousel -> handleResetOnboardingCarousel()
|
DebugMenuAction.RestartOnboardingCarousel -> handleResetOnboardingCarousel()
|
||||||
|
DebugMenuAction.ResetCoachMarkTourStatuses -> handleResetCoachMarkTourStatuses()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleResetCoachMarkTourStatuses() {
|
||||||
|
debugMenuRepository.resetCoachMarkTourStatuses()
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleResetOnboardingCarousel() {
|
private fun handleResetOnboardingCarousel() {
|
||||||
debugMenuRepository.modifyStateToShowOnboardingCarousel(
|
debugMenuRepository.modifyStateToShowOnboardingCarousel(
|
||||||
userStateUpdateTrigger = {
|
userStateUpdateTrigger = {
|
||||||
|
@ -133,6 +138,11 @@ sealed class DebugMenuAction {
|
||||||
*/
|
*/
|
||||||
data object RestartOnboardingCarousel : DebugMenuAction()
|
data object RestartOnboardingCarousel : DebugMenuAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User has clicked to reset coach mark values.
|
||||||
|
*/
|
||||||
|
data object ResetCoachMarkTourStatuses : DebugMenuAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal actions not triggered from the UI.
|
* Internal actions not triggered from the UI.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,5 +25,6 @@
|
||||||
<string name="new_device_permanent_dismiss">New device notice permanent dismiss</string>">
|
<string name="new_device_permanent_dismiss">New device notice permanent dismiss</string>">
|
||||||
<string name="new_device_temporary_dismiss">New device notice temporary dismiss</string>">
|
<string name="new_device_temporary_dismiss">New device notice temporary dismiss</string>">
|
||||||
<string name="ignore_environment_check">Ignore environment check</string>">
|
<string name="ignore_environment_check">Ignore environment check</string>">
|
||||||
|
<string name="reset_coach_mark_tour_status">Reset all coach mark tours</string>
|
||||||
<!-- /Debug Menu -->
|
<!-- /Debug Menu -->
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -203,6 +203,23 @@ class DebugMenuRepositoryTest {
|
||||||
}
|
}
|
||||||
assertTrue(lambdaHasBeenCalled)
|
assertTrue(lambdaHasBeenCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resetCoachMarkTourStatuses calls settings disk source setting values back to null`() {
|
||||||
|
every {
|
||||||
|
mockSettingsDiskSource.storeShouldShowGeneratorCoachMark(shouldShow = any())
|
||||||
|
} just runs
|
||||||
|
every {
|
||||||
|
mockSettingsDiskSource.storeShouldShowAddLoginCoachMark(shouldShow = any())
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
debugMenuRepository.resetCoachMarkTourStatuses()
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
mockSettingsDiskSource.storeShouldShowGeneratorCoachMark(shouldShow = null)
|
||||||
|
mockSettingsDiskSource.storeShouldShowAddLoginCoachMark(shouldShow = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TEST_STRING_VALUE = "test"
|
private const val TEST_STRING_VALUE = "test"
|
||||||
|
|
|
@ -89,7 +89,7 @@ class DebugMenuScreenTest : BaseComposeTest() {
|
||||||
.onNodeWithText("Email Verification", ignoreCase = true)
|
.onNodeWithText("Email Verification", ignoreCase = true)
|
||||||
.performClick()
|
.performClick()
|
||||||
|
|
||||||
verify {
|
verify(exactly = 1) {
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
DebugMenuAction.UpdateFeatureFlag(
|
DebugMenuAction.UpdateFeatureFlag(
|
||||||
FlagKey.EmailVerification,
|
FlagKey.EmailVerification,
|
||||||
|
@ -106,7 +106,7 @@ class DebugMenuScreenTest : BaseComposeTest() {
|
||||||
.performScrollTo()
|
.performScrollTo()
|
||||||
.performClick()
|
.performClick()
|
||||||
|
|
||||||
verify { viewModel.trySendAction(DebugMenuAction.ResetFeatureFlagValues) }
|
verify(exactly = 1) { viewModel.trySendAction(DebugMenuAction.ResetFeatureFlagValues) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -124,7 +124,7 @@ class DebugMenuScreenTest : BaseComposeTest() {
|
||||||
.assertIsEnabled()
|
.assertIsEnabled()
|
||||||
.performClick()
|
.performClick()
|
||||||
|
|
||||||
verify { viewModel.trySendAction(DebugMenuAction.RestartOnboarding) }
|
verify(exactly = 1) { viewModel.trySendAction(DebugMenuAction.RestartOnboarding) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -161,7 +161,7 @@ class DebugMenuScreenTest : BaseComposeTest() {
|
||||||
.assertIsEnabled()
|
.assertIsEnabled()
|
||||||
.performClick()
|
.performClick()
|
||||||
|
|
||||||
verify { viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel) }
|
verify(exactly = 1) { viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -182,4 +182,14 @@ class DebugMenuScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
verify(exactly = 0) { viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel) }
|
verify(exactly = 0) { viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `reset all coach mark tours should send ResetCoachMarkTourStatuses action`() {
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Reset all coach mark tours")
|
||||||
|
.performScrollTo()
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify(exactly = 1) { viewModel.trySendAction(DebugMenuAction.ResetCoachMarkTourStatuses) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,14 +78,16 @@ class DebugMenuViewModelTest : BaseViewModelTest() {
|
||||||
viewModel.trySendAction(
|
viewModel.trySendAction(
|
||||||
DebugMenuAction.UpdateFeatureFlag(FlagKey.EmailVerification, false),
|
DebugMenuAction.UpdateFeatureFlag(FlagKey.EmailVerification, false),
|
||||||
)
|
)
|
||||||
verify { mockDebugMenuRepository.updateFeatureFlag(FlagKey.EmailVerification, false) }
|
verify(exactly = 1) {
|
||||||
|
mockDebugMenuRepository.updateFeatureFlag(FlagKey.EmailVerification, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `handleResetOnboardingStatus should reset the onboarding status`() {
|
fun `handleResetOnboardingStatus should reset the onboarding status`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(DebugMenuAction.RestartOnboarding)
|
viewModel.trySendAction(DebugMenuAction.RestartOnboarding)
|
||||||
verify { mockDebugMenuRepository.resetOnboardingStatusForCurrentUser() }
|
verify(exactly = 1) { mockDebugMenuRepository.resetOnboardingStatusForCurrentUser() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
|
@ -93,12 +95,21 @@ class DebugMenuViewModelTest : BaseViewModelTest() {
|
||||||
fun `handleResetOnboardingCarousel should reset the onboarding carousel and update user state pending account action`() {
|
fun `handleResetOnboardingCarousel should reset the onboarding carousel and update user state pending account action`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel)
|
viewModel.trySendAction(DebugMenuAction.RestartOnboardingCarousel)
|
||||||
verify {
|
verify(exactly = 1) {
|
||||||
mockDebugMenuRepository.modifyStateToShowOnboardingCarousel(any())
|
mockDebugMenuRepository.modifyStateToShowOnboardingCarousel(any())
|
||||||
mockAuthRepository.hasPendingAccountAddition = true
|
mockAuthRepository.hasPendingAccountAddition = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleResetCoachMarkTourStatuses should call repository to reset values`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.trySendAction(DebugMenuAction.ResetCoachMarkTourStatuses)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
mockDebugMenuRepository.resetCoachMarkTourStatuses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(): DebugMenuViewModel = DebugMenuViewModel(
|
private fun createViewModel(): DebugMenuViewModel = DebugMenuViewModel(
|
||||||
featureFlagManager = mockFeatureFlagManager,
|
featureFlagManager = mockFeatureFlagManager,
|
||||||
debugMenuRepository = mockDebugMenuRepository,
|
debugMenuRepository = mockDebugMenuRepository,
|
||||||
|
|
Loading…
Add table
Reference in a new issue