JindongJindong

Quick Start

Build your first haptic pattern with Jindong in minutes

Quick Start

Let's build your first haptic feedback pattern with Jindong.

This guide covers the Compose API (jindong-compose module). For non-Compose usage, see Core API Guide.

Minimal Example

import io.github.compose.jindong.*
import io.github.compose.jindong.dsl.*
import kotlin.time.Duration.Companion.ms
 
@Composable
fun App() {
    var count by remember { mutableStateOf(0) }
 
    JindongProvider {
        // Haptic triggers when count changes
        Jindong(count) {
            Haptic(100.ms)
        }
 
        Button(onClick = { count++ }) {
            Text("Tap me!")
        }
    }
}

That's it! Every time you tap the button, a 100ms haptic pulse fires.

Understanding the Basics

JindongProvider

Provides the haptic execution context:

JindongProvider {
    // Haptics work inside this scope
}

Jindong

The main composable for defining haptic patterns:

Jindong(key1, key2, ...) {
    // Haptic pattern defined here
}
  • Works like LaunchedEffect
  • Executes when any key changes
  • Content block defines the pattern

Haptic

Creates a vibration:

Haptic(duration)
Haptic(duration, intensity)

Building Patterns

Sequential Events

Events execute one after another:

Jindong(trigger) {
    Haptic(100.ms)  // Starts at 0ms
    Haptic(50.ms)   // Starts at 100ms
    Haptic(30.ms)   // Starts at 150ms
}

Adding Delays

Use Delay for pauses between events:

Jindong(trigger) {
    Haptic(50.ms)   // 0-50ms
    Delay(100.ms)   // 50-150ms (silence)
    Haptic(50.ms)   // 150-200ms
}

Adjusting Intensity

Control vibration strength:

Jindong(trigger) {
    Haptic(100.ms, HapticIntensity.LIGHT)   // 25% intensity
    Haptic(100.ms, HapticIntensity.MEDIUM)  // 50% intensity
    Haptic(100.ms, HapticIntensity.STRONG)  // 75% intensity
    Haptic(100.ms, HapticIntensity.HIGH)    // 100% intensity
}

Repetition

Repeat patterns with Repeat:

Jindong(trigger) {
    Repeat(3) {
        Haptic(50.ms)
        Delay(30.ms)
    }
    // Equivalent to:
    // Haptic → Delay → Haptic → Delay → Haptic → Delay
}

Practical Examples

Screen Entry Feedback

Use Unit as key to trigger haptic immediately when entering a screen:

@Composable
fun WelcomeScreen() {
    // Executes once when screen appears
    Jindong(Unit) {
        Haptic(100.ms, HapticIntensity.MEDIUM)
    }
 
    Text("Welcome!")
}

Button Tap Feedback

@Composable
fun TapButton() {
    var taps by remember { mutableStateOf(0) }
 
    Jindong(taps) {
        Haptic(50.ms, HapticIntensity.LIGHT)
    }
 
    Button(onClick = { taps++ }) {
        Text("Tap")
    }
}

Success Notification

@Composable
fun SuccessHaptic(success: Boolean) {
    Jindong(success) {
        if (success) {
            // Double pulse for success
            Haptic(50.ms, HapticIntensity.MEDIUM)
            Delay(50.ms)
            Haptic(100.ms, HapticIntensity.STRONG)
        }
    }
}

Error Alert

@Composable
fun ErrorHaptic(error: Boolean) {
    Jindong(error) {
        if (error) {
            // Triple strong pulse for error
            Repeat(3) {
                Haptic(80.ms, HapticIntensity.HIGH)
                Delay(50.ms)
            }
        }
    }
}

Fade Out Effect

@Composable
fun FadeOutHaptic(trigger: Int) {
    Jindong(trigger) {
        RepeatWithIndex(5) { index ->
            val intensity = HapticIntensity.Custom(1.0f - (index * 0.2f))
            Haptic(50.ms, intensity)
            Delay(30.ms)
        }
    }
}

Heartbeat Pattern

@Composable
fun HeartbeatHaptic(trigger: Int) {
    Jindong(trigger) {
        // Lub
        Haptic(60.ms, HapticIntensity.STRONG)
        Delay(80.ms)
        // Dub
        Haptic(40.ms, HapticIntensity.MEDIUM)
        Delay(400.ms)
        // Repeat
        Haptic(60.ms, HapticIntensity.STRONG)
        Delay(80.ms)
        Haptic(40.ms, HapticIntensity.MEDIUM)
    }
}

Common Patterns

PatternKeyCode
On screen enterJindong(Unit)Haptic(100.ms)
Single tapJindong(count)Haptic(50.ms)
Double tapJindong(count)Repeat(2) { Haptic(40.ms); Delay(40.ms) }
Long pressJindong(count)Haptic(200.ms, HapticIntensity.MEDIUM)
SuccessJindong(success)Haptic(50.ms); Delay(50.ms); Haptic(100.ms)
ErrorJindong(error)Repeat(3) { Haptic(80.ms); Delay(50.ms) }

Tips

Key Management

Choose keys carefully:

// Good: Specific trigger
Jindong(buttonClickCount) { ... }
 
// Avoid: Changes too often
Jindong(System.currentTimeMillis()) { ... }

Reactive Patterns

When a key changes, the content block is recompiled with the current state, so a value the block captures must also be one of the keys for the pattern to reflect it:

var level by remember { mutableStateOf(1) }
 
// `level` is captured AND a key, so the pattern grows as `level` changes
Jindong(level) {
    RepeatWithIndex(level) { index ->
        Haptic(50.ms, HapticIntensity.Custom(1f - index * 0.1f))
    }
}

If a captured value is not in the keys, the pattern keeps the value from the last key change instead of updating. The Reactive Contract covers when values are read and when playback fires.

Next Steps