@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.
Files changed (58) hide show
  1. package/README.md +36 -2
  2. package/dist/native/cx.mobile.js +11 -0
  3. package/dist/native/escape-props.js +39 -0
  4. package/dist/native/index.js +182 -0
  5. package/dist/native/layout-child.js +38 -0
  6. package/dist/native/octane-client-build.json +8 -0
  7. package/dist/native/vendor/ui-lottie/index.android.js +286 -0
  8. package/dist/native/vendor/ui-lottie/index.ios.js +214 -0
  9. package/dist/native/vendor/ui-lottie/src/lottie/index.common.js +50 -0
  10. package/dist/native/vendor/ui-lottie/src/lottie/utils.js +6 -0
  11. package/dist/web/escape-props.web.js +17 -0
  12. package/dist/web/index.js +332 -0
  13. package/dist/web/layout-child.web.js +47 -0
  14. package/dist/web/octane-client-build.json +8 -0
  15. package/package.json +81 -10
  16. package/platforms/android/include.gradle +7 -0
  17. package/platforms/android/java/com/nativescript/lottie/LottieAnimationView.java +92 -0
  18. package/platforms/android/lottie.aar +0 -0
  19. package/platforms/android/native-api-usage.json +16 -0
  20. package/platforms/ios/Podfile +5 -0
  21. package/src/Lottie.tsrx +161 -0
  22. package/src/Lottie.web.tsrx +135 -0
  23. package/src/cx.mobile.ts +8 -0
  24. package/src/escape-props.ts +65 -0
  25. package/src/escape-props.web.ts +25 -0
  26. package/src/index.ts +2 -0
  27. package/src/index.web.ts +2 -0
  28. package/src/layout-child.ts +57 -0
  29. package/src/layout-child.web.ts +108 -0
  30. package/src/native-elements.d.ts +8 -0
  31. package/src/props.ts +128 -0
  32. package/src/references.d.ts +3 -0
  33. package/src/vendor/ui-lottie/LICENSE.md +21 -0
  34. package/src/vendor/ui-lottie/src/lottie/index.android.ts +447 -0
  35. package/src/vendor/ui-lottie/src/lottie/index.common.ts +84 -0
  36. package/src/vendor/ui-lottie/src/lottie/index.d.ts +133 -0
  37. package/src/vendor/ui-lottie/src/lottie/index.ios.ts +345 -0
  38. package/src/vendor/ui-lottie/src/lottie/typings/android.d.ts +4149 -0
  39. package/src/vendor/ui-lottie/src/lottie/typings/android.nativescript.d.ts +12 -0
  40. package/src/vendor/ui-lottie/src/lottie/typings/dotlottie.ios.d.ts +14 -0
  41. package/src/vendor/ui-lottie/src/lottie/typings/ios.d.ts +384 -0
  42. package/src/vendor/ui-lottie/src/lottie/utils.ts +9 -0
  43. package/types/generated/.tsrx-typegen-manifest.json +18 -0
  44. package/types/generated/Lottie.d.ts +7 -0
  45. package/types/generated/Lottie.web.d.ts +7 -0
  46. package/types/generated/cx.mobile.d.ts +6 -0
  47. package/types/generated/escape-props.d.ts +15 -0
  48. package/types/generated/escape-props.web.d.ts +5 -0
  49. package/types/generated/index.d.ts +2 -0
  50. package/types/generated/index.web.d.ts +2 -0
  51. package/types/generated/layout-child.d.ts +6 -0
  52. package/types/generated/layout-child.web.d.ts +15 -0
  53. package/types/generated/props.d.ts +106 -0
  54. package/types/generated/vendor/ui-lottie/src/lottie/index.common.d.ts +30 -0
  55. package/types/generated/vendor/ui-lottie/src/lottie/index.ios.d.ts +35 -0
  56. package/types/generated/vendor/ui-lottie/src/lottie/utils.d.ts +1 -0
  57. package/index.d.ts +0 -1
  58. 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
+ }