mirror of
https://github.com/bitwarden/android.git
synced 2025-01-30 19:53:47 +03:00
fubar
This commit is contained in:
parent
5b92e81823
commit
5145168a94
4 changed files with 92 additions and 48 deletions
app/src/main/java/com/x8bit/bitwarden/ui
platform/components/coachmark
vault/feature/addedit
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue