From 5145168a941be213d31f972ba9d5735160a451cf Mon Sep 17 00:00:00 2001 From: Dave Severns <dseverns@livefront.com> Date: Fri, 10 Jan 2025 16:00:01 -0500 Subject: [PATCH] fubar --- .../coachmark/CoachMarkContainer.kt | 94 +++++++++++-------- .../components/coachmark/CoachMarkScope.kt | 2 + .../components/coachmark/CoachMarkState.kt | 34 ++++++- .../feature/addedit/VaultAddEditScreen.kt | 10 +- 4 files changed, 92 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkContainer.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkContainer.kt index 4f1c53519..f9dc2b047 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkContainer.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkContainer.kt @@ -11,20 +11,26 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.GenericShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.graphics.ClipOp import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource @@ -35,6 +41,7 @@ import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconBu import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber @@ -61,55 +68,68 @@ fun <T : Enum<T>> CoachMarkContainer( content: @Composable CoachMarkScope<T>.() -> Unit, ) { val scope = rememberCoroutineScope() - Box(modifier = Modifier - .fillMaxSize() - .then(modifier), + Box( + modifier = Modifier + .fillMaxSize() + .then(modifier), ) { Timber.i("Coach: I am the full container") CoachMarkScopeInstance(coachMarkState = state).content() - if ( - state.currentHighlightBounds.value != Rect.Zero && state.isVisible.value - ) { + val boundedRectangle by state.currentHighlightBounds + val isVisible by state.isVisible + val currentHighlightShape by state.currentHighlightShape - Timber.i("Coach do I get called? Im trying draw the overlay") - val boundedRectangle by state.currentHighlightBounds - val highlightArea = Rect( - topLeft = boundedRectangle.topLeft, - bottomRight = boundedRectangle.bottomRight, - ) - val highlightPath = Path().apply { + val highlightPath = + remember(boundedRectangle, currentHighlightShape) { + if (boundedRectangle == Rect.Zero) { + Timber.w("Coach: highlightPath is Rect.Zero") + return@remember Path() + } + val highlightArea = Rect( + topLeft = boundedRectangle.topLeft, + bottomRight = boundedRectangle.bottomRight, + ) Timber.i("Coach: I am applying the path for $highlightArea") - when (state.currentHighlightShape.value) { - CoachMarkHighlightShape.SQUARE -> addRoundRect( - RoundRect( - rect = highlightArea, - cornerRadius = CornerRadius( - x = ROUNDED_RECT_RADIUS, + Path().apply { + when (currentHighlightShape) { + CoachMarkHighlightShape.SQUARE -> addRoundRect( + RoundRect( + rect = highlightArea, + cornerRadius = CornerRadius( + x = ROUNDED_RECT_RADIUS, + ), ), - ), - ) + ) - CoachMarkHighlightShape.OVAL -> addOval(highlightArea) + CoachMarkHighlightShape.OVAL -> addOval(highlightArea) + } } } + if (boundedRectangle != Rect.Zero && isVisible) { + Timber.i("Coach do I get called? Im trying draw the overlay") val backgroundColor = BitwardenTheme.colorScheme.text.primary Box( modifier = Modifier .pointerInput(Unit) { detectTapGestures( onTap = { - if (state.isVisible.value) { - scope.launch { - Timber.i("Coach calling it in gestures") - state.showToolTipForCurrentCoachMark() - } + scope.launch { + Timber.i("Coach calling it in gestures") + state.showToolTipForCurrentCoachMark() } }, ) } .fillMaxSize() +// .background( +// color = backgroundColor.copy(alpha = .75f) +// ) +// .clip( +// CircleShape +// ) .drawBehind { + Timber.i("Coach: in drawbehind, $highlightPath based on $boundedRectangle") clipPath( path = highlightPath, clipOp = ClipOp.Difference, @@ -123,17 +143,17 @@ fun <T : Enum<T>> CoachMarkContainer( }, ) } - } - LaunchedEffect(state.currentHighlightBounds.value, state.currentHighlightShape.value) { - if (state.currentHighlightBounds.value != Rect.Zero) { - Timber.i("Coach: bounds changed do I get called? Im trying to show the tooltip") - state.showToolTipForCurrentCoachMark() + LaunchedEffect(state.currentHighlightBounds.value, state.currentHighlightShape.value) { + if (state.currentHighlightBounds.value != Rect.Zero) { + Timber.i("Coach: bounds changed do I get called? Im trying to show the tooltip") + state.showToolTipForCurrentCoachMark() + } } - } - LaunchedEffect(Unit) { - if (state.isVisible.value && (state.currentHighlight.value != null)) { - Timber.i("Coach calling it in Launched effect cause state is visible") - state.showCoachMark(state.currentHighlight.value) + LaunchedEffect(Unit) { + if (state.isVisible.value && (state.currentHighlight.value != null)) { + Timber.i("Coach calling it in Launched effect cause state is visible") + state.showCoachMark(state.currentHighlight.value) + } } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkScope.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkScope.kt index 37220fb6c..78c3d95b8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkScope.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkScope.kt @@ -4,6 +4,7 @@ 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.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState @@ -199,6 +200,7 @@ private fun TooltipScope.CoachMarkToolTip( rightAction: (@Composable RowScope.() -> Unit)?, ) { RichTooltip( + modifier = Modifier.padding(start = 8.dp, end = 2.dp), caretSize = DpSize(width = 24.dp, height = 16.dp), title = { Row( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkState.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkState.kt index 76e6b86de..f54f9b7f7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkState.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/coachmark/CoachMarkState.kt @@ -1,5 +1,7 @@ package com.x8bit.bitwarden.ui.platform.components.coachmark +import androidx.compose.foundation.gestures.scrollBy +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TooltipState import androidx.compose.runtime.Composable @@ -9,9 +11,12 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.input.key.key +import androidx.compose.ui.unit.dp import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap /** * Manages the state of a coach mark sequence, guiding users through a series of highlights. @@ -92,7 +97,6 @@ class CoachMarkState<T : Enum<T>>( Timber.i("Coach: I have been requested to show the highlight for ${highlightToShow?.key} with bounds: ${currentHighlightBounds.value}") if (highlightToShow != null) { updateCoachMarkStateInternal(highlightToShow) - _isVisible.value = true } else { showNextCoachMark() } @@ -112,7 +116,6 @@ class CoachMarkState<T : Enum<T>>( val index = orderedList.indexOf(previousHighlight?.key) if (index < 0 && previousHighlight != null) return _currentHighlight.value = orderedList.getOrNull(index + 1) - _isVisible.value = currentHighlight.value != null getCurrentHighlight() } Timber.i("Coach: I have been requested to show next highlight which is: ${highlightToShow?.key} with bounds: ${highlightToShow?.highlightBounds}") @@ -135,7 +138,6 @@ class CoachMarkState<T : Enum<T>>( return } _currentHighlight.value = orderedList.getOrNull(index - 1) - _isVisible.value = this.currentHighlight.value != null getCurrentHighlight() } updateCoachMarkStateInternal(highlightToShow) @@ -162,6 +164,7 @@ class CoachMarkState<T : Enum<T>>( } private fun updateCoachMarkStateInternal(highlight: CoachMarkHighlightState<T>?) { + _isVisible.value = highlight != null Timber.i("Coach: I have updated the shape and bounds, the new bounds are ${highlight?.highlightBounds}") _currentHighlightShape.value = highlight?.shape ?: CoachMarkHighlightShape.SQUARE if (currentHighlightBounds.value != highlight?.highlightBounds) { @@ -170,7 +173,7 @@ class CoachMarkState<T : Enum<T>>( } } - internal suspend fun showToolTipForCurrentCoachMark() { + suspend fun showToolTipForCurrentCoachMark() { Timber.i("Coach: I am trying to show") val currentCoachMark = mutex.withLock { getCurrentHighlight() @@ -180,6 +183,29 @@ class CoachMarkState<T : Enum<T>>( currentCoachMark?.toolTipState?.show() } + suspend fun scrollUpToKey( + listState: LazyListState, + targetKey: T, + ) { + val scrollAmount = (-1).toFloat() + var found = false + while (!found) { + val layoutInfo = listState.layoutInfo + val visibleItems = layoutInfo.visibleItemsInfo + if (visibleItems.any { it.key == targetKey }) { + found = true + } else { + if (listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0) { + // Reached the start of the list without finding the key + println("Key $targetKey not found") + found = true + } else { + listState.scrollBy(scrollAmount) + } + } + } + } + /** * Cleans up the tooltip state by dismissing it if visible and calling onDispose. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index 1cd10a1a1..da9480093 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -258,12 +258,6 @@ fun VaultAddEditScreen( orderedList = AddEditItemCoachMark.entries, ) val coroutineScope = rememberCoroutineScope() - LaunchedEffect(Unit) { - delay(3000L) - if (coachMarkState.isVisible.value.not()) { - coachMarkState.showCoachMark(AddEditItemCoachMark.GENERATE_PASSWORD) - } - } CoachMarkContainer( state = coachMarkState, ) { @@ -287,7 +281,9 @@ fun VaultAddEditScreen( BitwardenTextButton( label = stringResource(id = R.string.save), onClick = remember(viewModel) { - { viewModel.trySendAction(VaultAddEditAction.Common.SaveClick) } + { coroutineScope.launch { + coachMarkState.showCoachMark() + }} }, modifier = Modifier.testTag("SaveButton"), )