@maplibre/maplibre-react-native 11.0.0-alpha.31 → 11.0.0-alpha.33

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 (99) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/org/maplibre/reactnative/MLRNPackage.kt +10 -12
  3. package/android/src/main/java/org/maplibre/reactnative/components/mapview/MLRNMapView.kt +9 -0
  4. package/android/src/main/java/org/maplibre/reactnative/components/mapview/MLRNMapViewManager.kt +5 -1
  5. package/android/src/main/java/org/maplibre/reactnative/modules/MLRNModule.java +6 -24
  6. package/android/src/main/java/org/maplibre/reactnative/modules/MLRNOfflineModule.kt +561 -0
  7. package/ios/components/map-view/MLRNMapView.h +1 -0
  8. package/ios/components/map-view/MLRNMapView.m +5 -0
  9. package/ios/components/map-view/MLRNMapViewComponentView.mm +4 -0
  10. package/ios/modules/mlrn/MLRNModule.m +0 -13
  11. package/ios/modules/offline/MLRNOfflineModule.h +4 -7
  12. package/ios/modules/offline/MLRNOfflineModule.mm +693 -0
  13. package/lib/commonjs/MLRNModule.js +1 -3
  14. package/lib/commonjs/MLRNModule.js.map +1 -1
  15. package/lib/commonjs/components/map-view/AndroidTextureMapViewNativeComponent.ts +1 -0
  16. package/lib/commonjs/components/map-view/MapView.js.map +1 -1
  17. package/lib/commonjs/components/map-view/MapViewNativeComponent.ts +1 -0
  18. package/lib/commonjs/index.js +0 -8
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/modules/offline/NativeOfflineModule.js +9 -0
  21. package/lib/commonjs/modules/offline/NativeOfflineModule.js.map +1 -0
  22. package/lib/commonjs/modules/offline/OfflineManager.js +124 -164
  23. package/lib/commonjs/modules/offline/OfflineManager.js.map +1 -1
  24. package/lib/commonjs/modules/offline/OfflinePack.js +15 -25
  25. package/lib/commonjs/modules/offline/OfflinePack.js.map +1 -1
  26. package/lib/module/MLRNModule.js +0 -1
  27. package/lib/module/MLRNModule.js.map +1 -1
  28. package/lib/module/components/map-view/AndroidTextureMapViewNativeComponent.ts +1 -0
  29. package/lib/module/components/map-view/MapView.js.map +1 -1
  30. package/lib/module/components/map-view/MapViewNativeComponent.ts +1 -0
  31. package/lib/module/index.js +0 -1
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/modules/offline/NativeOfflineModule.js +5 -0
  34. package/lib/module/modules/offline/NativeOfflineModule.js.map +1 -0
  35. package/lib/module/modules/offline/OfflineManager.js +123 -163
  36. package/lib/module/modules/offline/OfflineManager.js.map +1 -1
  37. package/lib/module/modules/offline/OfflinePack.js +14 -25
  38. package/lib/module/modules/offline/OfflinePack.js.map +1 -1
  39. package/lib/typescript/commonjs/src/MLRNModule.d.ts +1 -6
  40. package/lib/typescript/commonjs/src/MLRNModule.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/src/components/map-view/AndroidTextureMapViewNativeComponent.d.ts +1 -0
  42. package/lib/typescript/commonjs/src/components/map-view/AndroidTextureMapViewNativeComponent.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/src/components/map-view/MapView.d.ts +6 -0
  44. package/lib/typescript/commonjs/src/components/map-view/MapView.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/src/components/map-view/MapViewNativeComponent.d.ts +1 -0
  46. package/lib/typescript/commonjs/src/components/map-view/MapViewNativeComponent.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/src/index.d.ts +2 -5
  48. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/src/modules/offline/NativeOfflineModule.d.ts +50 -0
  50. package/lib/typescript/commonjs/src/modules/offline/NativeOfflineModule.d.ts.map +1 -0
  51. package/lib/typescript/commonjs/src/modules/offline/OfflineManager.d.ts +66 -69
  52. package/lib/typescript/commonjs/src/modules/offline/OfflineManager.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/src/modules/offline/OfflinePack.d.ts +11 -9
  54. package/lib/typescript/commonjs/src/modules/offline/OfflinePack.d.ts.map +1 -1
  55. package/lib/typescript/module/src/MLRNModule.d.ts +1 -6
  56. package/lib/typescript/module/src/MLRNModule.d.ts.map +1 -1
  57. package/lib/typescript/module/src/components/map-view/AndroidTextureMapViewNativeComponent.d.ts +1 -0
  58. package/lib/typescript/module/src/components/map-view/AndroidTextureMapViewNativeComponent.d.ts.map +1 -1
  59. package/lib/typescript/module/src/components/map-view/MapView.d.ts +6 -0
  60. package/lib/typescript/module/src/components/map-view/MapView.d.ts.map +1 -1
  61. package/lib/typescript/module/src/components/map-view/MapViewNativeComponent.d.ts +1 -0
  62. package/lib/typescript/module/src/components/map-view/MapViewNativeComponent.d.ts.map +1 -1
  63. package/lib/typescript/module/src/index.d.ts +2 -5
  64. package/lib/typescript/module/src/index.d.ts.map +1 -1
  65. package/lib/typescript/module/src/modules/offline/NativeOfflineModule.d.ts +50 -0
  66. package/lib/typescript/module/src/modules/offline/NativeOfflineModule.d.ts.map +1 -0
  67. package/lib/typescript/module/src/modules/offline/OfflineManager.d.ts +66 -69
  68. package/lib/typescript/module/src/modules/offline/OfflineManager.d.ts.map +1 -1
  69. package/lib/typescript/module/src/modules/offline/OfflinePack.d.ts +11 -9
  70. package/lib/typescript/module/src/modules/offline/OfflinePack.d.ts.map +1 -1
  71. package/package.json +7 -3
  72. package/src/MLRNModule.ts +0 -8
  73. package/src/components/map-view/AndroidTextureMapViewNativeComponent.ts +1 -0
  74. package/src/components/map-view/MapView.tsx +7 -0
  75. package/src/components/map-view/MapViewNativeComponent.ts +1 -0
  76. package/src/index.ts +13 -5
  77. package/src/modules/offline/NativeOfflineModule.ts +63 -0
  78. package/src/modules/offline/OfflineManager.ts +174 -210
  79. package/src/modules/offline/OfflinePack.ts +22 -32
  80. package/android/src/main/java/org/maplibre/reactnative/modules/MLRNOfflineModule.java +0 -586
  81. package/ios/modules/offline/MLRNOfflineModule.m +0 -524
  82. package/lib/commonjs/modules/offline/OfflineCreatePackOptions.js +0 -37
  83. package/lib/commonjs/modules/offline/OfflineCreatePackOptions.js.map +0 -1
  84. package/lib/commonjs/utils/makeNativeBounds.js +0 -11
  85. package/lib/commonjs/utils/makeNativeBounds.js.map +0 -1
  86. package/lib/module/modules/offline/OfflineCreatePackOptions.js +0 -32
  87. package/lib/module/modules/offline/OfflineCreatePackOptions.js.map +0 -1
  88. package/lib/module/utils/makeNativeBounds.js +0 -7
  89. package/lib/module/utils/makeNativeBounds.js.map +0 -1
  90. package/lib/typescript/commonjs/src/modules/offline/OfflineCreatePackOptions.d.ts +0 -20
  91. package/lib/typescript/commonjs/src/modules/offline/OfflineCreatePackOptions.d.ts.map +0 -1
  92. package/lib/typescript/commonjs/src/utils/makeNativeBounds.d.ts +0 -2
  93. package/lib/typescript/commonjs/src/utils/makeNativeBounds.d.ts.map +0 -1
  94. package/lib/typescript/module/src/modules/offline/OfflineCreatePackOptions.d.ts +0 -20
  95. package/lib/typescript/module/src/modules/offline/OfflineCreatePackOptions.d.ts.map +0 -1
  96. package/lib/typescript/module/src/utils/makeNativeBounds.d.ts +0 -2
  97. package/lib/typescript/module/src/utils/makeNativeBounds.d.ts.map +0 -1
  98. package/src/modules/offline/OfflineCreatePackOptions.ts +0 -53
  99. package/src/utils/makeNativeBounds.ts +0 -5
@@ -0,0 +1,693 @@
1
+ #import "MLRNOfflineModule.h"
2
+ #import "MLRNEvent.h"
3
+ #import "MLRNEventTypes.h"
4
+ #import "MLRNUtils.h"
5
+
6
+ #import <MapLibre/MapLibre.h>
7
+
8
+ static NSString *const MLRN_MIGRATION_KEY = @"migrationVersion";
9
+ static const NSInteger MLRN_MIGRATION_VERSION = 1;
10
+
11
+ @implementation MLRNOfflineModule {
12
+ NSUInteger lastPackState;
13
+ double lastPackTimestamp;
14
+ double eventThrottle;
15
+ NSMutableArray<RCTPromiseResolveBlock> *packRequestQueue;
16
+ }
17
+
18
+ + (NSString *)moduleName {
19
+ return @"MLRNOfflineModule";
20
+ }
21
+
22
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
23
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
24
+ return std::make_shared<facebook::react::NativeOfflineModuleSpecJSI>(params);
25
+ }
26
+
27
+ - (void)initialize {
28
+ [self runMigrations];
29
+ }
30
+
31
+ - (instancetype)init {
32
+ if (self = [super init]) {
33
+ packRequestQueue = [NSMutableArray new];
34
+ eventThrottle = 300;
35
+ lastPackState = -1;
36
+
37
+ NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
38
+ [defaultCenter addObserver:self
39
+ selector:@selector(offlinePackProgressDidChange:)
40
+ name:MLNOfflinePackProgressChangedNotification
41
+ object:nil];
42
+ [defaultCenter addObserver:self
43
+ selector:@selector(offlinePackDidReceiveError:)
44
+ name:MLNOfflinePackErrorNotification
45
+ object:nil];
46
+ [defaultCenter addObserver:self
47
+ selector:@selector(offlinePackDidReceiveMaxAllowedMapboxTiles:)
48
+ name:MLNOfflinePackMaximumMapboxTilesReachedNotification
49
+ object:nil];
50
+
51
+ [[MLNOfflineStorage sharedOfflineStorage] addObserver:self
52
+ forKeyPath:@"packs"
53
+ options:NSKeyValueObservingOptionInitial
54
+ context:NULL];
55
+ }
56
+ return self;
57
+ }
58
+
59
+ - (void)dealloc {
60
+ [[MLNOfflineStorage sharedOfflineStorage] removeObserver:self forKeyPath:@"packs"];
61
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
62
+ }
63
+
64
+ - (void)observeValueForKeyPath:(NSString *)keyPath
65
+ ofObject:(id)object
66
+ change:(NSDictionary<NSKeyValueChangeKey, id> *)change
67
+ context:(void *)context {
68
+ if ([keyPath isEqualToString:@"state"] && [object isKindOfClass:[MLNOfflinePack class]]) {
69
+ MLNOfflinePack *pack = (MLNOfflinePack *)object;
70
+ [self observerStateForPack:pack context:context];
71
+ return;
72
+ }
73
+
74
+ if (packRequestQueue.count == 0) {
75
+ return;
76
+ }
77
+
78
+ NSArray<MLNOfflinePack *> *packs = [[MLNOfflineStorage sharedOfflineStorage] packs];
79
+ if (packs == nil) {
80
+ return;
81
+ }
82
+
83
+ while (packRequestQueue.count > 0) {
84
+ RCTPromiseResolveBlock resolve = [packRequestQueue objectAtIndex:0];
85
+ resolve([self _convertPacksToJson:packs]);
86
+ [packRequestQueue removeObjectAtIndex:0];
87
+ }
88
+ }
89
+
90
+ - (void)createPack:(JS::NativeOfflineModule::NativeOfflinePackCreateOptions &)options
91
+ resolve:(RCTPromiseResolveBlock)resolve
92
+ reject:(RCTPromiseRejectBlock)reject {
93
+ NSString *styleURL = options.mapStyle();
94
+
95
+ auto boundsArray = options.bounds();
96
+ if (boundsArray.size() != 4) {
97
+ reject(@"createPack", @"Invalid bounds", nil);
98
+ return;
99
+ }
100
+
101
+ MLNCoordinateBounds bounds = [MLRNUtils fromReactBounds:@[
102
+ @(boundsArray[0]), @(boundsArray[1]), @(boundsArray[2]), @(boundsArray[3])
103
+ ]];
104
+
105
+ id<MLNOfflineRegion> offlineRegion =
106
+ [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:[NSURL URLWithString:styleURL]
107
+ bounds:bounds
108
+ fromZoomLevel:options.minZoom()
109
+ toZoomLevel:options.maxZoom()];
110
+
111
+ NSString *metadataString = options.metadata();
112
+
113
+ NSMutableDictionary *contextDictionary = [NSMutableDictionary new];
114
+ contextDictionary[MLRN_MIGRATION_KEY] = @(MLRN_MIGRATION_VERSION);
115
+ contextDictionary[@"id"] = [[[NSUUID UUID] UUIDString] lowercaseString];
116
+ contextDictionary[@"metadata"] = metadataString;
117
+
118
+ NSString *contextString = [self _serializeDictionaryToJSON:contextDictionary];
119
+ NSError *archiveError = nil;
120
+ NSData *contextData = [NSKeyedArchiver archivedDataWithRootObject:contextString
121
+ requiringSecureCoding:YES
122
+ error:&archiveError];
123
+ if (archiveError != nil) {
124
+ reject(@"createPack", @"Failed to archive context data", archiveError);
125
+ return;
126
+ }
127
+
128
+ [[MLNOfflineStorage sharedOfflineStorage]
129
+ addPackForRegion:offlineRegion
130
+ withContext:contextData
131
+ completionHandler:^(MLNOfflinePack *pack, NSError *error) {
132
+ if (error != nil) {
133
+ reject(@"createPack", error.description, error);
134
+ return;
135
+ }
136
+ resolve([self _convertPackToDict:pack]);
137
+ [pack resume];
138
+ }];
139
+ }
140
+
141
+ - (void)mergeOfflineRegions:(NSString *)path
142
+ resolve:(RCTPromiseResolveBlock)resolve
143
+ reject:(RCTPromiseRejectBlock)reject {
144
+ NSString *absolutePath;
145
+ if ([path isAbsolutePath]) {
146
+ absolutePath = path;
147
+ } else {
148
+ NSBundle *mainBundle = [NSBundle mainBundle];
149
+ NSString *fileName = [path stringByDeletingPathExtension];
150
+ NSString *extension = [path pathExtension];
151
+ absolutePath = [mainBundle pathForResource:fileName ofType:extension];
152
+ if (!absolutePath) {
153
+ return reject(
154
+ @"asset_does_not_exist",
155
+ [NSString
156
+ stringWithFormat:@"The given assetName, %@, can't be found in the app's bundle.",
157
+ path],
158
+ nil);
159
+ }
160
+ }
161
+
162
+ [[MLNOfflineStorage sharedOfflineStorage]
163
+ addContentsOfFile:absolutePath
164
+ withCompletionHandler:^(NSURL *fileURL, NSArray<MLNOfflinePack *> *packs, NSError *error) {
165
+ if (error != nil) {
166
+ reject(@"mergeOfflineRegions", error.description, error);
167
+ return;
168
+ }
169
+ resolve(nil);
170
+ }];
171
+ }
172
+
173
+ - (void)getPacks:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
174
+ dispatch_async(dispatch_get_main_queue(), ^{
175
+ NSArray<MLNOfflinePack *> *packs = [[MLNOfflineStorage sharedOfflineStorage] packs];
176
+
177
+ if (packs == nil) {
178
+ // Packs have not loaded yet
179
+ [self->packRequestQueue addObject:resolve];
180
+ return;
181
+ }
182
+
183
+ resolve([self _convertPacksToJson:packs]);
184
+ });
185
+ }
186
+
187
+ - (void)invalidateAmbientCache:(RCTPromiseResolveBlock)resolve
188
+ reject:(RCTPromiseRejectBlock)reject {
189
+ [[MLNOfflineStorage sharedOfflineStorage]
190
+ invalidateAmbientCacheWithCompletionHandler:^(NSError *error) {
191
+ if (error != nil) {
192
+ reject(@"invalidateAmbientCache", error.description, error);
193
+ return;
194
+ }
195
+ resolve(nil);
196
+ }];
197
+ }
198
+
199
+ - (void)clearAmbientCache:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
200
+ [[MLNOfflineStorage sharedOfflineStorage]
201
+ clearAmbientCacheWithCompletionHandler:^(NSError *error) {
202
+ if (error != nil) {
203
+ reject(@"clearAmbientCache", error.description, error);
204
+ return;
205
+ }
206
+ resolve(nil);
207
+ }];
208
+ }
209
+
210
+ - (void)setMaximumAmbientCacheSize:(double)size
211
+ resolve:(RCTPromiseResolveBlock)resolve
212
+ reject:(RCTPromiseRejectBlock)reject {
213
+ [[MLNOfflineStorage sharedOfflineStorage]
214
+ setMaximumAmbientCacheSize:(NSUInteger)size
215
+ withCompletionHandler:^(NSError *error) {
216
+ if (error != nil) {
217
+ reject(@"setMaximumAmbientCacheSize", error.description, error);
218
+ return;
219
+ }
220
+ resolve(nil);
221
+ }];
222
+ }
223
+
224
+ - (void)resetDatabase:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
225
+ [[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError *error) {
226
+ if (error != nil) {
227
+ reject(@"resetDatabase", error.description, error);
228
+ return;
229
+ }
230
+ resolve(nil);
231
+ }];
232
+ }
233
+
234
+ - (void)getPackStatus:(NSString *)packId
235
+ resolve:(RCTPromiseResolveBlock)resolve
236
+ reject:(RCTPromiseRejectBlock)reject {
237
+ MLNOfflinePack *pack = [self _getPackById:packId];
238
+
239
+ if (pack == nil) {
240
+ resolve(nil);
241
+ NSLog(@"[MLRNOfflineModule] Pack not found");
242
+
243
+ return;
244
+ }
245
+
246
+ if (pack.state == MLNOfflinePackStateUnknown) {
247
+ [pack addObserver:self
248
+ forKeyPath:@"state"
249
+ options:NSKeyValueObservingOptionNew
250
+ context:(__bridge_retained void *)resolve];
251
+ [pack requestProgress];
252
+ } else {
253
+ resolve([self _makeRegionStatusPayload:packId pack:pack]);
254
+ }
255
+ }
256
+
257
+ - (void)observerStateForPack:(MLNOfflinePack *)pack context:(nullable void *)context {
258
+ RCTPromiseResolveBlock resolve = (__bridge_transfer RCTPromiseResolveBlock)context;
259
+ dispatch_async(dispatch_get_main_queue(), ^{
260
+ NSDictionary *metadata = [self _unarchiveContext:pack];
261
+ NSString *packId = metadata[@"id"];
262
+ resolve([self _makeRegionStatusPayload:packId pack:pack]);
263
+ });
264
+ [pack removeObserver:self forKeyPath:@"state" context:context];
265
+ }
266
+
267
+ - (void)setPackObserver:(NSString *)packId
268
+ resolve:(RCTPromiseResolveBlock)resolve
269
+ reject:(RCTPromiseRejectBlock)reject {
270
+ MLNOfflinePack *pack = [self _getPackById:packId];
271
+
272
+ if (pack == nil) {
273
+ resolve(@NO);
274
+ return;
275
+ }
276
+
277
+ // On iOS, observers are set up automatically via NSNotificationCenter
278
+ resolve(@YES);
279
+ }
280
+
281
+ - (void)invalidatePack:(NSString *)packId
282
+ resolve:(RCTPromiseResolveBlock)resolve
283
+ reject:(RCTPromiseRejectBlock)reject {
284
+ MLNOfflinePack *pack = [self _getPackById:packId];
285
+
286
+ if (pack == nil) {
287
+ resolve(nil);
288
+ return;
289
+ }
290
+ [[MLNOfflineStorage sharedOfflineStorage] invalidatePack:pack
291
+ withCompletionHandler:^(NSError *error) {
292
+ if (error != nil) {
293
+ reject(@"invalidatePack", error.description, error);
294
+ return;
295
+ }
296
+ resolve(nil);
297
+ }];
298
+ }
299
+
300
+ - (void)deletePack:(NSString *)packId
301
+ resolve:(RCTPromiseResolveBlock)resolve
302
+ reject:(RCTPromiseRejectBlock)reject {
303
+ MLNOfflinePack *pack = [self _getPackById:packId];
304
+
305
+ if (pack == nil) {
306
+ resolve(nil);
307
+ return;
308
+ }
309
+ if (pack.state == MLNOfflinePackStateInvalid) {
310
+ NSError *error = [NSError errorWithDomain:MLNErrorDomain
311
+ code:1
312
+ userInfo:@{
313
+ NSLocalizedDescriptionKey :
314
+ NSLocalizedString(@"Pack has already been deleted", nil)
315
+ }];
316
+ reject(@"deletePack", error.description, error);
317
+ return;
318
+ }
319
+ [[MLNOfflineStorage sharedOfflineStorage] removePack:pack
320
+ withCompletionHandler:^(NSError *error) {
321
+ if (error != nil) {
322
+ reject(@"deletePack", error.description, error);
323
+ return;
324
+ }
325
+ resolve(nil);
326
+ }];
327
+ }
328
+
329
+ - (void)pausePackDownload:(NSString *)packId
330
+ resolve:(RCTPromiseResolveBlock)resolve
331
+ reject:(RCTPromiseRejectBlock)reject {
332
+ MLNOfflinePack *pack = [self _getPackById:packId];
333
+
334
+ if (pack == nil) {
335
+ reject(@"pausePackDownload", @"Unknown offline region", nil);
336
+ return;
337
+ }
338
+
339
+ if (pack.state == MLNOfflinePackStateInactive || pack.state == MLNOfflinePackStateComplete) {
340
+ resolve(nil);
341
+ return;
342
+ }
343
+
344
+ [pack suspend];
345
+ resolve(nil);
346
+ }
347
+
348
+ - (void)resumePackDownload:(NSString *)packId
349
+ resolve:(RCTPromiseResolveBlock)resolve
350
+ reject:(RCTPromiseRejectBlock)reject {
351
+ MLNOfflinePack *pack = [self _getPackById:packId];
352
+
353
+ if (pack == nil) {
354
+ reject(@"resumePack", @"Unknown offline region", nil);
355
+ return;
356
+ }
357
+
358
+ if (pack.state == MLNOfflinePackStateActive || pack.state == MLNOfflinePackStateComplete) {
359
+ resolve(nil);
360
+ return;
361
+ }
362
+
363
+ [pack resume];
364
+ resolve(nil);
365
+ }
366
+
367
+ - (void)setTileCountLimit:(double)limit {
368
+ [[MLNOfflineStorage sharedOfflineStorage] setMaximumAllowedMapboxTiles:(uint64_t)limit];
369
+ }
370
+
371
+ - (void)setProgressEventThrottle:(double)throttleValue {
372
+ eventThrottle = throttleValue;
373
+ }
374
+
375
+ - (void)offlinePackProgressDidChange:(NSNotification *)notification {
376
+ MLNOfflinePack *pack = notification.object;
377
+
378
+ if (pack.state == MLNOfflinePackStateInvalid) {
379
+ return; // Avoid invalid offline pack exception
380
+ }
381
+
382
+ if ([self _shouldSendProgressEvent:[self _getCurrentTimestamp] pack:pack]) {
383
+ NSDictionary *metadata = [self _unarchiveContext:pack];
384
+ NSString *packId = metadata[@"id"];
385
+ NSDictionary *payload = [self _makeRegionStatusPayload:packId pack:pack];
386
+ if (_eventEmitterCallback) {
387
+ [self emitOnProgress:payload];
388
+ }
389
+ lastPackTimestamp = [self _getCurrentTimestamp];
390
+ }
391
+
392
+ lastPackState = pack.state;
393
+ }
394
+
395
+ - (void)offlinePackDidReceiveError:(NSNotification *)notification {
396
+ MLNOfflinePack *pack = notification.object;
397
+ if (pack.state == MLNOfflinePackStateInvalid) {
398
+ return; // Avoid invalid offline pack exception
399
+ }
400
+ NSDictionary *metadata = [self _unarchiveContext:pack];
401
+
402
+ NSString *packId = metadata[@"id"];
403
+ if (packId != nil && _eventEmitterCallback) {
404
+ NSError *error = notification.userInfo[MLNOfflinePackUserInfoKeyError];
405
+ NSDictionary *payload = @{@"id" : packId, @"message" : error.description ?: @"Unknown error"};
406
+ [self emitOnError:payload];
407
+ }
408
+ }
409
+
410
+ - (void)offlinePackDidReceiveMaxAllowedMapboxTiles:(NSNotification *)notification {
411
+ MLNOfflinePack *pack = notification.object;
412
+ NSDictionary *metadata = [self _unarchiveContext:pack];
413
+
414
+ NSString *packId = metadata[@"id"];
415
+ if (packId != nil && _eventEmitterCallback) {
416
+ NSDictionary *payload = @{@"id" : packId, @"message" : @"Tile limit exceeded"};
417
+
418
+ [self emitOnError:payload];
419
+ }
420
+ }
421
+
422
+ - (double)_getCurrentTimestamp {
423
+ return CACurrentMediaTime() * 1000;
424
+ }
425
+
426
+ - (NSString *)_serializeDictionaryToJSON:(NSDictionary *)dictionary {
427
+ if (dictionary == nil) {
428
+ return @"{}";
429
+ }
430
+
431
+ NSError *serializeError;
432
+ NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary
433
+ options:0
434
+ error:&serializeError];
435
+
436
+ if (serializeError || data == nil) {
437
+ return @"{}";
438
+ }
439
+
440
+ NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
441
+ return string ?: @"{}";
442
+ }
443
+
444
+ - (NSDictionary *)_unarchiveContext:(MLNOfflinePack *)pack {
445
+ NSError *unarchiveError = nil;
446
+ id contextUnknown = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSString class]
447
+ fromData:pack.context
448
+ error:&unarchiveError];
449
+
450
+ if (unarchiveError != nil) {
451
+ NSLog(@"[MLRNOfflineModule] Failed to unarchive context: %@",
452
+ unarchiveError.localizedDescription);
453
+ return @{};
454
+ }
455
+
456
+ // After migration, all packs should be in JSON string format
457
+ if (contextUnknown == nil) {
458
+ return @{};
459
+ }
460
+
461
+ // Parse JSON string to dictionary
462
+ if ([contextUnknown isKindOfClass:[NSString class]]) {
463
+ NSDictionary *contextDictionary = [NSJSONSerialization
464
+ JSONObjectWithData:[contextUnknown dataUsingEncoding:NSUTF8StringEncoding]
465
+ options:NSJSONReadingMutableContainers
466
+ error:nil];
467
+ return contextDictionary ?: @{};
468
+ }
469
+
470
+ // This should not happen after migration, but handle gracefully
471
+ return @{};
472
+ }
473
+
474
+ /**
475
+ * Migrate offline packs to the latest context format.
476
+ *
477
+ * - Until v5 context uses NSDictionary
478
+ * - From v6 context uses JSON string
479
+ * - From v11 context uses JSON string with with `id` (UUID) and `metadata` (JSON string)
480
+ */
481
+ - (void)runMigrations {
482
+ NSArray<MLNOfflinePack *> *packs = [[MLNOfflineStorage sharedOfflineStorage] packs];
483
+
484
+ if (packs == nil) {
485
+ NSLog(@"[MLRNOfflineModule] No packs found for migration");
486
+ return;
487
+ }
488
+
489
+ NSMutableArray<MLNOfflinePack *> *packsToMigrate = [NSMutableArray new];
490
+
491
+ for (MLNOfflinePack *pack in packs) {
492
+ BOOL needsMigration = NO;
493
+
494
+ NSError *unarchiveError = nil;
495
+ NSSet *allowedClasses = [NSSet setWithArray:@[ [NSDictionary class], [NSString class] ]];
496
+ id contextUnknown = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses
497
+ fromData:pack.context
498
+ error:&unarchiveError];
499
+
500
+ if (unarchiveError != nil) {
501
+ NSLog(@"[MLRNOfflineModule] Failed to unarchive context during migration check: %@",
502
+ unarchiveError.localizedDescription);
503
+ needsMigration = YES;
504
+ }
505
+ // <= v5 format (NSDictionary)
506
+ else if ([contextUnknown isKindOfClass:[NSDictionary class]]) {
507
+ needsMigration = YES;
508
+ }
509
+ // Check >= v6+ format (JSON string)
510
+ else {
511
+ NSDictionary *contextDictionary = [self _unarchiveContext:pack];
512
+ if (contextDictionary == nil || contextDictionary[MLRN_MIGRATION_KEY] == nil ||
513
+ [contextDictionary[MLRN_MIGRATION_KEY] integerValue] != MLRN_MIGRATION_VERSION) {
514
+ needsMigration = YES;
515
+ }
516
+ }
517
+
518
+ if (needsMigration) {
519
+ [packsToMigrate addObject:pack];
520
+ }
521
+ }
522
+
523
+ if (packsToMigrate.count == 0) {
524
+ NSLog(@"[MLRNOfflineModule] No packs need migration");
525
+
526
+ return;
527
+ }
528
+
529
+ NSLog(@"[MLRNOfflineModule] Migrating %lu offline pack(s)", (unsigned long)packsToMigrate.count);
530
+
531
+ for (MLNOfflinePack *pack in packsToMigrate) {
532
+ [self migratePack:pack];
533
+ }
534
+ }
535
+
536
+ - (void)migratePack:(MLNOfflinePack *)pack {
537
+ NSDictionary *oldContextDictionary = nil;
538
+
539
+ NSError *unarchiveError = nil;
540
+ NSSet *allowedClasses = [NSSet setWithArray:@[ [NSDictionary class], [NSString class] ]];
541
+ id oldContextUnknown = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses
542
+ fromData:pack.context
543
+ error:&unarchiveError];
544
+
545
+ if (unarchiveError != nil) {
546
+ NSLog(@"[MLRNOfflineModule] Failed to unarchive context during migration: %@",
547
+ unarchiveError.localizedDescription);
548
+ oldContextDictionary = @{};
549
+ }
550
+
551
+ // Handle <= v5 NSDictionary
552
+ else if ([oldContextUnknown isKindOfClass:[NSDictionary class]]) {
553
+ oldContextDictionary = oldContextUnknown;
554
+ }
555
+ // Handle >= v6+ JSON string
556
+ else {
557
+ oldContextDictionary = [self _unarchiveContext:pack];
558
+ }
559
+
560
+ if (oldContextDictionary == nil) {
561
+ oldContextDictionary = @{};
562
+ }
563
+
564
+ NSString *packId = [[[NSUUID UUID] UUIDString] lowercaseString];
565
+ NSMutableDictionary *newContextDictionary = [NSMutableDictionary new];
566
+
567
+ newContextDictionary[MLRN_MIGRATION_KEY] = @(MLRN_MIGRATION_VERSION);
568
+ newContextDictionary[@"id"] = packId;
569
+ newContextDictionary[@"metadata"] = [self _serializeDictionaryToJSON:oldContextDictionary];
570
+
571
+ NSError *archiveError = nil;
572
+ NSData *newContextData = [NSKeyedArchiver
573
+ archivedDataWithRootObject:[self _serializeDictionaryToJSON:newContextDictionary]
574
+ requiringSecureCoding:YES
575
+ error:&archiveError];
576
+
577
+ if (archiveError != nil) {
578
+ NSLog(@"[MLRNOfflineModule] Failed to archive context for pack %@: %@", packId,
579
+ archiveError.localizedDescription);
580
+ return;
581
+ }
582
+
583
+ [pack setContext:newContextData
584
+ completionHandler:^(NSError *error) {
585
+ if (error != nil) {
586
+ NSLog(@"[MLRNOfflineModule] Failed to migrate pack %@: %@", packId,
587
+ error.localizedDescription);
588
+ } else {
589
+ NSLog(@"[MLRNOfflineModule] Successfully migrated pack %@", packId);
590
+ }
591
+ }];
592
+ }
593
+
594
+ - (NSString *)_stateToString:(MLNOfflinePackState)state {
595
+ switch (state) {
596
+ case MLNOfflinePackStateActive:
597
+ return @"active";
598
+ case MLNOfflinePackStateComplete:
599
+ return @"complete";
600
+ case MLNOfflinePackStateInactive:
601
+ default:
602
+ return @"inactive";
603
+ }
604
+ }
605
+
606
+ - (NSDictionary *)_makeRegionStatusPayload:(NSString *)packId pack:(MLNOfflinePack *)pack {
607
+ uint64_t completedResources = pack.progress.countOfResourcesCompleted;
608
+ uint64_t expectedResources = pack.progress.countOfResourcesExpected;
609
+
610
+ double percentage = 0.0;
611
+ if (expectedResources > 0) {
612
+ percentage = 100.0 * (double)completedResources / (double)expectedResources;
613
+ }
614
+
615
+ return @{
616
+ @"id" : packId,
617
+ @"state" : [self _stateToString:pack.state],
618
+ @"percentage" : @(percentage),
619
+ @"completedResourceCount" : @(pack.progress.countOfResourcesCompleted),
620
+ @"completedResourceSize" : @(pack.progress.countOfBytesCompleted),
621
+ @"completedTileSize" : @(pack.progress.countOfTileBytesCompleted),
622
+ @"completedTileCount" : @(pack.progress.countOfTilesCompleted),
623
+ @"requiredResourceCount" : @(pack.progress.maximumResourcesExpected)
624
+ };
625
+ }
626
+
627
+ - (NSArray<NSDictionary *> *)_convertPacksToJson:(NSArray<MLNOfflinePack *> *)packs {
628
+ NSMutableArray<NSDictionary *> *jsonPacks = [NSMutableArray new];
629
+
630
+ if (packs == nil) {
631
+ return jsonPacks;
632
+ }
633
+
634
+ for (MLNOfflinePack *pack in packs) {
635
+ NSDictionary *packDict = [self _convertPackToDict:pack];
636
+ if (packDict != nil) {
637
+ [jsonPacks addObject:packDict];
638
+ }
639
+ }
640
+
641
+ return jsonPacks;
642
+ }
643
+
644
+ - (NSDictionary *)_convertPackToDict:(MLNOfflinePack *)pack {
645
+ MLNTilePyramidOfflineRegion *region = (MLNTilePyramidOfflineRegion *)pack.region;
646
+ if (region == nil) {
647
+ return nil;
648
+ }
649
+
650
+ NSArray *bounds = [MLRNUtils fromCoordinateBounds:region.bounds];
651
+ NSDictionary *contextDictionary = [self _unarchiveContext:pack];
652
+
653
+ return @{
654
+ @"id" : contextDictionary[@"id"] ?: @"",
655
+ @"bounds" : bounds,
656
+ @"metadata" : contextDictionary[@"metadata"]
657
+ };
658
+ }
659
+
660
+ - (MLNOfflinePack *)_getPackById:(NSString *)packId {
661
+ NSArray<MLNOfflinePack *> *packs = [[MLNOfflineStorage sharedOfflineStorage] packs];
662
+
663
+ if (packs == nil || packId == nil) {
664
+ return nil;
665
+ }
666
+
667
+ for (MLNOfflinePack *pack in packs) {
668
+ NSDictionary *metadata = [self _unarchiveContext:pack];
669
+ if ([packId isEqualToString:metadata[@"id"]]) {
670
+ return pack;
671
+ }
672
+ }
673
+
674
+ return nil;
675
+ }
676
+
677
+ - (BOOL)_shouldSendProgressEvent:(double)currentTimestamp pack:(MLNOfflinePack *)currentPack {
678
+ if (lastPackState == -1) {
679
+ return YES;
680
+ }
681
+
682
+ if (lastPackState != currentPack.state) {
683
+ return YES;
684
+ }
685
+
686
+ if (currentTimestamp - lastPackTimestamp > eventThrottle) {
687
+ return YES;
688
+ }
689
+
690
+ return NO;
691
+ }
692
+
693
+ @end
@@ -3,11 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.setConnected = exports.removeCustomHeader = exports.addCustomHeader = exports.StyleURL = exports.StyleSource = exports.OfflinePackDownloadState = void 0;
6
+ exports.setConnected = exports.removeCustomHeader = exports.addCustomHeader = exports.StyleURL = exports.StyleSource = void 0;
7
7
  var _reactNative = require("react-native");
8
8
  const MLRNModule = Object.create(_reactNative.NativeModules.MLRNModule);
9
9
  const {
10
- OfflinePackDownloadState,
11
10
  StyleSource,
12
11
  StyleURL,
13
12
  addCustomHeader,
@@ -19,5 +18,4 @@ exports.removeCustomHeader = removeCustomHeader;
19
18
  exports.addCustomHeader = addCustomHeader;
20
19
  exports.StyleURL = StyleURL;
21
20
  exports.StyleSource = StyleSource;
22
- exports.OfflinePackDownloadState = OfflinePackDownloadState;
23
21
  //# sourceMappingURL=MLRNModule.js.map