@rafaelaugusto/jolt 0.1.0-beta.1 → 0.1.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +385 -18
- package/dist/animate.d.ts +19 -3
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +186 -14
- package/dist/animate.js.map +1 -1
- package/dist/color.d.ts +11 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/color.js +73 -0
- package/dist/color.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/decay.d.ts +11 -0
- package/dist/decay.d.ts.map +1 -0
- package/dist/decay.js +22 -0
- package/dist/decay.js.map +1 -0
- package/dist/decoration.d.ts +18 -0
- package/dist/decoration.d.ts.map +1 -0
- package/dist/decoration.js +155 -0
- package/dist/decoration.js.map +1 -0
- package/dist/drag-input.d.ts +15 -0
- package/dist/drag-input.d.ts.map +1 -0
- package/dist/drag-input.js +66 -0
- package/dist/drag-input.js.map +1 -0
- package/dist/drag-physics.d.ts +20 -0
- package/dist/drag-physics.d.ts.map +1 -0
- package/dist/drag-physics.js +75 -0
- package/dist/drag-physics.js.map +1 -0
- package/dist/drag.d.ts +58 -0
- package/dist/drag.d.ts.map +1 -0
- package/dist/drag.js +324 -0
- package/dist/drag.js.map +1 -0
- package/dist/driver/android-color.d.ts +20 -0
- package/dist/driver/android-color.d.ts.map +1 -0
- package/dist/driver/android-color.js +136 -0
- package/dist/driver/android-color.js.map +1 -0
- package/dist/driver/android-decoration.d.ts +22 -0
- package/dist/driver/android-decoration.d.ts.map +1 -0
- package/dist/driver/android-decoration.js +139 -0
- package/dist/driver/android-decoration.js.map +1 -0
- package/dist/driver/android-gradient.d.ts +5 -0
- package/dist/driver/android-gradient.d.ts.map +1 -0
- package/dist/driver/android-gradient.js +89 -0
- package/dist/driver/android-gradient.js.map +1 -0
- package/dist/driver/android-playback.d.ts +13 -0
- package/dist/driver/android-playback.d.ts.map +1 -0
- package/dist/driver/android-playback.js +141 -0
- package/dist/driver/android-playback.js.map +1 -0
- package/dist/driver/android.d.ts +1 -1
- package/dist/driver/android.d.ts.map +1 -1
- package/dist/driver/android.js +165 -13
- package/dist/driver/android.js.map +1 -1
- package/dist/driver/index.d.ts +25 -2
- package/dist/driver/index.d.ts.map +1 -1
- package/dist/driver/index.js.map +1 -1
- package/dist/driver/ios-color.d.ts +16 -0
- package/dist/driver/ios-color.d.ts.map +1 -0
- package/dist/driver/ios-color.js +392 -0
- package/dist/driver/ios-color.js.map +1 -0
- package/dist/driver/ios-decoration.d.ts +17 -0
- package/dist/driver/ios-decoration.d.ts.map +1 -0
- package/dist/driver/ios-decoration.js +271 -0
- package/dist/driver/ios-decoration.js.map +1 -0
- package/dist/driver/ios-gradient.d.ts +5 -0
- package/dist/driver/ios-gradient.d.ts.map +1 -0
- package/dist/driver/ios-gradient.js +109 -0
- package/dist/driver/ios-gradient.js.map +1 -0
- package/dist/driver/ios-layout.d.ts +9 -0
- package/dist/driver/ios-layout.d.ts.map +1 -0
- package/dist/driver/ios-layout.js +52 -0
- package/dist/driver/ios-layout.js.map +1 -0
- package/dist/driver/ios-playback.d.ts +10 -0
- package/dist/driver/ios-playback.d.ts.map +1 -0
- package/dist/driver/ios-playback.js +47 -0
- package/dist/driver/ios-playback.js.map +1 -0
- package/dist/driver/ios-shadow.d.ts +6 -0
- package/dist/driver/ios-shadow.d.ts.map +1 -0
- package/dist/driver/ios-shadow.js +52 -0
- package/dist/driver/ios-shadow.js.map +1 -0
- package/dist/driver/ios.d.ts.map +1 -1
- package/dist/driver/ios.js +163 -28
- package/dist/driver/ios.js.map +1 -1
- package/dist/engine.d.ts +75 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +293 -23
- package/dist/engine.js.map +1 -1
- package/dist/gradient.d.ts +90 -0
- package/dist/gradient.d.ts.map +1 -0
- package/dist/gradient.js +247 -0
- package/dist/gradient.js.map +1 -0
- package/dist/haptics.d.ts +21 -0
- package/dist/haptics.d.ts.map +1 -0
- package/dist/haptics.js +120 -0
- package/dist/haptics.js.map +1 -0
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/interaction-surface.d.ts +12 -0
- package/dist/interaction-surface.d.ts.map +1 -0
- package/dist/interaction-surface.js +36 -0
- package/dist/interaction-surface.js.map +1 -0
- package/dist/items.d.ts +20 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +109 -0
- package/dist/items.js.map +1 -0
- package/dist/keyboard.d.ts +41 -0
- package/dist/keyboard.d.ts.map +1 -0
- package/dist/keyboard.js +256 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +60 -0
- package/dist/label.js.map +1 -0
- package/dist/layout.d.ts +13 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +348 -0
- package/dist/layout.js.map +1 -0
- package/dist/liquid-grid.d.ts +3 -0
- package/dist/liquid-grid.d.ts.map +1 -0
- package/dist/liquid-grid.js +24 -0
- package/dist/liquid-grid.js.map +1 -0
- package/dist/liquid.d.ts +18 -0
- package/dist/liquid.d.ts.map +1 -0
- package/dist/liquid.js +234 -0
- package/dist/liquid.js.map +1 -0
- package/dist/loader.d.ts +19 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +173 -0
- package/dist/loader.js.map +1 -0
- package/dist/motion-scope.d.ts +39 -0
- package/dist/motion-scope.d.ts.map +1 -0
- package/dist/motion-scope.js +238 -0
- package/dist/motion-scope.js.map +1 -0
- package/dist/motion-states.d.ts +43 -0
- package/dist/motion-states.d.ts.map +1 -0
- package/dist/motion-states.js +220 -0
- package/dist/motion-states.js.map +1 -0
- package/dist/particles.d.ts +39 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +265 -0
- package/dist/particles.js.map +1 -0
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +90 -0
- package/dist/path.js.map +1 -0
- package/dist/playback-group.d.ts +6 -0
- package/dist/playback-group.d.ts.map +1 -0
- package/dist/playback-group.js +141 -0
- package/dist/playback-group.js.map +1 -0
- package/dist/playback.d.ts +25 -0
- package/dist/playback.d.ts.map +1 -0
- package/dist/playback.js +35 -0
- package/dist/playback.js.map +1 -0
- package/dist/press.d.ts.map +1 -1
- package/dist/press.js +23 -7
- package/dist/press.js.map +1 -1
- package/dist/properties.d.ts +6 -3
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +26 -2
- package/dist/properties.js.map +1 -1
- package/dist/reveal.d.ts +25 -0
- package/dist/reveal.d.ts.map +1 -0
- package/dist/reveal.js +294 -0
- package/dist/reveal.js.map +1 -0
- package/dist/roll-diff.d.ts +22 -0
- package/dist/roll-diff.d.ts.map +1 -0
- package/dist/roll-diff.js +36 -0
- package/dist/roll-diff.js.map +1 -0
- package/dist/roll.d.ts +29 -0
- package/dist/roll.d.ts.map +1 -0
- package/dist/roll.js +442 -0
- package/dist/roll.js.map +1 -0
- package/dist/scroll-linked.d.ts +10 -0
- package/dist/scroll-linked.d.ts.map +1 -0
- package/dist/scroll-linked.js +58 -0
- package/dist/scroll-linked.js.map +1 -0
- package/dist/sequence.d.ts +10 -5
- package/dist/sequence.d.ts.map +1 -1
- package/dist/sequence.js +208 -14
- package/dist/sequence.js.map +1 -1
- package/dist/shimmer.d.ts +12 -0
- package/dist/shimmer.d.ts.map +1 -0
- package/dist/shimmer.js +161 -0
- package/dist/shimmer.js.map +1 -0
- package/dist/svg-path.d.ts +14 -0
- package/dist/svg-path.d.ts.map +1 -0
- package/dist/svg-path.js +281 -0
- package/dist/svg-path.js.map +1 -0
- package/dist/symbol-effect.d.ts +23 -0
- package/dist/symbol-effect.d.ts.map +1 -0
- package/dist/symbol-effect.js +187 -0
- package/dist/symbol-effect.js.map +1 -0
- package/dist/text-layout.d.ts +30 -0
- package/dist/text-layout.d.ts.map +1 -0
- package/dist/text-layout.js +185 -0
- package/dist/text-layout.js.map +1 -0
- package/dist/text.d.ts +17 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +263 -0
- package/dist/text.js.map +1 -0
- package/dist/track.d.ts +3 -1
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +266 -13
- package/dist/track.js.map +1 -1
- package/dist/transition.d.ts +32 -1
- package/dist/transition.d.ts.map +1 -1
- package/dist/transition.js +58 -6
- package/dist/transition.js.map +1 -1
- package/dist/types.d.ts +48 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vue/index.d.ts +95 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +163 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +29 -5
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/GradientLayer.java +195 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
- package/platforms/android/proguard-rules.pro +21 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
- package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +6 -0
- package/src/android-plugin.d.ts +184 -0
- package/src/animate.ts +249 -36
- package/src/color.ts +101 -0
- package/src/config.ts +84 -0
- package/src/decay.ts +45 -0
- package/src/decoration.ts +180 -0
- package/src/drag-input.ts +74 -0
- package/src/drag-physics.ts +110 -0
- package/src/drag.ts +395 -0
- package/src/driver/android-color.ts +170 -0
- package/src/driver/android-decoration.ts +182 -0
- package/src/driver/android-gradient.ts +109 -0
- package/src/driver/android-playback.ts +157 -0
- package/src/driver/android.ts +208 -20
- package/src/driver/index.ts +37 -2
- package/src/driver/ios-color.ts +441 -0
- package/src/driver/ios-decoration.ts +332 -0
- package/src/driver/ios-gradient.ts +114 -0
- package/src/driver/ios-layout.ts +63 -0
- package/src/driver/ios-playback.ts +51 -0
- package/src/driver/ios-shadow.ts +50 -0
- package/src/driver/ios.ts +193 -33
- package/src/engine.ts +321 -27
- package/src/gradient.ts +372 -0
- package/src/haptics.ts +148 -0
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +75 -4
- package/src/interaction-surface.ts +41 -0
- package/src/ios-plugin.d.ts +81 -0
- package/src/items.ts +140 -0
- package/src/keyboard.ts +291 -0
- package/src/label.ts +76 -0
- package/src/layout.ts +408 -0
- package/src/liquid-grid.ts +28 -0
- package/src/liquid.ts +287 -0
- package/src/loader.ts +202 -0
- package/src/motion-scope.ts +279 -0
- package/src/motion-states.ts +269 -0
- package/src/particles.ts +323 -0
- package/src/path.ts +124 -0
- package/src/playback-group.ts +143 -0
- package/src/playback.ts +62 -0
- package/src/press.ts +26 -10
- package/src/properties.ts +32 -6
- package/src/reveal.ts +374 -0
- package/src/roll-diff.ts +57 -0
- package/src/roll.ts +515 -0
- package/src/scroll-linked.ts +73 -0
- package/src/sequence.ts +204 -14
- package/src/shimmer.ts +180 -0
- package/src/svg-path.ts +355 -0
- package/src/symbol-effect.ts +226 -0
- package/src/text-layout.ts +228 -0
- package/src/text.ts +279 -0
- package/src/track.ts +347 -12
- package/src/transition.ts +93 -6
- package/src/types.ts +49 -1
- package/src/vue/index.ts +231 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
package/src/gradient.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { isAndroid, isIOS, type View } from '@nativescript/core'
|
|
2
|
+
import { toView } from './animate.js'
|
|
3
|
+
import { colorMixer, parseColor, type ColorValue } from './color.js'
|
|
4
|
+
import { reducedMotion } from './config.js'
|
|
5
|
+
import { getDriver } from './driver/index.js'
|
|
6
|
+
import { androidGradient } from './driver/android-gradient.js'
|
|
7
|
+
import { iosGradient } from './driver/ios-gradient.js'
|
|
8
|
+
import type { Animation } from './engine.js'
|
|
9
|
+
import { createTrack, type Track } from './track.js'
|
|
10
|
+
import {
|
|
11
|
+
resolveTransition,
|
|
12
|
+
springs,
|
|
13
|
+
type SpringName,
|
|
14
|
+
type Transition,
|
|
15
|
+
} from './transition.js'
|
|
16
|
+
import type { Target } from './types.js'
|
|
17
|
+
|
|
18
|
+
export type GradientType = 'linear' | 'radial' | 'conic'
|
|
19
|
+
|
|
20
|
+
export interface GradientValues {
|
|
21
|
+
/** Two to eight colors. Animating keeps the count. */
|
|
22
|
+
colors?: readonly ColorValue[]
|
|
23
|
+
/** Where each color sits, from 0 to 1. Evenly spaced by default. */
|
|
24
|
+
stops?: readonly number[]
|
|
25
|
+
/** Degrees, like CSS: `linear` points this way (180 runs top to bottom), `conic` starts here (0 is up). */
|
|
26
|
+
angle?: number
|
|
27
|
+
/** `radial` and `conic`: the center, in fractions of the view's width and height. */
|
|
28
|
+
center?: { readonly x: number; readonly y: number }
|
|
29
|
+
/** `radial`: the radius, in fractions of the view's shorter side. */
|
|
30
|
+
radius?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GradientOptions extends GradientValues {
|
|
34
|
+
type: GradientType
|
|
35
|
+
colors: readonly ColorValue[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Gradient {
|
|
39
|
+
readonly type: GradientType
|
|
40
|
+
/** Animates to new values from the ones on screen. Colors and stops keep their count. */
|
|
41
|
+
animate(values: GradientValues, transition?: Transition | SpringName): Animation
|
|
42
|
+
/** Applies values at once, stopping the animation in progress. */
|
|
43
|
+
set(values: GradientValues): void
|
|
44
|
+
/** Stops the animation in progress where it is. */
|
|
45
|
+
stop(): void
|
|
46
|
+
/** Takes the gradient off the view. */
|
|
47
|
+
remove(): void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Everything the platform needs to paint one frame. */
|
|
51
|
+
export interface GradientFrame {
|
|
52
|
+
colors: number[]
|
|
53
|
+
stops: number[]
|
|
54
|
+
angle: number
|
|
55
|
+
x: number
|
|
56
|
+
y: number
|
|
57
|
+
radius: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface GradientTiming {
|
|
61
|
+
duration: number
|
|
62
|
+
delay: number
|
|
63
|
+
repeat: number
|
|
64
|
+
reverse: boolean
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface GradientPainter {
|
|
68
|
+
paint(frame: GradientFrame): void
|
|
69
|
+
/** Plays sampled frames natively; `onEnd(false)` when cancelled. */
|
|
70
|
+
play(
|
|
71
|
+
frames: readonly GradientFrame[],
|
|
72
|
+
times: readonly number[],
|
|
73
|
+
timing: GradientTiming,
|
|
74
|
+
onEnd: (finished: boolean) => void,
|
|
75
|
+
): { cancel(): void }
|
|
76
|
+
remove(): void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type PainterFactory = (view: View, type: GradientType) => GradientPainter | undefined
|
|
80
|
+
|
|
81
|
+
let factory: PainterFactory | undefined
|
|
82
|
+
|
|
83
|
+
/** Test seam, like `setDriver`. */
|
|
84
|
+
export function setGradientPainter(next: PainterFactory | undefined) {
|
|
85
|
+
factory = next
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function platformPainter(view: View, type: GradientType) {
|
|
89
|
+
if (isIOS) return iosGradient(view, type)
|
|
90
|
+
if (isAndroid) return androidGradient(view, type)
|
|
91
|
+
return undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const types: readonly GradientType[] = ['linear', 'radial', 'conic']
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Paints a native gradient behind a layout's children and animates it.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const glow = gradient(halo, { type: 'radial', colors: ['#FF0080', '#7928CA00'], radius: 0.8 })
|
|
101
|
+
* glow.animate({ center: { x: 0.8, y: 0.2 }, radius: 1.2 }, { duration: 2000 })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function gradient(target: Target, options: GradientOptions): Gradient {
|
|
105
|
+
const view = toView(target)
|
|
106
|
+
if (!options || !types.includes(options.type))
|
|
107
|
+
throw new TypeError(`Gradient type must be 'linear', 'radial' or 'conic'.`)
|
|
108
|
+
const first = resolve(
|
|
109
|
+
{ colors: [], stops: [], angle: 180, x: 0.5, y: 0.5, radius: 0.5 },
|
|
110
|
+
options,
|
|
111
|
+
false,
|
|
112
|
+
)
|
|
113
|
+
return new GradientController(view, options.type, first)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class GradientController implements Gradient {
|
|
117
|
+
private painter: GradientPainter | undefined
|
|
118
|
+
private pending: GradientFrame | undefined
|
|
119
|
+
private removed = false
|
|
120
|
+
private model: GradientFrame
|
|
121
|
+
private running:
|
|
122
|
+
| {
|
|
123
|
+
from: GradientFrame
|
|
124
|
+
to: GradientFrame
|
|
125
|
+
track: Track
|
|
126
|
+
startedAt: number
|
|
127
|
+
handle: { cancel(): void }
|
|
128
|
+
settle: (finished: boolean) => void
|
|
129
|
+
}
|
|
130
|
+
| undefined
|
|
131
|
+
|
|
132
|
+
constructor(
|
|
133
|
+
private readonly view: View,
|
|
134
|
+
readonly type: GradientType,
|
|
135
|
+
frame: GradientFrame,
|
|
136
|
+
) {
|
|
137
|
+
this.model = frame
|
|
138
|
+
this.attach((factory ?? platformPainter)(view, type))
|
|
139
|
+
view.on('unloaded', this.unloaded)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private unloaded = () => this.stop()
|
|
143
|
+
|
|
144
|
+
private attach(painter: GradientPainter | undefined) {
|
|
145
|
+
if (!painter) return
|
|
146
|
+
if (this.removed) return painter.remove()
|
|
147
|
+
this.painter = painter
|
|
148
|
+
painter.paint(this.pending ?? this.model)
|
|
149
|
+
this.pending = undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
animate(values: GradientValues, transition?: Transition | SpringName): Animation {
|
|
153
|
+
this.assertActive()
|
|
154
|
+
const from = this.presented()
|
|
155
|
+
const to = resolve(from, values, true)
|
|
156
|
+
const resolved =
|
|
157
|
+
typeof transition === 'string' ? springOf(transition) : (transition ?? {})
|
|
158
|
+
const track = createTrack(
|
|
159
|
+
'opacity',
|
|
160
|
+
[0, 1],
|
|
161
|
+
resolveTransition(resolved, 'opacity', 2),
|
|
162
|
+
0,
|
|
163
|
+
)
|
|
164
|
+
const reduced = reducedMotion()
|
|
165
|
+
const frameAt = (progress: number) => mix(from, to, progress, reduced)
|
|
166
|
+
this.cancelRunning()
|
|
167
|
+
this.model = frameAt(track.to)
|
|
168
|
+
|
|
169
|
+
let settle!: (finished: boolean) => void
|
|
170
|
+
const finished = new Promise<boolean>((resolve) => (settle = resolve))
|
|
171
|
+
const animation: Animation = {
|
|
172
|
+
finished,
|
|
173
|
+
stop: () => {
|
|
174
|
+
if (this.running?.settle === settle) this.stop()
|
|
175
|
+
},
|
|
176
|
+
then: (onfulfilled, onrejected) => finished.then(onfulfilled, onrejected),
|
|
177
|
+
}
|
|
178
|
+
if (track.end === 0 || !this.painter) {
|
|
179
|
+
this.paint(this.model)
|
|
180
|
+
settle(true)
|
|
181
|
+
return animation
|
|
182
|
+
}
|
|
183
|
+
const handle = this.painter.play(
|
|
184
|
+
track.values.map(frameAt),
|
|
185
|
+
track.times,
|
|
186
|
+
{
|
|
187
|
+
duration: track.duration,
|
|
188
|
+
delay: track.delay,
|
|
189
|
+
repeat: track.repeat,
|
|
190
|
+
reverse: track.reverse,
|
|
191
|
+
},
|
|
192
|
+
(completed) => {
|
|
193
|
+
if (this.running?.settle !== settle) return
|
|
194
|
+
this.running = undefined
|
|
195
|
+
settle(completed)
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
this.running = {
|
|
199
|
+
from,
|
|
200
|
+
to,
|
|
201
|
+
track,
|
|
202
|
+
startedAt: getDriver().now(),
|
|
203
|
+
handle,
|
|
204
|
+
settle,
|
|
205
|
+
}
|
|
206
|
+
return animation
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
set(values: GradientValues) {
|
|
210
|
+
this.assertActive()
|
|
211
|
+
const next = resolve(this.presented(), values, false)
|
|
212
|
+
this.cancelRunning()
|
|
213
|
+
this.model = next
|
|
214
|
+
this.paint(next)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
stop() {
|
|
218
|
+
if (!this.running) return
|
|
219
|
+
const current = this.presented()
|
|
220
|
+
this.cancelRunning()
|
|
221
|
+
this.model = current
|
|
222
|
+
this.paint(current)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
remove() {
|
|
226
|
+
if (this.removed) return
|
|
227
|
+
this.cancelRunning()
|
|
228
|
+
this.removed = true
|
|
229
|
+
this.view.off('unloaded', this.unloaded)
|
|
230
|
+
this.painter?.remove()
|
|
231
|
+
this.painter = undefined
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private paint(frame: GradientFrame) {
|
|
235
|
+
if (this.painter) this.painter.paint(frame)
|
|
236
|
+
else this.pending = frame
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private presented(): GradientFrame {
|
|
240
|
+
const running = this.running
|
|
241
|
+
if (!running) return this.model
|
|
242
|
+
const elapsed = getDriver().now() - running.startedAt
|
|
243
|
+
return mix(running.from, running.to, running.track.valueAt(elapsed), reducedMotion())
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private cancelRunning() {
|
|
247
|
+
const running = this.running
|
|
248
|
+
if (!running) return
|
|
249
|
+
this.running = undefined
|
|
250
|
+
running.handle.cancel()
|
|
251
|
+
running.settle(false)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private assertActive() {
|
|
255
|
+
if (this.removed) throw new Error('This gradient was removed.')
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function springOf(name: SpringName): Transition {
|
|
260
|
+
const spring = springs[name]
|
|
261
|
+
if (!spring || !Object.hasOwn(springs, name))
|
|
262
|
+
throw new Error(`Unknown spring "${name}".`)
|
|
263
|
+
return spring
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Validates values and fills in what they leave out from `base`. */
|
|
267
|
+
function resolve(
|
|
268
|
+
base: GradientFrame,
|
|
269
|
+
values: GradientValues,
|
|
270
|
+
sameCount: boolean,
|
|
271
|
+
): GradientFrame {
|
|
272
|
+
if (!values || typeof values !== 'object')
|
|
273
|
+
throw new TypeError('Gradient values must be an object.')
|
|
274
|
+
const colors = values.colors ? values.colors.map(parseColor) : base.colors
|
|
275
|
+
if (colors.length < 2 || colors.length > 8)
|
|
276
|
+
throw new RangeError('A gradient needs between two and eight colors.')
|
|
277
|
+
if (sameCount && values.colors && colors.length !== base.colors.length)
|
|
278
|
+
throw new RangeError(
|
|
279
|
+
`Animating a gradient keeps its ${base.colors.length} colors; use set() to change the count.`,
|
|
280
|
+
)
|
|
281
|
+
const stops = values.stops
|
|
282
|
+
? [...values.stops]
|
|
283
|
+
: values.colors || base.stops.length !== colors.length
|
|
284
|
+
? colors.map((_, i) => i / (colors.length - 1))
|
|
285
|
+
: base.stops
|
|
286
|
+
if (
|
|
287
|
+
stops.length !== colors.length ||
|
|
288
|
+
stops.some(
|
|
289
|
+
(stop, i) =>
|
|
290
|
+
!Number.isFinite(stop) || stop < 0 || stop > 1 || stop < (stops[i - 1] ?? 0),
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
throw new RangeError(
|
|
294
|
+
'Gradient stops need one value per color, from 0 to 1, without going back.',
|
|
295
|
+
)
|
|
296
|
+
const angle = values.angle ?? base.angle
|
|
297
|
+
const x = values.center?.x ?? base.x
|
|
298
|
+
const y = values.center?.y ?? base.y
|
|
299
|
+
const radius = values.radius ?? base.radius
|
|
300
|
+
for (const [name, value] of [
|
|
301
|
+
['angle', angle],
|
|
302
|
+
['center', x],
|
|
303
|
+
['center', y],
|
|
304
|
+
['radius', radius],
|
|
305
|
+
] as const)
|
|
306
|
+
if (!Number.isFinite(value))
|
|
307
|
+
throw new RangeError(`Invalid gradient ${name}: ${value}.`)
|
|
308
|
+
if (radius < 0)
|
|
309
|
+
throw new RangeError(`Gradient radius must not be negative, got ${radius}.`)
|
|
310
|
+
return { colors, stops, angle, x, y, radius }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** One frame between two states. Geometry follows the curve, overshoot included; colors and stops stay between their ends. */
|
|
314
|
+
export function mix(
|
|
315
|
+
from: GradientFrame,
|
|
316
|
+
to: GradientFrame,
|
|
317
|
+
progress: number,
|
|
318
|
+
reduced = false,
|
|
319
|
+
): GradientFrame {
|
|
320
|
+
const p = Math.min(Math.max(progress, 0), 1)
|
|
321
|
+
const g = reduced ? 1 : progress
|
|
322
|
+
const lerp = (a: number, b: number, t: number) => a + (b - a) * t
|
|
323
|
+
return {
|
|
324
|
+
colors: to.colors.map((color, i) => colorMixer(from.colors[i] ?? color, color)(p)),
|
|
325
|
+
stops: to.stops.map((stop, i) => lerp(from.stops[i] ?? stop, stop, p)),
|
|
326
|
+
angle: lerp(from.angle, to.angle, g),
|
|
327
|
+
x: lerp(from.x, to.x, g),
|
|
328
|
+
y: lerp(from.y, to.y, g),
|
|
329
|
+
radius: Math.max(0, lerp(from.radius, to.radius, g)),
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The points a platform gradient needs, in unit coordinates of a `width` × `height` box.
|
|
335
|
+
* Linear follows CSS: the line runs through the center at `angle` and is long enough
|
|
336
|
+
* for the corners to get the first and last colors.
|
|
337
|
+
*/
|
|
338
|
+
export function gradientPoints(
|
|
339
|
+
type: GradientType,
|
|
340
|
+
frame: GradientFrame,
|
|
341
|
+
width: number,
|
|
342
|
+
height: number,
|
|
343
|
+
) {
|
|
344
|
+
const radians = (frame.angle * Math.PI) / 180
|
|
345
|
+
const sin = Math.sin(radians)
|
|
346
|
+
const cos = Math.cos(radians)
|
|
347
|
+
if (type === 'linear') {
|
|
348
|
+
const length = Math.abs(width * sin) + Math.abs(height * cos)
|
|
349
|
+
const dx = width > 0 ? (sin * length) / (2 * width) : 0
|
|
350
|
+
const dy = height > 0 ? (-cos * length) / (2 * height) : 0
|
|
351
|
+
return { start: { x: 0.5 - dx, y: 0.5 - dy }, end: { x: 0.5 + dx, y: 0.5 + dy } }
|
|
352
|
+
}
|
|
353
|
+
const center = { x: frame.x, y: frame.y }
|
|
354
|
+
const shorter = Math.min(width, height)
|
|
355
|
+
if (type === 'radial') {
|
|
356
|
+
const r = frame.radius * shorter
|
|
357
|
+
return {
|
|
358
|
+
start: center,
|
|
359
|
+
end: {
|
|
360
|
+
x: center.x + (width > 0 ? r / width : 0),
|
|
361
|
+
y: center.y + (height > 0 ? r / height : 0),
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
start: center,
|
|
367
|
+
end: {
|
|
368
|
+
x: center.x + (width > 0 ? (sin * shorter) / (2 * width) : 0),
|
|
369
|
+
y: center.y + (height > 0 ? (-cos * shorter) / (2 * height) : 0),
|
|
370
|
+
},
|
|
371
|
+
}
|
|
372
|
+
}
|
package/src/haptics.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { isIOS, type View } from '@nativescript/core'
|
|
2
|
+
import type { Driver } from './driver/index.js'
|
|
3
|
+
import type { Track } from './track.js'
|
|
4
|
+
|
|
5
|
+
export type HapticStyle = 'light' | 'medium' | 'heavy'
|
|
6
|
+
|
|
7
|
+
export interface HapticOptions {
|
|
8
|
+
style: HapticStyle
|
|
9
|
+
/** Points from 0 to 1 over the finite animation, excluding its initial delay. Defaults to arrival. */
|
|
10
|
+
times?: readonly number[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Haptic = HapticStyle | HapticOptions | false
|
|
14
|
+
|
|
15
|
+
/** Validate before any running animation is interrupted. */
|
|
16
|
+
export function resolveHaptic(haptic: Haptic | undefined): HapticOptions | undefined {
|
|
17
|
+
if (haptic === undefined || haptic === false) return undefined
|
|
18
|
+
const options = typeof haptic === 'string' ? { style: haptic } : haptic
|
|
19
|
+
if (!options || !['light', 'medium', 'heavy'].includes(options.style))
|
|
20
|
+
throw new TypeError('Haptic style must be light, medium or heavy.')
|
|
21
|
+
const times = options.times
|
|
22
|
+
if (
|
|
23
|
+
times !== undefined &&
|
|
24
|
+
(!Array.isArray(times) ||
|
|
25
|
+
times.length === 0 ||
|
|
26
|
+
Array.from(times).some(
|
|
27
|
+
(time, i) =>
|
|
28
|
+
!Number.isFinite(time) ||
|
|
29
|
+
time <= 0 ||
|
|
30
|
+
time > 1 ||
|
|
31
|
+
(i > 0 && time <= times[i - 1]!),
|
|
32
|
+
))
|
|
33
|
+
)
|
|
34
|
+
throw new RangeError(
|
|
35
|
+
'Haptic times must be increasing points greater than 0 and at most 1.',
|
|
36
|
+
)
|
|
37
|
+
return { style: options.style, times: times && [...times] }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const PREPARE_AHEAD = 100
|
|
41
|
+
const MAX_LATENESS = 100
|
|
42
|
+
const COMPLETION_TOLERANCE = 20
|
|
43
|
+
|
|
44
|
+
/** Discrete feedback only: animation playback remains entirely native. */
|
|
45
|
+
export function playHaptics(
|
|
46
|
+
view: View,
|
|
47
|
+
tracks: readonly Track[],
|
|
48
|
+
options: HapticOptions | undefined,
|
|
49
|
+
driver: Driver,
|
|
50
|
+
startedAt: number,
|
|
51
|
+
elapsedTime = () => driver.now() - startedAt,
|
|
52
|
+
) {
|
|
53
|
+
if (!isIOS || !options || typeof UIImpactFeedbackGenerator === 'undefined') return
|
|
54
|
+
const moving = tracks.filter(
|
|
55
|
+
(track) =>
|
|
56
|
+
track.duration > 0 && track.values.some((value) => value !== track.values[0]),
|
|
57
|
+
)
|
|
58
|
+
if (!moving.length) return
|
|
59
|
+
const end = Math.max(...tracks.map((track) => track.end))
|
|
60
|
+
if (!Number.isFinite(end)) return
|
|
61
|
+
const delay = Math.min(...moving.map((track) => track.delay))
|
|
62
|
+
const offsets = options.times
|
|
63
|
+
? options.times.map((time) => delay + (end - delay) * time)
|
|
64
|
+
: [Math.max(...tracks.map((track) => track.arrival))]
|
|
65
|
+
const styles = {
|
|
66
|
+
light: 0,
|
|
67
|
+
medium: 1,
|
|
68
|
+
heavy: 2,
|
|
69
|
+
} satisfies Record<HapticStyle, UIImpactFeedbackStyle>
|
|
70
|
+
let generator: UIImpactFeedbackGenerator | undefined =
|
|
71
|
+
UIImpactFeedbackGenerator.alloc().initWithStyle(styles[options.style])
|
|
72
|
+
const native = view.nativeViewProtected as UIView
|
|
73
|
+
let paused = false
|
|
74
|
+
let next = 0
|
|
75
|
+
let cancelPrepare: (() => void) | undefined
|
|
76
|
+
let cancelImpact: (() => void) | undefined
|
|
77
|
+
|
|
78
|
+
const active = () =>
|
|
79
|
+
view.isLoaded &&
|
|
80
|
+
view.nativeViewProtected === native &&
|
|
81
|
+
!!native.window &&
|
|
82
|
+
UIApplication.sharedApplication.applicationState === 0 // UIApplicationState.Active
|
|
83
|
+
|
|
84
|
+
const cancelTimers = () => {
|
|
85
|
+
cancelPrepare?.()
|
|
86
|
+
cancelImpact?.()
|
|
87
|
+
cancelPrepare = cancelImpact = undefined
|
|
88
|
+
}
|
|
89
|
+
const cancel = () => {
|
|
90
|
+
cancelPrepare?.()
|
|
91
|
+
cancelImpact?.()
|
|
92
|
+
cancelPrepare = cancelImpact = undefined
|
|
93
|
+
generator = undefined
|
|
94
|
+
view.off('unloaded', cancel)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const fire = (tolerance = 0) => {
|
|
98
|
+
const elapsed = elapsedTime()
|
|
99
|
+
// A busy UI thread can delay timers. Drop stale impacts instead of replaying
|
|
100
|
+
// several old taps at once when it becomes responsive again.
|
|
101
|
+
while (next < offsets.length && offsets[next]! <= elapsed + tolerance) {
|
|
102
|
+
const offset = offsets[next++]!
|
|
103
|
+
if (active() && elapsed - offset <= MAX_LATENESS) generator?.impactOccurred()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const schedule = () => {
|
|
108
|
+
if (paused) return
|
|
109
|
+
if (!generator || next === offsets.length) return cancel()
|
|
110
|
+
const remaining = offsets[next]! - elapsedTime()
|
|
111
|
+
if (active()) generator.prepare()
|
|
112
|
+
if (remaining > PREPARE_AHEAD) {
|
|
113
|
+
cancelPrepare = driver.after(remaining - PREPARE_AHEAD, () => {
|
|
114
|
+
if (active()) generator?.prepare()
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
cancelImpact = driver.after(Math.max(0, remaining), () => {
|
|
118
|
+
fire()
|
|
119
|
+
schedule()
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
view.on('unloaded', cancel)
|
|
124
|
+
schedule()
|
|
125
|
+
return {
|
|
126
|
+
cancel,
|
|
127
|
+
pause() {
|
|
128
|
+
paused = true
|
|
129
|
+
cancelTimers()
|
|
130
|
+
},
|
|
131
|
+
play() {
|
|
132
|
+
if (paused) {
|
|
133
|
+
paused = false
|
|
134
|
+
schedule()
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
seek() {
|
|
138
|
+
cancelTimers()
|
|
139
|
+
while (next < offsets.length && offsets[next]! <= elapsedTime()) next++
|
|
140
|
+
schedule()
|
|
141
|
+
},
|
|
142
|
+
finish() {
|
|
143
|
+
// Native completion may beat the timer at the final keyframe by a frame.
|
|
144
|
+
fire(COMPLETION_TOLERANCE)
|
|
145
|
+
cancel()
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Utils, isAndroid, isIOS, type Image, type ImageSource } from '@nativescript/core'
|
|
2
|
+
|
|
3
|
+
export interface ImageEffectSettings {
|
|
4
|
+
mode: number
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
amplitude: number
|
|
8
|
+
radius: number
|
|
9
|
+
duration: number
|
|
10
|
+
seed: number
|
|
11
|
+
to?: ImageSource
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ImageRenderer {
|
|
15
|
+
readonly backend: 'metal' | 'agsl'
|
|
16
|
+
readonly progress: number
|
|
17
|
+
readonly frames: number
|
|
18
|
+
readonly textureBytes: number
|
|
19
|
+
readonly longestFrameMs: number
|
|
20
|
+
play(endpoint: number): void
|
|
21
|
+
pause(): void
|
|
22
|
+
resume(): void
|
|
23
|
+
seek(progress: number): void
|
|
24
|
+
finish(endpoint: number): void
|
|
25
|
+
stop(preserveImage?: boolean): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createImageRenderer(
|
|
29
|
+
image: Image,
|
|
30
|
+
settings: ImageEffectSettings,
|
|
31
|
+
ended: (completed: boolean, progress: number) => void,
|
|
32
|
+
): ImageRenderer | undefined {
|
|
33
|
+
const { mode, x, y, amplitude, radius, duration, seed, to } = settings
|
|
34
|
+
if (isIOS && typeof JoltImageEffect !== 'undefined') {
|
|
35
|
+
const native =
|
|
36
|
+
JoltImageEffect.createWithViewDestinationModeOriginXOriginYAmplitudeRadiusDurationSeedListener(
|
|
37
|
+
image.nativeViewProtected,
|
|
38
|
+
to?.ios ?? null,
|
|
39
|
+
mode,
|
|
40
|
+
x,
|
|
41
|
+
y,
|
|
42
|
+
amplitude,
|
|
43
|
+
radius,
|
|
44
|
+
duration,
|
|
45
|
+
seed,
|
|
46
|
+
ended,
|
|
47
|
+
)
|
|
48
|
+
if (!native) return undefined
|
|
49
|
+
return {
|
|
50
|
+
backend: 'metal',
|
|
51
|
+
get progress() {
|
|
52
|
+
return native.progress
|
|
53
|
+
},
|
|
54
|
+
get frames() {
|
|
55
|
+
return native.frames
|
|
56
|
+
},
|
|
57
|
+
get textureBytes() {
|
|
58
|
+
return native.textureBytes
|
|
59
|
+
},
|
|
60
|
+
get longestFrameMs() {
|
|
61
|
+
return native.longestFrame * 1000
|
|
62
|
+
},
|
|
63
|
+
play: (p) => native.play(p),
|
|
64
|
+
pause: () => native.pause(),
|
|
65
|
+
resume: () => native.resume(),
|
|
66
|
+
seek: (p) => native.seek(p),
|
|
67
|
+
finish: (p) => native.finish(p),
|
|
68
|
+
stop: (preserveImage) =>
|
|
69
|
+
preserveImage ? native.stopPreservingImage() : native.stop(),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (isAndroid && android.os.Build.VERSION.SDK_INT >= 33) {
|
|
73
|
+
const density = Utils.layout.getDisplayDensity()
|
|
74
|
+
const background = image.nativeViewProtected.getBackground()
|
|
75
|
+
// NativeScript draws bitmap pixels itself: ImageView.getImageMatrix() does
|
|
76
|
+
// not describe its stretch, EXIF rotation or inner border clipping.
|
|
77
|
+
const geometry = Array.create('float', 6)
|
|
78
|
+
geometry[0] = image.imageSource?.rotationAngle ?? 0
|
|
79
|
+
geometry[1] = to?.rotationAngle ?? 0
|
|
80
|
+
const corners = ['TopLeft', 'TopRight', 'BottomRight', 'BottomLeft']
|
|
81
|
+
corners.forEach((corner, i) => {
|
|
82
|
+
geometry[i + 2] = background?.[`getBorder${corner}Radius`]?.() ?? 0
|
|
83
|
+
})
|
|
84
|
+
const native = new org.rafaelaugusto.jolt.ImageEffect(
|
|
85
|
+
image.nativeViewProtected,
|
|
86
|
+
image.imageSource?.android ?? null,
|
|
87
|
+
to?.android ?? null,
|
|
88
|
+
geometry,
|
|
89
|
+
mode,
|
|
90
|
+
x * density,
|
|
91
|
+
y * density,
|
|
92
|
+
amplitude * density,
|
|
93
|
+
radius * density,
|
|
94
|
+
duration,
|
|
95
|
+
seed,
|
|
96
|
+
(image as Image & { touchListener?: android.view.View.OnTouchListener })
|
|
97
|
+
.touchListener ?? null,
|
|
98
|
+
new org.rafaelaugusto.jolt.ImageEffect.Listener({ onEnd: ended }),
|
|
99
|
+
)
|
|
100
|
+
if (!native.isSupported()) {
|
|
101
|
+
const reason = native.getFailureReason()
|
|
102
|
+
native.stop()
|
|
103
|
+
throw new Error(reason)
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
backend: 'agsl',
|
|
107
|
+
get progress() {
|
|
108
|
+
return native.getProgress()
|
|
109
|
+
},
|
|
110
|
+
get frames() {
|
|
111
|
+
return native.getFrames()
|
|
112
|
+
},
|
|
113
|
+
get textureBytes() {
|
|
114
|
+
return native.getTextureBytes()
|
|
115
|
+
},
|
|
116
|
+
get longestFrameMs() {
|
|
117
|
+
return native.getLongestFrameNanos() / 1e6
|
|
118
|
+
},
|
|
119
|
+
play: (p) => native.play(p),
|
|
120
|
+
pause: () => native.pause(),
|
|
121
|
+
resume: () => native.resume(),
|
|
122
|
+
seek: (p) => native.seek(p),
|
|
123
|
+
finish: (p) => native.finish(p),
|
|
124
|
+
stop: (preserveImage) =>
|
|
125
|
+
preserveImage ? native.stopPreservingImage() : native.stop(),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined
|
|
129
|
+
}
|