From ae1f8b7a17c4742f7f2c69bc1b7026139c4c92d9 Mon Sep 17 00:00:00 2001 From: paul-at-equal Date: Tue, 18 Aug 2026 18:46:40 +0530 Subject: [PATCH] feat: add ProgressIndicatorStyle.PILL for multi-step progress indicators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an alternative visual style for the multi-step tooltip progress indicator: instead of every step being a same-size dot, the current step renders as an elongated pill while the other steps stay small dots — matching an iOS-style page indicator, per a requested reference design (a "MyGate Security" style onboarding screen). - ProgressIndicatorStyle enum: DOTS (existing fill-up-to-current-step look, now the explicit default) and PILL (only the current step is highlighted, as a pill; other steps are plain dots regardless of whether they're before or after the current one). - CoachmarkConfig.progressIndicatorStyle selects the style; CoachmarkConfig.progressActivePillWidth sizes the pill (height matches the existing 8dp dot size, so PILL is a proper stadium shape via RoundedCornerShape rather than a stretched ellipse). - Reuses the existing CoachmarkColors.progressActiveColor / progressInactiveColor — no new color config needed. - Added a "Start Tour (Pill)" button to the Multi-Step Sequence sample to demo the new style alongside the existing dots tour. Bumped to 1.0.0-beta16 and updated CHANGELOG. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 8 +++ lumen/api/android/lumen.api | 54 +++++++++++------- lumen/api/jvm/lumen.api | 54 +++++++++++------- lumen/build.gradle.kts | 2 +- .../CoachmarkTooltipScreenshotTest.kt | 22 +++++++ .../tooltip_multiStep_pillIndicator.png | Bin 0 -> 35217 bytes .../kotlin/io/luminos/CoachmarkScrim.kt | 6 ++ .../kotlin/io/luminos/CoachmarkTarget.kt | 18 ++++++ .../kotlin/io/luminos/CoachmarkTooltip.kt | 39 ++++++++++--- .../kotlin/io/luminos/CoachmarkTargetTest.kt | 8 +++ .../sample/examples/SequenceExample.kt | 19 +++++- 11 files changed, 178 insertions(+), 52 deletions(-) create mode 100644 lumen/src/androidUnitTest/snapshots/tooltip_multiStep_pillIndicator.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d33f43..46f8e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [1.0.0-beta16] - 2026-08-18 + +### Features +- New `ProgressIndicatorStyle.PILL`: the current step in a multi-step progress indicator renders as an elongated pill while the other steps stay small dots, like an iOS-style page indicator. `CoachmarkConfig.progressIndicatorStyle` (default `DOTS`, preserving the existing fill-up-to-current-step look) and `CoachmarkConfig.progressActivePillWidth` control it + +### Sample App +- Added a "Start Tour (Pill)" button to the Multi-Step Sequence example to demo `ProgressIndicatorStyle.PILL` + ## [1.0.0-beta15] - 2026-03-18 ### Features diff --git a/lumen/api/android/lumen.api b/lumen/api/android/lumen.api index 69e1a26..ac8694e 100644 --- a/lumen/api/android/lumen.api +++ b/lumen/api/android/lumen.api @@ -72,8 +72,8 @@ public final class io/luminos/CoachmarkColorsKt { public final class io/luminos/CoachmarkConfig { public static final field $stable I - public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1-D9Ej5fM ()F public final fun component10 ()I public final fun component11 ()I @@ -81,39 +81,41 @@ public final class io/luminos/CoachmarkConfig { public final fun component13 ()Lio/luminos/ScrimOpacity; public final fun component14 ()Lio/luminos/ScrimTapBehavior; public final fun component15 ()Z - public final fun component16 ()Z - public final fun component17 ()Lio/luminos/BackPressBehavior; - public final fun component18 ()Lio/luminos/HighlightAnimation; - public final fun component19 ()I + public final fun component16 ()Lio/luminos/ProgressIndicatorStyle; + public final fun component17-D9Ej5fM ()F + public final fun component18 ()Z + public final fun component19 ()Lio/luminos/BackPressBehavior; public final fun component2-D9Ej5fM ()F - public final fun component20 ()Z - public final fun component21 ()Ljava/lang/String; - public final fun component22 ()J - public final fun component23 ()Z + public final fun component20 ()Lio/luminos/HighlightAnimation; + public final fun component21 ()I + public final fun component22 ()Z + public final fun component23 ()Ljava/lang/String; public final fun component24 ()J - public final fun component25 ()Ljava/lang/String; + public final fun component25 ()Z public final fun component26 ()J - public final fun component27 ()Z - public final fun component28-D9Ej5fM ()F - public final fun component29-D9Ej5fM ()F + public final fun component27 ()Ljava/lang/String; + public final fun component28 ()J + public final fun component29 ()Z public final fun component3-D9Ej5fM ()F public final fun component30-D9Ej5fM ()F public final fun component31-D9Ej5fM ()F - public final fun component32-e0LSkKk ()I - public final fun component33-e0LSkKk ()I + public final fun component32-D9Ej5fM ()F + public final fun component33-D9Ej5fM ()F public final fun component34-e0LSkKk ()I public final fun component35-e0LSkKk ()I public final fun component36-e0LSkKk ()I - public final fun component37 ()Landroidx/compose/foundation/layout/Arrangement$Horizontal; - public final fun component38 ()Z + public final fun component37-e0LSkKk ()I + public final fun component38-e0LSkKk ()I + public final fun component39 ()Landroidx/compose/foundation/layout/Arrangement$Horizontal; public final fun component4 ()F + public final fun component40 ()Z public final fun component5 ()Lkotlin/jvm/functions/Function3; public final fun component6-D9Ej5fM ()F public final fun component7-D9Ej5fM ()F public final fun component8-D9Ej5fM ()F public final fun component9-D9Ej5fM ()F - public final fun copy-ZvfVXdg (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;Z)Lio/luminos/CoachmarkConfig; - public static synthetic fun copy-ZvfVXdg$default (Lio/luminos/CoachmarkConfig;FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILjava/lang/Object;)Lio/luminos/CoachmarkConfig; + public final fun copy-C8SqqoU (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;Z)Lio/luminos/CoachmarkConfig; + public static synthetic fun copy-C8SqqoU$default (Lio/luminos/CoachmarkConfig;FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILjava/lang/Object;)Lio/luminos/CoachmarkConfig; public fun equals (Ljava/lang/Object;)Z public final fun getBackPressBehavior ()Lio/luminos/BackPressBehavior; public final fun getConnectorAnimationDuration ()I @@ -133,6 +135,8 @@ public final class io/luminos/CoachmarkConfig { public final fun getDontShowAgainTextAlign-e0LSkKk ()I public final fun getFadeAnimationDuration ()I public final fun getHighlightAnimation ()Lio/luminos/HighlightAnimation; + public final fun getProgressActivePillWidth-D9Ej5fM ()F + public final fun getProgressIndicatorStyle ()Lio/luminos/ProgressIndicatorStyle; public final fun getPulseDurationMs ()I public final fun getRetrySkippedTargets ()Z public final fun getScrimOpacity ()Lio/luminos/ScrimOpacity; @@ -323,7 +327,7 @@ public final class io/luminos/CoachmarkTarget { } public final class io/luminos/CoachmarkTooltipKt { - public static final fun CoachmarkTooltip-eWZOFlk (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILio/luminos/CoachmarkColors;FZZZLjava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;FFFLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;IIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZJFFLkotlin/jvm/functions/Function1;ZLandroidx/compose/runtime/Composer;IIIIII)V + public static final fun CoachmarkTooltip-CyZyjUA (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILio/luminos/CoachmarkColors;FZLio/luminos/ProgressIndicatorStyle;FZZLjava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;FFFLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;IIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZJFFLkotlin/jvm/functions/Function1;ZLandroidx/compose/runtime/Composer;IIIIII)V } public final class io/luminos/ConnectorEndStyle : java/lang/Enum { @@ -465,6 +469,14 @@ public final class io/luminos/OverlayCoordinatorKt { public static final fun rememberOverlayCoordinator (Landroidx/compose/runtime/Composer;I)Lio/luminos/OverlayCoordinator; } +public final class io/luminos/ProgressIndicatorStyle : java/lang/Enum { + public static final field DOTS Lio/luminos/ProgressIndicatorStyle; + public static final field PILL Lio/luminos/ProgressIndicatorStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/luminos/ProgressIndicatorStyle; + public static fun values ()[Lio/luminos/ProgressIndicatorStyle; +} + public final class io/luminos/ScrimOpacity : java/lang/Enum { public static final field DARK Lio/luminos/ScrimOpacity; public static final field EXTRA_DARK Lio/luminos/ScrimOpacity; diff --git a/lumen/api/jvm/lumen.api b/lumen/api/jvm/lumen.api index f7942da..1294528 100644 --- a/lumen/api/jvm/lumen.api +++ b/lumen/api/jvm/lumen.api @@ -68,8 +68,8 @@ public final class io/luminos/CoachmarkColorsKt { public final class io/luminos/CoachmarkConfig { public static final field $stable I - public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1-D9Ej5fM ()F public final fun component10 ()I public final fun component11 ()I @@ -77,39 +77,41 @@ public final class io/luminos/CoachmarkConfig { public final fun component13 ()Lio/luminos/ScrimOpacity; public final fun component14 ()Lio/luminos/ScrimTapBehavior; public final fun component15 ()Z - public final fun component16 ()Z - public final fun component17 ()Lio/luminos/BackPressBehavior; - public final fun component18 ()Lio/luminos/HighlightAnimation; - public final fun component19 ()I + public final fun component16 ()Lio/luminos/ProgressIndicatorStyle; + public final fun component17-D9Ej5fM ()F + public final fun component18 ()Z + public final fun component19 ()Lio/luminos/BackPressBehavior; public final fun component2-D9Ej5fM ()F - public final fun component20 ()Z - public final fun component21 ()Ljava/lang/String; - public final fun component22 ()J - public final fun component23 ()Z + public final fun component20 ()Lio/luminos/HighlightAnimation; + public final fun component21 ()I + public final fun component22 ()Z + public final fun component23 ()Ljava/lang/String; public final fun component24 ()J - public final fun component25 ()Ljava/lang/String; + public final fun component25 ()Z public final fun component26 ()J - public final fun component27 ()Z - public final fun component28-D9Ej5fM ()F - public final fun component29-D9Ej5fM ()F + public final fun component27 ()Ljava/lang/String; + public final fun component28 ()J + public final fun component29 ()Z public final fun component3-D9Ej5fM ()F public final fun component30-D9Ej5fM ()F public final fun component31-D9Ej5fM ()F - public final fun component32-e0LSkKk ()I - public final fun component33-e0LSkKk ()I + public final fun component32-D9Ej5fM ()F + public final fun component33-D9Ej5fM ()F public final fun component34-e0LSkKk ()I public final fun component35-e0LSkKk ()I public final fun component36-e0LSkKk ()I - public final fun component37 ()Landroidx/compose/foundation/layout/Arrangement$Horizontal; - public final fun component38 ()Z + public final fun component37-e0LSkKk ()I + public final fun component38-e0LSkKk ()I + public final fun component39 ()Landroidx/compose/foundation/layout/Arrangement$Horizontal; public final fun component4 ()F + public final fun component40 ()Z public final fun component5 ()Lkotlin/jvm/functions/Function3; public final fun component6-D9Ej5fM ()F public final fun component7-D9Ej5fM ()F public final fun component8-D9Ej5fM ()F public final fun component9-D9Ej5fM ()F - public final fun copy-ZvfVXdg (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;Z)Lio/luminos/CoachmarkConfig; - public static synthetic fun copy-ZvfVXdg$default (Lio/luminos/CoachmarkConfig;FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILjava/lang/Object;)Lio/luminos/CoachmarkConfig; + public final fun copy-C8SqqoU (FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;Z)Lio/luminos/CoachmarkConfig; + public static synthetic fun copy-C8SqqoU$default (Lio/luminos/CoachmarkConfig;FFFFLkotlin/jvm/functions/Function3;FFFFIIILio/luminos/ScrimOpacity;Lio/luminos/ScrimTapBehavior;ZLio/luminos/ProgressIndicatorStyle;FZLio/luminos/BackPressBehavior;Lio/luminos/HighlightAnimation;IZLjava/lang/String;JZJLjava/lang/String;JZFFFFIIIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZIILjava/lang/Object;)Lio/luminos/CoachmarkConfig; public fun equals (Ljava/lang/Object;)Z public final fun getBackPressBehavior ()Lio/luminos/BackPressBehavior; public final fun getConnectorAnimationDuration ()I @@ -129,6 +131,8 @@ public final class io/luminos/CoachmarkConfig { public final fun getDontShowAgainTextAlign-e0LSkKk ()I public final fun getFadeAnimationDuration ()I public final fun getHighlightAnimation ()Lio/luminos/HighlightAnimation; + public final fun getProgressActivePillWidth-D9Ej5fM ()F + public final fun getProgressIndicatorStyle ()Lio/luminos/ProgressIndicatorStyle; public final fun getPulseDurationMs ()I public final fun getRetrySkippedTargets ()Z public final fun getScrimOpacity ()Lio/luminos/ScrimOpacity; @@ -319,7 +323,7 @@ public final class io/luminos/CoachmarkTarget { } public final class io/luminos/CoachmarkTooltipKt { - public static final fun CoachmarkTooltip-eWZOFlk (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILio/luminos/CoachmarkColors;FZZZLjava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;FFFLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;IIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZJFFLkotlin/jvm/functions/Function1;ZLandroidx/compose/runtime/Composer;IIIIII)V + public static final fun CoachmarkTooltip-CyZyjUA (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IILio/luminos/CoachmarkColors;FZLio/luminos/ProgressIndicatorStyle;FZZLjava/lang/String;ZLjava/lang/String;ZLkotlin/jvm/functions/Function1;FFFLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;IIIILandroidx/compose/foundation/layout/Arrangement$Horizontal;ZJFFLkotlin/jvm/functions/Function1;ZLandroidx/compose/runtime/Composer;IIIIII)V } public final class io/luminos/ConnectorEndStyle : java/lang/Enum { @@ -476,6 +480,14 @@ public final class io/luminos/PreferencesCoachmarkStorage : io/luminos/Coachmark public fun remove (Ljava/lang/String;)V } +public final class io/luminos/ProgressIndicatorStyle : java/lang/Enum { + public static final field DOTS Lio/luminos/ProgressIndicatorStyle; + public static final field PILL Lio/luminos/ProgressIndicatorStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/luminos/ProgressIndicatorStyle; + public static fun values ()[Lio/luminos/ProgressIndicatorStyle; +} + public final class io/luminos/ScrimOpacity : java/lang/Enum { public static final field DARK Lio/luminos/ScrimOpacity; public static final field EXTRA_DARK Lio/luminos/ScrimOpacity; diff --git a/lumen/build.gradle.kts b/lumen/build.gradle.kts index 3edea9a..a11daa5 100644 --- a/lumen/build.gradle.kts +++ b/lumen/build.gradle.kts @@ -106,7 +106,7 @@ android { } } -val libraryVersion = "1.0.0-beta15" +val libraryVersion = "1.0.0-beta16" val libraryGroup = "io.github.aldefy" val libraryArtifact = "lumen" diff --git a/lumen/src/androidUnitTest/kotlin/io/luminos/screenshot/CoachmarkTooltipScreenshotTest.kt b/lumen/src/androidUnitTest/kotlin/io/luminos/screenshot/CoachmarkTooltipScreenshotTest.kt index 9ab4527..f609e16 100644 --- a/lumen/src/androidUnitTest/kotlin/io/luminos/screenshot/CoachmarkTooltipScreenshotTest.kt +++ b/lumen/src/androidUnitTest/kotlin/io/luminos/screenshot/CoachmarkTooltipScreenshotTest.kt @@ -17,6 +17,7 @@ import com.github.takahirom.roborazzi.captureRoboImage import io.luminos.CoachmarkTooltip import io.luminos.DarkCoachmarkColors import io.luminos.LightCoachmarkColors +import io.luminos.ProgressIndicatorStyle import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -83,6 +84,27 @@ class CoachmarkTooltipScreenshotTest { capture("tooltip_multiStep_step2of4") } + @Test + fun tooltip_multiStep_pillIndicator() { + composeTestRule.setContent { + MaterialTheme(colorScheme = lightColorScheme()) { + Box(modifier = Modifier.background(Color(0xFF1A1A2E)).padding(16.dp)) { + CoachmarkTooltip( + title = "Step 2", + description = "The current step renders as a pill; other steps stay dots.", + ctaText = "Next", + currentStep = 2, + totalSteps = 4, + colors = LightCoachmarkColors, + progressIndicatorStyle = ProgressIndicatorStyle.PILL, + onCtaClick = {}, + ) + } + } + } + capture("tooltip_multiStep_pillIndicator") + } + @Test fun tooltip_withSkipButton() { composeTestRule.setContent { diff --git a/lumen/src/androidUnitTest/snapshots/tooltip_multiStep_pillIndicator.png b/lumen/src/androidUnitTest/snapshots/tooltip_multiStep_pillIndicator.png new file mode 100644 index 0000000000000000000000000000000000000000..47c3f0059bc456642f03a52098226614068fca1c GIT binary patch literal 35217 zcmeEuXH-*Nx2~dyh=6pZD;)#`q(cCuNDUpOO0S{!Dx%T^M0!UcBm@Dag^tpTfOH5Y z^b&gL{cc`;zj5vv_xwJ8ZhmC2vy-*=o@=f-pZPp<5w5PLKzN7x&b4dT2o+z*YF@i` z1Agt=^_knZfKTAV(!l$hYl^Zm+TOLCfi2VZ6tK^o$m3p%uI|?Z?hR0 z*&mS0w!DeR&I!A&A&q~B6j$b+y33C@ckaN$a5%Fqo-|F0&)?kg7js)K-1=$+Q`0G; z>st*-9fYCJB^vQ+YMT%`wvDxRqZcuD!x6Z^RsVT8R~{2z5d3>IZ+87{7%>@%KPl?b?62%!}JYxBeacn#ja|z0&_MG|p}9 ze}@&lev|X>-*3{d{SR08pC07@4}a}{p5^~7;|wuV89IN*y%dL|vIG81?WX`ly7b~R zCL?cO#>*LxOd<*HKPRN!5g&mQ&ou2^E{;hr{JbP?$NGni@;WgfApiNXB-S|F7e9Kp zkJkmRQy9MT`wMJk$}j9bpIO~~L5xs8tWUka*oau8%*tG&NZ;iVt;}qF>+L>bZICxj z?6Y#&A$b1__YIWZT{s@V?{a_EL;IhdMW?!R}4i`C8c4UZP{NR3pCqo*us zIH@_p6f(^%X9bZ2jy7KG^RoS_@t|qV@KPh8G8$s!c%j5k`bfh6o&-+^Q6(D6UXSfs zYx&Gl*b;oh1h+CK=xqKng1140-PnJ}vcK`NLw15)DO_h7Q5_J|;^Ah1>kj4$%B4OZLdnU;aJp#$4-xSHa5gn(ue>cAlNf3-sl@ z4#VondfAP+HX0{zc~n!LI-wKI-+|NW$l&=aaE;vW-k5^-bFi$yIp0U~YoQEkkt9rx zB+D68s$t0o$rF)NRmd=GxwJ$wh_zAbe9vtz>++1P1XIpSiY(MC@v4ko3%w5q{~0ys zvmVYcc}SAsfa>}LGRr>fWpU8uDcY<*hXOxV|I_5b8u#PjzW48oGK#Vb^_-r(%#yv3 za9t>ho)MJe>G+vfPvYcrk?9&lFP1)6bhhJe>!PeGqXIbj9 zqwvS>$(qR}Yo*hrbRD*fp=GVai6oSWF8r~B)Wy-DMFz?spxWr~L@FBNYm;6_`|r|? z;%AOtoa42=!;h1z_l;^T3G~o-OFcK*SiJhX)Y6*IZE3Q@Y=2l!*snM+pG|$4twkkc zK?v>L`Vj(_u(j$MFMW=T)%5Y!uDjrg}1pyU&HA0l0ms5gH%3DHe0Ka?Q@R4plQ~*1 z>#hPh8=W2mJnR)+erC<`@!bcBaO7{2dwcW+c^xDX%<}q39s-J|?o%G4=h62>wd1dz**O>dljyd-q)@`T4E} zj#d%5@@#%kWk5lb=lYoPU26BZs{N79tB62lX&E55kD>A#n{|4gzapf`Y3#eIv+28* zGd##+RQwe4tX>M^Nb~+Zck5MT%Y5ennL26MnYsQq|Gr_wDrFBGvNacHN}jUayb(@( z*jY_9|=^b|*{XxWWzIyE)~jxg+H0 zAA-+pFgbS6VWu*dP;eiZgkFnX28qfc_Nk_&EOiupw8qy z-olb;8QXozHpoS(XUHf0)c~oH4hr~?He(0`9B{bU1V>v;)l zr3y;L=9AAGgZdlTDNYZAI^(_-8#t!02VS0Pez)i(xJnwcK?n7q)ActiBSlWxjD6%~ zJm7SZ*0RcCBzhX-aG6*L+w5yj4rDWEK9eD)wGm8onpzV2i=wb9*~P~7nQJ#jD=fQf zJ1L4z5?{}lb&x+IkG^>eLXcmDDK}9qS};|{GG>V2B1StK@*z_-`&0_iw96WU4K@i# z+d;KVoc=wRWJbS*Shm)YqKV@YCZqBkF|!u#f}&AFTB``#%TR_*Pof4f?`F*V@CWxO*t}4oaJ;>c-XIk(4MQ8mTT)`(lZVXm~w(FLDy?R|2g5hpcS@4Tz z)+C@ayv zpKqbwcz*!so3MpgX>JN`15b*}vt_Aj`RzK-^`jT=Yo1X|@79Zo0?l+7KzU%dgfS_q zR(5qTGx1t1j?NMu^PE;r%KTlcBOLV^vA z71M@2T?-||Tz?*{;P-7;Xky|yvE)xxFC!*}>2xs(uISP~0rFGdLX@I1;So z@U?3)eKF%hO8H6ZDpX|`zp!KQxUh|yL$zFHcl-UUi@oJ{zj-`$*FtnH{-X5HO1$d$ z+|&`RmPz|0mq+cFi~0}u#5~vYUY6Uuuflc(M<<7|>I!P^y$BZbz})@rFwXhieU-qh z^K*h(Ys7Zn&^oHZ;IZ+U|09X-P2q<}^I&M@BW1t5C#jNt4`Vic&d+xH=H_FhTpHG@ zZ3olL`Z5E0x0;H9Kv9wdhs)Uw^J)bvWjTEDl0*j_O0orsK}D1<-1`-qp-{-jqVJ_d z-ct(iN0aOX9M_*1+kTRw_puNX9``7ExnqX?!E)MSFg$j3I$pqOBbQ{BU-+W5pHA_6U_jZMve zRG@&z2en7*i5z$Hcb?G*IWP%1PW}q3CtN4qw&X(w6_Qy%$^EN=A&jFMYy z_FU`Fj1MrD+(e*!6}f`6@+ByJDtfnaD5Ot}S5$?kR!<#sNNJb)Tw7yhS?ZTxGV(N3 zaf4AFvC=FLVMMF3P1;Mg#hJayd`Su(Mox|OJtVcvw=I2_Ox5<&(f!sgZ+U!)wKEvM zcG2l%7aN_r+FX>>AKZ#rsyWy=o!liiH@+Nib1>Oc%<7Yp`l0G!Hv{-@l5v!$BQ(TVNdmJCiLisj!3f21CCG+Pcp%9eyi zQWWRq=g%Ww3P0_=$ySUbiYAP|93OjRKf*EG{(&&9Dy4X>o0phnZUjspP2NXo`Nwbw z93LjYZJGCkCa49eBi<7vS??JmM)!Z>F^F$fBZ^8iWbJb0>3o3Xaueah_&KFmUvgvY zI~DN!iM%EW`r(4adY@Nq`&EK+y`F^><1wUfI%w(U7~UBQkx2FkxHt~9HYmwx3~bjb z;?YXwZKw)X@G>%Qo1{DyLc_=A%AaZ|^ttXDQdN{WPWhIF5>nU;|GBY*ggSD#KCRM2 zSsa*7VR5gijE2;6GWYWhryw#UIxy>dD)nQ{K{EJ(r*4I>(yB@d=|#q9VF`UlWnwCx zq;2qviO?k8pjZWDv7X6P{A5m{u1xOjp&Q|6_TG*V!GLFhG-B?LV>WA+`b-wSwse7e zwbOqKY(V|Ds;%|&4pZ5{nV5L=MQ^cMbX#M(`Kj+XXlbSjY34EH`>oY{c7Kj{-)J_A+z!gN7B8x;pa1}E{# zzxO-Y{OJ0>>f+=QXkW(J_h1dZO>gY%_BC!Ze_h)jMS7pSAN8BG5m#>Q5eAbto{?U> zHs3%I77x!y81MhV=h^&mUN{NPk>VA0@b`98HLB^u#l)a7iW8oEwA`cphEsaAaBUv> zh0A#|xVYEmT>sa#pv%LcR^Tn`e$c@zI69lKrr`whtwJY3mv4{+ZoCzR;qN3%THOW7 zj7HNj=o~3lY9#c(25< z`W^T$h4s4gkn(Pzj2v$(H$VkJ!PJX;VcYHgAuPoSR!JNU*P{(1*6}{_%=y-f_Y>2?Sz6@#;DT zL92AbC(*Z=g1yHr^}?x)U(zTh!y_xdoHV*U2$fD~I%x?n@hSgm44?>e?9=J>+uA37 zV~Fk9W`*a2kpzQiNk3!Wi#-@awPmz#qWs8okFtkAjM}KGR+aaQ@Xf|RJ zPL(i14sE~*DQzcs>m$3QLUXVmm9qm6r-&u=b$L0|%aE-}7Y){Z%M$o&DCAb;@4c?b z)#Z_*l6x?THjvrlUI zgf%Nqm{`i|)buKFzfdBWyX2#464;h&fXeHK2g+d*$K!CbULw^aV7CW3cX^hD7R^fi z(wA|D(G#R4kt_7?f6_dv6rr{2Ca57LK=znjCuJpAW(>dDseZpWj-D#Gj@#nxD*E&1 z$}g6=YXIP*1X7oqe`H_hpIN=f>+?umxur917Vmb2P5(Fx``N(L_oit=NxON5qf{oe zFK&ymzlehcm{(70RVN(_?2|wRyZOE1fbm2|hhw|vZv(Js3v<=rP=_B|H7WkQ*Zeskl z0)FhjTl3xixf*Q~iG6TSV56INt1r&gC@X*!osj3Hm+(Z{B>VHdT~hc!G=~=be81sn zj$C&fSMpL9TX;9)0tOyM4t1?9lf5&<>ytP_!K&Va{gsAHzKXH!)3Y3tJ^|2>1N(w2 z(qXI;!qzw9(JySZ)o>b^B(kp`h4;rM7V^`#heX{n=TnvQHIs& zqHZ8?mj`5`a;kztkS=m9S3;I9ebe4 z*?gXkvVgaXW;r~448q>NYP4uZRg_>K8&QQ1t`1s9xb;?d6fkg~`Ap#7c=i_)Kf%go z4+M+LnYBm9R5fhZ*(AiszKWtIETVXxX2$2aJ{8+2={x1YWMrU`@aD^&;u$Wm{8HY& zwNkm(ISjTWg9^B>yrduA%^98p_y86;PKfQ2M3>COk3EP}%# z_l~yM3meQ(yJXj+!K&k>+E%dCc}s3}aXWq51K$}7>&D_f?CyQfi6s8BVi9UkO8C3F@gxKJS}%rszMNlu&X49zT6!~TTe=eL zGY%hG_xQ>`HmovqK$Xj-i&2~%lkJKHp2=$dfFBw_@A2Vc7%q>{5~*t^C6^~oK-8om z&9V9Nvd$aoy_nF0PvYaFBt6*399`)+)tl>U%q$^w?w;knl)_>**HVCQDxx8={bN2n zD~UDmGR18rW^^B@5MIS67{!rJ=re{o?Fg0ttj`rH#W>J4?2jno{}i3rIc&TQmE7b$m5zEA&qhy6hccVa`#)XIs(gHca$sMDsdYAS$kmzt$RUXwlo4;<_ z`4n4czFI%!;c~j6q3*J309%T1ku%aW7X4kZm@;wuAA3scusPjCkiIR4CQRcM!R_bU zdJ2ZM8U|pkZsBE|shv8yBkg1PTzVR-m%`KD1}njei`Af$l{ncz=8q5;Lz1fuqZXMe z$gAd|TF)q2@CCUaoESh5x=b!O#an@i4$xTN2^F3++b>XbEVu2pY_{J!PKfEB}QRc zn;z4lItGb_I)zmf+MIo1B<5OKU3^HDB!!`ZgZ`Y>aS}K{Gk6r;a-;bZ*Wj3i6rk`5 zP|O>l8Z`;Z74bh}AHB2K5O};u1ytY>?`Gf|VOrJvOO%?1B2p(Sk2AGbpf?fIc3fzC zox)b(sizM|HX7DO>OYfC*|qX!a5rG;yH$8lzTzGj^WM&SW3GX^>A_hCf+4Qrl{9v; ze;EXsh4<1db#ivWaO3)y5@*|SU(k~uU1Q&zY&XZqAsy@KNaPIED?}=g2d=e42sS!u z@jCgPOrRK_+16#QmwA7QN>vGcMn zE^Z3(pS=g0;@^EZX@H&gavKp}d%s|C7BCVpo`X`jN7e9*Yb!E?u}T zhoK?AO%pfL`sfGk>6TwTCD6)H5W~?4YSd2b z@1}GNK{{142()gnEncqAFPk_r{kecnMja*m4o^+|i|@FF3`$R{TMcR+g|riv{p2#q z%=fC9wGB<^$q1~$(aE?*Yo*|Q;qr6$w`IxgQWMah0XW5Vo@H}Zz33p%bgbFl%V`?i zdE2f?m6@p~Ko_ipep0mc?vT`4X1i|w!?KsqjL=^^!>pr}?fbJegi1m!eY;LX!e4B= zyl%#&?vrU5m%2~ppRwN{6cVW5jOijbIl*LqKDs7y56(#buk0u-2k4j2g8!9AwE@}l zzn=uM%EkFloc&1NJZZ@-iU0i4S33Kx`jBGn-qZj)&c1MCLB>5eu9|oHz~6`%cIPJN z?N)Mly)Pl@sF7F5eG%up`%hk1RUPP?CGYZ{Pv$BF?M$FY$c7OVGXH9_12{Mo?{Z}G zQA>W)KJRN3n#FLcs_8rsezTR8qaKBE!vv4uk{HDTr-uHf3WqwB$U_}>y}gaNYm)l1sv zKlZrxf6K-HTSDT0)rjp<4V{0sW^OwJcgjf*5;FgIOS<{=y~*WF&>?{y#S94E)o?94 z=Z$pKZH8pPA%L%G-}RR`c-wwGoAc*s2-WJlZZGeY`l~ZLpQvyWi0`|&T^vK(`t~k0 zm%XruOXw#ruV0gvGmja+pDQ!`7G35%Y3hCX?es;w^W+Erkkg+V1QU(d-gp-b;SAe9 ziDKYT2lBF5#IksYU!y*0eocN?ti^@E&Gv%xzp~FQL8}T3g1blAAQq(LM-*= zDdV|#**Q2(NMKkhERx4A{>U$!Oo8CrjI+)ch5~*e2nzQw;95E^XOZ;c4SvFOQfiS1 zxeB|*=g{0iZi*yh-wKg{lNkJ75r^qIxpXh%V6q0pT~xNK*pTRIQ8jXc$X}T@HR&t`wk(r2 zmcr3qm?0W-CEVtJe;e}f2jAco=xqeiK7g`L^}15im)CPqM& z^$6!2Ws!WEHK*{Qi z*3;NN*#>6vSP?)QPo@2Ld%Q-jk+MN1j5Gc9b(zIH<8gG(A%pqCt$;Gse!slmN7~Vp zc>RP`4usn`1#3(gc69bs-^jDq<51_54?(=K>ywj%v>c+dDmOz|RjQ==H=$tztRy2baZpVg)=n}OFIcud|X9)6-ep!u$GTR#&{BSLp($%f~IsSa=xBG@KAuv6b z>gKOA#Clo*@M1rOH|0J^$1|&V$Oq^oiin_lK&|olwgT7NN@>D7dgCV=tZot1T0^~} zA34TZ;>2){RgG^Cb;dDJ9!?#8t2&&v4ZoCH_^Exx7iit!-U*eed&sKl`{bwg^46wE z%To>Y-;em%3n{dDgY~)i)Q8)pab1a~6qH(ly6}^4jaqzSrK+P*Up?E8D?=T8g8T$$ zn|-`>9PY#L)UwOn1-mdhSG(0fWk1@Geq;8~UCNGde_Hh6#>=&iBf$*jn<&3lG)ht#ZreXuVOzm;oq+KVG3d zy58`{a7oYxJ^XCOGVBhRE&AzT@N(zuZ2b#if3cmjCTDS<`z7cd^y&NA?Qa_M$54_z zVIt!0`QOL^;P>Df1B>|M(yDwkqNV! zW!mT#7MGyU{U#?vkm%%y%8cy?5?ZNxQMvlV6*hIZwHB;CX`XlSt9SS~ zs$fN%5a^AXEUkq;KMN@RrQH|B_^`k`houKy+P%P#$WuZPJS##wFf)2xgh}JKug8E2F~t0nm+kH z?{*1|Od^~8?4rEcH^_?Mt=o7K1e;4E9=7y9M?RS?*o`9XEssp%a0}XT zmD`OGH_804r@AL9w@AgT?zYp=$3@5Mj@sO-f&_XmQaF@NI0}H?1HM$ZzG(Yl!eW9X zKHBKRufZkYWO{MX@ou;hw>tjxwMlgrDz=-L`OaIsG?Mt*B~(-O>V&N(*_FRPrZUpDwhB?~)!zc0K=@3Panp?Tv$Uwh-@(qJJw_kybVXpY`?fZz&@+TFSm zY|l@kzP=EYk+-SpFzINy5)$V`zyGe`me*{jWdXY=Lg7UefAY69XN+4*UV z(i48u-534UZccjMyfiB<76QieZAtlaohR%R`xqm$_g_Q?7+Q0lhh|1+QSt`LJT|IP z`gq@^aK6NDSV?xOhMR0wLDVIu#SOgY`-T%t$KfuTr@DdKcW&a+uGRnc`qk@N%}Npg zb1CHuKik_7H}pT6b+rG%AmaRPWzgDr&Z#;52@4A(ZV_8dCAD7KQD=Z`XxhOWuXY%> zYmXTU1Qcav{qmJAN9XdMczIe+zj5tpF-~?H$gsxlQW6vcv+8M66HgJ{eE|C!Nmb%b zF|G<#lm%WtQp&bq=Xnz`b^ggjlMI6PLL@hCO(uWysT6<}z5Fh;v$d=C;N+0_ zKta9cSiACxcDh3p!&0A~v7$L!MJ-9^LD%xFF5ZakkJM@-gi+(U3g8z*X`EV19&Kyk z1DxCtUdMWm(!s5SpiFPG%=_X8j>}qNlk^dVrcsL8e-2sOg;vh6c*zX zmJ5%t-Oar}&T7$}hi`g%;SOJ-A%P3NJ*i0VWxglhGXv$#%r z%ni^07Bhh2)lFS`T@u>}Ydl;No*WkPq1kErD!|#^&Fe&o4m_>z*2O&s#Pg_}fKz(; zfHrI76y986ZfWU7qS0W!WJ;B3?MsQHnY%7W+kASd5do(MY>v~LFDj3Jox<<*O(?}z z+8?HU9`kR4be2!~9=DvOT!1XQ=7R&L8_u7*Y*aAGF8As|QK3Epowx{9|BZL2%u45D z=E{m~=V39h)z>F)TPF5a-GHXCP{ZTY^{Lb9C_k#gqGZcnceU8vf+XplsnZ5*^mC2V6kE6a02J*me-V?8@dR?`n9tx`NKHz< zsI2S7^lfckck@UdbsuUmI;1o-?#)(%Uxb@orrJc-aN(%<@y^e#@&xQdmltzjL8VM0 z>tTdPr+dLqU$1wJzqZMPF3zB8H5ypfX_OzmW#Iu22vZcnmMK*DMrjM1h`Na)`?D~z7zS@P_$ z;hZGwMNm~Aedi)DpC6waA6ghE3njt8diy?CD_KlP_ZFi%U4LAP^f{)mdG2&8=mk_6 zY-GEg!!!-(44aeionDU#NZe;1jC`v&6~mtG_SCo8igz{!tj6A^kQ{*thTp6}qX+v6 z>sJY~8{>TTBW1tew~!7G zE5)`pcRV{M!u8U=d@DEe1k@tk2Sf}Y%#nG2&Z61Q9EFs>hCD8B1*YA1w>UdDNt+V|~zn^sIp}6AdKma3M__#ZSbwtran)1{NX0T_E3&0LCiCa*dyc}EEV0Mu?xmml7r3Q;%saw5>}hZzz!TlxLH;6heNGsWhv&*{eAGI`1e zgX|@2fJy%Ry{Y-_&?j}#-jWGOu->AN6jfVW*R`8=qBjw59e)9;S?lv3MbrL_n)kO5$a{G8MN*R$}SAx`e^3J+QzTye*}?`#~eV3o z!^N;9xknZO?sK-Th&1YleI9C2@{snB0p*GqARUoSROC-d8c&Ah)KKJLwD!$I+-XPn zhT~LCGqycdkVY1ey-8O`zoAr;cQ}C>uYdY2 zG+?g~DqCiip*a`YrO_bT+6ZnJ>|JaUSwI#>t(?1K(y#+2w zYmNb-xTyv0)}CtXKger)MyKjo=^HLT$$dN|_kr#D3k9oPm{f~0Z@r!15TKrXw#yrG z*(hI&0EmCPG1oSqw@HrXf&ne!UAZx!)dg_I^CjM0UCXuUdXv&PWtx?6wGC9%yG+_i z9Gzln@6)Mr0Jvf{oQ*@ai;c3FWi70m9rqqN_k;U z4rMRe62+`JY@LXyR%?w5sVsj564jo2n&GLHnmp+X^_N*6F9U0r7_Zn0pEr7}IrfsN zS`tj%u5>iA8dHtaIq@tElm$hVF%*blQ{gM&U(=t~4?@LGFkGanzNM*>rzzSfjZ^rl zQwd*3$wt?Ka>L`URnIj^dMtM&to1Vs*WI99;9=D=d|BlX2goexD8D*FY`DpJvbkm` zkzW2fBmb(xfq6-EC=0-?2U9QQR`pcVvEEcWb&0Q zeDVwW;{#Ya??bUj2Bsi$JzShl9)m(Y{*zcMo_pQ74GabGj#8LbCO&AnX4cPd7noV}mV90nsKl{JS&W0hdeHMt?oFAaC>a@uUPGK={I=rO z*CXa$&+hi->o**~wK3^GD9|aCdDCc#LQV;dD@_11AT@n*%nT3IMv*%$batb0Hq7d~qRNN=EgJ2ts-l9oT|!=-zZNsY~5_M=ukelUU%S z!&mdRj$!F>+XZPszXKeujZk{u$@|w9BY)SJl9k5wE01}iFh)JwY{@7oXfMgB)rHq2 zA4iH5)Y4J0ynCc<+2eyrUl_2JKxo{hLR#=VQ#Dv=3vQzU0a#%AuF%sL0xljx?#kgHhv&^z@smS;?MMY^+@k;RhZj zZ~e$rt~#Ev+v4N&J&qe&>%$~ja0|N5&dj92uS{Y)F_k(gw%rhC?dcV=2=x7hQP1J3 z&#GB1 zchdgFr#Jm_)D87Cis|Im$J1vgHKxr6ih%U7RkxcdSh-f(Dj>h0IX*zgRxKT6fA*=8 z?BXrI-)wo&K;Z6S{}QQ@0<@DXxFW`e%g#A#^xPHL{*YJ%mf&lr)?#<4argp!BNYs3}FLn=kE9d6PK$mF_efpzoUA<}3m6 z?m`>;3yQ4*>H#^n^8mU@jj3K_SmwOQZ1SV!pY>QM=KXc*vOiHKxTy!M zuRb>dmS{};X0TGgxACBB(DKzAgI3dC(>_`K*A_`HfGR^XNXm2F+w9HiW}Vh>_3p45 z+ZU~Fo6m!LE`d_cGwg+Azd^ng5tJ2%L$7+c`Llu=r1r_%l~*v>0wN}4xw`e2Adhy0 zO)$oUVX;tQl}W7bVXv4b+ENupn;&zAO3>d?^U_?}V7k3XRqC{lA=E|f1&Mh$&b8oW zgsW7zNGh&ybDcOHJP&qfMMS`v4rQThVOYU{4oJ;bgF;dH>X3R_rndK{Id6~xq&A

wYw2;z4cxVqBK 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 ZjBT$8?%uiwq=*QVo~bDmKmF(3{{rS&JE8yp literal 0 HcmV?d00001 diff --git a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkScrim.kt b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkScrim.kt index fdddefb..e88e6b1 100644 --- a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkScrim.kt +++ b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkScrim.kt @@ -197,6 +197,10 @@ data class CoachmarkConfig( val scrimTapBehavior: ScrimTapBehavior = ScrimTapBehavior.DISMISS, /** Whether to show progress indicator dots for multi-step sequences */ val showProgressIndicator: Boolean = true, + /** Visual style for the progress indicator: small dots, or a pill for the current step */ + val progressIndicatorStyle: ProgressIndicatorStyle = ProgressIndicatorStyle.DOTS, + /** Width of the active pill when [progressIndicatorStyle] is [ProgressIndicatorStyle.PILL] */ + val progressActivePillWidth: Dp = 20.dp, /** Whether to wrap the tooltip content in a card/box */ val showTooltipCard: Boolean = false, /** Behavior when user presses the back button */ @@ -1049,6 +1053,8 @@ private fun BoxScope.TooltipContainer( colors = colors, cornerRadius = config.tooltipCornerRadius, showProgressIndicator = showProgressIndicator, + progressIndicatorStyle = config.progressIndicatorStyle, + progressActivePillWidth = config.progressActivePillWidth, showCard = config.showTooltipCard, showSkipButton = config.showSkipButton, skipButtonText = config.skipButtonText, diff --git a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTarget.kt b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTarget.kt index f0992e9..d45b485 100644 --- a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTarget.kt +++ b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTarget.kt @@ -195,6 +195,24 @@ enum class TargetTapBehavior { BOTH, } +/** + * Visual style for the multi-step progress indicator shown in the tooltip footer. + * + * @see CoachmarkConfig.progressIndicatorStyle + */ +enum class ProgressIndicatorStyle { + /** Every step is a small circle; steps up to and including the current one are filled with [CoachmarkColors.progressActiveColor] (default). */ + DOTS, + + /** + * The current step is an elongated pill; every other step remains a small circle in + * [CoachmarkColors.progressInactiveColor], like an iOS-style page indicator. + * + * Sized via [CoachmarkConfig.progressActivePillWidth]. + */ + PILL, +} + /** Visual style at the endpoint of the connector line. */ enum class ConnectorEndStyle { /** Small filled circle (current default). */ diff --git a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTooltip.kt b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTooltip.kt index 4a93c15..b3e5954 100644 --- a/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTooltip.kt +++ b/lumen/src/commonMain/kotlin/io/luminos/CoachmarkTooltip.kt @@ -55,6 +55,8 @@ import androidx.compose.ui.unit.sp * @param colors Theme colors * @param cornerRadius Corner radius for the card * @param showProgressIndicator Whether to show progress dots for multi-step sequences + * @param progressIndicatorStyle Visual style for the progress indicator (dots, or a pill for the current step) + * @param progressActivePillWidth Width of the active pill when [progressIndicatorStyle] is [ProgressIndicatorStyle.PILL] * @param showCard Whether to wrap content in a card/box background * @param showSkipButton Whether to show a skip button to dismiss the entire sequence * @param skipButtonText Text for the skip button @@ -78,6 +80,8 @@ fun CoachmarkTooltip( colors: CoachmarkColors, cornerRadius: Dp = 16.dp, showProgressIndicator: Boolean = true, + progressIndicatorStyle: ProgressIndicatorStyle = ProgressIndicatorStyle.DOTS, + progressActivePillWidth: Dp = 20.dp, showCard: Boolean = false, showSkipButton: Boolean = false, skipButtonText: String = "Skip", @@ -271,6 +275,8 @@ fun CoachmarkTooltip( totalSteps = totalSteps, activeColor = colors.progressActiveColor, inactiveColor = colors.progressInactiveColor, + style = progressIndicatorStyle, + activePillWidth = progressActivePillWidth, ) } @@ -380,6 +386,8 @@ private fun ProgressIndicator( totalSteps: Int, activeColor: Color, inactiveColor: Color, + style: ProgressIndicatorStyle = ProgressIndicatorStyle.DOTS, + activePillWidth: Dp = 20.dp, modifier: Modifier = Modifier, ) { Row( @@ -392,14 +400,29 @@ private fun ProgressIndicator( verticalAlignment = Alignment.CenterVertically, ) { repeat(totalSteps) { index -> - val isActive = index < currentStep - Box( - modifier = - Modifier - .size(8.dp) - .clip(CircleShape) - .background(if (isActive) activeColor else inactiveColor), - ) + when (style) { + ProgressIndicatorStyle.DOTS -> { + val isActive = index < currentStep + Box( + modifier = + Modifier + .size(8.dp) + .clip(CircleShape) + .background(if (isActive) activeColor else inactiveColor), + ) + } + + ProgressIndicatorStyle.PILL -> { + val isCurrent = index == currentStep - 1 + Box( + modifier = + Modifier + .size(width = if (isCurrent) activePillWidth else 8.dp, height = 8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(if (isCurrent) activeColor else inactiveColor), + ) + } + } } } } diff --git a/lumen/src/commonTest/kotlin/io/luminos/CoachmarkTargetTest.kt b/lumen/src/commonTest/kotlin/io/luminos/CoachmarkTargetTest.kt index f598093..00b2489 100644 --- a/lumen/src/commonTest/kotlin/io/luminos/CoachmarkTargetTest.kt +++ b/lumen/src/commonTest/kotlin/io/luminos/CoachmarkTargetTest.kt @@ -178,6 +178,14 @@ class CoachmarkTargetTest { assertTrue(values.contains(ConnectorEndStyle.CUSTOM)) } + @Test + fun progressIndicatorStyle_values() { + val values = ProgressIndicatorStyle.entries + assertEquals(2, values.size) + assertTrue(values.contains(ProgressIndicatorStyle.DOTS)) + assertTrue(values.contains(ProgressIndicatorStyle.PILL)) + } + // ── TargetTapBehavior ────────────────────────────────────────────────── @Test diff --git a/sample/src/commonMain/kotlin/com/aditlal/sample/examples/SequenceExample.kt b/sample/src/commonMain/kotlin/com/aditlal/sample/examples/SequenceExample.kt index 3298b7b..7c6c38b 100644 --- a/sample/src/commonMain/kotlin/com/aditlal/sample/examples/SequenceExample.kt +++ b/sample/src/commonMain/kotlin/com/aditlal/sample/examples/SequenceExample.kt @@ -40,6 +40,7 @@ import io.luminos.CoachmarkTarget import io.luminos.ConnectorStyle import io.luminos.CutoutShape import io.luminos.HighlightAnimation +import io.luminos.ProgressIndicatorStyle import io.luminos.ScrimTapBehavior import io.luminos.coachmarkColors import io.luminos.coachmarkTarget @@ -54,6 +55,7 @@ fun SequenceExample( val controller = rememberCoachmarkController() var showSequence by remember { mutableStateOf(false) } var selectedTab by remember { mutableIntStateOf(0) } + var progressIndicatorStyle by remember { mutableStateOf(ProgressIndicatorStyle.DOTS) } CoachmarkHost( controller = controller, @@ -63,6 +65,7 @@ fun SequenceExample( skipButtonText = "Skip tour", backPressBehavior = BackPressBehavior.NAVIGATE, scrimTapBehavior = ScrimTapBehavior.NONE, + progressIndicatorStyle = progressIndicatorStyle, ), colors = coachmarkColors(), ) { @@ -159,10 +162,24 @@ fun SequenceExample( Row( horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - Button(onClick = { showSequence = true }) { + Button( + onClick = { + progressIndicatorStyle = ProgressIndicatorStyle.DOTS + showSequence = true + }, + ) { Text("Start Tour") } + Button( + onClick = { + progressIndicatorStyle = ProgressIndicatorStyle.PILL + showSequence = true + }, + ) { + Text("Start Tour (Pill)") + } + Button(onClick = { controller.dismiss() }) { Text("Dismiss") }