canopas / compose-intro-showcase

Highlight different features of the app using Jetpack Compose
https://canopas.github.io/compose-intro-showcase/
Other
467 stars 36 forks source link

Feature request: dismiss target when clicking outside of it #13

Closed ZacSweers closed 9 months ago

ZacSweers commented 1 year ago

Currently this library only dismisses targets if the user clicks the exact target space, but this isn't normal behavior when the target being showcased isn't normally clickable. It would be ideal if the library allowed dismissal of targets when tapping outside of the target area.

sepidevatankhah commented 1 year ago

Thank you for adjusting the library with Jetpack Compose.

I have the same request, could you pls give us the onclick event for this area(outside the target area), then the developer can decide to close the helping hand with onclick in this area or not !

Thank you in advance for taking care. cp-radhika-s Jimmy Sanghani Jan Bína

Best, Sepi

sepidevatankhah commented 1 year ago

cp-radhika-s Jimmy Sanghani Jan Bína

If you just add the .clickable { onShowcaseCompleted() } inside the TargetContent function, it will fix the problem Look :

@Composable
fun TargetContent(
    target: IntroShowcaseTargets,
    modifier: Modifier = Modifier,
    onShowcaseCompleted: () -> Unit,
) {
    val screenHeight = LocalConfiguration.current.screenHeightDp
    val targetCords = target.coordinates
    val gutterArea = 88.dp
    val targetRect = targetCords.boundsInRoot()

    val yOffset = with(LocalDensity.current) {
        targetCords.positionInRoot().y.toDp()
    }

    val isTargetInGutter = gutterArea > yOffset || yOffset > screenHeight.dp.minus(gutterArea)

    val maxDimension =
        max(targetCords.size.width.absoluteValue, targetCords.size.height.absoluteValue)
    val targetRadius = maxDimension / 2f + 40f

    val animationSpec = infiniteRepeatable<Float>(
        animation = tween(2000, easing = FastOutLinearInEasing),
        repeatMode = RepeatMode.Restart,
    )

    var outerOffset by remember {
        mutableStateOf(Offset(0f, 0f))
    }

    var outerRadius by remember {
        mutableStateOf(0f)
    }

    val outerAnimatable = remember { Animatable(0.6f) }

    val animatables = listOf(
        remember { Animatable(0f) },
        remember { Animatable(0f) }
    )

    LaunchedEffect(target) {
        outerAnimatable.snapTo(0.6f)

        outerAnimatable.animateTo(
            targetValue = 1f,
            animationSpec = tween(
                durationMillis = 500,
                easing = FastOutSlowInEasing,
            ),
        )
    }

    animatables.forEachIndexed { index, animatable ->
        LaunchedEffect(animatable) {
            delay(index * 1000L)
            animatable.animateTo(
                targetValue = 1f, animationSpec = animationSpec
            )
        }
    }

    val dys = animatables.map { it.value }
    Box {
        Canvas(
            modifier = modifier
                .fillMaxSize()
                .pointerInput(target) {
                    detectTapGestures { tapOffeset ->
                        if (targetRect.contains(tapOffeset)) {
                            onShowcaseCompleted()
                        }
                    }
                }
                .clickable { onShowcaseCompleted() }
                .graphicsLayer(alpha = 0.99f)
        ) {

            drawCircle(
                color = target.style.backgroundColor,
                center = outerOffset,
                radius = outerRadius * outerAnimatable.value,
                alpha = target.style.backgroundAlpha
            )

            dys.forEach { dy ->
                drawCircle(
                    color = target.style.targetCircleColor,
                    radius = maxDimension * dy * 2f,
                    center = targetRect.center,
                    alpha = 1 - dy
                )
            }

            drawCircle(
                color = target.style.targetCircleColor,
                radius = targetRadius,
                center = targetRect.center,
                blendMode = BlendMode.Xor
            )
        }

        ShowCaseText(target, targetRect, targetRadius) { textCoords ->
            val contentRect = textCoords.boundsInParent()
            val outerRect = getOuterRect(contentRect, targetRect, isTargetInGutter)
            val possibleOffset = getOuterCircleCenter(targetRect, contentRect, targetRadius)

            outerOffset = if (isTargetInGutter) {
                outerRect.center
            } else {
                possibleOffset
            }

            outerRadius = getOuterRadius(outerRect) + targetRadius
        }
    }
}
wongcain commented 1 year ago

Alternatively, expose a method on IntroShowCaseState for incrementing currentTargetIndex. Something like this:

class IntroShowCaseState internal constructor(
  initialIndex: Int,
) {

  internal var targets = mutableStateMapOf<Int, IntroShowcaseTargets>()

  var currentTargetIndex by mutableStateOf(initialIndex)
    internal set

  val currentTarget: IntroShowcaseTargets?
    get() = targets[currentTargetIndex]

  /* NEW METHOD */
  fun onShowcaseStepCompleted() {
    if (currentTarget != null) {
      currentTargetIndex++
    }
  }
}
cp-radhika-s commented 9 months ago

Closing this issue since this has been fixed in version 2.0.0. Thanks for reporting this issue!