diff --git a/.gitignore b/.gitignore
index c7e6e20..d8a86b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.iml
.gradle
.idea
+!.idea/runConfigurations
.DS_Store
build
captures
diff --git a/android-tests/build.gradle.kts b/android-tests/build.gradle.kts
index a857bb9..b96243b 100644
--- a/android-tests/build.gradle.kts
+++ b/android-tests/build.gradle.kts
@@ -51,6 +51,7 @@ android {
dependencies {
implementation(project(":reveal-core"))
+ implementation(project(":reveal-shapes"))
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
@@ -59,6 +60,7 @@ dependencies {
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.activity.compose)
debugImplementation(libs.androidx.compose.ui.test.manifest)
diff --git a/android-tests/src/main/AndroidManifest.xml b/android-tests/src/main/AndroidManifest.xml
index 8072ee0..694006a 100644
--- a/android-tests/src/main/AndroidManifest.xml
+++ b/android-tests/src/main/AndroidManifest.xml
@@ -1,2 +1,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/.gitcreate b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/.gitcreate
deleted file mode 100644
index e69de29..0000000
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/App.kt b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/App.kt
new file mode 100644
index 0000000..e460e45
--- /dev/null
+++ b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/App.kt
@@ -0,0 +1,14 @@
+package com.svenjacobs.reveal.android.tests
+
+import android.app.Application
+import android.util.Log
+import com.svenjacobs.reveal.common.internal.log.Logger
+
+class App : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+
+ Logger.adapter = Logger.Adapter { message, tag -> Log.d(tag, message) }
+ }
+}
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/MainActivity.kt b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/MainActivity.kt
new file mode 100644
index 0000000..288c6f8
--- /dev/null
+++ b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/MainActivity.kt
@@ -0,0 +1,20 @@
+package com.svenjacobs.reveal.android.tests
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.core.view.WindowCompat
+import com.svenjacobs.reveal.android.tests.presentation.App
+
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ setContent {
+ App()
+ }
+ }
+}
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/App.kt b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/App.kt
new file mode 100644
index 0000000..82d58c5
--- /dev/null
+++ b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/App.kt
@@ -0,0 +1,21 @@
+package com.svenjacobs.reveal.android.tests.presentation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.svenjacobs.reveal.RevealCanvas
+import com.svenjacobs.reveal.android.tests.presentation.theme.AppTheme
+import com.svenjacobs.reveal.rememberRevealCanvasState
+
+@Composable
+fun App(modifier: Modifier = Modifier) {
+ AppTheme {
+ val revealCanvasState = rememberRevealCanvasState()
+
+ RevealCanvas(
+ revealCanvasState = revealCanvasState,
+ modifier = modifier,
+ ) {
+ MainScreen(revealCanvasState = revealCanvasState)
+ }
+ }
+}
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/MainScreen.kt b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/MainScreen.kt
new file mode 100644
index 0000000..a0d3cf2
--- /dev/null
+++ b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/MainScreen.kt
@@ -0,0 +1,145 @@
+package com.svenjacobs.reveal.android.tests.presentation
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.svenjacobs.reveal.Key
+import com.svenjacobs.reveal.Reveal
+import com.svenjacobs.reveal.RevealCanvasState
+import com.svenjacobs.reveal.RevealOverlayArrangement
+import com.svenjacobs.reveal.RevealOverlayScope
+import com.svenjacobs.reveal.RevealShape
+import com.svenjacobs.reveal.rememberRevealState
+import com.svenjacobs.reveal.shapes.balloon.Arrow
+import com.svenjacobs.reveal.shapes.balloon.Balloon
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private enum class Keys { Fab, Explanation }
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun MainScreen(revealCanvasState: RevealCanvasState, modifier: Modifier = Modifier) {
+ val scope = rememberCoroutineScope()
+ val revealState = rememberRevealState()
+
+ LaunchedEffect(Unit) {
+ if (revealState.isVisible) return@LaunchedEffect
+ delay(2.seconds)
+ revealState.reveal(Keys.Fab)
+ }
+
+ Reveal(
+ onOverlayClick = { scope.launch { revealState.hide() } },
+ modifier = modifier,
+ revealCanvasState = revealCanvasState,
+ revealState = revealState,
+ overlayContent = { key -> RevealOverlayContent(key) },
+ ) {
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ CenterAlignedTopAppBar(
+ title = { Text("Reveal Demo") },
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ modifier = Modifier.revealable(
+ key = Keys.Fab,
+ shape = RevealShape.RoundRect(16.dp),
+ onClick = {
+ scope.launch { revealState.reveal(Keys.Explanation) }
+ },
+ ),
+ onClick = {
+ scope.launch { revealState.reveal(Keys.Explanation) }
+ },
+ ) {
+ Icon(
+ Icons.Filled.Add,
+ contentDescription = null,
+ )
+ }
+ },
+ ) { contentPadding ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(contentPadding)
+ .padding(horizontal = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ modifier = Modifier
+ .padding(top = 16.dp)
+ .revealable(
+ key = Keys.Explanation,
+ onClick = {
+ scope.launch { revealState.hide() }
+ },
+ ),
+ text = "Reveal is a lightweight, simple reveal effect (also known as " +
+ "coach mark or onboarding) library for Compose Multiplatform.",
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Justify,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun RevealOverlayScope.RevealOverlayContent(key: Key) {
+ when (key) {
+ Keys.Fab -> OverlayText(
+ modifier = Modifier.align(
+ horizontalArrangement = RevealOverlayArrangement.Start,
+ ),
+ text = "Click button to get started",
+ arrow = Arrow.end(),
+ )
+
+ Keys.Explanation -> OverlayText(
+ modifier = Modifier.align(
+ verticalArrangement = RevealOverlayArrangement.Bottom,
+ ),
+ text = "Actually we already started. This was an example of the reveal effect.",
+ arrow = Arrow.top(),
+ )
+ }
+}
+
+@Composable
+private fun OverlayText(text: String, arrow: Arrow, modifier: Modifier = Modifier) {
+ Balloon(
+ modifier = modifier.padding(8.dp),
+ arrow = arrow,
+ backgroundColor = MaterialTheme.colorScheme.secondaryContainer,
+ elevation = 2.dp,
+ ) {
+ Text(
+ modifier = Modifier.padding(8.dp),
+ text = text,
+ style = MaterialTheme.typography.labelLarge,
+ textAlign = TextAlign.Center,
+ )
+ }
+}
diff --git a/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/theme/Theme.kt b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/theme/Theme.kt
new file mode 100644
index 0000000..24c9851
--- /dev/null
+++ b/android-tests/src/main/kotlin/com/svenjacobs/reveal/android/tests/presentation/theme/Theme.kt
@@ -0,0 +1,11 @@
+package com.svenjacobs.reveal.android.tests.presentation.theme
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+
+@Composable
+fun AppTheme(content: @Composable () -> Unit) {
+ MaterialTheme(
+ content = content,
+ )
+}
diff --git a/android-tests/src/main/res/.gitcreate b/android-tests/src/main/res/.gitcreate
deleted file mode 100644
index e69de29..0000000
diff --git a/android-tests/src/main/res/values/styles.xml b/android-tests/src/main/res/values/styles.xml
new file mode 100644
index 0000000..400580f
--- /dev/null
+++ b/android-tests/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/reveal-common/src/commonMain/kotlin/com/svenjacobs/reveal/common/internal/log/Logger.kt b/reveal-common/src/commonMain/kotlin/com/svenjacobs/reveal/common/internal/log/Logger.kt
new file mode 100644
index 0000000..3d8b98a
--- /dev/null
+++ b/reveal-common/src/commonMain/kotlin/com/svenjacobs/reveal/common/internal/log/Logger.kt
@@ -0,0 +1,18 @@
+package com.svenjacobs.reveal.common.internal.log
+
+/**
+ * Minimal logging adapter for development purposes.
+ * For internal use only.
+ */
+public object Logger {
+
+ public fun interface Adapter {
+ public fun d(message: String, tag: String)
+ }
+
+ public var adapter: Adapter? = null
+
+ public fun d(message: String, tag: String = "Reveal") {
+ adapter?.d(message, tag)
+ }
+}