diff --git a/.gitignore b/.gitignore index fb4f917..6339288 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,8 @@ node_modules/ *.properties gradle.properties -iosApp \ No newline at end of file +iosApp + +# Internal testing module (not for public release) +testing/ +testing \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9e8d55f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to PiscesSpotlight will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] - 2025-01-XX + +### Fixed +- Fixed tooltip position jumping/flickering between tutorial steps +- Eliminated layout recalculation that caused visible position shifts + +### Changed +- Tooltip height now defaults to fixed 200dp (previously dynamic/nullable) +- Tooltip content is now scrollable when it exceeds available space +- Title text limited to 2 lines with ellipsis +- Description text limited to 5 lines with ellipsis +- Button now anchored to bottom of tooltip card using weighted spacer + +### Added +- Binary compatibility validator plugin for API tracking +- Improved tooltip layout consistency across all steps + +## [0.1.0] - 2025-01-XX + +### Added +- Initial release of PiscesSpotlight +- Cross-platform support for Android, iOS, and Desktop +- Type-safe spotlight target system +- Smart tooltip positioning with automatic edge detection +- Support for multiple concurrent tutorials +- Reactive state management using Compose primitives +- Customizable tooltip positions (Top, Bottom, Left, Right) +- Flat API structure using CompositionLocal +- String-based target support as alternative to type-safe targets +- Tutorial progression controls (next, complete, reset) +- Platform-specific screen dimension handling + +[0.2.0]: https://github.com/xcodebn/piscespotlight/compare/v0.1.0...v0.2.0 +[0.1.0]: https://github.com/xcodebn/piscespotlight/releases/tag/v0.1.0 \ No newline at end of file diff --git a/README.md b/README.md index fe4e008..9c92d1d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Add PiscesSpotlight to your Compose Multiplatform project: ```kotlin commonMain.dependencies { - implementation("io.github.xcodebn:pisces-spotlight:0.1.0") + implementation("io.github.xcodebn:pisces-spotlight:0.2.0") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index cf780d5..750e64f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,16 @@ plugins { // in each subproject's classloader alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false + alias(libs.plugins.binaryCompatibilityValidator) alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false +} + +apiValidation { + // Only validate the spotlight library module + ignoredProjects.addAll(listOf("demo", "testing")) + + // Validate public API for all supported platforms + nonPublicMarkers.add("io.piscesbn.xcodebn.piscespotlight.InternalPiscesSpotlightApi") } \ No newline at end of file diff --git a/demo/README.md b/demo/README.md index de59ec1..6d64b6d 100644 --- a/demo/README.md +++ b/demo/README.md @@ -23,7 +23,7 @@ The demo showcases a 7-step tutorial covering: This demo uses the published Maven Central version of PiscesSpotlight: ```kotlin -implementation("io.github.xcodebn:pisces-spotlight:0.1.0") +implementation("io.github.xcodebn:pisces-spotlight:0.2.0") ``` ## Key Implementation Highlights diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index cb48d1f..51ef349 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -31,7 +31,7 @@ kotlin { implementation(libs.androidx.activity.compose) } commonMain.dependencies { - implementation("io.github.xcodebn:pisces-spotlight:0.1.0") + implementation("io.github.xcodebn:pisces-spotlight:0.2.0") implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) diff --git a/demo/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/App.kt b/demo/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/App.kt index 06efa5f..9ca063a 100644 --- a/demo/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/App.kt +++ b/demo/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/App.kt @@ -40,7 +40,7 @@ fun PDFSummarizerApp() { UploadButtonTarget, "Upload PDF", "Start by uploading a PDF document you want to summarize", - TooltipPosition.Bottom + TooltipPosition.Top ), SpotlightStep( SummarizeButtonTarget, @@ -52,19 +52,25 @@ fun PDFSummarizerApp() { HighlightToolTarget, "Highlight Tool", "Use this to highlight important sections in your document", - TooltipPosition.Bottom + TooltipPosition.Top ), SpotlightStep( AnnotateToolTarget, "Annotate Tool", "Add notes and comments to your PDF", - TooltipPosition.Bottom + TooltipPosition.Top + ), + SpotlightStep( + ExportToolTarget, + "Export", + "Export your document with annotations", + TooltipPosition.Top ), SpotlightStep( SaveButtonTarget, "Save", "Save your work and annotations", - TooltipPosition.Left + TooltipPosition.Top ), SpotlightStep( AIAssistantTarget, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce3748a..26931ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ androidx-core = "1.17.0" androidx-espresso = "3.7.0" androidx-lifecycle = "2.9.5" androidx-testExt = "1.3.0" +binaryCompatibilityValidator = "0.17.0" composeMultiplatform = "1.9.1" composeIcons = "1.1.1" junit = "4.13.2" @@ -30,6 +31,7 @@ composeIcons-tablerIcons = { module = "br.com.devsrsouza.compose.icons:tabler-ic [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } +binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidator" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file diff --git a/media/pisces_spotlight.gif b/media/pisces_spotlight.gif new file mode 100644 index 0000000..e301ecf Binary files /dev/null and b/media/pisces_spotlight.gif differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 539286a..0754919 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,4 +29,5 @@ dependencyResolutionManagement { } include(":demo") -include(":spotlight") \ No newline at end of file +include(":spotlight") +include(":testing") \ No newline at end of file diff --git a/spotlight/README.md b/spotlight/README.md index f4ac510..b9e1d7e 100644 --- a/spotlight/README.md +++ b/spotlight/README.md @@ -33,7 +33,7 @@ Add to your `libs.versions.toml`: ```toml [versions] -piscesSpotlight = "0.1.0" +piscesSpotlight = "0.2.0" [libraries] piscesSpotlight = { module = "io.github.xcodebn:pisces-spotlight", version.ref = "piscesSpotlight" } @@ -55,7 +55,7 @@ kotlin { ```kotlin commonMain.dependencies { - implementation("io.github.xcodebn:pisces-spotlight:0.1.0") + implementation("io.github.xcodebn:pisces-spotlight:0.2.0") } ``` diff --git a/spotlight/api/android/spotlight.api b/spotlight/api/android/spotlight.api new file mode 100644 index 0000000..affad6e --- /dev/null +++ b/spotlight/api/android/spotlight.api @@ -0,0 +1,129 @@ +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesSpotlightKt { + public static final fun PiscesSpotlightContainer (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState;Ljava/util/List;Lkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun getLocalPiscesTutorialState ()Landroidx/compose/runtime/ProvidableCompositionLocal; + public static final fun piscesSpotlightTarget (Landroidx/compose/ui/Modifier;Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/Modifier; + public static final fun piscesSpotlightTarget (Landroidx/compose/ui/Modifier;Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/Modifier; + public static final fun rememberPiscesTutorialController (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialController; + public static final fun rememberPiscesTutorialState (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig { + public static final field $stable I + public fun (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Z + public final fun component4 ()Lkotlin/jvm/functions/Function0; + public final fun copy (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public static synthetic fun copy$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig;Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnabled ()Z + public final fun getId ()Ljava/lang/String; + public final fun getScreenPredicate ()Lkotlin/jvm/functions/Function0; + public final fun getSteps ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialController { + public static final field $stable I + public fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState;)V + public final fun getTargetPositions ()Ljava/util/Map; + public final fun startTutorial (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun stopTutorial ()V +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState { + public static final field $stable I + public fun ()V + public final fun clearTutorials ()V + public final fun getActiveTutorial ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public final fun getAllTargetPositions ()Ljava/util/Map; + public final fun getCurrentStep ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public final fun getCurrentStepIndex ()I + public final fun getCurrentTutorialId ()Ljava/lang/String; + public final fun getTargetPosition (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;)Landroidx/compose/ui/geometry/Rect; + public final fun isCompleting ()Z + public final fun isReady ()Z + public final fun nextStep (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerTutorial (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig;)V + public final fun reset ()V + public final fun resetAll ()V + public final fun shouldShowTutorial ()Z + public final fun startTutorial (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateTargetPosition (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Landroidx/compose/ui/geometry/Rect;)V +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions { + public static final field $stable I + public synthetic fun (FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-D9Ej5fM ()F + public final fun component2-D9Ej5fM ()F + public final fun copy-YgX7TsA (FF)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; + public static synthetic fun copy-YgX7TsA$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions;FFILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeightDp-D9Ej5fM ()F + public final fun getWidthDp-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions_androidKt { + public static final fun getScreenDimensions (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep { + public static final field $stable I + public synthetic fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public final fun component5-D9Ej5fM ()F + public final fun component6-D9Ej5fM ()F + public final fun copy-Kr38-dQ (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FF)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public static synthetic fun copy-Kr38-dQ$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep;Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getTargetKey ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; + public final fun getTitle ()Ljava/lang/String; + public final fun getTooltipHeight-D9Ej5fM ()F + public final fun getTooltipPosition ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public final fun getTooltipWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget { +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTargetKt { + public static final fun toTarget (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget : io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget { + public static final field $stable I + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget; + public static synthetic fun copy$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget;Ljava/lang/String;ILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition : java/lang/Enum { + public static final field Bottom Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Left Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Right Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Top Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static fun values ()[Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; +} + diff --git a/spotlight/api/jvm/spotlight.api b/spotlight/api/jvm/spotlight.api new file mode 100644 index 0000000..eaa678b --- /dev/null +++ b/spotlight/api/jvm/spotlight.api @@ -0,0 +1,129 @@ +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesSpotlightKt { + public static final fun PiscesSpotlightContainer (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState;Ljava/util/List;Lkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public static final fun getLocalPiscesTutorialState ()Landroidx/compose/runtime/ProvidableCompositionLocal; + public static final fun piscesSpotlightTarget (Landroidx/compose/ui/Modifier;Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/Modifier; + public static final fun piscesSpotlightTarget (Landroidx/compose/ui/Modifier;Ljava/lang/String;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/Modifier; + public static final fun rememberPiscesTutorialController (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialController; + public static final fun rememberPiscesTutorialState (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig { + public static final field $stable I + public fun (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()Z + public final fun component4 ()Lkotlin/jvm/functions/Function0; + public final fun copy (Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public static synthetic fun copy$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig;Ljava/lang/String;Ljava/util/List;ZLkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getEnabled ()Z + public final fun getId ()Ljava/lang/String; + public final fun getScreenPredicate ()Lkotlin/jvm/functions/Function0; + public final fun getSteps ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialController { + public static final field $stable I + public fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState;)V + public final fun getTargetPositions ()Ljava/util/Map; + public final fun startTutorial (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun stopTutorial ()V +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialState { + public static final field $stable I + public fun ()V + public final fun clearTutorials ()V + public final fun getActiveTutorial ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig; + public final fun getAllTargetPositions ()Ljava/util/Map; + public final fun getCurrentStep ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public final fun getCurrentStepIndex ()I + public final fun getCurrentTutorialId ()Ljava/lang/String; + public final fun getTargetPosition (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;)Landroidx/compose/ui/geometry/Rect; + public final fun isCompleting ()Z + public final fun isReady ()Z + public final fun nextStep (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun registerTutorial (Lio/piscesbn/xcodebn/piscespotlight/spotlight/PiscesTutorialConfig;)V + public final fun reset ()V + public final fun resetAll ()V + public final fun shouldShowTutorial ()Z + public final fun startTutorial (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun updateTargetPosition (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Landroidx/compose/ui/geometry/Rect;)V +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions { + public static final field $stable I + public synthetic fun (FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-D9Ej5fM ()F + public final fun component2-D9Ej5fM ()F + public final fun copy-YgX7TsA (FF)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; + public static synthetic fun copy-YgX7TsA$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions;FFILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; + public fun equals (Ljava/lang/Object;)Z + public final fun getHeightDp-D9Ej5fM ()F + public final fun getWidthDp-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions_jvmKt { + public static final fun getScreenDimensions (Landroidx/compose/runtime/Composer;I)Lio/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep { + public static final field $stable I + public synthetic fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public final fun component5-D9Ej5fM ()F + public final fun component6-D9Ej5fM ()F + public final fun copy-Kr38-dQ (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FF)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public static synthetic fun copy-Kr38-dQ$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep;Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget;Ljava/lang/String;Ljava/lang/String;Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition;FFILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightStep; + public fun equals (Ljava/lang/Object;)Z + public final fun getDescription ()Ljava/lang/String; + public final fun getTargetKey ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; + public final fun getTitle ()Ljava/lang/String; + public final fun getTooltipHeight-D9Ej5fM ()F + public final fun getTooltipPosition ()Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public final fun getTooltipWidth-D9Ej5fM ()F + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget { +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTargetKt { + public static final fun toTarget (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget : io/piscesbn/xcodebn/piscespotlight/spotlight/SpotlightTarget { + public static final field $stable I + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget; + public static synthetic fun copy$default (Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget;Ljava/lang/String;ILjava/lang/Object;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/StringTarget; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition : java/lang/Enum { + public static final field Bottom Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Left Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Right Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static final field Top Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; + public static fun values ()[Lio/piscesbn/xcodebn/piscespotlight/spotlight/TooltipPosition; +} + diff --git a/spotlight/build.gradle.kts b/spotlight/build.gradle.kts index a49ccc8..e958367 100644 --- a/spotlight/build.gradle.kts +++ b/spotlight/build.gradle.kts @@ -76,7 +76,7 @@ mavenPublishing { coordinates( groupId = "io.github.xcodebn", artifactId = "pisces-spotlight", - version = "0.1.0" + version = "0.2.0" ) pom { diff --git a/spotlight/src/androidMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.android.kt b/spotlight/src/androidMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.android.kt new file mode 100644 index 0000000..715e8ae --- /dev/null +++ b/spotlight/src/androidMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.android.kt @@ -0,0 +1,17 @@ +package io.piscesbn.xcodebn.piscespotlight.spotlight + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp + +/** + * Android implementation using LocalConfiguration + */ +@Composable +actual fun getScreenDimensions(): ScreenDimensions { + val configuration = LocalConfiguration.current + return ScreenDimensions( + widthDp = configuration.screenWidthDp.dp, + heightDp = configuration.screenHeightDp.dp + ) +} diff --git a/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/PiscesSpotlight.kt b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/PiscesSpotlight.kt index 42bf041..0e58e06 100644 --- a/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/PiscesSpotlight.kt +++ b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/PiscesSpotlight.kt @@ -15,12 +15,15 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -521,7 +524,6 @@ class PiscesTutorialController( /** * Spotlight overlay rendering with smart tooltip positioning - * Uses BoxWithConstraints for multiplatform screen size detection */ @Composable private fun PiscesSpotlightOverlay( @@ -533,7 +535,6 @@ private fun PiscesSpotlightOverlay( ) { val density = LocalDensity.current - // Use BoxWithConstraints for multiplatform-compatible screen size BoxWithConstraints( modifier = Modifier.fillMaxSize() ) { @@ -608,33 +609,27 @@ private fun PiscesSpotlightOverlay( ) } - // Smart tooltip positioning with screen edge detection - var tooltipSize by remember { mutableStateOf(Size.Zero) } - + // Calculate tooltip position once with fixed size estimates val tooltipOffset = calculatePiscesTooltipOffset( targetRect = targetRect, tooltipPosition = step.tooltipPosition, density = density, screenWidth = screenWidth, screenHeight = screenHeight, - tooltipSize = tooltipSize + tooltipWidthDp = step.tooltipWidth, + tooltipHeightDp = step.tooltipHeight ) Card( modifier = Modifier - .onGloballyPositioned { coordinates -> - tooltipSize = Size( - coordinates.size.width.toFloat(), - coordinates.size.height.toFloat() - ) - } .offset { IntOffset( tooltipOffset.x.toInt(), tooltipOffset.y.toInt() ) } - .width(280.dp), + .width(step.tooltipWidth) + .height(step.tooltipHeight), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface @@ -642,7 +637,10 @@ private fun PiscesSpotlightOverlay( elevation = CardDefaults.cardElevation(8.dp) ) { Column( - modifier = Modifier.padding(20.dp) + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(20.dp) ) { Text( text = "${currentStepIndex + 1}/$totalSteps", @@ -655,7 +653,9 @@ private fun PiscesSpotlightOverlay( Text( text = step.title, style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, + maxLines = 2, + overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(8.dp)) @@ -663,10 +663,12 @@ private fun PiscesSpotlightOverlay( Text( text = step.description, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 5, + overflow = TextOverflow.Ellipsis ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.weight(1f)) Button( onClick = onNext, @@ -689,11 +691,12 @@ private fun calculatePiscesTooltipOffset( density: Density, screenWidth: Float, screenHeight: Float, - tooltipSize: Size + tooltipWidthDp: androidx.compose.ui.unit.Dp, + tooltipHeightDp: androidx.compose.ui.unit.Dp ): Offset { - // Use actual measured size if available, otherwise use estimates - val tooltipWidth = if (tooltipSize.width > 0) tooltipSize.width else with(density) { 280.dp.toPx() } - val tooltipHeight = if (tooltipSize.height > 0) tooltipSize.height else with(density) { 200.dp.toPx() } + // Use fixed size estimates for position calculation + val tooltipWidth = with(density) { tooltipWidthDp.toPx() } + val tooltipHeight = with(density) { tooltipHeightDp.toPx() } val margin = with(density) { 16.dp.toPx() } val edgePadding = with(density) { 16.dp.toPx() } diff --git a/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/SpotlightStep.kt b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/SpotlightStep.kt index 8521e32..02a7c0d 100644 --- a/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/SpotlightStep.kt +++ b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/SpotlightStep.kt @@ -1,5 +1,8 @@ package io.piscesbn.xcodebn.piscespotlight.spotlight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + /** * Represents a single step in a spotlight tutorial sequence. * @@ -15,6 +18,10 @@ package io.piscesbn.xcodebn.piscespotlight.spotlight * Defaults to [TooltipPosition.Bottom]. The system will * automatically adjust if the preferred position would cause * the tooltip to render off-screen. + * @property tooltipWidth Custom width for the tooltip. Defaults to 280.dp. + * @property tooltipHeight Fixed height for the tooltip. Defaults to 200.dp. Using a fixed + * height ensures consistent positioning and prevents tooltip jumping + * between steps. Content is scrollable if it exceeds this height. * * @see SpotlightTarget * @see TooltipPosition @@ -24,7 +31,9 @@ data class SpotlightStep( val targetKey: SpotlightTarget, val title: String, val description: String, - val tooltipPosition: TooltipPosition = TooltipPosition.Bottom + val tooltipPosition: TooltipPosition = TooltipPosition.Bottom, + val tooltipWidth: Dp = 280.dp, + val tooltipHeight: Dp = 200.dp ) { /** * Convenience constructor for string-based targets. @@ -41,13 +50,17 @@ data class SpotlightStep( * @param title The title text displayed in the tooltip * @param description The description text displayed in the tooltip * @param tooltipPosition Preferred tooltip position + * @param tooltipWidth Custom width for the tooltip + * @param tooltipHeight Custom height for the tooltip */ constructor( key: String, title: String, description: String, - tooltipPosition: TooltipPosition = TooltipPosition.Bottom - ) : this(StringTarget(key), title, description, tooltipPosition) + tooltipPosition: TooltipPosition = TooltipPosition.Bottom, + tooltipWidth: Dp = 280.dp, + tooltipHeight: Dp = 200.dp + ) : this(StringTarget(key), title, description, tooltipPosition, tooltipWidth, tooltipHeight) } /** diff --git a/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.kt b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.kt new file mode 100644 index 0000000..1844474 --- /dev/null +++ b/spotlight/src/commonMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.kt @@ -0,0 +1,18 @@ +package io.piscesbn.xcodebn.piscespotlight.spotlight + +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp + +/** + * Platform-specific screen dimensions provider + */ +data class ScreenDimensions( + val widthDp: Dp, + val heightDp: Dp +) + +/** + * Get current screen dimensions in a platform-specific way + */ +@Composable +expect fun getScreenDimensions(): ScreenDimensions diff --git a/spotlight/src/iosMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.ios.kt b/spotlight/src/iosMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.ios.kt new file mode 100644 index 0000000..9e8eb87 --- /dev/null +++ b/spotlight/src/iosMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.ios.kt @@ -0,0 +1,17 @@ +package io.piscesbn.xcodebn.piscespotlight.spotlight + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.unit.dp + +/** + * iOS implementation using LocalWindowInfo + */ +@Composable +actual fun getScreenDimensions(): ScreenDimensions { + val containerSize = LocalWindowInfo.current.containerSize + return ScreenDimensions( + widthDp = containerSize.width.dp, + heightDp = containerSize.height.dp + ) +} diff --git a/spotlight/src/jvmMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.jvm.kt b/spotlight/src/jvmMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.jvm.kt new file mode 100644 index 0000000..39b1938 --- /dev/null +++ b/spotlight/src/jvmMain/kotlin/io/piscesbn/xcodebn/piscespotlight/spotlight/ScreenDimensions.jvm.kt @@ -0,0 +1,20 @@ +package io.piscesbn.xcodebn.piscespotlight.spotlight + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import java.awt.Toolkit + +/** + * JVM/Desktop implementation using AWT Toolkit + */ +@Composable +actual fun getScreenDimensions(): ScreenDimensions { + val density = LocalDensity.current + val screenSize = Toolkit.getDefaultToolkit().screenSize + + return ScreenDimensions( + widthDp = with(density) { screenSize.width.toDp() }, + heightDp = with(density) { screenSize.height.toDp() } + ) +}