@octane-xplat/lottie 0.0.1 → 0.9.0
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 +36 -2
- package/dist/native/cx.mobile.js +11 -0
- package/dist/native/escape-props.js +39 -0
- package/dist/native/index.js +182 -0
- package/dist/native/layout-child.js +38 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/native/vendor/ui-lottie/index.android.js +286 -0
- package/dist/native/vendor/ui-lottie/index.ios.js +214 -0
- package/dist/native/vendor/ui-lottie/src/lottie/index.common.js +50 -0
- package/dist/native/vendor/ui-lottie/src/lottie/utils.js +6 -0
- package/dist/web/escape-props.web.js +17 -0
- package/dist/web/index.js +332 -0
- package/dist/web/layout-child.web.js +47 -0
- package/dist/web/octane-client-build.json +8 -0
- package/package.json +81 -10
- package/platforms/android/include.gradle +7 -0
- package/platforms/android/java/com/nativescript/lottie/LottieAnimationView.java +92 -0
- package/platforms/android/lottie.aar +0 -0
- package/platforms/android/native-api-usage.json +16 -0
- package/platforms/ios/Podfile +5 -0
- package/src/Lottie.tsrx +161 -0
- package/src/Lottie.web.tsrx +135 -0
- package/src/cx.mobile.ts +8 -0
- package/src/escape-props.ts +65 -0
- package/src/escape-props.web.ts +25 -0
- package/src/index.ts +2 -0
- package/src/index.web.ts +2 -0
- package/src/layout-child.ts +57 -0
- package/src/layout-child.web.ts +108 -0
- package/src/native-elements.d.ts +8 -0
- package/src/props.ts +128 -0
- package/src/references.d.ts +3 -0
- package/src/vendor/ui-lottie/LICENSE.md +21 -0
- package/src/vendor/ui-lottie/src/lottie/index.android.ts +447 -0
- package/src/vendor/ui-lottie/src/lottie/index.common.ts +84 -0
- package/src/vendor/ui-lottie/src/lottie/index.d.ts +133 -0
- package/src/vendor/ui-lottie/src/lottie/index.ios.ts +345 -0
- package/src/vendor/ui-lottie/src/lottie/typings/android.d.ts +4149 -0
- package/src/vendor/ui-lottie/src/lottie/typings/android.nativescript.d.ts +12 -0
- package/src/vendor/ui-lottie/src/lottie/typings/dotlottie.ios.d.ts +14 -0
- package/src/vendor/ui-lottie/src/lottie/typings/ios.d.ts +384 -0
- package/src/vendor/ui-lottie/src/lottie/utils.ts +9 -0
- package/types/generated/.tsrx-typegen-manifest.json +18 -0
- package/types/generated/Lottie.d.ts +7 -0
- package/types/generated/Lottie.web.d.ts +7 -0
- package/types/generated/cx.mobile.d.ts +6 -0
- package/types/generated/escape-props.d.ts +15 -0
- package/types/generated/escape-props.web.d.ts +5 -0
- package/types/generated/index.d.ts +2 -0
- package/types/generated/index.web.d.ts +2 -0
- package/types/generated/layout-child.d.ts +6 -0
- package/types/generated/layout-child.web.d.ts +15 -0
- package/types/generated/props.d.ts +106 -0
- package/types/generated/vendor/ui-lottie/src/lottie/index.common.d.ts +30 -0
- package/types/generated/vendor/ui-lottie/src/lottie/index.ios.d.ts +35 -0
- package/types/generated/vendor/ui-lottie/src/lottie/utils.d.ts +1 -0
- package/index.d.ts +0 -1
- package/index.js +0 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**********************************************************************************
|
|
2
|
+
* (c) 2017, Nathan Walker.
|
|
3
|
+
* Licensed under the MIT license.
|
|
4
|
+
*
|
|
5
|
+
* Version 1.0.0 walkerrunpdx@gmail.com
|
|
6
|
+
**********************************************************************************/
|
|
7
|
+
|
|
8
|
+
// Vendored from @nativescript-community/ui-lottie (MIT), fork branch
|
|
9
|
+
// xplat-fixes on octane-xplat/ui-lottie — device-probed fixes for dead
|
|
10
|
+
// sync-src, missing events/pause-resume, URL/.lottie sources, async
|
|
11
|
+
// autoPlay, completion dedupe, opacity scale, Podfile pin.
|
|
12
|
+
// @ts-nocheck — vendored upstream code, not strict-clean; kept
|
|
13
|
+
// diff-identical to the fork so fix branches stay PR-able.
|
|
14
|
+
/// <reference path="./typings/ios.d.ts" />
|
|
15
|
+
import { Color, Http, Utils, View, knownFolders, path } from '@nativescript/core';
|
|
16
|
+
import { LottieViewBase, autoPlayProperty, keyPathColorsProperty, loopProperty, progressProperty, srcProperty, stretchProperty } from './index.common';
|
|
17
|
+
import { clamp } from './utils';
|
|
18
|
+
|
|
19
|
+
const appPath = knownFolders.currentApp().path;
|
|
20
|
+
|
|
21
|
+
// this remove assert for lottie allowing sync .lottie files loading on main thread
|
|
22
|
+
CompatibleLottieLogger.overrideLottieLogger();
|
|
23
|
+
|
|
24
|
+
export class LottieView extends LottieViewBase {
|
|
25
|
+
nativeViewProtected: CompatibleAnimationView;
|
|
26
|
+
private _imageSourceAffectsLayout = true;
|
|
27
|
+
|
|
28
|
+
public createNativeView() {
|
|
29
|
+
return CompatibleAnimationView.new();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public initNativeView(): void {
|
|
33
|
+
super.initNativeView();
|
|
34
|
+
this.nativeViewProtected.translatesAutoresizingMaskIntoConstraints = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public onLoaded() {
|
|
38
|
+
super.onLoaded();
|
|
39
|
+
if (this.autoPlay && !this.isAnimating()) {
|
|
40
|
+
this.playAnimation();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[keyPathColorsProperty.setNative](value) {
|
|
45
|
+
Object.keys(value).forEach((k) => this.setColor(value[k], k.split('|')));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[srcProperty.setNative](src: string) {
|
|
49
|
+
try {
|
|
50
|
+
if (!src) {
|
|
51
|
+
this.nativeViewProtected.compatibleAnimation = null;
|
|
52
|
+
} else if (src[0] === '{') {
|
|
53
|
+
this.nativeViewProtected.compatibleAnimation = CompatibleAnimation.alloc().initWithJson(src);
|
|
54
|
+
} else if (src.startsWith(Utils.RESOURCE_PREFIX)) {
|
|
55
|
+
const resName = src.replace(Utils.RESOURCE_PREFIX, '');
|
|
56
|
+
|
|
57
|
+
this.nativeViewProtected.compatibleAnimation = CompatibleAnimation.alloc().initWithNameBundle(resName.replace('.json', '').replace('.lottie', ''), NSBundle.mainBundle);
|
|
58
|
+
} else if (/^https?:/i.test(src)) {
|
|
59
|
+
// Remote URL — download to a temp file and re-enter as a local
|
|
60
|
+
// path (initWithUrl exists only as a view initializer, so it
|
|
61
|
+
// can't be applied to an already-created view).
|
|
62
|
+
const match = /\.(zip|lottie|json)(?=[?#]|$)/i.exec(src);
|
|
63
|
+
const dest = path.join(knownFolders.temp().path, `lottie-${Date.now()}${match ? match[0].toLowerCase() : '.json'}`);
|
|
64
|
+
Http.getFile(src, dest)
|
|
65
|
+
.then(() => {
|
|
66
|
+
this.src = dest;
|
|
67
|
+
})
|
|
68
|
+
.catch((error) => {
|
|
69
|
+
console.error(error);
|
|
70
|
+
this.notify({ eventName: 'loadFailed', error });
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
if (src[0] === '~') {
|
|
74
|
+
src = `${path.join(appPath, src.substring(2))}`;
|
|
75
|
+
}
|
|
76
|
+
if (!/.(json|zip|lottie)$/.test(src)) {
|
|
77
|
+
src += '.json';
|
|
78
|
+
}
|
|
79
|
+
if (!src.startsWith('file:/') && src[0] !== '/') {
|
|
80
|
+
// seen as res
|
|
81
|
+
this.nativeViewProtected.compatibleAnimation = CompatibleAnimation.alloc().initWithNameSubdirectoryBundle(src.replace('.json', '').replace('.lottie', ''), null, NSBundle.mainBundle);
|
|
82
|
+
} else {
|
|
83
|
+
this.nativeViewProtected.compatibleAnimation = CompatibleAnimation.alloc().initWithFilepath(src);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (this.keyPathColors) {
|
|
87
|
+
this[keyPathColorsProperty.setNative](this.keyPathColors);
|
|
88
|
+
}
|
|
89
|
+
if (this._imageSourceAffectsLayout) {
|
|
90
|
+
this.requestLayout();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.autoPlay && !this.isAnimating()) {
|
|
94
|
+
this.playAnimation();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (src && !/^https?:/i.test(src)) {
|
|
98
|
+
// CompatibleAnimation decodes synchronously when assigned (the
|
|
99
|
+
// bundled lottie-ios fork overrides the main-thread logger
|
|
100
|
+
// assert so .lottie decodes inline too). Notify deferred,
|
|
101
|
+
// matching the Android event shape and letting listeners
|
|
102
|
+
// attached around the src assignment still fire. Success is
|
|
103
|
+
// animationDuration >= 0 — `compatibleAnimation`'s didSet
|
|
104
|
+
// writes `animationView.animation` without touching the view's
|
|
105
|
+
// own `animation` property, so `view.animation` stays nil and
|
|
106
|
+
// can't be used as the signal. `animationDuration` is -1 only
|
|
107
|
+
// when nothing decoded.
|
|
108
|
+
const view = this.nativeViewProtected;
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
if (view && view.compatibleAnimation && view.animationDuration >= 0) {
|
|
111
|
+
this.notify({ eventName: LottieViewBase.compositionLoadedEvent, composition: view.compatibleAnimation });
|
|
112
|
+
} else {
|
|
113
|
+
this.notify({ eventName: LottieViewBase.loadFailedEvent, error: src });
|
|
114
|
+
}
|
|
115
|
+
}, 0);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(error);
|
|
119
|
+
this.notify({ eventName: LottieViewBase.loadFailedEvent, error });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
[loopProperty.setNative](loop: boolean) {
|
|
124
|
+
this.nativeViewProtected.loopAnimationCount = loop ? -1 : 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[autoPlayProperty.setNative](autoPlay: boolean) {
|
|
128
|
+
if (autoPlay) {
|
|
129
|
+
if (!this.isAnimating()) {
|
|
130
|
+
this.playAnimation();
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
if (this.isAnimating()) {
|
|
134
|
+
this.cancelAnimation();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public setColor(value: Color, keyPath: string[]): void {
|
|
140
|
+
if (this.nativeView && value && keyPath && keyPath.length) {
|
|
141
|
+
if (keyPath[keyPath.length - 1].toLowerCase() !== 'color') {
|
|
142
|
+
keyPath = [...keyPath];
|
|
143
|
+
keyPath.push('Color'); // ios expects the property as the last item in the keyPath
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const color = value instanceof Color ? value : new Color(value);
|
|
147
|
+
this.nativeViewProtected.setColorValueForKeypath(color.ios, CompatibleAnimationKeypath.alloc().initWithKeypath(keyPath.join('.')));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public setOpacity(value: number, keyPath: string[]): void {
|
|
152
|
+
if (this.nativeView && value != null && keyPath && keyPath.length) {
|
|
153
|
+
keyPath = [...keyPath];
|
|
154
|
+
if (keyPath[keyPath.length - 1].toLowerCase() !== 'opacity') {
|
|
155
|
+
keyPath.push('Opacity'); // ios expects the property as the last item in the keyPath
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Opacity float values are on a 0–100 scale in lottie-ios
|
|
159
|
+
// (After Effects percent), matching the Android setter's input
|
|
160
|
+
// contract of a 0–1 fraction.
|
|
161
|
+
this.nativeViewProtected.setFloatValueForKeypath(clamp(value) * 100, CompatibleAnimationKeypath.alloc().initWithKeypath(keyPath.join('.')));
|
|
162
|
+
}
|
|
163
|
+
// TODO: not working
|
|
164
|
+
// if (this.nativeView && value && keyPath && keyPath.length) {
|
|
165
|
+
// if (keyPath[keyPath.length - 1].toLowerCase() !== 'opacity') {
|
|
166
|
+
// keyPath.push('Opacity'); // ios expects the property as the last item in the keyPath
|
|
167
|
+
// }
|
|
168
|
+
// value = clamp(value);
|
|
169
|
+
// this.nativeViewProtected.getValue(
|
|
170
|
+
// LOTNumberValueCallback.withFloatValue(value),
|
|
171
|
+
// AnimationKeypath.keypathWithString(keyPath.join('.'))
|
|
172
|
+
// );
|
|
173
|
+
// }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public playAnimation(): void {
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (this.nativeViewProtected) {
|
|
179
|
+
if (this.completionBlock) {
|
|
180
|
+
this.nativeViewProtected.playWithCompletion((animationFinished: boolean) => {
|
|
181
|
+
if (this.completionBlock) {
|
|
182
|
+
this.completionBlock(animationFinished);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} else {
|
|
186
|
+
this.nativeViewProtected.play();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}, 0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public playAnimationFromProgressToProgress(startProgress: number, endProgress: number): void {
|
|
193
|
+
if (this.nativeViewProtected) {
|
|
194
|
+
startProgress = startProgress ? clamp(startProgress) : 0;
|
|
195
|
+
endProgress = endProgress ? clamp(endProgress) : 1;
|
|
196
|
+
if (this.completionBlock) {
|
|
197
|
+
this.nativeViewProtected.playFromProgressToProgressCompletion(startProgress, endProgress, this.completionBlock);
|
|
198
|
+
} else {
|
|
199
|
+
this.nativeViewProtected.playFromProgressToProgressCompletion(startProgress, endProgress, null);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public cancelAnimation(): void {
|
|
205
|
+
if (this.nativeViewProtected) {
|
|
206
|
+
this.nativeViewProtected.pause();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public pauseAnimation(): void {
|
|
211
|
+
if (this.nativeViewProtected) {
|
|
212
|
+
this.nativeViewProtected.pause();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public resumeAnimation(): void {
|
|
217
|
+
if (this.nativeViewProtected) {
|
|
218
|
+
// play() with no fromProgress resumes from currentProgress
|
|
219
|
+
this.nativeViewProtected.play();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public isAnimating(): boolean {
|
|
224
|
+
return this.nativeView ? this.nativeViewProtected.isAnimationPlaying : false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[progressProperty.setNative](value: number) {
|
|
228
|
+
this.nativeViewProtected.currentProgress = value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public set speed(value: number) {
|
|
232
|
+
if (this.nativeView && value) {
|
|
233
|
+
this.nativeViewProtected.animationSpeed = value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public get speed(): number | undefined {
|
|
238
|
+
return this.nativeView ? this.nativeViewProtected.animationSpeed : undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public get duration(): number | undefined {
|
|
242
|
+
return this.nativeView && this.nativeViewProtected.animationDuration;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public set contentMode(mode: any) {
|
|
246
|
+
// this._contentMode = mode;
|
|
247
|
+
if (this.nativeViewProtected) {
|
|
248
|
+
this.nativeViewProtected.contentMode = mode;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
253
|
+
const intrinsicContentSize = this.nativeViewProtected.intrinsicContentSize;
|
|
254
|
+
// We don't call super because we measure native view with specific size.
|
|
255
|
+
const width = Utils.layout.getMeasureSpecSize(widthMeasureSpec);
|
|
256
|
+
const widthMode = Utils.layout.getMeasureSpecMode(widthMeasureSpec);
|
|
257
|
+
|
|
258
|
+
const height = Utils.layout.getMeasureSpecSize(heightMeasureSpec);
|
|
259
|
+
const heightMode = Utils.layout.getMeasureSpecMode(heightMeasureSpec);
|
|
260
|
+
|
|
261
|
+
const nativeWidth = intrinsicContentSize ? Utils.layout.toDevicePixels(intrinsicContentSize.width) : 0;
|
|
262
|
+
const nativeHeight = intrinsicContentSize ? Utils.layout.toDevicePixels(intrinsicContentSize.height) : 0;
|
|
263
|
+
|
|
264
|
+
let measureWidth = Math.max(nativeWidth, this.effectiveMinWidth);
|
|
265
|
+
let measureHeight = Math.max(nativeHeight, this.effectiveMinHeight);
|
|
266
|
+
|
|
267
|
+
const finiteWidth: boolean = widthMode !== Utils.layout.UNSPECIFIED;
|
|
268
|
+
const finiteHeight: boolean = heightMode !== Utils.layout.UNSPECIFIED;
|
|
269
|
+
|
|
270
|
+
this._imageSourceAffectsLayout = widthMode !== Utils.layout.EXACTLY || heightMode !== Utils.layout.EXACTLY;
|
|
271
|
+
|
|
272
|
+
if (nativeWidth !== 0 && nativeHeight !== 0 && (finiteWidth || finiteHeight)) {
|
|
273
|
+
const scale = LottieView.computeScaleFactor(width, height, finiteWidth, finiteHeight, nativeWidth, nativeHeight, this.stretch);
|
|
274
|
+
const resultW = Math.round(nativeWidth * scale.width);
|
|
275
|
+
const resultH = Math.round(nativeHeight * scale.height);
|
|
276
|
+
|
|
277
|
+
measureWidth = finiteWidth ? Math.min(resultW, width) : resultW;
|
|
278
|
+
measureHeight = finiteHeight ? Math.min(resultH, height) : resultH;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const widthAndState = View.resolveSizeAndState(measureWidth, width, widthMode, 0);
|
|
282
|
+
const heightAndState = View.resolveSizeAndState(measureHeight, height, heightMode, 0);
|
|
283
|
+
|
|
284
|
+
this.setMeasuredDimension(widthAndState, heightAndState);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private static computeScaleFactor(
|
|
288
|
+
measureWidth: number,
|
|
289
|
+
measureHeight: number,
|
|
290
|
+
widthIsFinite: boolean,
|
|
291
|
+
heightIsFinite: boolean,
|
|
292
|
+
nativeWidth: number,
|
|
293
|
+
nativeHeight: number,
|
|
294
|
+
imageStretch: string
|
|
295
|
+
): { width: number; height: number } {
|
|
296
|
+
let scaleW = 1;
|
|
297
|
+
let scaleH = 1;
|
|
298
|
+
|
|
299
|
+
if ((imageStretch === 'aspectFill' || imageStretch === 'aspectFit' || imageStretch === 'fill') && (widthIsFinite || heightIsFinite)) {
|
|
300
|
+
scaleW = nativeWidth > 0 ? measureWidth / nativeWidth : 0;
|
|
301
|
+
scaleH = nativeHeight > 0 ? measureHeight / nativeHeight : 0;
|
|
302
|
+
|
|
303
|
+
if (!widthIsFinite) {
|
|
304
|
+
scaleW = scaleH;
|
|
305
|
+
} else if (!heightIsFinite) {
|
|
306
|
+
scaleH = scaleW;
|
|
307
|
+
} else {
|
|
308
|
+
// No infinite dimensions.
|
|
309
|
+
switch (imageStretch) {
|
|
310
|
+
case 'aspectFit':
|
|
311
|
+
scaleH = scaleW < scaleH ? scaleW : scaleH;
|
|
312
|
+
scaleW = scaleH;
|
|
313
|
+
break;
|
|
314
|
+
case 'aspectFill':
|
|
315
|
+
scaleH = scaleW > scaleH ? scaleW : scaleH;
|
|
316
|
+
scaleW = scaleH;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { width: scaleW, height: scaleH };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
[stretchProperty.setNative](value: 'none' | 'aspectFill' | 'aspectFit' | 'fill') {
|
|
326
|
+
switch (value) {
|
|
327
|
+
case 'aspectFit':
|
|
328
|
+
this.nativeViewProtected.contentMode = UIViewContentMode.ScaleAspectFit;
|
|
329
|
+
break;
|
|
330
|
+
|
|
331
|
+
case 'aspectFill':
|
|
332
|
+
this.nativeViewProtected.contentMode = UIViewContentMode.ScaleAspectFill;
|
|
333
|
+
break;
|
|
334
|
+
|
|
335
|
+
case 'fill':
|
|
336
|
+
this.nativeViewProtected.contentMode = UIViewContentMode.ScaleToFill;
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'none':
|
|
340
|
+
default:
|
|
341
|
+
this.nativeViewProtected.contentMode = UIViewContentMode.TopLeft;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|