diff --git a/README.ko.md b/README.ko.md
index 8295bea..0725746 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -1,11 +1,22 @@
# Wisp
-[](https://github.com/angrypodo/wisp/actions/workflows/ci.yml)
-
-**Wisp**는 Jetpack Compose를 위한 타입 세이프(type-safe), 서버 주도(server-driven) 딥링크 라이브러리입니다. 단일 표준 URI를 기반으로 내비게이션 백스택을 동적으로 구성할 수 있게 하여, 표준 `navigation-compose` 라이브러리의 정적 백스택 한계를 극복합니다.
+[](https://opensource.org/licenses/Apache-2.0)
+[](https://android-arsenal.com/api?level=21)
+[](https://github.com/angrypodo/wisp/actions/workflows/ci.yml)
+[](https://central.sonatype.com/artifact/io.github.angrypodo/wisp-runtime)
번역: [Read in English](README.md)
+
+
+
+ Wisp는 Jetpack Compose를 위한 타입 세이프(type-safe), 서버 주도(server-driven) 딥링크 라이브러리입니다.
+ 단일 표준 URI를 기반으로 내비게이션 백스택을 동적으로 구성할 수 있게 하여,
+ 표준 navigation-compose 라이브러리의 정적 백스택 한계를 극복합니다.
+
+
+
+
## 🤔 Wisp, 왜 필요한가요?
Jetpack Compose의 표준 딥링크 기능은 미리 정의된 정적 백스택을 만드는 데 주로 사용됩니다. 이 때문에 서버가 실시간으로 동적인 사용자 여정(예: `상품 화면 -> 쿠폰 화면 -> 결제 화면`)을 제어해야 하는 시나리오를 구현하기는 어렵습니다.
@@ -14,17 +25,64 @@ Wisp는 URI의 경로 세그먼트(path segments)로부터 전체 백스택을
## 🏛️ 아키텍처 및 요구사항
-- **싱글 액티비티 아키텍처 (Single-Activity Architecture):** Wisp는 **싱글 액티비티 구조** 전용으로 설계되었으며, 여러 Activity 간의 내비게이션은 지원하지 않습니다. 이는 Jetpack Compose에 권장되는 최신 안드로이드 개발 방식과 일치합니다.
-- **Jetpack Navigation 및 타입 안정성:** 이 라이브러리는 Jetpack Navigation Compose의 확장 기능이며, **타입 세이프(type-safe) 내비게이션** 패러다임 전용으로 설계되었습니다. `NavController`가 반드시 필요하며, 전통적인 문자열 기반의 라우트는 지원하지 않습니다.
-- **멀티 모듈 지원 (Multi-Module Support):** Wisp는 멀티 모듈 프로젝트를 완벽하게 지원합니다. `ServiceLoader` 패턴을 사용하여, 라이브러리가 포함된 모든 모듈로부터 `@Wisp` 라우트 정의를 자동으로 탐지합니다.
+- **싱글 액티비티 아키텍처:** Wisp는 **싱글 액티비티 구조(Single-Activity Architecture)**를 위해 설계되었으며, 여러 Activity 간의 내비게이션은 지원하지 않습니다. 이는 Jetpack Compose에 권장되는 최신 안드로이드 개발 방식과 일치합니다.
+- **Jetpack Navigation 및 타입 안정성:** 이 라이브러리는 Jetpack Navigation Compose의 **타입 세이프(type-safe) 내비게이션** 패러다임 전용으로 설계되었습니다. `NavController`가 반드시 필요하며, 전통적인 문자열 기반의 라우트는 지원하지 않습니다.
+- **멀티 모듈 지원:** Wisp는 멀티 모듈 프로젝트를 완벽하게 지원합니다. `ServiceLoader` 패턴을 사용하여, 라이브러리가 포함된 모든 모듈로부터 `@Wisp` 라우트 정의를 자동으로 탐지합니다.
+- **최소 요구사항:**
+ - **minSdk:** 21 (Android 5.0)
+ - **Kotlin:** 1.9.0 이상 (KSP 호환 버전)
+
+## 다운로드 (Download)
-## 🚀 사용법
+[](https://central.sonatype.com/artifact/io.github.angrypodo/wisp-runtime)
-**참고:** Wisp는 아직 Maven Central에 배포되지 않았습니다. 현재로서는 이 리포지토리를 클론하여 프로젝트에 로컬 모듈로 포함해야 합니다.
+### Version Catalog
+
+Version Catalog를 사용 중이라면, `libs.versions.toml` 파일에 다음과 같이 의존성을 추가할 수 있습니다:
+
+```toml
+[versions]
+#...
+wisp = "0.1.0"
+
+[libraries]
+#...
+wisp-runtime = { module = "io.github.angrypodo:wisp-runtime", version.ref = "wisp" }
+wisp-processor = { module = "io.github.angrypodo:wisp-processor", version.ref = "wisp" }
+```
+
+### Gradle
+
+프로젝트 수준의 `build.gradle.kts` 파일에 KSP 플러그인을 추가합니다. **반드시 사용하는 Kotlin 버전과 호환되는 KSP 버전을 사용하세요.** ([KSP 릴리즈 확인](https://github.com/google/ksp/releases))
+
+```kotlin
+plugins {
+ id("com.google.devtools.ksp") version "YOUR_KSP_VERSION" apply false
+}
+```
+
+그리고 **모듈** 수준의 `build.gradle.kts` 파일에 의존성을 추가합니다:
+
+```kotlin
+plugins {
+ id("com.google.devtools.ksp")
+}
+
+dependencies {
+ implementation("io.github.angrypodo:wisp-runtime:0.1.0")
+ ksp("io.github.angrypodo:wisp-processor:0.1.0")
+
+ // Version Catalog를 사용하는 경우
+ // implementation(libs.wisp.runtime)
+ // ksp(libs.wisp.processor)
+}
+```
+
+## 사용법 (Usage)
### 1. 라우트 정의하기
-`@Serializable` 어노테이션이 달린 `data class`나 `object`에 `@Wisp` 어노테이션을 추가하여 딥링크 대상을 지정합니다. `@Wisp`에 전달하는 문자열은 딥링크 URI에서 사용될 경로 세그먼트가 됩니다.
+`@Serializable` 어노테이션이 달린 `data class`나 `object`에 `@Wisp` 어노테이션을 추가하여 딥링크 대상을 지정합니다.
라우트 클래스의 속성들은 URI의 **쿼리 파라미터**로부터 자동으로 값이 채워집니다. 만약 속성에 **기본값(default value)**이 있다면, 해당 속성은 선택적(optional)인 값이 됩니다.
@@ -34,7 +92,7 @@ import com.angrypodo.wisp.annotations.Wisp
import kotlinx.serialization.Serializable
@Serializable
-@Wisp("product") // "product" 경로와 매칭
+@Wisp("product") // "product" 경로 세그먼트와 매칭
data class ProductDetail(
val productId: Int, // "?productId=..." 로부터 값을 받음
val showReviews: Boolean = false // 선택적. "?showReviews=..." 값이 없으면 false 사용
@@ -91,23 +149,7 @@ val uri = "app://wisp/product/user?productId=123&userId=99".toUri()
navController.navigateTo(uri)
```
-## 🧪 테스트 방법
-
-### 샘플 앱 실행하기
-
-1. 이 리포지토리를 클론하여 Android Studio에서 엽니다.
-2. `app` 실행 구성을 선택하고 에뮬레이터나 실제 기기에서 실행합니다.
-3. 앱 내의 버튼을 눌러 내비게이션을 테스트합니다.
-
-### ADB로 테스트하기
-
-`adb`를 사용하여 커맨드 라인에서 직접 딥링크를 테스트할 수 있습니다. 이는 외부 소스로부터의 링크 클릭을 시뮬레이션하는 좋은 방법입니다.
-
-```bash
-adb shell am start -a android.intent.action.VIEW -d "app://wisp/product/user?productId=123&userId=99"
-```
-
-## 고급 사용법
+## 고급 사용법 (Advanced Usage)
### 커스텀 URI 파서
@@ -124,9 +166,9 @@ Wisp.initialize(parser = myParser)
- **Kotlinx Serialization:** Wisp는 쿼리 파라미터를 라우트 데이터 클래스로 역직렬화하기 위해 `kotlinx.serialization`에 크게 의존합니다.
- **파라미터 이름:** URI의 쿼리 파라미터 키는 라우트 `data class`의 속성 이름과 정확히 일치해야 합니다.
-## 📜 라이선스
+# License
-```
+```xml
Copyright 2025 angrypodo
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/README.md b/README.md
index 210b278..6bee796 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,22 @@
# Wisp
-[](https://github.com/angrypodo/wisp/actions/workflows/ci.yml)
-
-**Wisp** is a type-safe, server-driven deep link library for Jetpack Compose. It allows you to dynamically build your navigation backstack from a single, standard URI, overcoming the static backstack limitations of the `navigation-compose` library.
+[](https://opensource.org/licenses/Apache-2.0)
+[](https://android-arsenal.com/api?level=21)
+[](https://github.com/angrypodo/wisp/actions/workflows/ci.yml)
+[](https://central.sonatype.com/artifact/io.github.angrypodo/wisp-runtime)
translation: [Read in Korean (한국어)](./README.ko.md)
+
+
+
+ Wisp is a type-safe, server-driven deep link library for Jetpack Compose.
+ It allows you to dynamically build your navigation backstack from a single, standard URI,
+ overcoming the static backstack limitations of the navigation-compose library.
+
+
+
+
## 🤔 Why Wisp?
Standard deep links in Jetpack Compose often lead to predefined, static backstacks. It's challenging to implement scenarios where a server needs to dictate a dynamic user journey on the fly (e.g., `Product Screen -> Coupon Screen -> Checkout Screen`).
@@ -14,17 +25,64 @@ Wisp automates this process by building the entire backstack from the URI's path
## 🏛️ Architecture & Prerequisites
-- **Single-Activity Architecture:** Wisp is designed for a **Single-Activity Architecture** and does not support navigating between different Activities. This aligns with the modern Android development practices recommended for Jetpack Compose.
-- **Jetpack Navigation & Type-Safety:** The library is an extension of Jetpack Navigation Compose and is exclusively designed for its **type-safe navigation** paradigm. It requires a `NavController` and does not support traditional string-based routes.
-- **Multi-Module Support:** Wisp fully supports multi-module projects. It automatically discovers `@Wisp` route definitions from all modules that include the library, using a `ServiceLoader` pattern.
+- **Single-Activity Architecture:** Wisp is designed for a **Single-Activity Architecture** and does not support navigating between different Activities.
+- **Jetpack Navigation & Type-Safety:** The library is exclusively designed for the **type-safe navigation** paradigm of Jetpack Navigation Compose. It requires a `NavController` and does not support traditional string-based routes.
+- **Multi-Module Support:** Wisp fully supports multi-module projects using a `ServiceLoader` pattern.
+- **Minimum Requirements:**
+ - **minSdk:** 21 (Android 5.0)
+ - **Kotlin:** 1.9.0 or higher (Compatible with KSP)
+
+## Download
-## 🚀 How to Use
+[](https://central.sonatype.com/artifact/io.github.angrypodo/wisp-runtime)
-**Note:** Wisp is not yet published to Maven Central. To use it, you currently need to clone this repository and include the modules in your project locally.
+### Version Catalog
+
+If you're using Version Catalog, you can configure the dependency by adding it to your `libs.versions.toml` file as follows:
+
+```toml
+[versions]
+#...
+wisp = "0.1.0"
+
+[libraries]
+#...
+wisp-runtime = { module = "io.github.angrypodo:wisp-runtime", version.ref = "wisp" }
+wisp-processor = { module = "io.github.angrypodo:wisp-processor", version.ref = "wisp" }
+```
+
+### Gradle
+
+Add the KSP plugin to your project-level `build.gradle.kts`. **Make sure to use a KSP version that matches your Kotlin version.** (Check [KSP Releases](https://github.com/google/ksp/releases))
+
+```kotlin
+plugins {
+ id("com.google.devtools.ksp") version "YOUR_KSP_VERSION" apply false
+}
+```
+
+Then, add the dependencies to your **module**'s `build.gradle.kts` file:
+
+```kotlin
+plugins {
+ id("com.google.devtools.ksp")
+}
+
+dependencies {
+ implementation("io.github.angrypodo:wisp-runtime:0.1.0")
+ ksp("io.github.angrypodo:wisp-processor:0.1.0")
+
+ // if you're using Version Catalog
+ // implementation(libs.wisp.runtime)
+ // ksp(libs.wisp.processor)
+}
+```
+
+## Usage
### 1. Define Routes
-Designate a deep link destination by adding the `@Wisp` annotation to any `@Serializable` `data class` or `object`. The string passed to `@Wisp` is the path segment that will be used in the deep link URI.
+Designate a deep link destination by adding the `@Wisp` annotation to any `@Serializable` `data class` or `object`.
Route properties are automatically populated from the URI's **query parameters**. If a property has a **default value**, it is considered optional.
@@ -43,7 +101,7 @@ data class ProductDetail(
### 2. Configure the Manifest
-For deep links to be accessible from outside your app, you must register an `` in your `AndroidManifest.xml`. Both `scheme` and `host` are required.
+Register an `` in your `AndroidManifest.xml`. Both `scheme` and `host` are required.
```xml
@@ -92,22 +150,6 @@ val uri = "app://wisp/product/user?productId=123&userId=99".toUri()
navController.navigateTo(uri)
```
-## 🧪 Testing
-
-### Running the Sample App
-
-1. Clone this repository and open it in Android Studio.
-2. Select the `app` run configuration and run it on an emulator or a physical device.
-3. Use the buttons in the app to test navigation.
-
-### Testing with ADB
-
-You can test your deep links directly from the command line using `adb`. This is a great way to simulate a link click from an external source.
-
-```bash
-adb shell am start -a android.intent.action.VIEW -d "app://wisp/product/user?productId=123&userId=99"
-```
-
## Advanced Usage
### Custom URI Parser
@@ -125,9 +167,9 @@ Wisp.initialize(parser = myParser)
- **Kotlinx Serialization:** Wisp relies heavily on `kotlinx.serialization` to deserialize query parameters into your route data classes.
- **Parameter Naming:** The query parameter keys in the URI must exactly match the property names in your route `data class`.
-## 📜 License
+# License
-```
+```xml
Copyright 2025 angrypodo
Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ec69797..0246b14 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -43,10 +43,6 @@ kotlin {
}
}
-tasks.withType {
- useJUnitPlatform()
-}
-
dependencies {
implementation(project(":wisp-runtime"))
ksp(project(":wisp-processor"))
diff --git a/build.gradle.kts b/build.gradle.kts
index b051aff..41aa8a7 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,14 +1,20 @@
import org.jlleitschuh.gradle.ktlint.KtlintExtension
plugins {
+ // Android
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
+
+ // Kotlin
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.compose) apply false
- alias(libs.plugins.ksp) apply false
alias(libs.plugins.kotlin.serialization) apply false
+
+ // Tools
+ alias(libs.plugins.ksp) apply false
alias(libs.plugins.ktlint) apply false
+ alias(libs.plugins.vanniktech.maven.publish) apply false
}
subprojects {
@@ -21,4 +27,45 @@ subprojects {
verbose.set(true)
outputToConsole.set(true)
}
+
+ tasks.withType {
+ useJUnitPlatform()
+ }
+
+ if (name != "app") {
+ apply(plugin = "com.vanniktech.maven.publish")
+
+ extensions.configure {
+ publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL)
+ signAllPublications()
+
+ coordinates("io.github.angrypodo", name, "0.1.0")
+
+ pom {
+ name.set(project.name)
+ description.set("Wisp library: ${project.name}")
+ inceptionYear.set("2025")
+ url.set("https://github.com/angrypodo/wisp")
+ licenses {
+ license {
+ name.set("The Apache License, Version 2.0")
+ url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
+ }
+ }
+ developers {
+ developer {
+ id.set("angrypodo")
+ name.set("MinJae Han")
+ url.set("https://github.com/angrypodo")
+ }
+ }
+ scm {
+ url.set("https://github.com/angrypodo/wisp")
+ connection.set("scm:git:git://github.com/angrypodo/wisp.git")
+ developerConnection.set("scm:git:ssh://git@github.com/angrypodo/wisp.git")
+ }
+ }
+ }
+ }
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5c69935..86ec710 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,50 +1,52 @@
[versions]
-# Build Configuration
+# Build & Publish
androidGradlePlugin = "8.13.0"
-compileSdk = "36"
-minSdk = "28"
-jvmTarget = "11"
+vanniktechMavenPublish = "0.30.0"
+ksp = "2.3.0"
+ktlint = "11.5.1"
# Kotlin
kotlin = "2.2.21"
-ksp = "2.3.0"
kotlinxSerialization = "1.9.0"
kotlinpoet = "2.2.0"
-# AndroidX
+# Android SDK
+compileSdk = "36"
+minSdk = "28"
+jvmTarget = "11"
+
+# AndroidX & Compose
coreKtx = "1.17.0"
appcompat = "1.7.1"
activityCompose = "1.11.0"
composeBom = "2025.10.01"
composeNavigation = "2.9.5"
+material = "1.13.0"
# Test
junit = "6.0.1"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
-# Third Party
-material = "1.13.0"
-ktlint = "11.5.1"
-
[libraries]
-# Build Tools
+# --- Build Tools & Plugins ---
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+vanniktech-maven-publish = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "vanniktechMavenPublish" }
-# Code Generation
+# --- Code Generation ---
ksp-api = { group = "com.google.devtools.ksp", name = "symbol-processing-api", version.ref = "ksp" }
kotlinpoet-ksp = { group = "com.squareup", name = "kotlinpoet-ksp", version.ref = "kotlinpoet" }
-# Kotlin & Coroutines
+# --- Kotlin & Libraries ---
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
-# AndroidX Core
+# --- AndroidX Core & UI ---
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
-# AndroidX Compose
+# --- AndroidX Compose ---
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
@@ -55,7 +57,7 @@ androidx-compose-material3 = { group = "androidx.compose.material3", name = "mat
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "composeNavigation" }
-# Test
+# --- Test ---
junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" }
junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api" }
junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine" }
@@ -76,8 +78,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
-# KSP
+# Tools
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
-
-# Lint
-ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
\ No newline at end of file
+ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
+vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechMavenPublish" }
diff --git a/wisp-processor/build.gradle.kts b/wisp-processor/build.gradle.kts
index a8bb046..2b4608d 100644
--- a/wisp-processor/build.gradle.kts
+++ b/wisp-processor/build.gradle.kts
@@ -12,7 +12,3 @@ dependencies {
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly(libs.junit.platform.launcher)
}
-
-tasks.withType {
- useJUnitPlatform()
-}
diff --git a/wisp-runtime/build.gradle.kts b/wisp-runtime/build.gradle.kts
index 7cf1463..0323544 100644
--- a/wisp-runtime/build.gradle.kts
+++ b/wisp-runtime/build.gradle.kts
@@ -38,10 +38,6 @@ kotlin {
}
}
-tasks.withType {
- useJUnitPlatform()
-}
-
dependencies {
api(project(":wisp-annotations"))