@nativescript-community/ui-image 4.6.6 → 5.0.1

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 (55) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -20
  3. package/index-common.d.ts +47 -18
  4. package/index-common.js +5 -69
  5. package/index-common.js.map +1 -1
  6. package/index.android.d.ts +35 -60
  7. package/index.android.js +642 -703
  8. package/index.android.js.map +1 -1
  9. package/index.d.ts +17 -92
  10. package/index.ios.d.ts +5 -10
  11. package/index.ios.js +72 -54
  12. package/index.ios.js.map +1 -1
  13. package/package.json +4 -4
  14. package/platforms/android/include.gradle +21 -16
  15. package/platforms/android/java/com/nativescript/image/CacheKeyStore.java +65 -0
  16. package/platforms/android/java/com/nativescript/image/CapturingEngineKeyFactory.java +43 -0
  17. package/platforms/android/java/com/nativescript/image/CompositeRequestListener.java +58 -0
  18. package/platforms/android/java/com/nativescript/image/ConditionalCrossFadeFactory.java +33 -0
  19. package/platforms/android/java/com/nativescript/image/CustomDataFetcher.java +203 -0
  20. package/platforms/android/java/com/nativescript/image/CustomGlideModule.java +220 -0
  21. package/platforms/android/java/com/nativescript/image/CustomGlideUrl.java +52 -0
  22. package/platforms/android/java/com/nativescript/image/CustomUrlLoader.java +74 -0
  23. package/platforms/android/java/com/nativescript/image/EvictionManager.java +735 -0
  24. package/platforms/android/java/com/nativescript/image/ExtractRequestOptions.java +109 -0
  25. package/platforms/android/java/com/nativescript/image/ImageLoadSourceCallback.java +5 -0
  26. package/platforms/android/java/com/nativescript/image/ImageProgressCallback.java +5 -0
  27. package/platforms/android/java/com/nativescript/image/LoadSourceInterceptor.java +28 -0
  28. package/platforms/android/java/com/nativescript/image/MatrixDrawable.java +200 -0
  29. package/platforms/android/java/com/nativescript/image/MatrixDrawableImageViewTarget.java +172 -0
  30. package/platforms/android/java/com/nativescript/image/MatrixImageView.java +696 -0
  31. package/platforms/android/java/com/nativescript/image/ProgressInterceptor.java +25 -0
  32. package/platforms/android/java/com/nativescript/image/ProgressResponseBody.java +70 -0
  33. package/platforms/android/java/com/nativescript/image/RecordingDigest.java +48 -0
  34. package/platforms/android/java/com/nativescript/image/RecreatedResourceKey.java +95 -0
  35. package/platforms/android/java/com/nativescript/image/SaveKeysRequestListener.java +145 -0
  36. package/platforms/android/java/com/nativescript/image/ScaleUtils.java +129 -0
  37. package/platforms/android/java/com/nativescript/image/SharedPrefCacheKeyStore.java +92 -0
  38. package/platforms/android/native-api-usage.json +39 -37
  39. package/platforms/ios/Podfile +1 -1
  40. package/references.d.ts +0 -1
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/typings/android.d.ts +4 -27
  43. package/typings/glide.android.d.ts +9395 -0
  44. package/typings/glide.okhttp.android.d.ts +104 -0
  45. package/typings/glide.transform.android.d.ts +540 -0
  46. package/typings/ui_image.android.d.ts +520 -0
  47. package/platforms/android/java/com/nativescript/image/BaseDataSubscriber.java +0 -22
  48. package/platforms/android/java/com/nativescript/image/BaseDataSubscriberListener.java +0 -9
  49. package/platforms/android/java/com/nativescript/image/DraweeView.java +0 -371
  50. package/platforms/android/java/com/nativescript/image/NetworkImageRequest.java +0 -55
  51. package/platforms/android/java/com/nativescript/image/OkHttpNetworkFetcher.java +0 -56
  52. package/platforms/android/java/com/nativescript/image/ScalingBlurPostprocessor.java +0 -64
  53. package/platforms/android/java/com/nativescript/image/ScalingUtils.java +0 -519
  54. package/typings/fresco-processors.d.ts +0 -53
  55. package/typings/fresco.d.ts +0 -12070
package/index.android.js CHANGED
@@ -1,179 +1,198 @@
1
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
1
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
2
2
  export * from './index-common';
3
- import { Application, ImageAsset, ImageSource, Trace, Utils, backgroundInternalProperty, knownFolders, path } from '@nativescript/core';
3
+ import { ImageAsset, ImageSource, Trace, Utils, backgroundInternalProperty, knownFolders, path } from '@nativescript/core';
4
4
  import { isString } from '@nativescript/core/utils/types';
5
5
  import { layout } from '@nativescript/core/utils/layout-helper';
6
- import { CLog, CLogTypes, EventData, ImageBase, ScaleType, aspectRatioProperty, backgroundUriProperty, blurDownSamplingProperty, blurRadiusProperty, fadeDurationProperty, failureImageUriProperty, headersProperty, imageRotationProperty, lowerResSrcProperty, needRequestImage, noRatioEnforceProperty, placeholderImageUriProperty, progressBarColorProperty, roundAsCircleProperty, roundBottomLeftRadiusProperty, roundBottomRightRadiusProperty, roundTopLeftRadiusProperty, roundTopRightRadiusProperty, showProgressBarProperty, srcProperty, stretchProperty, tintColorProperty, wrapNativeException } from './index-common';
6
+ import { CLog, CLogTypes, ImageBase, ScaleType, aspectRatioProperty, backgroundUriProperty, blurDownSamplingProperty, blurRadiusProperty, decodeHeightProperty, decodeWidthProperty, fadeDurationProperty, failureImageUriProperty, headersProperty, imageRotationProperty, lowerResSrcProperty, needRequestImage, noRatioEnforceProperty, placeholderImageUriProperty, roundAsCircleProperty, roundBottomLeftRadiusProperty, roundBottomRightRadiusProperty, roundTopLeftRadiusProperty, roundTopRightRadiusProperty, srcProperty, stretchProperty, tintColorProperty, wrapNativeException } from './index-common';
7
7
  let initialized = false;
8
- let initializeConfig;
8
+ let glideInstance;
9
+ // global signature to invalidate all cache if needed by plugin
10
+ let signature;
11
+ const globalSignatureKey = 'v1';
9
12
  export function initialize(config) {
10
13
  if (!initialized) {
11
14
  const context = Utils.android.getApplicationContext();
12
15
  if (!context) {
13
- initializeConfig = config;
14
16
  return;
15
17
  }
16
- let builder;
17
- const useOkhttp = config?.useOkhttp !== false;
18
- if (useOkhttp) {
19
- //@ts-ignore
20
- let client;
21
- //@ts-ignore
22
- if (useOkhttp instanceof okhttp3.OkHttpClient) {
23
- client = useOkhttp;
24
- }
25
- else {
26
- //@ts-ignore
27
- client = new okhttp3.OkHttpClient();
28
- }
29
- builder = com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory.newBuilder(context, client);
30
- builder.setNetworkFetcher(new com.nativescript.image.OkHttpNetworkFetcher(client));
31
- }
32
- else {
33
- builder = com.facebook.imagepipeline.core.ImagePipelineConfig.newBuilder(context);
18
+ if (config?.usePersistentCacheKeyStore) {
19
+ const sharedStore = new com.nativescript.image.SharedPrefCacheKeyStore(context.getApplicationContext());
20
+ com.nativescript.image.EvictionManager.get().setPersistentStore(sharedStore);
34
21
  }
35
- builder.setDownsampleEnabled(config?.isDownsampleEnabled === true);
36
- if (config?.leakTracker) {
37
- builder.setCloseableReferenceLeakTracker(config.leakTracker);
38
- }
39
- // builder.experiment().setNativeCodeDisabled(true);
40
- const imagePipelineConfig = builder.build();
41
- com.facebook.drawee.backends.pipeline.Fresco.initialize(context, imagePipelineConfig);
22
+ // bumping `v1` will invalidate all cache
23
+ signature = new com.bumptech.glide.signature.ObjectKey(config?.globalSignatureKey ?? globalSignatureKey);
42
24
  initialized = true;
43
- initializeConfig = null;
44
- }
45
- }
46
- let imagePineLine;
47
- export function getImagePipeline() {
48
- if (!imagePineLine) {
49
- if (Application.android.nativeApp) {
50
- const nativePipe = com.facebook.drawee.backends.pipeline.Fresco.getImagePipeline();
51
- imagePineLine = new ImagePipeline();
52
- imagePineLine.android = nativePipe;
53
- }
25
+ glideInstance = com.bumptech.glide.Glide.get(context);
26
+ com.nativescript.image.EvictionManager.get().clearAll();
27
+ // this is needed for further buildKey to trigger ...
28
+ com.bumptech.glide.Glide.with(context)
29
+ .load('toto?ts=' + Date().valueOf())
30
+ .apply(new com.bumptech.glide.request.RequestOptions().signature(new com.bumptech.glide.signature.ObjectKey(Date().valueOf())))
31
+ .preload();
32
+ // com.nativescript.image.EngineKeyFactoryMethodDumper.dumpKeyFactoryMethods(glideInstance);
33
+ // com.nativescript.image.ForcePreloadTest.forcePreloadAfterInjection(context, 'https://example.com/test-image.png');
54
34
  }
55
- return imagePineLine;
56
- }
57
- export function shutDown() {
58
- if (!initialized) {
59
- return;
60
- }
61
- initialized = false;
62
- com.facebook.drawee.view.SimpleDraweeView.shutDown();
63
- com.facebook.drawee.backends.pipeline.Fresco.shutDown();
64
- }
65
- function getUri(src, asNative = true) {
66
- let uri;
67
- let imagePath;
68
- if (src instanceof ImageAsset) {
69
- imagePath = src.android;
70
- }
71
- else {
72
- imagePath = src;
73
- }
74
- if (Utils.isFileOrResourcePath(imagePath)) {
75
- if (imagePath.indexOf(Utils.RESOURCE_PREFIX) === 0) {
76
- const resName = imagePath.substring(Utils.RESOURCE_PREFIX.length);
77
- const identifier = Utils.android.resources.getDrawableId(resName);
78
- if (0 < identifier) {
79
- const netUri = new android.net.Uri.Builder().scheme(com.facebook.common.util.UriUtil.LOCAL_RESOURCE_SCHEME).path(java.lang.String.valueOf(identifier)).build();
80
- if (asNative) {
81
- return netUri;
82
- }
83
- uri = netUri.toString();
84
- }
85
- }
86
- else if (imagePath.indexOf('~/') === 0) {
87
- uri = `file:${path.join(knownFolders.currentApp().path, imagePath.replace('~/', ''))}`;
88
- }
89
- else if (imagePath.indexOf('/') === 0) {
90
- uri = `file:${imagePath}`;
91
- }
92
- }
93
- else {
94
- uri = imagePath;
95
- }
96
- return asNative ? android.net.Uri.parse(uri) : uri;
97
- }
98
- function isVectorDrawable(context, resId) {
99
- const resources = context.getResources();
100
- // VectorDrawable resources are usually stored as "drawable" in XML format
101
- const value = new android.util.TypedValue();
102
- resources.getValue(resId, value, true);
103
- if (value.string.toString().endsWith('.xml')) {
104
- // It's most likely a VectorDrawable
105
- return true;
106
- }
107
- // If it's not a vector, it's probably a BitmapDrawable or another type
108
- return false;
109
- }
110
- function getBitmapFromVectorDrawable(context, drawableId) {
111
- const drawable = Utils.android.getApplicationContext().getDrawable(drawableId);
112
- const bitmap = android.graphics.Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), android.graphics.Bitmap.Config.ARGB_8888);
113
- const canvas = new android.graphics.Canvas(bitmap);
114
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
115
- drawable.draw(canvas);
116
- console.log('getBitmapFromVectorDrawable', bitmap, bitmap.getWidth(), bitmap.getHeight);
117
- return new android.graphics.drawable.BitmapDrawable(context.getResources(), bitmap);
118
35
  }
119
36
  export class ImagePipeline {
120
37
  toUri(value) {
121
38
  if (value instanceof android.net.Uri) {
122
- return value;
39
+ return value.toString();
123
40
  }
124
- return android.net.Uri.parse(value);
41
+ return value;
125
42
  }
126
43
  getCacheKey(uri, context) {
127
- // iOS only
128
44
  return uri;
129
45
  }
130
46
  isInDiskCache(uri) {
131
- return this._android.isInDiskCacheSync(this.toUri(uri));
47
+ const url = this.toUri(uri);
48
+ return new Promise((resolve, reject) => {
49
+ com.nativescript.image.EvictionManager.get().isInDiskCacheAsync(url, new com.nativescript.image.EvictionManager.DiskPresenceCallback({
50
+ onResult(source, transform) {
51
+ resolve(source || transform);
52
+ }
53
+ }));
54
+ });
132
55
  }
133
56
  isInBitmapMemoryCache(uri) {
134
- return this._android.isInBitmapMemoryCache(this.toUri(uri));
57
+ // Still not directly accessible, but we can check if it's registered
58
+ const url = this.toUri(uri);
59
+ return com.nativescript.image.EvictionManager.get().isInMemoryCache(url);
135
60
  }
136
61
  evictFromMemoryCache(uri) {
137
- this._android.evictFromMemoryCache(this.toUri(uri));
62
+ const url = this.toUri(uri);
63
+ return new Promise((resolve, reject) => {
64
+ com.nativescript.image.EvictionManager.get().evictMemoryForId(url, new com.nativescript.image.EvictionManager.EvictionCallback({
65
+ onComplete(success, error) {
66
+ if (success) {
67
+ resolve();
68
+ }
69
+ else {
70
+ if (Trace.isEnabled()) {
71
+ CLog(CLogTypes.error, error);
72
+ }
73
+ reject(error);
74
+ }
75
+ }
76
+ }));
77
+ });
138
78
  }
139
- async evictFromDiskCache(uri) {
140
- this._android.evictFromDiskCache(this.toUri(uri));
79
+ evictFromDiskCache(uri) {
80
+ const url = this.toUri(uri);
81
+ return new Promise((resolve, reject) => {
82
+ com.nativescript.image.EvictionManager.get().evictDiskForId(url, new com.nativescript.image.EvictionManager.EvictionCallback({
83
+ onComplete(success, error) {
84
+ if (success) {
85
+ resolve();
86
+ }
87
+ else {
88
+ if (Trace.isEnabled()) {
89
+ CLog(CLogTypes.error, error);
90
+ }
91
+ reject(error);
92
+ }
93
+ }
94
+ }));
95
+ });
141
96
  }
142
- async evictFromCache(uri) {
143
- this._android.evictFromCache(this.toUri(uri));
97
+ evictFromCache(uri) {
98
+ const url = this.toUri(uri);
99
+ return new Promise((resolve, reject) => {
100
+ com.nativescript.image.EvictionManager.get().evictAllForId(url, new com.nativescript.image.EvictionManager.EvictionCallback({
101
+ onComplete(success, error) {
102
+ if (success) {
103
+ resolve();
104
+ }
105
+ else {
106
+ if (Trace.isEnabled()) {
107
+ CLog(CLogTypes.error, error);
108
+ }
109
+ reject(error);
110
+ }
111
+ }
112
+ }));
113
+ });
144
114
  }
145
115
  clearCaches() {
146
- this._android.clearCaches();
116
+ return new Promise((resolve, reject) => {
117
+ com.nativescript.image.EvictionManager.get().clearAll(new com.nativescript.image.EvictionManager.EvictionCallback({
118
+ onComplete(success, error) {
119
+ if (success) {
120
+ resolve();
121
+ }
122
+ else {
123
+ if (Trace.isEnabled()) {
124
+ CLog(CLogTypes.error, error);
125
+ }
126
+ reject(error);
127
+ }
128
+ }
129
+ }));
130
+ });
147
131
  }
148
132
  clearMemoryCaches() {
149
- this._android.clearMemoryCaches();
133
+ return new Promise((resolve, reject) => {
134
+ com.nativescript.image.EvictionManager.get().clearMemory(new com.nativescript.image.EvictionManager.EvictionCallback({
135
+ onComplete(success, error) {
136
+ if (success) {
137
+ resolve();
138
+ }
139
+ else {
140
+ if (Trace.isEnabled()) {
141
+ CLog(CLogTypes.error, error);
142
+ }
143
+ reject(error);
144
+ }
145
+ }
146
+ }));
147
+ });
150
148
  }
151
149
  clearDiskCaches() {
152
- this._android.clearDiskCaches();
150
+ return new Promise((resolve, reject) => {
151
+ com.nativescript.image.EvictionManager.get().clearDiskCache(new com.nativescript.image.EvictionManager.EvictionCallback({
152
+ onComplete(success, error) {
153
+ if (success) {
154
+ resolve();
155
+ }
156
+ else {
157
+ if (Trace.isEnabled()) {
158
+ CLog(CLogTypes.error, error);
159
+ }
160
+ reject(error);
161
+ }
162
+ }
163
+ }));
164
+ });
153
165
  }
154
166
  prefetchToDiskCache(uri) {
155
167
  return this.prefetchToCache(uri, true);
156
168
  }
157
169
  prefetchToMemoryCache(uri) {
158
- return this.prefetchToCache(uri, false);
170
+ return new Promise((resolve, reject) => {
171
+ try {
172
+ const context = Utils.android.getApplicationContext();
173
+ const requestManager = com.bumptech.glide.Glide.with(context);
174
+ // Preload into memory cache
175
+ requestManager.asBitmap().load(uri).preload();
176
+ // Give Glide time to load into memory
177
+ setTimeout(() => resolve(), 100);
178
+ }
179
+ catch (error) {
180
+ reject(error);
181
+ }
182
+ });
159
183
  }
160
184
  prefetchToCache(uri, toDiskCache) {
161
185
  return new Promise((resolve, reject) => {
162
186
  try {
163
- const nativeUri = android.net.Uri.parse(uri);
164
- const request = com.facebook.imagepipeline.request.ImageRequestBuilder.newBuilderWithSource(nativeUri).build();
165
- let datasource;
187
+ const context = Utils.android.getApplicationContext();
188
+ const requestManager = com.bumptech.glide.Glide.with(context);
166
189
  if (toDiskCache) {
167
- datasource = this._android.prefetchToDiskCache(request, uri);
190
+ requestManager.downloadOnly().load(uri).submit();
168
191
  }
169
192
  else {
170
- datasource = this._android.prefetchToBitmapCache(request, uri);
193
+ requestManager.asBitmap().load(uri).submit();
171
194
  }
172
- // initializeBaseDataSubscriber();
173
- datasource.subscribe(new com.nativescript.image.BaseDataSubscriber(new com.nativescript.image.BaseDataSubscriberListener({
174
- onFailure: reject,
175
- onNewResult: resolve
176
- })), com.facebook.common.executors.CallerThreadExecutor.getInstance());
195
+ resolve();
177
196
  }
178
197
  catch (error) {
179
198
  reject(error);
@@ -181,49 +200,55 @@ export class ImagePipeline {
181
200
  });
182
201
  }
183
202
  get android() {
184
- return this._android;
185
- }
186
- set android(value) {
187
- this._android = value;
188
- }
189
- fetchImage() {
190
- // ImagePipeline imagePipeline = Fresco.getImagePipeline();
191
- // ImageRequest imageRequest = ImageRequestBuilder
192
- // .newBuilderWithSource(imageUri)
193
- // .setRequestPriority(Priority.HIGH)
194
- // .setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
195
- // .build();
196
- // DataSource<CloseableReference<CloseableImage>> dataSource =
197
- // imagePipeline.fetchDecodedImage(imageRequest, mContext);
198
- // try {
199
- // dataSource.subscribe(new BaseBitmapDataSubscriber() {
200
- // @Override
201
- // public void onNewResultImpl(Bitmap bitmap) {
202
- // if (bitmap == null) {
203
- // Log.d(TAG, "Bitmap data source returned success, but bitmap null.");
204
- // return;
205
- // }
206
- // // The bitmap provided to this method is only guaranteed to be around
207
- // // for the lifespan of this method. The image pipeline frees the
208
- // // bitmap's memory after this method has completed.
209
- // //
210
- // // This is fine when passing the bitmap to a system process as
211
- // // Android automatically creates a copy.
212
- // //
213
- // // If you need to keep the bitmap around, look into using a
214
- // // BaseDataSubscriber instead of a BaseBitmapDataSubscriber.
215
- // }
216
- // @Override
217
- // public void onFailureImpl(DataSource dataSource) {
218
- // // No cleanup required here
219
- // }
220
- // }, CallerThreadExecutor.getInstance());
221
- // } finally {
222
- // if (dataSource != null) {
223
- // dataSource.close();
224
- // }
225
- // }
203
+ return glideInstance;
204
+ }
205
+ }
206
+ let imagePipeLine;
207
+ export function getImagePipeline() {
208
+ if (!imagePipeLine) {
209
+ imagePipeLine = new ImagePipeline();
210
+ }
211
+ return imagePipeLine;
212
+ }
213
+ export function shutDown() {
214
+ if (!initialized) {
215
+ return;
216
+ }
217
+ initialized = false;
218
+ // Glide cleanup
219
+ const context = Utils.android.getApplicationContext();
220
+ if (context) {
221
+ com.bumptech.glide.Glide.get(context).clearMemory();
222
+ }
223
+ }
224
+ function getUri(src, asNative = true) {
225
+ let uri;
226
+ let imagePath;
227
+ if (src instanceof ImageAsset) {
228
+ imagePath = src.android;
229
+ }
230
+ else {
231
+ imagePath = src;
232
+ }
233
+ if (Utils.isFileOrResourcePath(imagePath)) {
234
+ if (imagePath.indexOf(Utils.RESOURCE_PREFIX) === 0) {
235
+ const resName = imagePath.substring(Utils.RESOURCE_PREFIX.length);
236
+ const identifier = Utils.android.resources.getDrawableId(resName);
237
+ if (0 < identifier) {
238
+ return `android.resource://${Utils.android.getApplicationContext().getPackageName()}/${identifier}`;
239
+ }
240
+ }
241
+ else if (imagePath.indexOf('~/') === 0) {
242
+ uri = `file://${path.join(knownFolders.currentApp().path, imagePath.replace('~/', ''))}`;
243
+ }
244
+ else if (imagePath.indexOf('/') === 0) {
245
+ uri = `file://${imagePath}`;
246
+ }
226
247
  }
248
+ else {
249
+ uri = imagePath;
250
+ }
251
+ return uri;
227
252
  }
228
253
  export class ImageError {
229
254
  constructor(throwable) {
@@ -242,42 +267,22 @@ export class ImageError {
242
267
  }
243
268
  }
244
269
  export class ImageInfo {
245
- constructor(imageInfo) {
246
- this._nativeImageInfo = imageInfo;
270
+ constructor(width, height) {
271
+ this._width = width;
272
+ this._height = height;
247
273
  }
248
274
  getHeight() {
249
- return this._nativeImageInfo.getHeight();
275
+ return this._height;
250
276
  }
251
277
  getWidth() {
252
- return this._nativeImageInfo.getWidth();
278
+ return this._width;
253
279
  }
254
280
  getQualityInfo() {
255
- return this._nativeImageInfo.getQualityInfo();
256
- }
257
- }
258
- export class FinalEventData extends EventData {
259
- get imageInfo() {
260
- return this._imageInfo;
261
- }
262
- set imageInfo(value) {
263
- this._imageInfo = value;
264
- }
265
- get animatable() {
266
- return this._animatable;
267
- }
268
- set animatable(value) {
269
- this._animatable = value;
270
- }
271
- get android() {
272
- return this._animatable;
273
- }
274
- }
275
- export class IntermediateEventData extends EventData {
276
- get imageInfo() {
277
- return this._imageInfo;
278
- }
279
- set imageInfo(value) {
280
- this._imageInfo = value;
281
+ return {
282
+ getQuality: () => 1,
283
+ isOfFullQuality: () => true,
284
+ isOfGoodEnoughQuality: () => true
285
+ };
281
286
  }
282
287
  }
283
288
  export const needUpdateHierarchy = function (targetOrNeedsLayout, propertyKey, descriptor) {
@@ -313,13 +318,16 @@ export const needUpdateHierarchy = function (targetOrNeedsLayout, propertyKey, d
313
318
  export class Img extends ImageBase {
314
319
  constructor() {
315
320
  super(...arguments);
316
- this.isLoading = false;
321
+ this.preventPreClearDrawable = false;
317
322
  this.mCanUpdateHierarchy = true;
318
323
  this.mNeedUpdateHierarchy = false;
319
324
  this.mNeedUpdateLayout = false;
325
+ this.currentTarget = null;
326
+ this.isNetworkRequest = false;
327
+ this.progressCallback = null;
328
+ this.loadSourceCallback = null;
320
329
  }
321
330
  onResumeNativeUpdates() {
322
- // {N} suspends properties update on `_suspendNativeUpdates`. So we only need to do this in onResumeNativeUpdates
323
331
  this.mCanUpdateHierarchy = false;
324
332
  super.onResumeNativeUpdates();
325
333
  this.mCanUpdateHierarchy = true;
@@ -334,40 +342,15 @@ export class Img extends ImageBase {
334
342
  }
335
343
  createNativeView() {
336
344
  if (!initialized) {
337
- initialize(initializeConfig);
345
+ initialize();
338
346
  }
339
- const view = new com.nativescript.image.DraweeView(this._context);
340
- // (view as any).setClipToBounds(false);
341
- return view;
347
+ return new com.nativescript.image.MatrixImageView(this._context);
342
348
  }
343
- updateViewSize(imageInfo) {
344
- const draweeView = this.nativeImageViewProtected;
345
- if (!draweeView) {
346
- return;
347
- }
348
- if (imageInfo != null) {
349
- draweeView.imageWidth = imageInfo.getWidth();
350
- draweeView.imageHeight = imageInfo.getHeight();
351
- }
352
- if (!this.aspectRatio && imageInfo != null) {
353
- const ratio = imageInfo.getWidth() / imageInfo.getHeight();
354
- draweeView.setAspectRatio(ratio);
355
- }
356
- else if (this.aspectRatio) {
357
- draweeView.setAspectRatio(this.aspectRatio);
358
- }
359
- else {
360
- draweeView.setAspectRatio(0);
361
- }
362
- }
363
- // public initNativeView(): void {
364
- // this.initDrawee();
365
- // this.updateHierarchy();
366
- // }
367
349
  disposeNativeView() {
368
- this.controllerListener = null;
369
- this.requestListener = null;
370
- // this.nativeImageViewProtected.setImageURI(null, null);
350
+ // Cancel any running Glide requests before dispose
351
+ this.cancelCurrentRequest();
352
+ this.progressCallback = null;
353
+ this.loadSourceCallback = null;
371
354
  }
372
355
  get cacheKey() {
373
356
  const src = this.src;
@@ -381,10 +364,7 @@ export class Img extends ImageBase {
381
364
  const imagePipeLine = getImagePipeline();
382
365
  const cacheKey = this.cacheKey;
383
366
  if (cacheKey) {
384
- // const isInCache = imagePipeLine.isInBitmapMemoryCache(uri);
385
- // // if (isInCache) {
386
367
  await imagePipeLine.evictFromCache(cacheKey);
387
- // }
388
368
  }
389
369
  this.handleImageSrc(null);
390
370
  this.initImage();
@@ -395,437 +375,509 @@ export class Img extends ImageBase {
395
375
  [_b = failureImageUriProperty.setNative]() {
396
376
  this.updateHierarchy();
397
377
  }
398
- [_c = stretchProperty.setNative]() {
399
- this.updateHierarchy();
378
+ [stretchProperty.setNative]() {
379
+ // Scale type
380
+ if (this.stretch) {
381
+ const scaleType = getScaleType(this.stretch);
382
+ if (scaleType) {
383
+ this.nativeViewProtected.setScaleType(scaleType);
384
+ }
385
+ }
400
386
  }
401
- [_d = fadeDurationProperty.setNative]() {
387
+ [_c = fadeDurationProperty.setNative]() {
402
388
  this.updateHierarchy();
403
389
  }
404
- [_e = backgroundUriProperty.setNative]() {
390
+ [_d = backgroundUriProperty.setNative]() {
405
391
  this.updateHierarchy();
406
392
  }
407
- [_f = showProgressBarProperty.setNative]() {
393
+ [_e = roundAsCircleProperty.setNative](value) {
408
394
  this.updateHierarchy();
409
395
  }
410
- [_g = progressBarColorProperty.setNative]() {
396
+ [_f = roundTopLeftRadiusProperty.setNative]() {
411
397
  this.updateHierarchy();
412
398
  }
413
- [_h = roundAsCircleProperty.setNative]() {
414
- this.updateHierarchy();
399
+ [imageRotationProperty.setNative](value) {
400
+ this.nativeImageViewProtected?.setImageRotation(value);
415
401
  }
416
- [_j = roundTopLeftRadiusProperty.setNative]() {
402
+ [_g = roundTopRightRadiusProperty.setNative]() {
417
403
  this.updateHierarchy();
418
404
  }
419
- [_k = imageRotationProperty.setNative](value) {
420
- const scaleType = this.nativeImageViewProtected.getHierarchy().getActualImageScaleType();
421
- scaleType['setImageRotation']?.(value);
422
- this.nativeImageViewProtected.invalidate();
423
- }
424
- [_l = roundTopRightRadiusProperty.setNative]() {
405
+ [_h = roundBottomLeftRadiusProperty.setNative]() {
425
406
  this.updateHierarchy();
426
407
  }
427
- [_m = roundBottomLeftRadiusProperty.setNative]() {
408
+ [_j = roundBottomRightRadiusProperty.setNative]() {
428
409
  this.updateHierarchy();
429
410
  }
430
- [_o = roundBottomRightRadiusProperty.setNative]() {
431
- this.updateHierarchy();
411
+ [tintColorProperty.setNative](value) {
412
+ if (this.nativeImageViewProtected) {
413
+ this.nativeImageViewProtected.setColorFilter(value?.android ?? -1, android.graphics.PorterDuff.Mode.MULTIPLY);
414
+ }
432
415
  }
433
- [_p = tintColorProperty.setNative](value) {
434
- this.updateHierarchy();
416
+ [_k = blurRadiusProperty.setNative](value) {
417
+ this.initImage();
435
418
  }
436
- [_q = blurRadiusProperty.setNative]() {
419
+ [_l = srcProperty.setNative]() {
437
420
  this.initImage();
438
421
  }
439
- [_r = srcProperty.setNative]() {
422
+ [_m = decodeWidthProperty.setNative]() {
440
423
  this.initImage();
441
424
  }
442
- [_s = lowerResSrcProperty.setNative]() {
425
+ [_o = decodeHeightProperty.setNative]() {
443
426
  this.initImage();
444
427
  }
445
- [_t = blurDownSamplingProperty.setNative]() {
428
+ [_p = lowerResSrcProperty.setNative]() {
446
429
  this.initImage();
447
430
  }
448
- [_u = aspectRatioProperty.setNative]() {
431
+ [_q = blurDownSamplingProperty.setNative]() {
449
432
  this.initImage();
450
433
  }
451
- [_v = headersProperty.setNative](value) {
434
+ [_r = aspectRatioProperty.setNative](value) {
435
+ if (this.nativeViewProtected) {
436
+ this.nativeViewProtected.setAspectRatio(value || 0);
437
+ }
438
+ }
439
+ [_s = headersProperty.setNative](value) {
452
440
  this.initImage();
453
441
  }
454
442
  [backgroundInternalProperty.setNative](value) {
455
443
  super[backgroundInternalProperty.setNative](value);
456
- this.nativeViewProtected.setClipToOutline(value?.hasBorderRadius());
444
+ if (this.nativeViewProtected) {
445
+ this.nativeViewProtected.setClipToOutline(value?.hasBorderRadius());
446
+ }
457
447
  }
458
448
  [noRatioEnforceProperty.setNative](value) {
459
- this.nativeViewProtected.noRatioEnforce = value;
449
+ if (this.nativeViewProtected) {
450
+ this.nativeViewProtected.setNoRatioEnforce(value);
451
+ }
460
452
  }
461
- async handleImageSrc(src) {
462
- const view = this.nativeViewProtected;
463
- if (view) {
464
- if (src instanceof Promise) {
465
- this.handleImageSrc(await src);
466
- return;
453
+ // Clear any active Glide target/request attached to this view.
454
+ cancelCurrentRequest() {
455
+ const ctx = this._context;
456
+ if (!ctx) {
457
+ return;
458
+ }
459
+ if (this.currentTarget) {
460
+ // cancel and drop reference; Any callbacks from the old target will be swallowed.
461
+ try {
462
+ com.bumptech.glide.Glide.with(ctx).clear(this.currentTarget);
467
463
  }
468
- else if (typeof src === 'function') {
469
- const newSrc = src();
470
- if (newSrc instanceof Promise) {
471
- await newSrc;
472
- }
473
- this.handleImageSrc(newSrc);
474
- return;
464
+ catch (err) {
465
+ // ignore
475
466
  }
476
- if (src) {
477
- let drawable;
478
- if (typeof src === 'string') {
479
- // disabled for now: loading vector drawables
480
- // if (src.indexOf(Utils.RESOURCE_PREFIX) === 0) {
481
- // const identifier = Utils.android.resources.getDrawableId(src.substring(Utils.RESOURCE_PREFIX.length));
482
- // if (identifier >= 0 && isVectorDrawable(this._context, identifier)) {
483
- // drawable = getBitmapFromVectorDrawable(this._context, identifier);
484
- // }
485
- // } else
486
- if (Utils.isFontIconURI(src)) {
487
- const fontIconCode = src.split('//')[1];
488
- if (fontIconCode !== undefined) {
489
- // support sync mode only
490
- const font = this.style.fontInternal;
491
- const color = this.style.color;
492
- drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), ImageSource.fromFontIconCodeSync(fontIconCode, font, color).android);
493
- }
467
+ this.currentTarget = null;
468
+ }
469
+ // if (this.nativeViewProtected) {
470
+ // try {
471
+ // com.bumptech.glide.Glide.with(ctx).clear(this.nativeViewProtected);
472
+ // } catch (err) {
473
+ // // ignore
474
+ // }
475
+ // }
476
+ }
477
+ loadImageWithGlide(uri) {
478
+ const view = this.nativeViewProtected;
479
+ const context = this._context;
480
+ // Cancel any prior Glide request/target for this view before starting a new one.
481
+ this.cancelCurrentRequest();
482
+ // Determine if this is a network request
483
+ this.isNetworkRequest = typeof uri === 'string' && (uri.startsWith('http://') || uri.startsWith('https://'));
484
+ let requestBuilder;
485
+ let loadModel = uri;
486
+ // Create callbacks separately based on what's needed
487
+ const needsProgress = this.isNetworkRequest && this.hasListeners(ImageBase.progressEvent);
488
+ const needsLoadSource = this.isNetworkRequest && this.hasListeners('loadSource');
489
+ // Create progress callback if needed (only for network requests with listener)
490
+ if (needsProgress) {
491
+ const owner = new WeakRef(this);
492
+ this.progressCallback = new com.nativescript.image.ImageProgressCallback({
493
+ onProgress(url, bytesRead, totalBytes) {
494
+ const instance = owner.get();
495
+ if (instance) {
496
+ const progress = totalBytes > 0 ? bytesRead / totalBytes : 0;
497
+ instance.notifyProgress({
498
+ loaded: bytesRead,
499
+ total: totalBytes,
500
+ progress,
501
+ finished: bytesRead >= totalBytes
502
+ });
494
503
  }
495
504
  }
496
- else if (src instanceof ImageSource) {
497
- drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), src.android);
498
- this.updateViewSize(src.android);
499
- }
500
- if (drawable) {
501
- const hierarchy = this.nativeImageViewProtected.getHierarchy();
502
- hierarchy.setImage(drawable, 1, hierarchy.getFadeDuration() === 0);
503
- return;
504
- }
505
- const uri = getUri(src);
506
- if (!uri) {
507
- console.error(`Error: 'src' not valid: ${src}`);
508
- return;
509
- }
510
- if (this.noCache) {
511
- // testing if is in cache is slow so lets remove without testing
512
- // const imagePipeLine = getImagePipeline();
513
- // const isInCache = imagePipeLine.isInBitmapMemoryCache(uri) || imagePipeLine.isInDiskCache(uri);
514
- // if (isInCache) {
515
- getImagePipeline().evictFromCache(uri);
516
- // }
517
- }
518
- this.isLoading = true;
519
- if (!this.controllerListener) {
520
- const that = new WeakRef(this);
521
- this.controllerListener = new com.facebook.drawee.controller.ControllerListener({
522
- onFinalImageSet(id, imageInfo, animatable) {
523
- if (Trace.isEnabled()) {
524
- CLog(CLogTypes.info, 'onFinalImageSet', id, imageInfo, animatable);
525
- }
526
- const owner = that?.get();
527
- if (owner) {
528
- owner.updateViewSize(imageInfo);
529
- owner.isLoading = false;
530
- const eventName = ImageBase.finalImageSetEvent;
531
- if (owner.hasListeners(eventName)) {
532
- const info = new ImageInfo(imageInfo);
533
- owner.notify({
534
- eventName,
535
- imageInfo: info,
536
- animatable: animatable
537
- });
538
- }
539
- }
540
- },
541
- onFailure(id, throwable) {
542
- if (Trace.isEnabled()) {
543
- CLog(CLogTypes.info, 'onFailure', id, throwable.getLocalizedMessage());
544
- }
545
- const owner = that?.get();
546
- if (owner) {
547
- // const nView = nativeView.nativeViewProtected;
548
- owner.isLoading = false;
549
- const eventName = ImageBase.failureEvent;
550
- if (owner.hasListeners(eventName)) {
551
- const imageError = new ImageError(throwable);
552
- owner.notify({
553
- eventName,
554
- error: wrapNativeException(throwable)
555
- });
556
- }
557
- }
558
- },
559
- onIntermediateImageFailed(id, throwable) {
560
- if (Trace.isEnabled()) {
561
- CLog(CLogTypes.info, 'onIntermediateImageFailed', id, throwable);
562
- }
563
- const owner = that?.get();
564
- if (owner) {
565
- const eventName = ImageBase.intermediateImageFailedEvent;
566
- if (owner.hasListeners(eventName)) {
567
- owner.notify({
568
- eventName,
569
- error: wrapNativeException(throwable)
570
- });
571
- }
572
- }
573
- },
574
- onIntermediateImageSet(id, imageInfo) {
575
- if (Trace.isEnabled()) {
576
- CLog(CLogTypes.info, 'onIntermediateImageSet', id, imageInfo);
577
- }
578
- const owner = that?.get();
579
- if (owner) {
580
- owner.updateViewSize(imageInfo);
581
- const eventName = ImageBase.intermediateImageSetEvent;
582
- if (owner.hasListeners(eventName)) {
583
- const info = new ImageInfo(imageInfo);
584
- owner.notify({
585
- eventName,
586
- imageInfo: info
587
- });
588
- }
589
- }
590
- },
591
- onRelease(id) {
592
- if (Trace.isEnabled()) {
593
- CLog(CLogTypes.info, 'onRelease', id);
594
- }
595
- const owner = that?.get();
596
- if (owner) {
597
- const eventName = ImageBase.releaseEvent;
598
- if (owner.hasListeners(eventName)) {
599
- owner.notify({
600
- eventName
601
- });
602
- }
603
- }
604
- },
605
- onSubmit(id, callerContext) {
606
- if (Trace.isEnabled()) {
607
- CLog(CLogTypes.info, 'onSubmit', id, callerContext);
608
- }
609
- const owner = that?.get();
610
- const eventName = ImageBase.submitEvent;
611
- if (owner?.hasListeners(eventName)) {
612
- owner.notify({
613
- eventName
614
- });
615
- }
616
- }
617
- });
505
+ });
506
+ }
507
+ // Create load source callback if needed (separate from progress)
508
+ if (needsLoadSource) {
509
+ const owner = new WeakRef(this);
510
+ this.loadSourceCallback = new com.nativescript.image.ImageLoadSourceCallback({
511
+ onLoadStarted(url, source) {
512
+ const instance = owner.get();
513
+ if (instance) {
514
+ instance.notifyLoadSource(source);
515
+ }
618
516
  }
619
- if (!this.requestListener && this.hasListeners(ImageBase.fetchingFromEvent)) {
620
- const that = new WeakRef(this);
621
- this.requestListener = new com.facebook.imagepipeline.listener.RequestListener({
622
- onRequestStart(request, callerContext, requestId, isPrefetch) {
623
- },
624
- onRequestSuccess(param0, param1, param2) { },
625
- onRequestFailure(param0, param1, param2, param3) { },
626
- onRequestCancellation(param0) { },
627
- onProducerStart(param0, param1) { },
628
- onProducerEvent(param0, param1, param2) { },
629
- onProducerFinishWithSuccess(requestId, producerName, extraMap) {
630
- const owner = that?.get();
631
- const eventName = ImageBase.fetchingFromEvent;
632
- if (owner?.hasListeners(eventName)) {
633
- let source = 'local';
634
- if (producerName.indexOf('Network') !== -1) {
635
- source = 'network';
636
- }
637
- else if (producerName.indexOf('Cache') !== -1) {
638
- source = 'cache';
639
- }
640
- owner.notify({
641
- eventName,
642
- source
643
- });
644
- }
645
- },
646
- onProducerFinishWithFailure(param0, param1, param2, param3) { },
647
- onProducerFinishWithCancellation(param0, param1, param2) { },
648
- onUltimateProducerReached(param0, param1, param2) { },
649
- requiresExtraMap(param0) { return false; },
650
- });
517
+ });
518
+ }
519
+ // Use CustomGlideUrl if we need headers, progress, or load source
520
+ if (this.isNetworkRequest && (this.headers || this.progressCallback || this.loadSourceCallback)) {
521
+ const headersMap = new java.util.HashMap();
522
+ if (this.headers) {
523
+ for (const key in this.headers) {
524
+ headersMap.put(key, this.headers[key]);
651
525
  }
652
- const options = JSON.stringify({
653
- progressiveRenderingEnabled: this.blurRadius,
654
- localThumbnailPreviewsEnabled: this.blurRadius,
655
- decodeWidth: this.decodeWidth,
656
- decodeHeight: this.decodeHeight,
657
- blurRadius: this.blurRadius,
658
- lowerResSrc: this.lowerResSrc ? getUri(this.lowerResSrc, false) : undefined,
659
- blurDownSampling: this.blurDownSampling,
660
- autoPlayAnimations: this.autoPlayAnimations,
661
- tapToRetryEnabled: this.tapToRetryEnabled,
662
- headers: this.headers
663
- });
664
- view.setUri(uri, options, this.controllerListener, this.requestListener);
665
- // const async = this.loadMode === 'async';
666
- // if (async) {
667
- // const builder = com.facebook.drawee.backends.pipeline.Fresco.newDraweeControllerBuilder();
668
- // builder.setImageRequest(request);
669
- // builder.setCallerContext(src);
670
- // builder.setControllerListener(listener);
671
- // builder.setOldController(this.nativeImageViewProtected.getController());
672
- // if (Trace.isEnabled()) {
673
- // builder.setPerfDataListener(
674
- // new com.facebook.drawee.backends.pipeline.info.ImagePerfDataListener({
675
- // onImageLoadStatusUpdated(param0: com.facebook.drawee.backends.pipeline.info.ImagePerfData, param1: number) {
676
- // CLog(CLogTypes.info, 'onImageLoadStatusUpdated', param0, param1);
677
- // },
678
- // onImageVisibilityUpdated(param0: com.facebook.drawee.backends.pipeline.info.ImagePerfData, param1: number) {
679
- // CLog(CLogTypes.info, 'onImageVisibilityUpdated', param0, param1);
680
- // }
681
- // })
682
- // );
683
- // }
684
- // if (this.lowerResSrc) {
685
- // builder.setLowResImageRequest(com.facebook.imagepipeline.request.ImageRequest.fromUri(getUri(this.lowerResSrc)));
686
- // }
687
- // if (this.autoPlayAnimations) {
688
- // builder.setAutoPlayAnimations(this.autoPlayAnimations);
689
- // }
690
- // if (this.tapToRetryEnabled) {
691
- // builder.setTapToRetryEnabled(this.tapToRetryEnabled);
692
- // }
693
- // const controller = builder.build();
694
- // this.nativeImageViewProtected.setController(controller);
695
- // } else {
696
- // const dataSource = com.facebook.drawee.backends.pipeline.Fresco.getImagePipeline().fetchDecodedImage(request, src);
697
- // const result = com.facebook.datasource.DataSources.waitForFinalResult(dataSource);
698
- // const bitmap = result.get().underlyingBitmap;
699
- // CloseableReference.closeSafely(result);
700
- // dataSource.close();
701
- // }
702
526
  }
703
- else {
704
- this.nativeImageViewProtected.setController(null);
705
- this.nativeImageViewProtected.setImageBitmap(null);
527
+ loadModel = new com.nativescript.image.CustomGlideUrl(uri, headersMap, this.progressCallback, // Can be null
528
+ this.loadSourceCallback // Can be null
529
+ );
530
+ }
531
+ requestBuilder = com.bumptech.glide.Glide.with(context).load(loadModel).signature(new com.bumptech.glide.signature.ObjectKey(Date().valueOf()));
532
+ // Apply transformations (blur, rounded corners, etc.)
533
+ const transformations = [];
534
+ if (this.blurRadius) {
535
+ transformations.push(new jp.wasabeef.glide.transformations.BlurTransformation(Math.round(this.blurRadius), this.blurDownSampling || 1));
536
+ }
537
+ if (this.roundAsCircle) {
538
+ transformations.push(new jp.wasabeef.glide.transformations.CropCircleTransformation());
539
+ }
540
+ else {
541
+ const topLeft = Utils.layout.toDevicePixels(this.roundTopLeftRadius || 0);
542
+ const topRight = Utils.layout.toDevicePixels(this.roundTopRightRadius || 0);
543
+ const bottomRight = Utils.layout.toDevicePixels(this.roundBottomRightRadius || 0);
544
+ const bottomLeft = Utils.layout.toDevicePixels(this.roundBottomLeftRadius || 0);
545
+ if (topLeft || topRight || bottomRight || bottomLeft) {
546
+ const radius = Math.max(topLeft, topRight, bottomRight, bottomLeft);
547
+ transformations.push(new jp.wasabeef.glide.transformations.RoundedCornersTransformation(Math.round(radius), 0, jp.wasabeef.glide.transformations.RoundedCornersTransformation.CornerType.ALL));
706
548
  }
707
549
  }
708
- }
709
- async initImage() {
710
- // this.nativeImageViewProtected.setImageURI(null);
711
- this.handleImageSrc(this.src);
712
- }
713
- updateHierarchy() {
714
- if (!this.mCanUpdateHierarchy) {
715
- this.mNeedUpdateHierarchy = true;
716
- return;
550
+ // Tint color
551
+ if (this.tintColor) {
552
+ transformations.push(new jp.wasabeef.glide.transformations.ColorFilterTransformation(this.tintColor.android));
717
553
  }
718
- if (this.nativeImageViewProtected) {
719
- let failureImageDrawable;
720
- let placeholderImageDrawable;
721
- let backgroundDrawable;
722
- if (this.failureImageUri) {
723
- failureImageDrawable = this.getDrawable(this.failureImageUri);
554
+ let multiTransform;
555
+ if (transformations.length > 0) {
556
+ multiTransform = new com.bumptech.glide.load.MultiTransformation(java.util.Arrays.asList(transformations));
557
+ requestBuilder = requestBuilder.transform(multiTransform);
558
+ }
559
+ // Placeholder
560
+ if (this.placeholderImageUri) {
561
+ const placeholder = this.getDrawable(this.placeholderImageUri);
562
+ if (placeholder) {
563
+ requestBuilder = requestBuilder.placeholder(placeholder);
724
564
  }
725
- if (this.placeholderImageUri) {
726
- placeholderImageDrawable = this.getDrawable(this.placeholderImageUri);
565
+ }
566
+ // Error image
567
+ if (this.failureImageUri) {
568
+ const error = this.getDrawable(this.failureImageUri);
569
+ if (error) {
570
+ requestBuilder = requestBuilder.error(error);
727
571
  }
728
- if (this.backgroundUri) {
729
- backgroundDrawable = this.getDrawable(this.backgroundUri);
572
+ }
573
+ // Thumbnail
574
+ if (this.lowerResSrc) {
575
+ const lowerResUri = getUri(this.lowerResSrc);
576
+ if (lowerResUri) {
577
+ const thumbnailRequest = com.bumptech.glide.Glide.with(context).load(lowerResUri);
578
+ requestBuilder = requestBuilder.thumbnail(thumbnailRequest);
730
579
  }
731
- const builder = new GenericDraweeHierarchyBuilder();
732
- if (this.failureImageUri && failureImageDrawable) {
733
- builder.setFailureImage(failureImageDrawable, this.stretch);
580
+ }
581
+ // Fade duration + conditional crossfade
582
+ if (this.fadeDuration > 0) {
583
+ const factory = new com.nativescript.image.ConditionalCrossFadeFactory(this.fadeDuration, !this.alwaysFade);
584
+ requestBuilder = requestBuilder.transition(com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade(factory));
585
+ }
586
+ // Cache settings
587
+ if (this.noCache) {
588
+ requestBuilder = requestBuilder.skipMemoryCache(true).diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy.NONE);
589
+ }
590
+ // Decode size
591
+ if (this.decodeWidth || this.decodeHeight) {
592
+ const Target = com.bumptech.glide.request.target.Target;
593
+ const width = this.decodeWidth || Target.SIZE_ORIGINAL;
594
+ const height = this.decodeHeight || Target.SIZE_ORIGINAL;
595
+ if (width === Target.SIZE_ORIGINAL) {
596
+ requestBuilder = requestBuilder.override(height);
734
597
  }
735
- if (this.tintColor) {
736
- builder.setActualImageColorFilter(new android.graphics.PorterDuffColorFilter(this.tintColor.android, android.graphics.PorterDuff.Mode.MULTIPLY));
598
+ else if (height === Target.SIZE_ORIGINAL) {
599
+ requestBuilder = requestBuilder.override(width);
737
600
  }
738
- if (this.placeholderImageUri && placeholderImageDrawable) {
739
- builder.setPlaceholderImage(placeholderImageDrawable, this.stretch);
601
+ else {
602
+ requestBuilder = requestBuilder.override(width, height);
740
603
  }
741
- if (this.stretch) {
742
- builder.setActualImageScaleType(this.stretch, this.imageRotation);
604
+ }
605
+ const owner = new WeakRef(this);
606
+ const sourceKey = new com.bumptech.glide.signature.ObjectKey(uri);
607
+ const objectArr = Array.create(com.bumptech.glide.request.RequestListener, 2);
608
+ const ro = new com.bumptech.glide.request.RequestOptions().signature(signature);
609
+ objectArr[0] = new com.nativescript.image.SaveKeysRequestListener(uri, uri, sourceKey, signature, this.decodeWidth || com.bumptech.glide.request.target.Target.SIZE_ORIGINAL, this.decodeHeight || com.bumptech.glide.request.target.Target.SIZE_ORIGINAL, multiTransform, ro, null);
610
+ objectArr[1] = new com.bumptech.glide.request.RequestListener({
611
+ onLoadFailed(e, model, target, isFirstResource) {
612
+ const instance = owner.get();
613
+ if (instance) {
614
+ // If this callback is for a previously canceled request, swallow it to avoid
615
+ // emitting/logging errors for obsolete requests.
616
+ if (instance.currentTarget && target !== instance.currentTarget) {
617
+ instance.progressCallback = null;
618
+ instance.loadSourceCallback = null;
619
+ // Swallow: we handled it (don't let Glide default log/clear)
620
+ return true;
621
+ }
622
+ instance.currentTarget = null;
623
+ instance.progressCallback = null; // Clean up
624
+ instance.loadSourceCallback = null;
625
+ instance.notifyFailure(e);
626
+ }
627
+ return false;
628
+ },
629
+ onResourceReady(resource, model, target, dataSource, isFirstResource) {
630
+ const instance = owner.get();
631
+ if (instance) {
632
+ // Ignore if the callback is from a previous request (stale).
633
+ if (instance.currentTarget && target !== instance.currentTarget) {
634
+ instance.progressCallback = null;
635
+ instance.loadSourceCallback = null;
636
+ return true;
637
+ }
638
+ instance.currentTarget = null;
639
+ instance.progressCallback = null; // Clean up
640
+ instance.loadSourceCallback = null;
641
+ instance.notifyFinalImageSet(resource, dataSource);
642
+ // Handle auto-play for animated drawables
643
+ if (!instance.autoPlayAnimations && resource instanceof android.graphics.drawable.Animatable) {
644
+ setTimeout(() => {
645
+ resource.stop();
646
+ }, 0);
647
+ }
648
+ // Notify load source for cache hits (network notified by interceptor)
649
+ let source = 'local';
650
+ if (dataSource) {
651
+ try {
652
+ const sourceName = dataSource.name ? dataSource.name() : String(dataSource);
653
+ switch ((sourceName || '').toUpperCase()) {
654
+ case 'MEMORY_CACHE':
655
+ source = 'memory';
656
+ break;
657
+ case 'DATA_DISK_CACHE':
658
+ case 'RESOURCE_DISK_CACHE':
659
+ source = 'disk';
660
+ break;
661
+ case 'REMOTE':
662
+ source = 'network';
663
+ break;
664
+ case 'LOCAL':
665
+ source = 'local';
666
+ break;
667
+ }
668
+ }
669
+ catch (err) {
670
+ source = 'unknown';
671
+ }
672
+ }
673
+ // Only notify if not network (network already notified by interceptor)
674
+ if (source !== 'network' && instance.hasListeners('loadSource')) {
675
+ instance.notifyLoadSource(source);
676
+ }
677
+ }
678
+ return false;
679
+ }
680
+ });
681
+ // Always create and track our custom target so we can detect stale callbacks later.
682
+ const target = new com.nativescript.image.MatrixDrawableImageViewTarget(view);
683
+ this.currentTarget = target;
684
+ if (this.preventPreClearDrawable) {
685
+ target.setClearFirst(false);
686
+ }
687
+ requestBuilder.signature(signature).listener(new com.nativescript.image.CompositeRequestListener(objectArr)).into(target);
688
+ }
689
+ notifyLoadSource(source) {
690
+ const eventName = ImageBase.loadSourceEvent;
691
+ if (this.hasListeners(eventName)) {
692
+ this.notify({
693
+ eventName,
694
+ source // 'network', 'memory', 'disk', 'local'
695
+ });
696
+ }
697
+ }
698
+ notifyProgress(payload) {
699
+ const eventName = ImageBase.progressEvent;
700
+ if (this.hasListeners(eventName)) {
701
+ this.notify({
702
+ eventName,
703
+ current: payload.loaded,
704
+ total: payload.total,
705
+ progress: payload.progress,
706
+ finished: payload.finished
707
+ });
708
+ }
709
+ }
710
+ notifyFinalImageSet(drawable, dataSource) {
711
+ let sourceLabel = 'local';
712
+ if (dataSource) {
713
+ try {
714
+ sourceLabel = dataSource.name ? dataSource.name() : String(dataSource);
743
715
  }
744
- builder.setFadeDuration(this.fadeDuration || 0);
745
- if (this.backgroundUri && backgroundDrawable) {
746
- builder.setBackground(backgroundDrawable);
716
+ catch (err) {
717
+ sourceLabel = String(dataSource);
718
+ }
719
+ switch ((sourceLabel || '').toUpperCase()) {
720
+ case 'MEMORY_CACHE':
721
+ sourceLabel = 'memory';
722
+ break;
723
+ case 'DATA_DISK_CACHE':
724
+ case 'RESOURCE_DISK_CACHE':
725
+ sourceLabel = 'disk';
726
+ break;
727
+ case 'REMOTE':
728
+ sourceLabel = 'network';
729
+ break;
730
+ case 'LOCAL':
731
+ sourceLabel = 'local';
732
+ break;
733
+ default:
734
+ break;
735
+ }
736
+ }
737
+ const eventName = ImageBase.finalImageSetEvent;
738
+ if (this.hasListeners(eventName)) {
739
+ const width = drawable ? drawable.getIntrinsicWidth() : 0;
740
+ const height = drawable ? drawable.getIntrinsicHeight() : 0;
741
+ const info = new ImageInfo(width, height);
742
+ this.notify({
743
+ eventName,
744
+ imageInfo: info,
745
+ animatable: this.getAnimatable(drawable),
746
+ android: drawable,
747
+ source: sourceLabel
748
+ });
749
+ }
750
+ }
751
+ notifyFailure(error) {
752
+ const eventName = ImageBase.failureEvent;
753
+ if (this.hasListeners(eventName)) {
754
+ this.notify({
755
+ eventName,
756
+ error: error instanceof java.lang.Throwable ? wrapNativeException(error) : error
757
+ });
758
+ }
759
+ }
760
+ getAnimatable(drawable) {
761
+ if (drawable && drawable instanceof android.graphics.drawable.Animatable) {
762
+ return drawable;
763
+ }
764
+ return null;
765
+ }
766
+ async handleImageSrc(src) {
767
+ const view = this.nativeViewProtected;
768
+ if (!view) {
769
+ return;
770
+ }
771
+ if (src instanceof Promise) {
772
+ this.handleImageSrc(await src);
773
+ return;
774
+ }
775
+ else if (typeof src === 'function') {
776
+ const newSrc = src();
777
+ if (newSrc instanceof Promise) {
778
+ await newSrc;
747
779
  }
748
- if (this.showProgressBar) {
749
- builder.setProgressBarImage(this.progressBarColor?.hex, this.stretch);
780
+ this.handleImageSrc(newSrc);
781
+ return;
782
+ }
783
+ if (src) {
784
+ let drawable;
785
+ if (typeof src === 'string') {
786
+ if (Utils.isFontIconURI(src)) {
787
+ const fontIconCode = src.split('//')[1];
788
+ if (fontIconCode !== undefined) {
789
+ const font = this.style.fontInternal;
790
+ const color = this.style.color;
791
+ drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), ImageSource.fromFontIconCodeSync(fontIconCode, font, color).android);
792
+ }
793
+ }
750
794
  }
751
- if (this.roundAsCircle) {
752
- builder.setRoundingParamsAsCircle();
795
+ else if (src instanceof ImageSource) {
796
+ drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), src.android);
753
797
  }
754
- const topLeftRadius = this.roundTopLeftRadius || 0;
755
- const topRightRadius = this.roundTopRightRadius || 0;
756
- const bottomRightRadius = this.roundBottomRightRadius || 0;
757
- const bottomLeftRadius = this.roundBottomLeftRadius || 0;
758
- if (topLeftRadius || topRightRadius || bottomRightRadius || bottomLeftRadius) {
759
- builder.setCornersRadii(Utils.layout.toDevicePixels(topLeftRadius), Utils.layout.toDevicePixels(topRightRadius), Utils.layout.toDevicePixels(bottomRightRadius), Utils.layout.toDevicePixels(bottomLeftRadius));
798
+ if (drawable) {
799
+ view.setImageDrawable(drawable);
800
+ this.notifyFinalImageSet(drawable);
801
+ return;
760
802
  }
761
- this.nativeImageViewProtected.setHierarchy(builder.build());
762
- if (!this.mNeedRequestImage) {
763
- this.nativeImageViewProtected.setController(this.nativeImageViewProtected.getController());
803
+ const uri = getUri(src);
804
+ if (!uri) {
805
+ console.error(`Error: 'src' not valid: ${src}`);
806
+ return;
764
807
  }
808
+ this.loadImageWithGlide(uri);
809
+ }
810
+ else {
811
+ // Clear existing request before removing the drawable
812
+ this.cancelCurrentRequest();
813
+ view.setImageDrawable(null);
814
+ }
815
+ }
816
+ async initImage() {
817
+ try {
818
+ await this.handleImageSrc(this.src);
819
+ }
820
+ catch (error) {
821
+ console.error(error, error.stack);
822
+ }
823
+ }
824
+ updateHierarchy() {
825
+ if (!this.mCanUpdateHierarchy) {
826
+ this.mNeedUpdateHierarchy = true;
827
+ return;
828
+ }
829
+ // Force reload with new settings
830
+ if (this.nativeImageViewProtected && this.src) {
831
+ this.initImage();
765
832
  }
766
833
  }
767
834
  getDrawable(path) {
768
- let drawable;
769
835
  if (typeof path === 'string') {
770
836
  if (Utils.isFontIconURI(path)) {
771
837
  const fontIconCode = path.split('//')[1];
772
838
  if (fontIconCode !== undefined) {
773
- // support sync mode only
774
839
  const font = this.style.fontInternal;
775
840
  const color = this.style.color;
776
- drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), ImageSource.fromFontIconCodeSync(fontIconCode, font, color).android);
841
+ return new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), ImageSource.fromFontIconCodeSync(fontIconCode, font, color).android);
777
842
  }
778
843
  }
779
844
  else if (Utils.isFileOrResourcePath(path)) {
780
845
  if (path.indexOf(Utils.RESOURCE_PREFIX) === 0) {
781
- return this.getDrawableFromResource(path); // number!
846
+ return this.getDrawableFromResource(path);
782
847
  }
783
848
  else {
784
- drawable = this.getDrawableFromLocalFile(path);
849
+ return this.getDrawableFromLocalFile(path);
785
850
  }
786
851
  }
787
852
  }
788
- else {
789
- drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), path.android);
853
+ else if (path instanceof ImageSource) {
854
+ return new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), path.android);
790
855
  }
791
- return drawable;
856
+ return null;
792
857
  }
793
858
  getDrawableFromLocalFile(localFilePath) {
794
859
  const img = ImageSource.fromFileSync(localFilePath);
795
- let drawable = null;
796
860
  if (img) {
797
- drawable = new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), img.android);
861
+ return new android.graphics.drawable.BitmapDrawable(Utils.android.getApplicationContext().getResources(), img.android);
798
862
  }
799
- return drawable;
863
+ return null;
800
864
  }
801
865
  getDrawableFromResource(resourceName) {
802
866
  const application = Utils.android.getApplication();
803
867
  const resources = application.getResources();
804
868
  const identifier = resources.getIdentifier(resourceName.substring(Utils.RESOURCE_PREFIX.length), 'drawable', application.getPackageName());
805
- // we return the identifier to allow Fresco to handle memory / caching
806
869
  return identifier;
807
- // return Utils.android.getApplicationContext().getDrawable(identifier);
808
870
  }
809
871
  startAnimating() {
810
- if (this.nativeImageViewProtected) {
811
- const controller = this.nativeImageViewProtected.getController();
812
- if (controller) {
813
- const animatable = controller.getAnimatable();
814
- if (animatable) {
815
- animatable.start();
816
- }
817
- }
872
+ const drawable = this.nativeViewProtected.getDrawable();
873
+ if (drawable && drawable instanceof android.graphics.drawable.Animatable) {
874
+ drawable.start();
818
875
  }
819
876
  }
820
877
  stopAnimating() {
821
- if (this.nativeImageViewProtected) {
822
- const controller = this.nativeImageViewProtected.getController();
823
- if (controller) {
824
- const animatable = controller.getAnimatable();
825
- if (animatable) {
826
- animatable.stop();
827
- }
828
- }
878
+ const drawable = this.nativeViewProtected.getDrawable();
879
+ if (drawable && drawable instanceof android.graphics.drawable.Animatable) {
880
+ drawable.stop();
829
881
  }
830
882
  }
831
883
  }
@@ -857,19 +909,19 @@ __decorate([
857
909
  needUpdateHierarchy
858
910
  ], Img.prototype, _j, null);
859
911
  __decorate([
860
- needUpdateHierarchy(true)
912
+ needRequestImage
861
913
  ], Img.prototype, _k, null);
862
914
  __decorate([
863
- needUpdateHierarchy
915
+ needRequestImage
864
916
  ], Img.prototype, _l, null);
865
917
  __decorate([
866
- needUpdateHierarchy
918
+ needRequestImage
867
919
  ], Img.prototype, _m, null);
868
920
  __decorate([
869
- needUpdateHierarchy
921
+ needRequestImage
870
922
  ], Img.prototype, _o, null);
871
923
  __decorate([
872
- needUpdateHierarchy
924
+ needRequestImage
873
925
  ], Img.prototype, _p, null);
874
926
  __decorate([
875
927
  needRequestImage
@@ -880,145 +932,32 @@ __decorate([
880
932
  __decorate([
881
933
  needRequestImage
882
934
  ], Img.prototype, _s, null);
883
- __decorate([
884
- needRequestImage
885
- ], Img.prototype, _t, null);
886
- __decorate([
887
- needRequestImage
888
- ], Img.prototype, _u, null);
889
- __decorate([
890
- needRequestImage
891
- ], Img.prototype, _v, null);
892
- class GenericDraweeHierarchyBuilder {
893
- constructor() {
894
- const res = Utils.android.getApplicationContext().getResources();
895
- this.nativeBuilder = new com.facebook.drawee.generic.GenericDraweeHierarchyBuilder(res);
896
- }
897
- setPlaceholderImage(drawable, scaleType) {
898
- if (!this.nativeBuilder) {
899
- return this;
900
- }
901
- if (scaleType) {
902
- this.nativeBuilder.setPlaceholderImage(drawable, getScaleType(scaleType));
903
- }
904
- else {
905
- this.nativeBuilder.setPlaceholderImage(drawable);
906
- }
907
- return this;
908
- }
909
- setActualImageColorFilter(filter) {
910
- if (!this.nativeBuilder) {
911
- return this;
912
- }
913
- this.nativeBuilder.setActualImageColorFilter(filter);
914
- return this;
915
- }
916
- setFailureImage(drawable, scaleType) {
917
- if (!this.nativeBuilder) {
918
- return null;
919
- }
920
- if (scaleType) {
921
- this.nativeBuilder.setFailureImage(drawable, getScaleType(scaleType));
922
- }
923
- else {
924
- this.nativeBuilder.setFailureImage(drawable);
925
- }
926
- return this;
927
- }
928
- setActualImageScaleType(scaleType, imageRotation = 0) {
929
- if (!this.nativeBuilder) {
930
- return this;
931
- }
932
- const nativeScaleType = getScaleType(scaleType);
933
- if (imageRotation !== 0 && nativeScaleType['setImageRotation']) {
934
- nativeScaleType['setImageRotation'](imageRotation);
935
- }
936
- this.nativeBuilder.setActualImageScaleType(nativeScaleType);
937
- return this;
938
- }
939
- build() {
940
- if (!this.nativeBuilder) {
941
- return null;
942
- }
943
- return this.nativeBuilder.build();
944
- }
945
- setFadeDuration(duration) {
946
- if (!this.nativeBuilder) {
947
- return null;
948
- }
949
- this.nativeBuilder.setFadeDuration(duration);
950
- return this;
951
- }
952
- setBackground(drawable) {
953
- if (!this.nativeBuilder) {
954
- return this;
955
- }
956
- this.nativeBuilder.setBackground(drawable);
957
- return this;
958
- }
959
- setProgressBarImage(color, stretch) {
960
- if (!this.nativeBuilder) {
961
- return null;
962
- }
963
- const drawable = new com.facebook.drawee.drawable.ProgressBarDrawable();
964
- if (color) {
965
- drawable.setColor(android.graphics.Color.parseColor(color));
966
- }
967
- this.nativeBuilder.setProgressBarImage(drawable, getScaleType(stretch));
968
- return this;
969
- }
970
- setRoundingParamsAsCircle() {
971
- if (!this.nativeBuilder) {
972
- return this;
973
- }
974
- const params = com.facebook.drawee.generic.RoundingParams.asCircle();
975
- this.nativeBuilder.setRoundingParams(params);
976
- return this;
977
- }
978
- setCornersRadii(topLeft, topRight, bottomRight, bottomLeft) {
979
- if (!this.nativeBuilder) {
980
- return this;
981
- }
982
- const params = new com.facebook.drawee.generic.RoundingParams();
983
- params.setCornersRadii(topLeft, topRight, bottomRight, bottomLeft);
984
- this.nativeBuilder.setRoundingParams(params);
985
- return this;
986
- }
987
- }
988
935
  function getScaleType(scaleType) {
989
936
  if (isString(scaleType)) {
990
937
  switch (scaleType) {
991
938
  case ScaleType.Center:
992
- //@ts-ignore
993
- return new com.nativescript.image.ScalingUtils.ScaleTypeCenter();
939
+ return android.widget.ImageView.ScaleType.CENTER;
994
940
  case ScaleType.AspectFill:
995
941
  case ScaleType.CenterCrop:
996
- //@ts-ignore
997
- return new com.nativescript.image.ScalingUtils.ScaleTypeCenterCrop();
942
+ return android.widget.ImageView.ScaleType.CENTER_CROP;
998
943
  case ScaleType.CenterInside:
999
- //@ts-ignore
1000
- return new com.nativescript.image.ScalingUtils.ScaleTypeCenterInside();
944
+ return android.widget.ImageView.ScaleType.CENTER_INSIDE;
1001
945
  case ScaleType.FitCenter:
1002
946
  case ScaleType.AspectFit:
1003
- //@ts-ignore
1004
- return new com.nativescript.image.ScalingUtils.ScaleTypeFitCenter();
947
+ return android.widget.ImageView.ScaleType.FIT_CENTER;
1005
948
  case ScaleType.FitEnd:
1006
- //@ts-ignore
1007
- return new com.nativescript.image.ScalingUtils.ScaleTypeFitEnd();
949
+ return android.widget.ImageView.ScaleType.FIT_END;
1008
950
  case ScaleType.FitStart:
1009
- //@ts-ignore
1010
- return new com.nativescript.image.ScalingUtils.ScaleTypeFitStart();
951
+ return android.widget.ImageView.ScaleType.FIT_START;
1011
952
  case ScaleType.Fill:
1012
953
  case ScaleType.FitXY:
1013
- //@ts-ignore
1014
- return new com.nativescript.image.ScalingUtils.ScaleTypeFitXY();
954
+ return android.widget.ImageView.ScaleType.FIT_XY;
1015
955
  case ScaleType.FocusCrop:
1016
- //@ts-ignore
1017
- return new com.nativescript.image.ScalingUtils.ScaleTypeFocusCrop();
956
+ return android.widget.ImageView.ScaleType.CENTER_CROP;
1018
957
  default:
1019
- break;
958
+ return android.widget.ImageView.ScaleType.FIT_CENTER;
1020
959
  }
1021
960
  }
1022
- return null;
961
+ return android.widget.ImageView.ScaleType.FIT_CENTER;
1023
962
  }
1024
963
  //# sourceMappingURL=index.android.js.map