Skip to content

Commit 8a42e2b

Browse files
committed
Add Target showcase mode with circle, Rectangle and round rectangle
1 parent 482b1fb commit 8a42e2b

4 files changed

Lines changed: 1307 additions & 24 deletions

File tree

composeApp/src/commonMain/kotlin/App.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
2323
import kotlinx.coroutines.delay
2424
import kotlinx.coroutines.launch
2525
import ly.com.tahaben.showcase_layout_compose.model.*
26-
import ly.com.tahaben.showcase_layout_compose.ui.ShowcaseLayout
26+
import ly.com.tahaben.showcase_layout_compose.ui.TargetShowcaseLayout
2727
import org.jetbrains.compose.resources.ExperimentalResourceApi
2828
import org.jetbrains.compose.resources.painterResource
2929
import org.jetbrains.compose.ui.tooling.preview.Preview
@@ -88,12 +88,14 @@ fun App(openUrl: (String) -> Boolean, onWebLoadFinish: () -> Unit = {}) {
8888
}
8989

9090
MyTheme(useDarkTheme = false) {
91-
ShowcaseLayout(
91+
TargetShowcaseLayout(
9292
isShowcasing = isShowcasing,
9393
onFinish = { isShowcasing = false; finishedSubsequentShowcase = true },
9494
greeting = ShowcaseMsg(greetingMsg, textStyle = TextStyle(color = Color.White)),
9595
lineThickness = lineThinckness.dp,
96-
animationDuration = animationDuration
96+
animationDuration = animationDuration,
97+
// circleMode = true
98+
targetShape = TargetShape.ROUNDED_RECTANGLE
9799
) {
98100
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
99101
TopAppBar(title = {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ly.com.tahaben.showcase_layout_compose.model
2+
3+
/**
4+
* Enum class that defines the shape of the target highlight in the TargetShowcaseLayout.
5+
*
6+
* CIRCLE: Draws a circular highlight around the target.
7+
* RECTANGLE: Draws a rectangular highlight around the target.
8+
* ROUNDED_RECTANGLE: Draws a rounded rectangular highlight around the target.
9+
*/
10+
enum class TargetShape {
11+
CIRCLE, RECTANGLE, ROUNDED_RECTANGLE
12+
}

showcase-layout-compose/src/commonMain/kotlin/ly/com/tahaben/showcase_layout_compose/ui/ShowcaseLayout.kt

Lines changed: 110 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ fun ShowcaseLayout(
659659
if (circleMode && isShowcasing) {
660660
val itemSize = scope.getSizeFor(currentIndex)
661661
val offset = scope.getPositionFor(currentIndex)
662+
val coroutineScope = rememberCoroutineScope()
662663
val animatedWidth = remember { Animatable(itemSize.width) }
663664
val animatedHeight = remember { Animatable(itemSize.height) }
664665

@@ -671,6 +672,9 @@ fun ShowcaseLayout(
671672
val outerAnimatable = remember { Animatable(0.6f) }
672673
val outerAlphaAnimatable = remember(currentIndex) { Animatable(0f) }
673674

675+
// Animation for message text opacity to create smooth transitions
676+
val messageTextAlpha = remember { Animatable(1f) }
677+
674678
LaunchedEffect(currentIndex) {
675679
outerAnimatable.snapTo(0.6f)
676680

@@ -732,22 +736,92 @@ fun ShowcaseLayout(
732736
delay(pulseDuration+200L)
733737
}
734738
}
735-
LaunchedEffect(currentIndex) {
736-
animatedX.snapTo(offset.x)
737-
animatedY.snapTo(offset.y)
738-
animatedHeight.snapTo(0f)
739-
animatedWidth.snapTo(0f)
740-
/*launch {
741-
animatedX.animateTo(offset.x)
742-
}
743-
launch {
744-
animatedY.animateTo(offset.y)
745-
}*/
746-
launch {
747-
animatedHeight.animateTo(itemSize.height)
739+
// Animation for message text - fade out when changing targets
740+
/*LaunchedEffect(currentIndex) {
741+
// If not the first showcase, fade out the message text
742+
if (currentIndex > 1 && currentIndex != initIndex) {
743+
messageTextAlpha.animateTo(
744+
0f,
745+
animationSpec = tween(
746+
durationMillis = animationDuration / 3,
747+
easing = FastOutSlowInEasing
748+
)
749+
)
750+
//messageTextAlpha.snapTo(0f)
748751
}
749-
launch {
750-
animatedWidth.animateTo(itemSize.width)
752+
}*/
753+
754+
LaunchedEffect(currentIndex) {
755+
// Get the previous position and size for smooth transition
756+
val prevX = animatedX.value
757+
val prevY = animatedY.value
758+
val prevWidth = animatedWidth.value
759+
val prevHeight = animatedHeight.value
760+
761+
// If this is the first showcase or we're resetting, snap to initial values
762+
if (currentIndex == 1 || currentIndex == initIndex) {
763+
animatedX.snapTo(offset.x)
764+
animatedY.snapTo(offset.y)
765+
animatedHeight.snapTo(0f)
766+
animatedWidth.snapTo(0f)
767+
768+
launch {
769+
animatedHeight.animateTo(itemSize.height)
770+
}
771+
launch {
772+
animatedWidth.animateTo(itemSize.width)
773+
}
774+
} else {
775+
// For transitions between targets, animate smoothly
776+
messageTextAlpha.snapTo(0f)
777+
launch {
778+
animatedX.animateTo(
779+
offset.x,
780+
animationSpec = tween(
781+
durationMillis = animationDuration,
782+
easing = FastOutSlowInEasing
783+
)
784+
)
785+
}
786+
launch {
787+
animatedY.animateTo(
788+
offset.y,
789+
animationSpec = tween(
790+
durationMillis = animationDuration,
791+
easing = FastOutSlowInEasing
792+
)
793+
)
794+
}
795+
launch {
796+
animatedHeight.animateTo(
797+
itemSize.height,
798+
animationSpec = tween(
799+
durationMillis = animationDuration,
800+
easing = FastOutSlowInEasing
801+
)
802+
)
803+
}
804+
launch {
805+
animatedWidth.animateTo(
806+
itemSize.width,
807+
animationSpec = tween(
808+
durationMillis = animationDuration,
809+
easing = FastOutSlowInEasing
810+
)
811+
)
812+
}
813+
814+
// After the circle has moved to its new position, fade the message text back in
815+
launch {
816+
delay(animationDuration.toLong() * 2)
817+
messageTextAlpha.animateTo(
818+
1f,
819+
animationSpec = tween(
820+
durationMillis = animationDuration,
821+
easing = FastOutSlowInEasing
822+
)
823+
)
824+
}
751825
}
752826
}
753827
LaunchedEffect(isShowcasing){
@@ -770,7 +844,16 @@ fun ShowcaseLayout(
770844
TAG + "tapped here $it"
771845
)
772846
if (currentIndex + 1 < scope.getHashMapSize()) {
847+
coroutineScope.launch {
848+
messageTextAlpha.animateTo(
849+
0f,
850+
animationSpec = tween(
851+
durationMillis = animationDuration / 3,
852+
easing = FastOutSlowInEasing
853+
)
854+
)
773855
currentIndex++
856+
}
774857
} else {
775858
currentIndex = initIndex
776859
onFinish()
@@ -1112,25 +1195,31 @@ fun ShowcaseLayout(
11121195
)
11131196
}
11141197

1198+
// Apply the message text alpha to both the background and text
1199+
val backgroundAlpha = messageTextAlpha.value
1200+
11151201
if (msg.roundedCorner == 0.dp) {
11161202
drawRect(
1117-
color = msg.msgBackground ?: Color.Black.copy(alpha = 0.7f),
1203+
color = msg.msgBackground ?: Color.Black,
11181204
topLeft = Offset(bgRect.left, bgRect.top),
1119-
size = Size(bgRect.width, bgRect.height)
1205+
size = Size(bgRect.width, bgRect.height),
1206+
alpha = backgroundAlpha
11201207
)
11211208
} else {
11221209
drawRoundRect(
1123-
color = msg.msgBackground ?: Color.Black.copy(alpha = 0.7f),
1210+
color = msg.msgBackground ?: Color.Black,
11241211
topLeft = Offset(bgRect.left, bgRect.top),
11251212
size = Size(bgRect.width, bgRect.height),
1126-
cornerRadius = CornerRadius(msg.roundedCorner.value)
1213+
cornerRadius = CornerRadius(msg.roundedCorner.value),
1214+
alpha = backgroundAlpha
11271215
)
11281216
}
11291217

1130-
// Draw the text
1218+
// Draw the text with the animated alpha
11311219
drawText(
11321220
textResult,
1133-
topLeft = Offset(textX, textY)
1221+
topLeft = Offset(textX, textY),
1222+
alpha = messageTextAlpha.value
11341223
)
11351224
}
11361225
}

0 commit comments

Comments
 (0)