@@ -88,8 +88,24 @@ fun TargetShowcaseLayout(
8888 }
8989 }
9090 }
91+ val singleGreeting = scope.greetingActionFlow.collectAsState()
92+ val isSingleGreeting by remember {
93+ derivedStateOf {
94+ if (singleGreeting.value != null ) {
95+ scope.showcaseEventListener?.onEvent(
96+ Level .DEBUG ,
97+ TAG + " showcase single greeting: ${singleGreeting.value?.text} "
98+ )
99+ singleGreetingMsg = singleGreeting.value
100+ currentIndex = 0
101+ true
102+ } else {
103+ false
104+ }
105+ }
106+ }
91107 BoxWithConstraints (modifier = Modifier .fillMaxSize()) {
92- if (isShowcasing) {
108+ if (isShowcasing || showCasingItem || isSingleGreeting ) {
93109 val itemSize = scope.getSizeFor(currentIndex)
94110 val offset = scope.getPositionFor(currentIndex)
95111 val coroutineScope = rememberCoroutineScope()
@@ -106,10 +122,10 @@ fun TargetShowcaseLayout(
106122 val outerAlphaAnimatable = remember(currentIndex) { Animatable (0f ) }
107123
108124 // Animation for message text opacity to create smooth transitions
109- val messageTextAlpha = remember { Animatable (1f ) }
125+ val messageTextAlpha = remember { Animatable (0f ) }
110126
111127 // Animation for overall canvas alpha to make the circle completely disappear
112- val canvasAlpha = remember { Animatable (1f ) }
128+ val canvasAlpha = remember { Animatable (0f ) }
113129
114130 LaunchedEffect (currentIndex) {
115131 outerAnimatable.snapTo(0.6f )
@@ -193,9 +209,12 @@ fun TargetShowcaseLayout(
193209 val prevY = animatedY.value
194210 val prevWidth = animatedWidth.value
195211 val prevHeight = animatedHeight.value
196-
212+ if (currentIndex == 0 || isSingleGreeting){
213+ // canvasAlpha.snapTo(0f)
214+ canvasAlpha.animateTo(1f , animationSpec = tween(durationMillis = animationDuration / 2 , easing = FastOutSlowInEasing ))
215+ }
197216 // If this is the first showcase or we're resetting, snap to initial values
198- if (currentIndex == 1 || currentIndex == initIndex) {
217+ if (currentIndex == 0 || currentIndex == initIndex) {
199218 animatedX.snapTo(offset.x)
200219 animatedY.snapTo(offset.y)
201220 animatedHeight.snapTo(0f )
@@ -207,7 +226,16 @@ fun TargetShowcaseLayout(
207226 launch {
208227 animatedWidth.animateTo(itemSize.width)
209228 }
210- } else if (currentIndex == scope.getHashMapSize()){
229+ delay(animationDuration.toLong())
230+ messageTextAlpha.animateTo(
231+ 1f ,
232+ animationSpec = tween(
233+ durationMillis = animationDuration / 2 ,
234+ easing = FastOutSlowInEasing
235+ )
236+ )
237+ }
238+ else if (currentIndex == scope.getHashMapSize()){
211239 // last index
212240 messageTextAlpha.animateTo(0f , animationSpec = tween(durationMillis = animationDuration / 2 , easing = FastOutSlowInEasing ))
213241 canvasAlpha.animateTo(0f )
@@ -351,13 +379,12 @@ fun TargetShowcaseLayout(
351379 }
352380 }
353381 LaunchedEffect (isShowcasing){
354- if (isShowcasing && currentIndex == 0 ){
355- currentIndex = 1
382+ if (isShowcasing && currentIndex != 0 && ! isSingleGreeting){
356383 pulseAlpha.snapTo(0.6f )
357384 pulseRadius.snapTo(0f )
358385 }
359386 }
360- val message = scope.getMessageFor(currentIndex)
387+ val message = if (isSingleGreeting) singleGreetingMsg else scope.getMessageFor(currentIndex)
361388 val textMeasurer = rememberTextMeasurer()
362389
363390 Canvas (
@@ -369,7 +396,72 @@ fun TargetShowcaseLayout(
369396 Level .VERBOSE ,
370397 TAG + " tapped here $it "
371398 )
372- if (currentIndex + 1 < scope.getHashMapSize()) {
399+ if (showCasingItem) {
400+ coroutineScope.launch {
401+ // Fade out the message text
402+ messageTextAlpha.animateTo(
403+ 0f ,
404+ animationSpec = tween(
405+ durationMillis = animationDuration / 3 ,
406+ easing = FastOutSlowInEasing
407+ )
408+ )
409+
410+ // Fade out the entire canvas to make the circle completely disappear
411+ launch {
412+ canvasAlpha.animateTo(
413+ 0f ,
414+ animationSpec = tween(
415+ durationMillis = animationDuration / 3 ,
416+ easing = FastOutSlowInEasing
417+ )
418+ )
419+ }
420+
421+ // Wait for animations to complete
422+ delay((animationDuration / 3 ).toLong())
423+
424+ // Finish showcasing the single item
425+ scope.showcaseItemFinished()
426+
427+ currentIndex = initIndex
428+ println (" Showcase index reset to $currentIndex " )
429+ }
430+ return @detectTapGestures
431+ }
432+ else if (isSingleGreeting) {
433+ coroutineScope.launch {
434+ // Fade out the message text
435+ messageTextAlpha.animateTo(
436+ 0f ,
437+ animationSpec = tween(
438+ durationMillis = animationDuration / 3 ,
439+ easing = FastOutSlowInEasing
440+ )
441+ )
442+
443+ // Fade out the entire canvas to make the circle completely disappear
444+ launch {
445+ canvasAlpha.animateTo(
446+ 0f ,
447+ animationSpec = tween(
448+ durationMillis = animationDuration / 3 ,
449+ easing = FastOutSlowInEasing
450+ )
451+ )
452+ }
453+
454+ // Wait for animations to complete
455+ delay((animationDuration / 3 ).toLong())
456+
457+ // Finish showcasing the greeting
458+ scope.showGreetingFinished()
459+
460+ currentIndex = initIndex
461+ }
462+ return @detectTapGestures
463+ }
464+ else if (currentIndex + 1 < scope.getHashMapSize()) {
373465 if (! animateToNextTarget){
374466 // Shrink at current location, then move to new location, then expand
375467 // Step 1: Shrink at current location
@@ -535,6 +627,67 @@ fun TargetShowcaseLayout(
535627 }
536628 }
537629 ) {
630+ if (isSingleGreeting || currentIndex == 0 ){
631+ // For greeting, fill the entire screen with a solid color
632+ drawRect(
633+ color = if (isDarkLayout) Color .White .copy(alpha = 0.9f ) else Color .Black .copy(alpha = 0.9f ),
634+ size = size,
635+ alpha = canvasAlpha.value
636+ )
637+
638+ // Display the greeting message in the middle of the screen
639+ message?.let { msg ->
640+ // Measure text with appropriate constraints to ensure it wraps if needed
641+ val maxTextWidth = max(1 , (size.width * 0.8f ).toInt()) // Use 80% of screen width
642+ val textResult = textMeasurer.measure(
643+ msg.text,
644+ style = msg.textStyle,
645+ overflow = TextOverflow .Visible ,
646+ constraints = Constraints (0 , maxTextWidth)
647+ )
648+
649+ // Center the text on the screen
650+ val textX = (size.width - textResult.size.width) / 2
651+ val textY = (size.height - textResult.size.height) / 2
652+
653+ // Draw a background for the text with padding
654+ val bgPadding = 40f
655+ val bgRect = Rect (
656+ left = textX - bgPadding,
657+ top = textY - bgPadding,
658+ right = textX + textResult.size.width + bgPadding,
659+ bottom = textY + textResult.size.height + bgPadding
660+ )
661+
662+ // Draw the text background
663+ if (msg.roundedCorner == 0 .dp) {
664+ drawRect(
665+ color = msg.msgBackground ? : Color .Transparent ,
666+ topLeft = Offset (bgRect.left, bgRect.top),
667+ size = Size (bgRect.width, bgRect.height),
668+ alpha = messageTextAlpha.value
669+ )
670+ } else {
671+ drawRoundRect(
672+ color = msg.msgBackground ? : Color .Transparent ,
673+ topLeft = Offset (bgRect.left, bgRect.top),
674+ size = Size (bgRect.width, bgRect.height),
675+ cornerRadius = CornerRadius (msg.roundedCorner.value),
676+ alpha = messageTextAlpha.value
677+ )
678+ }
679+
680+ // Draw the text
681+ drawText(
682+ textResult,
683+ topLeft = Offset (textX, textY),
684+ alpha = messageTextAlpha.value
685+ )
686+ }
687+
688+ // Return early to avoid drawing the target shape
689+ return @Canvas
690+ }
538691 // Calculate the radius for the target shape
539692 // For a circle, this is the actual radius
540693 // For a rectangle, we'll use the actual width and height
@@ -801,7 +954,7 @@ fun TargetShowcaseLayout(
801954 style = Fill // Fill the donut shape
802955 )
803956
804- // Draw the pulsing ring (outside the hole )
957+ // Draw the pulsing ring (outside the punch )
805958 val pulsePath = Path ().apply {
806959 op(
807960 Path ().apply {
0 commit comments