--- title: Migrating to 2.0 description: Logical timelines, common transient and continuous events, and normalized curves --- # Migrating to 2.0 This checkout uses `2.0.0-SNAPSHOT`. No release publication is part of this change. The development line requires Android API 26 and compiles against API 36. Kotlin remains at 2.3.0. ## Pattern values and compatibility `HapticPattern.durationMs` includes the complete timeline, including leading and trailing silence. Events-only construction defaults to the latest event end. Pass a duration when you construct a timeline with trailing silence: ```kotlin val pattern = HapticPattern(events, durationMs = 200L) val (scheduledEvents, logicalDurationMs) = pattern val longer = pattern.copy(durationMs = 300L) ``` `durationMs` must cover every event end. An event-free pattern can have a positive duration. `HapticPattern.Empty` has no events and a zero duration. The model is an immutable class with structural equality, `copy`, and destructuring functions. Its event list is a snapshot. It no longer stores or compares the mutable builder tree. Equality and hashing include events and duration. Consequently, a trailing-silence change invalidates a pattern used as a Compose key. This is an ABI change to the JVM and klib model APIs. Recompile consumers for 2.0. The constructor's second argument and `component2()` now represent `durationMs`, replacing the implementation tree. Code that supplied `rootElement` must pass the compiled duration instead. `copy(events = ...)` preserves the current duration. Pass a new duration when replacement events extend the timeline. Existing builder and Compose DSL calls retain their source forms: ```kotlin val tick = buildHapticPattern { haptic(50.ms) delay(50.ms) } Jindong(tick) { Clip(tick) Clip(tick) } // Event starts: 0ms and 100ms. Logical duration: 200ms. ``` `include`, `Clip`, `then`, `plus`, and repetition preserve the logical duration. `reversed()` reflects across that duration, exchanging leading and trailing silence. `timeStretch()` scales event boundaries and the duration together. `scaleIntensity()` preserves timing. Playback preserves the logical duration. Native pulse length can exceed a transient's zero logical duration. ## Input validation Duration inputs must be finite and non-negative. They round to the nearest millisecond, with positive half-milliseconds rounded up. A vibration must retain at least 1ms. An explicit zero delay is allowed. Non-finite factors and intensities, negative event times, and millisecond overflow raise `IllegalArgumentException` before native playback. Finite custom intensities outside `0..1` retain their existing clamp behavior. A stretch that collapses a positive event to 0ms fails. Increase the factor to preserve at least 1ms. The compiler rejects patterns above 10,000 expanded events before unfolding repetitions. Core `repeatWithIndex` and Compose `RepeatWithIndex` allow at most 10,000 eager iterations. They reject larger counts before invoking content, including when the content would produce no events. Ordinary `repeat` and `Repeat` retain support for large delay-only counts without expanding each iteration. Patterns also allow at most 100,000 intensity and sharpness control points in total, counting each repeated event. These internal resource limits are not public configuration options. ## Common events and curves Use `transient` for a single impact and `continuous` for positive-duration feedback. Compose provides `Transient` and `Continuous` with the same parameters. Both DSLs compile to the same common event values. ```kotlin import io.github.compose.jindong.core.dsl.buildHapticPattern import io.github.compose.jindong.core.dsl.hapticCurve import io.github.compose.jindong.core.model.HapticIntensity import io.github.compose.jindong.core.ms val rise = hapticCurve { point(0.ms, 0f) point(80.ms, 0.8f) point(300.ms, 0f) } val celebration = buildHapticPattern { repeat(3) { transient(HapticIntensity.STRONG, sharpness = 0.9f) delay(80.ms) } continuous(300.ms, intensityCurve = rise, sharpness = 0.6f) } // Impact starts: 0ms, 80ms, 160ms. Continuous interval: 240..540ms. ``` A transient has zero logical duration. Add `delay` to separate successive impacts. A continuous event must last at least 1ms after rounding. Sharpness expresses texture intent in `0..1`; it does not specify frequency in Hz. The default sharpness is `0.5f`. Each curve contains absolute normalized values, with linear interpolation between control points. A curve replaces the corresponding fixed event value. Intensity `0` means silence. Control point values and sharpness must be finite and within `0..1`. A continuous curve requires at least two points, starting at 0ms and ending at the event duration. Rounded point times must strictly increase. Transients cannot contain curves. Direct event and curve constructors apply the same validation as the DSLs. `scaleIntensity` scales and clamps fixed intensity and intensity curves, preserving sharpness. `timeStretch` scales event and control point times on the same absolute timeline. It rejects control points that round to the same millisecond. `reversed` reflects each curve within its event. Reversing twice restores the original event order and values. `then`, repetition, `include`, and `Clip` preserve event type, sharpness, and curves. Curves snapshot their input lists, so later list mutations cannot change pattern equality or Compose keys. `ScheduledHapticEvent` is now an ordinary immutable class with value equality, `copy`, and destructuring. The original four-argument constructor, four-argument `copy`, and first four component functions retain their JVM and klib signatures. The extended constructor and `copy` require `eventType` to keep old calls unambiguous: ```kotlin val moved = event.copy(startTimeMs = 100L) // Preserves rich properties. val softerTexture = event.copy(eventType = event.eventType, sharpness = 0.2f) ``` The original constructor still permits zero-length continuous no-ops. Existing `haptic` calls continue to require positive duration. The explicit continuous constructor rejects zero duration. Data-class reflection metadata is no longer present. Hash codes and string output now include the rich properties. Recompile consumers when migrating to the 2.0 development line. Device execution can approximate the requested texture or shape; common values do not guarantee identical physical feedback across devices.