@shenai/react-native-sdk 3.1.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.
@@ -0,0 +1,1121 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridgeModule.h>
3
+ #import <React/RCTEventEmitter.h>
4
+ #import <ShenaiSDK/ShenaiHealthRisks.h>
5
+ #import <ShenaiSDK/ShenaiSDK.h>
6
+
7
+ static id ShenaiMeasurementQualityMetricsToDictionary(MeasurementQualityMetrics *metrics) {
8
+ if (metrics == nil) {
9
+ return (NSDictionary *)[NSNull null];
10
+ }
11
+ return @{
12
+ @"ppgQualityIndex" : metrics.ppgQualityIndex ?: [NSNull null],
13
+ @"bcgQualityIndex" : metrics.bcgQualityIndex ?: [NSNull null],
14
+ @"breathingQualityIndex" : metrics.breathingQualityIndex ?: [NSNull null],
15
+ @"bloodPressureQualityIndex" : metrics.bloodPressureQualityIndex ?: [NSNull null],
16
+ @"expectedSbpMedianAbsErrorMmhg" : metrics.expectedSbpMedianAbsErrorMmhg ?: [NSNull null],
17
+ @"expectedSbpP80AbsErrorMmhg" : metrics.expectedSbpP80AbsErrorMmhg ?: [NSNull null],
18
+ @"expectedSbpMeanAbsErrorMmhg" : metrics.expectedSbpMeanAbsErrorMmhg ?: [NSNull null],
19
+ @"expectedSbpBalancedMaeMmhg" : metrics.expectedSbpBalancedMaeMmhg ?: [NSNull null],
20
+ @"expectedDbpMedianAbsErrorMmhg" : metrics.expectedDbpMedianAbsErrorMmhg ?: [NSNull null],
21
+ @"expectedDbpP80AbsErrorMmhg" : metrics.expectedDbpP80AbsErrorMmhg ?: [NSNull null],
22
+ @"expectedDbpMeanAbsErrorMmhg" : metrics.expectedDbpMeanAbsErrorMmhg ?: [NSNull null],
23
+ @"expectedDbpBalancedMaeMmhg" : metrics.expectedDbpBalancedMaeMmhg ?: [NSNull null],
24
+ };
25
+ }
26
+
27
+ @interface ShenaiSdkNativeModule : RCTEventEmitter <RCTBridgeModule>
28
+
29
+ @end
30
+
31
+ @implementation ShenaiSdkNativeModule {
32
+ BOOL hasListeners;
33
+ }
34
+
35
+ RCT_EXPORT_MODULE(); // This macro exports the module to React Native
36
+
37
+ - (NSArray<NSString *> *)supportedEvents {
38
+ return @[ @"ShenAIEvent" ];
39
+ }
40
+
41
+ - (void)startObserving {
42
+ hasListeners = YES;
43
+ }
44
+
45
+ - (void)stopObserving {
46
+ hasListeners = NO;
47
+ }
48
+
49
+ - (void)sendShenaiEvent:(NSString *)name {
50
+ [self sendEventWithName:@"ShenAIEvent" body:@{@"EventName" : name}];
51
+ }
52
+
53
+ RCT_EXPORT_METHOD(initialize
54
+ : (NSString *)apiKey userId
55
+ : (NSString *)userId settings
56
+ : (NSDictionary *)settings resolver
57
+ : (RCTPromiseResolveBlock)resolve rejecter
58
+ : (RCTPromiseRejectBlock)reject) {
59
+ InitializationSettings *initSettings = [[InitializationSettings alloc] init];
60
+
61
+ // Set settings if they are provided
62
+ if (settings[@"precisionMode"]) {
63
+ initSettings.precisionMode = [settings[@"precisionMode"] integerValue];
64
+ }
65
+ if (settings[@"operatingMode"]) {
66
+ initSettings.operatingMode = [settings[@"operatingMode"] integerValue];
67
+ }
68
+ if (settings[@"measurementPreset"]) {
69
+ NSNumber *presetValue = settings[@"measurementPreset"];
70
+ NSLog(@"Received preset: %@", presetValue);
71
+ initSettings.measurementPreset = presetValue.integerValue;
72
+ } else {
73
+ NSLog(@"No preset received");
74
+ }
75
+ if (settings[@"cameraMode"]) {
76
+ initSettings.cameraMode = [settings[@"cameraMode"] integerValue];
77
+ }
78
+ if (settings[@"onboardingMode"]) {
79
+ initSettings.onboardingMode = [settings[@"onboardingMode"] integerValue];
80
+ }
81
+ if (settings[@"initializationMode"]) {
82
+ initSettings.initializationMode = [settings[@"initializationMode"] integerValue];
83
+ }
84
+ if (settings[@"offlineProcessing"]) {
85
+ initSettings.offlineProcessing = [settings[@"offlineProcessing"] boolValue];
86
+ }
87
+ if (settings[@"showUserInterface"]) {
88
+ initSettings.showUserInterface = [settings[@"showUserInterface"] boolValue];
89
+ }
90
+ if (settings[@"showFacePositioningOverlay"]) {
91
+ initSettings.showFacePositioningOverlay = [settings[@"showFacePositioningOverlay"] boolValue];
92
+ }
93
+ if (settings[@"showVisualWarnings"]) {
94
+ initSettings.showVisualWarnings = [settings[@"showVisualWarnings"] boolValue];
95
+ }
96
+ if (settings[@"enableCameraSwap"]) {
97
+ initSettings.enableCameraSwap = [settings[@"enableCameraSwap"] boolValue];
98
+ }
99
+ if (settings[@"showFaceMask"]) {
100
+ initSettings.showFaceMask = [settings[@"showFaceMask"] boolValue];
101
+ }
102
+ if (settings[@"showBloodFlow"]) {
103
+ initSettings.showBloodFlow = [settings[@"showBloodFlow"] boolValue];
104
+ }
105
+ if (settings[@"hideShenaiLogo"]) {
106
+ initSettings.hideShenaiLogo = [settings[@"hideShenaiLogo"] boolValue];
107
+ }
108
+ if (settings[@"includeTimestampInPdf"]) {
109
+ initSettings.includeTimestampInPdf = [settings[@"includeTimestampInPdf"] boolValue];
110
+ }
111
+ if (settings[@"pdfEmailSubject"]) {
112
+ initSettings.pdfEmailSubject = settings[@"pdfEmailSubject"];
113
+ }
114
+ if (settings[@"pdfEmailBody"]) {
115
+ initSettings.pdfEmailBody = settings[@"pdfEmailBody"];
116
+ }
117
+ if (settings[@"enableStartAfterSuccess"]) {
118
+ initSettings.enableStartAfterSuccess = [settings[@"enableStartAfterSuccess"] boolValue];
119
+ }
120
+ if (settings[@"enableSummaryScreen"]) {
121
+ initSettings.enableSummaryScreen = [settings[@"enableSummaryScreen"] boolValue];
122
+ }
123
+ if (settings[@"showResultsFinishButton"]) {
124
+ initSettings.showResultsFinishButton = [settings[@"showResultsFinishButton"] boolValue];
125
+ }
126
+ if (settings[@"enableHealthRisks"]) {
127
+ initSettings.enableHealthRisks = [settings[@"enableHealthRisks"] boolValue];
128
+ }
129
+ if (settings[@"showHealthIndicesFinishButton"]) {
130
+ initSettings.showHealthIndicesFinishButton = [settings[@"showHealthIndicesFinishButton"] boolValue];
131
+ }
132
+ if (settings[@"saveHealthRisksFactors"]) {
133
+ initSettings.saveHealthRisksFactors = [settings[@"saveHealthRisksFactors"] boolValue];
134
+ }
135
+ if (settings[@"showOutOfRangeResultIndicators"]) {
136
+ initSettings.showOutOfRangeResultIndicators = [settings[@"showOutOfRangeResultIndicators"] boolValue];
137
+ }
138
+ if (settings[@"showSignalQualityIndicator"]) {
139
+ initSettings.showSignalQualityIndicator = [settings[@"showSignalQualityIndicator"] boolValue];
140
+ }
141
+ if (settings[@"showSignalTile"]) {
142
+ initSettings.showSignalTile = [settings[@"showSignalTile"] boolValue];
143
+ }
144
+ if (settings[@"showStartStopButton"]) {
145
+ initSettings.showStartStopButton = [settings[@"showStartStopButton"] boolValue];
146
+ }
147
+ if (settings[@"showInfoButton"]) {
148
+ initSettings.showInfoButton = [settings[@"showInfoButton"] boolValue];
149
+ }
150
+ if (settings[@"showDisclaimer"]) {
151
+ initSettings.showDisclaimer = [settings[@"showDisclaimer"] boolValue];
152
+ }
153
+ if (settings[@"enableMeasurementsDashboard"]) {
154
+ initSettings.enableMeasurementsDashboard = [settings[@"enableMeasurementsDashboard"] boolValue];
155
+ }
156
+ if (settings[@"uiVersion"]) {
157
+ initSettings.uiVersion = (UiVersion)[settings[@"uiVersion"] integerValue];
158
+ }
159
+ if (settings[@"showTrialMetricLabels"]) {
160
+ initSettings.showTrialMetricLabels = [settings[@"showTrialMetricLabels"] boolValue];
161
+ }
162
+ if (settings[@"applyPrecisionModeToBloodPressure"]) {
163
+ initSettings.applyPrecisionModeToBloodPressure = [settings[@"applyPrecisionModeToBloodPressure"] boolValue];
164
+ }
165
+ if (settings[@"blockingMeasurementConditions"]) {
166
+ NSArray *conditions = settings[@"blockingMeasurementConditions"];
167
+ NSMutableArray<NSNumber *> *values = [NSMutableArray arrayWithCapacity:conditions.count];
168
+ for (NSNumber *condition in conditions) {
169
+ [values addObject:@(condition.integerValue)];
170
+ }
171
+ initSettings.blockingMeasurementConditions = values;
172
+ }
173
+ if (settings[@"warningMeasurementConditions"]) {
174
+ NSArray *conditions = settings[@"warningMeasurementConditions"];
175
+ NSMutableArray<NSNumber *> *values = [NSMutableArray arrayWithCapacity:conditions.count];
176
+ for (NSNumber *condition in conditions) {
177
+ [values addObject:@(condition.integerValue)];
178
+ }
179
+ initSettings.warningMeasurementConditions = values;
180
+ }
181
+ if (settings[@"uiFlowScreens"]) {
182
+ NSArray *screens = settings[@"uiFlowScreens"];
183
+ NSMutableArray<NSNumber *> *values = [NSMutableArray arrayWithCapacity:screens.count];
184
+ for (NSNumber *screen in screens) {
185
+ [values addObject:@(screen.integerValue)];
186
+ }
187
+ initSettings.uiFlowScreens = values;
188
+ }
189
+ if (settings[@"frameWidth"]) {
190
+ initSettings.frameWidth = [settings[@"frameWidth"] intValue];
191
+ }
192
+ if (settings[@"frameHeight"]) {
193
+ initSettings.frameHeight = [settings[@"frameHeight"] intValue];
194
+ }
195
+ if (settings[@"rotation"]) {
196
+ initSettings.rotation = [settings[@"rotation"] intValue];
197
+ }
198
+ if (settings[@"risksFactors"]) {
199
+ initSettings.risksFactors = [self risksFactorsFromDictionary:settings[@"risksFactors"]];
200
+ }
201
+
202
+ initSettings.eventCallback = ^(Event event) {
203
+ NSString *jsEvent = @"UNKNOWN";
204
+
205
+ switch (event) {
206
+ case EventStartButtonClicked:
207
+ jsEvent = @"START_BUTTON_CLICKED";
208
+ break;
209
+ case EventStopButtonClicked:
210
+ jsEvent = @"STOP_BUTTON_CLICKED";
211
+ break;
212
+ case EventMeasurementFinished:
213
+ jsEvent = @"MEASUREMENT_FINISHED";
214
+ break;
215
+ case EventUserFlowFinished:
216
+ jsEvent = @"USER_FLOW_FINISHED";
217
+ break;
218
+ case EventScreenChanged:
219
+ jsEvent = @"SCREEN_CHANGED";
220
+ break;
221
+ }
222
+ if (hasListeners) {
223
+ [self sendEventWithName:@"ShenAIEvent" body:@{@"EventName" : jsEvent}];
224
+ }
225
+ };
226
+ InitializationResult result = [ShenaiSDK initialize:apiKey userID:userId settings:initSettings];
227
+
228
+ resolve(@(result));
229
+ }
230
+
231
+ RCT_EXPORT_METHOD(getRealtimeMetrics
232
+ : (float)periodSec resolver
233
+ : (RCTPromiseResolveBlock)resolve rejecter
234
+ : (RCTPromiseRejectBlock)reject) {
235
+ MeasurementResults *results = [ShenaiSDK getRealtimeMetrics:periodSec];
236
+ if (results) {
237
+ NSMutableArray *heartbeatsArray = [NSMutableArray arrayWithCapacity:results.heartbeats.count];
238
+ for (Heartbeat *heartbeat in results.heartbeats) {
239
+ [heartbeatsArray addObject:@{
240
+ @"startLocationSec" : @(heartbeat.startLocationSec),
241
+ @"endLocationSec" : @(heartbeat.endLocationSec),
242
+ @"durationMs" : @(heartbeat.durationMs)
243
+ }];
244
+ }
245
+
246
+ NSDictionary *resultsDict = @{
247
+ @"heartRateBpm" : @(results.heartRateBpm),
248
+ @"hrvSdnnMs" : results.hrvSdnnMs ?: [NSNull null],
249
+ @"hrvLnrmssdMs" : results.hrvLnrmssdMs ?: [NSNull null],
250
+ @"stressIndex" : results.stressIndex ?: [NSNull null],
251
+ @"parasympatheticActivity" : results.parasympatheticActivity ?: [NSNull null],
252
+ @"breathingRateBpm" : results.breathingRateBpm ?: [NSNull null],
253
+ @"systolicBloodPressureMmhg" : results.systolicBloodPressureMmhg ?: [NSNull null],
254
+ @"diastolicBloodPressureMmhg" : results.diastolicBloodPressureMmhg ?: [NSNull null],
255
+ @"cardiacWorkloadMmhgPerSec" : results.cardiacWorkloadMmhgPerSec ?: [NSNull null],
256
+ @"ageYears" : results.ageYears ?: [NSNull null],
257
+ @"bmiKgPerM2" : results.bmiKgPerM2 ?: [NSNull null],
258
+ @"bmiCategory" : @(results.bmiCategory),
259
+ @"weightKg" : results.weightKg ?: [NSNull null],
260
+ @"heightCm" : results.heightCm ?: [NSNull null],
261
+ @"qualityMetrics" : ShenaiMeasurementQualityMetricsToDictionary(results.qualityMetrics),
262
+ @"heartbeats" : heartbeatsArray,
263
+ @"averageSignalQuality" : @(results.averageSignalQuality)
264
+ };
265
+ resolve(resultsDict);
266
+ } else {
267
+ resolve([NSNull null]);
268
+ }
269
+ }
270
+
271
+ RCT_EXPORT_METHOD(setCustomMeasurementConfig
272
+ : (NSDictionary *)config resolver
273
+ : (RCTPromiseResolveBlock)resolve rejecter
274
+ : (RCTPromiseRejectBlock)reject) {
275
+ if (!config) {
276
+ reject(@"E_CONFIG_NULL", @"Configuration is null", nil);
277
+ return;
278
+ }
279
+
280
+ CustomMeasurementConfig *measurementConfig = [[CustomMeasurementConfig alloc] init];
281
+
282
+ // Assuming your SDK has a method to apply this config
283
+ measurementConfig.durationSeconds = config[@"durationSeconds"];
284
+ measurementConfig.infiniteMeasurement = [config[@"infiniteMeasurement"] boolValue];
285
+
286
+ {
287
+ NSArray *metricsArray = config[@"instantMetrics"];
288
+ if (metricsArray != nil) {
289
+ NSMutableArray<NSNumber *> *metricsEnumArray = [NSMutableArray arrayWithCapacity:[metricsArray count]];
290
+ for (NSNumber *metricNumber in metricsArray) {
291
+ [metricsEnumArray addObject:@([metricNumber integerValue])];
292
+ }
293
+ measurementConfig.instantMetrics = metricsEnumArray;
294
+ }
295
+ }
296
+ {
297
+ NSArray *metricsArray = config[@"summaryMetrics"];
298
+ if (metricsArray != nil) {
299
+ NSMutableArray<NSNumber *> *metricsEnumArray = [NSMutableArray arrayWithCapacity:[metricsArray count]];
300
+ for (NSNumber *metricNumber in metricsArray) {
301
+ [metricsEnumArray addObject:@([metricNumber integerValue])];
302
+ }
303
+ measurementConfig.summaryMetrics = metricsEnumArray;
304
+ }
305
+ }
306
+ {
307
+ NSArray *healthIndicesArray = config[@"healthIndices"];
308
+ if (healthIndicesArray != nil) {
309
+ NSMutableArray<NSNumber *> *healthIndicesEnumArray =
310
+ [NSMutableArray arrayWithCapacity:[healthIndicesArray count]];
311
+ for (NSNumber *healthIndexNumber in healthIndicesArray) {
312
+ [healthIndicesEnumArray addObject:@([healthIndexNumber integerValue])];
313
+ }
314
+ measurementConfig.healthIndices = healthIndicesEnumArray;
315
+ }
316
+ }
317
+
318
+ measurementConfig.realtimeHrPeriodSeconds = config[@"realtimeHrPeriodSeconds"];
319
+ measurementConfig.realtimeHrvPeriodSeconds = config[@"realtimeHrvPeriodSeconds"];
320
+ measurementConfig.realtimeCardiacStressPeriodSeconds = config[@"realtimeCardiacStressPeriodSeconds"];
321
+
322
+ // Apply the configuration to your SDK
323
+ [ShenaiSDK setCustomMeasurementConfig:measurementConfig];
324
+
325
+ resolve(@(YES));
326
+ }
327
+
328
+ RCT_EXPORT_METHOD(setCustomColorTheme
329
+ : (NSDictionary *)theme resolver
330
+ : (RCTPromiseResolveBlock)resolve rejecter
331
+ : (RCTPromiseRejectBlock)reject) {
332
+ if (!theme) {
333
+ reject(@"E_THEME_NULL", @"Theme is null", nil);
334
+ return;
335
+ }
336
+
337
+ CustomColorTheme *colorTheme = [[CustomColorTheme alloc] init];
338
+
339
+ // Assuming your SDK has a method to apply this theme
340
+ colorTheme.themeColor = theme[@"themeColor"];
341
+ colorTheme.textColor = theme[@"textColor"];
342
+ colorTheme.backgroundColor = theme[@"backgroundColor"];
343
+ colorTheme.tileColor = theme[@"tileColor"];
344
+ colorTheme.buttonMainColor = theme[@"buttonMainColor"];
345
+ colorTheme.buttonSecondaryColor = theme[@"buttonSecondaryColor"];
346
+
347
+ // Apply the theme to your SDK
348
+ [ShenaiSDK setCustomColorTheme:colorTheme];
349
+
350
+ resolve(@(YES));
351
+ }
352
+
353
+ RCT_EXPORT_METHOD(getMeasurementResults : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
354
+ MeasurementResults *results = [ShenaiSDK getMeasurementResults];
355
+ if (results) {
356
+ NSMutableArray *heartbeatsArray = [NSMutableArray arrayWithCapacity:results.heartbeats.count];
357
+ for (Heartbeat *heartbeat in results.heartbeats) {
358
+ [heartbeatsArray addObject:@{
359
+ @"startLocationSec" : @(heartbeat.startLocationSec),
360
+ @"endLocationSec" : @(heartbeat.endLocationSec),
361
+ @"durationMs" : @(heartbeat.durationMs)
362
+ }];
363
+ }
364
+
365
+ NSDictionary *resultsDict = @{
366
+ @"heartRateBpm" : @(results.heartRateBpm),
367
+ @"hrvSdnnMs" : results.hrvSdnnMs ?: [NSNull null],
368
+ @"hrvLnrmssdMs" : results.hrvLnrmssdMs ?: [NSNull null],
369
+ @"stressIndex" : results.stressIndex ?: [NSNull null],
370
+ @"parasympatheticActivity" : results.parasympatheticActivity ?: [NSNull null],
371
+ @"breathingRateBpm" : results.breathingRateBpm ?: [NSNull null],
372
+ @"systolicBloodPressureMmhg" : results.systolicBloodPressureMmhg ?: [NSNull null],
373
+ @"diastolicBloodPressureMmhg" : results.diastolicBloodPressureMmhg ?: [NSNull null],
374
+ @"cardiacWorkloadMmhgPerSec" : results.cardiacWorkloadMmhgPerSec ?: [NSNull null],
375
+ @"ageYears" : results.ageYears ?: [NSNull null],
376
+ @"bmiKgPerM2" : results.bmiKgPerM2 ?: [NSNull null],
377
+ @"bmiCategory" : @(results.bmiCategory),
378
+ @"weightKg" : results.weightKg ?: [NSNull null],
379
+ @"heightCm" : results.heightCm ?: [NSNull null],
380
+ @"qualityMetrics" : ShenaiMeasurementQualityMetricsToDictionary(results.qualityMetrics),
381
+ @"heartbeats" : heartbeatsArray,
382
+ @"averageSignalQuality" : @(results.averageSignalQuality)
383
+ };
384
+ resolve(resultsDict);
385
+ } else {
386
+ resolve([NSNull null]);
387
+ }
388
+ }
389
+
390
+ RCT_EXPORT_METHOD(getMeasurementResultsHistory
391
+ : (RCTPromiseResolveBlock)resolve rejecter
392
+ : (RCTPromiseRejectBlock)reject) {
393
+ MeasurementResultsHistory *history = [ShenaiSDK getMeasurementResultsHistory];
394
+
395
+ if (!history) {
396
+ resolve([NSNull null]);
397
+ return;
398
+ }
399
+
400
+ NSMutableArray *outerArray = [NSMutableArray arrayWithCapacity:history.history.count];
401
+ for (MeasurementResultsWithMetadata *item in history.history) {
402
+ MeasurementResults *res = item.measurementResults;
403
+ NSMutableArray *heartbeatsArray = [NSMutableArray arrayWithCapacity:res.heartbeats.count];
404
+ for (Heartbeat *hb in res.heartbeats) {
405
+ [heartbeatsArray addObject:@{
406
+ @"startLocationSec" : @(hb.startLocationSec),
407
+ @"endLocationSec" : @(hb.endLocationSec),
408
+ @"durationMs" : @(hb.durationMs)
409
+ }];
410
+ }
411
+
412
+ NSDictionary *resultsDict = @{
413
+ @"heartRateBpm" : @(res.heartRateBpm),
414
+ @"hrvSdnnMs" : res.hrvSdnnMs ?: [NSNull null],
415
+ @"hrvLnrmssdMs" : res.hrvLnrmssdMs ?: [NSNull null],
416
+ @"stressIndex" : res.stressIndex ?: [NSNull null],
417
+ @"parasympatheticActivity" : res.parasympatheticActivity ?: [NSNull null],
418
+ @"breathingRateBpm" : res.breathingRateBpm ?: [NSNull null],
419
+ @"systolicBloodPressureMmhg" : res.systolicBloodPressureMmhg ?: [NSNull null],
420
+ @"diastolicBloodPressureMmhg" : res.diastolicBloodPressureMmhg ?: [NSNull null],
421
+ @"cardiacWorkloadMmhgPerSec" : res.cardiacWorkloadMmhgPerSec ?: [NSNull null],
422
+ @"ageYears" : res.ageYears ?: [NSNull null],
423
+ @"bmiKgPerM2" : res.bmiKgPerM2 ?: [NSNull null],
424
+ @"bmiCategory" : @(res.bmiCategory),
425
+ @"weightKg" : res.weightKg ?: [NSNull null],
426
+ @"heightCm" : res.heightCm ?: [NSNull null],
427
+ @"qualityMetrics" : ShenaiMeasurementQualityMetricsToDictionary(res.qualityMetrics),
428
+ @"heartbeats" : heartbeatsArray,
429
+ @"averageSignalQuality" : @(res.averageSignalQuality)
430
+ };
431
+
432
+ NSDictionary *itemDict = @{
433
+ @"measurementResults" : resultsDict,
434
+ @"epochTimestamp" : item.epochTimestamp,
435
+ @"isCalibration" : @(item.isCalibration)
436
+ };
437
+
438
+ [outerArray addObject:itemDict];
439
+ }
440
+
441
+ NSDictionary *finalDict = @{@"history" : outerArray};
442
+ resolve(finalDict);
443
+ }
444
+
445
+ RCT_EXPORT_METHOD(isInitialized : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
446
+ BOOL isInitialized = [ShenaiSDK isInitialized];
447
+ resolve(@(isInitialized));
448
+ }
449
+
450
+ RCT_EXPORT_METHOD(deinitialize) { [ShenaiSDK deinitialize]; }
451
+
452
+ RCT_EXPORT_METHOD(setOperatingMode : (NSInteger)mode) { [ShenaiSDK setOperatingMode:mode]; }
453
+
454
+ RCT_EXPORT_METHOD(startMeasurement) { [ShenaiSDK startMeasurement]; }
455
+
456
+ RCT_EXPORT_METHOD(stopMeasurement) { [ShenaiSDK stopMeasurement]; }
457
+
458
+ RCT_EXPORT_METHOD(resetMeasurementSession) { [ShenaiSDK resetMeasurementSession]; }
459
+
460
+ RCT_EXPORT_METHOD(getOperatingMode : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
461
+ OperatingMode mode = [ShenaiSDK getOperatingMode];
462
+ resolve(@(mode));
463
+ }
464
+
465
+ RCT_EXPORT_METHOD(getCalibrationState : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
466
+ CalibrationState mode = [ShenaiSDK getCalibrationState];
467
+ resolve(@(mode));
468
+ }
469
+
470
+ RCT_EXPORT_METHOD(setPrecisionMode : (NSInteger)mode) { [ShenaiSDK setPrecisionMode:mode]; }
471
+
472
+ RCT_EXPORT_METHOD(getPrecisionMode : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
473
+ PrecisionMode mode = [ShenaiSDK getPrecisionMode];
474
+ resolve(@(mode));
475
+ }
476
+
477
+ RCT_EXPORT_METHOD(setApplyPrecisionModeToBloodPressure : (BOOL)apply) {
478
+ [ShenaiSDK setApplyPrecisionModeToBloodPressure:apply];
479
+ }
480
+
481
+ RCT_EXPORT_METHOD(getApplyPrecisionModeToBloodPressure
482
+ : (RCTPromiseResolveBlock)resolve rejecter
483
+ : (RCTPromiseRejectBlock)reject) {
484
+ resolve(@([ShenaiSDK getApplyPrecisionModeToBloodPressure]));
485
+ }
486
+
487
+ RCT_EXPORT_METHOD(setBlockingMeasurementConditions : (NSArray<NSNumber *> *)conditions) {
488
+ [ShenaiSDK setBlockingMeasurementConditions:conditions ?: @[]];
489
+ }
490
+
491
+ RCT_EXPORT_METHOD(getBlockingMeasurementConditions
492
+ : (RCTPromiseResolveBlock)resolve rejecter
493
+ : (RCTPromiseRejectBlock)reject) {
494
+ resolve([ShenaiSDK getBlockingMeasurementConditions]);
495
+ }
496
+
497
+ RCT_EXPORT_METHOD(setWarningMeasurementConditions : (NSArray<NSNumber *> *)conditions) {
498
+ [ShenaiSDK setWarningMeasurementConditions:conditions ?: @[]];
499
+ }
500
+
501
+ RCT_EXPORT_METHOD(getWarningMeasurementConditions
502
+ : (RCTPromiseResolveBlock)resolve rejecter
503
+ : (RCTPromiseRejectBlock)reject) {
504
+ resolve([ShenaiSDK getWarningMeasurementConditions]);
505
+ }
506
+
507
+ RCT_EXPORT_METHOD(getCurrentViolatedMeasurementEnvironmentCondition
508
+ : (RCTPromiseResolveBlock)resolve rejecter
509
+ : (RCTPromiseRejectBlock)reject) {
510
+ resolve([ShenaiSDK getCurrentViolatedMeasurementEnvironmentCondition]);
511
+ }
512
+
513
+ RCT_EXPORT_METHOD(setMeasurementPreset : (NSInteger)preset) { [ShenaiSDK setMeasurementPreset:preset]; }
514
+
515
+ RCT_EXPORT_METHOD(getMeasurementPreset : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
516
+ MeasurementPreset preset = [ShenaiSDK getMeasurementPreset];
517
+ resolve(@(preset));
518
+ }
519
+
520
+ RCT_EXPORT_METHOD(setCameraMode : (NSInteger)mode) { [ShenaiSDK setCameraMode:mode]; }
521
+
522
+ RCT_EXPORT_METHOD(getCameraMode : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
523
+ CameraMode mode = [ShenaiSDK getCameraMode];
524
+ resolve(@(mode));
525
+ }
526
+
527
+ RCT_EXPORT_METHOD(getLastCameraError : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
528
+ resolve([ShenaiSDK getLastCameraError]);
529
+ }
530
+
531
+ RCT_EXPORT_METHOD(setScreen : (NSInteger)screen) { [ShenaiSDK setScreen:screen]; }
532
+
533
+ RCT_EXPORT_METHOD(getScreen : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
534
+ Screen screen = [ShenaiSDK getScreen];
535
+ resolve(@(screen));
536
+ }
537
+
538
+ // SDK Interface Elements
539
+
540
+ // User Interface
541
+ RCT_EXPORT_METHOD(setShowUserInterface : (BOOL)show) { [ShenaiSDK setShowUserInterface:show]; }
542
+
543
+ RCT_EXPORT_METHOD(getShowUserInterface : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
544
+ BOOL show = [ShenaiSDK getShowUserInterface];
545
+ resolve(@(show));
546
+ }
547
+
548
+ // Face Positioning Overlay
549
+ RCT_EXPORT_METHOD(setShowFacePositioningOverlay : (BOOL)show) { [ShenaiSDK setShowFacePositioningOverlay:show]; }
550
+
551
+ RCT_EXPORT_METHOD(getShowFacePositioningOverlay
552
+ : (RCTPromiseResolveBlock)resolve rejecter
553
+ : (RCTPromiseRejectBlock)reject) {
554
+ BOOL show = [ShenaiSDK getShowFacePositioningOverlay];
555
+ resolve(@(show));
556
+ }
557
+
558
+ // Visual Warnings
559
+ RCT_EXPORT_METHOD(setShowVisualWarnings : (BOOL)show) { [ShenaiSDK setShowVisualWarnings:show]; }
560
+
561
+ RCT_EXPORT_METHOD(getShowVisualWarnings : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
562
+ BOOL show = [ShenaiSDK getShowVisualWarnings];
563
+ resolve(@(show));
564
+ }
565
+
566
+ // Camera Swap
567
+ RCT_EXPORT_METHOD(setEnableCameraSwap : (BOOL)enable) { [ShenaiSDK setEnableCameraSwap:enable]; }
568
+
569
+ RCT_EXPORT_METHOD(getEnableCameraSwap : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
570
+ BOOL enable = [ShenaiSDK getEnableCameraSwap];
571
+ resolve(@(enable));
572
+ }
573
+
574
+ // Face Mask
575
+ RCT_EXPORT_METHOD(setShowFaceMask : (BOOL)show) { [ShenaiSDK setShowFaceMask:show]; }
576
+
577
+ RCT_EXPORT_METHOD(getShowFaceMask : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
578
+ BOOL show = [ShenaiSDK getShowFaceMask];
579
+ resolve(@(show));
580
+ }
581
+
582
+ // Blood Flow
583
+ RCT_EXPORT_METHOD(setShowBloodFlow : (BOOL)show) { [ShenaiSDK setShowBloodFlow:show]; }
584
+
585
+ RCT_EXPORT_METHOD(getShowBloodFlow : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
586
+ BOOL show = [ShenaiSDK getShowBloodFlow];
587
+ resolve(@(show));
588
+ }
589
+
590
+ RCT_EXPORT_METHOD(setIncludeTimestampInPdf : (BOOL)include) { [ShenaiSDK setIncludeTimestampInPdf:include]; }
591
+
592
+ RCT_EXPORT_METHOD(getIncludeTimestampInPdf : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
593
+ BOOL include = [ShenaiSDK getIncludeTimestampInPdf];
594
+ resolve(@(include));
595
+ }
596
+
597
+ RCT_EXPORT_METHOD(setPdfEmailSubject : (NSString *)subject) { [ShenaiSDK setPdfEmailSubject:subject]; }
598
+
599
+ RCT_EXPORT_METHOD(setPdfEmailBody : (NSString *)body) { [ShenaiSDK setPdfEmailBody:body]; }
600
+
601
+ RCT_EXPORT_METHOD(setShowStartStopButton : (BOOL)show) { [ShenaiSDK setShowStartStopButton:show]; }
602
+
603
+ RCT_EXPORT_METHOD(getShowStartStopButton : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
604
+ BOOL show = [ShenaiSDK getShowStartStopButton];
605
+ resolve(@(show));
606
+ }
607
+
608
+ RCT_EXPORT_METHOD(setEnableMeasurementsDashboard : (BOOL)enable) { [ShenaiSDK setEnableMeasurementsDashboard:enable]; }
609
+
610
+ RCT_EXPORT_METHOD(getEnableMeasurementsDashboard
611
+ : (RCTPromiseResolveBlock)resolve rejecter
612
+ : (RCTPromiseRejectBlock)reject) {
613
+ BOOL enabled = [ShenaiSDK getEnableMeasurementsDashboard];
614
+ resolve(@(enabled));
615
+ }
616
+
617
+ RCT_EXPORT_METHOD(setShowInfoButton : (BOOL)show) { [ShenaiSDK setShowInfoButton:show]; }
618
+
619
+ RCT_EXPORT_METHOD(getShowInfoButton : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
620
+ BOOL show = [ShenaiSDK getShowInfoButton];
621
+ resolve(@(show));
622
+ }
623
+
624
+ RCT_EXPORT_METHOD(getShowDisclaimer : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
625
+ BOOL show = [ShenaiSDK getShowDisclaimer];
626
+ resolve(@(show));
627
+ }
628
+
629
+ // Start After Success
630
+ RCT_EXPORT_METHOD(setEnableStartAfterSuccess : (BOOL)enable) { [ShenaiSDK setEnableStartAfterSuccess:enable]; }
631
+
632
+ RCT_EXPORT_METHOD(getEnableStartAfterSuccess
633
+ : (RCTPromiseResolveBlock)resolve rejecter
634
+ : (RCTPromiseRejectBlock)reject) {
635
+ BOOL enable = [ShenaiSDK getEnableStartAfterSuccess];
636
+ resolve(@(enable));
637
+ }
638
+
639
+ // SDK Face Positioning
640
+
641
+ RCT_EXPORT_METHOD(getFaceState : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
642
+ FaceState faceState = [ShenaiSDK getFaceState];
643
+ resolve(@(faceState));
644
+ }
645
+
646
+ RCT_EXPORT_METHOD(getNormalizedFaceBbox : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
647
+ NormalizedFaceBbox *faceBbox = [ShenaiSDK getNormalizedFaceBbox];
648
+ if (faceBbox) {
649
+ NSDictionary *bboxDict =
650
+ @{@"x" : @(faceBbox.x),
651
+ @"y" : @(faceBbox.y),
652
+ @"width" : @(faceBbox.width),
653
+ @"height" : @(faceBbox.height)};
654
+ resolve(bboxDict);
655
+ } else {
656
+ resolve([NSNull null]);
657
+ }
658
+ }
659
+
660
+ // SDK Measurement State
661
+
662
+ RCT_EXPORT_METHOD(getMeasurementState : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
663
+ MeasurementState measurementState = [ShenaiSDK getMeasurementState];
664
+ resolve(@(measurementState));
665
+ }
666
+
667
+ RCT_EXPORT_METHOD(isReadyToStartMeasurement
668
+ : (RCTPromiseResolveBlock)resolve rejecter
669
+ : (RCTPromiseRejectBlock)reject) {
670
+ resolve(@([ShenaiSDK isReadyToStartMeasurement]));
671
+ }
672
+
673
+ RCT_EXPORT_METHOD(areRequiredModelsDownloaded
674
+ : (RCTPromiseResolveBlock)resolve rejecter
675
+ : (RCTPromiseRejectBlock)reject) {
676
+ resolve(@([ShenaiSDK areRequiredModelsDownloaded]));
677
+ }
678
+
679
+ RCT_EXPORT_METHOD(getMeasurementProgressPercentage
680
+ : (RCTPromiseResolveBlock)resolve rejecter
681
+ : (RCTPromiseRejectBlock)reject) {
682
+ float progressPercentage = [ShenaiSDK getMeasurementProgressPercentage];
683
+ resolve(@(progressPercentage));
684
+ }
685
+
686
+ // SDK Measurement Results
687
+
688
+ RCT_EXPORT_METHOD(getHeartRate10s : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
689
+ NSNumber *heartRate10s = [ShenaiSDK getHeartRate10s];
690
+ if (heartRate10s) {
691
+ resolve(heartRate10s);
692
+ } else {
693
+ resolve([NSNull null]);
694
+ }
695
+ }
696
+
697
+ RCT_EXPORT_METHOD(getHeartRate4s : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
698
+ NSNumber *heartRate4s = [ShenaiSDK getHeartRate4s];
699
+ if (heartRate4s) {
700
+ resolve(heartRate4s);
701
+ } else {
702
+ resolve([NSNull null]);
703
+ }
704
+ }
705
+
706
+ // SDK Signals
707
+
708
+ // The JS API exposes these methods on iOS as well. The underlying iOS SDK currently doesn't provide
709
+ // a native HR time-series, so return an empty array instead of crashing with "undefined is not a function".
710
+ RCT_EXPORT_METHOD(getHeartRateHistory10s
711
+ : (nullable NSNumber *)maxTimeSec resolver
712
+ : (RCTPromiseResolveBlock)resolve rejecter
713
+ : (RCTPromiseRejectBlock)reject) {
714
+ (void)maxTimeSec;
715
+ resolve(@[]);
716
+ }
717
+
718
+ RCT_EXPORT_METHOD(getHeartRateHistory4s
719
+ : (nullable NSNumber *)maxTimeSec resolver
720
+ : (RCTPromiseResolveBlock)resolve rejecter
721
+ : (RCTPromiseRejectBlock)reject) {
722
+ (void)maxTimeSec;
723
+ resolve(@[]);
724
+ }
725
+
726
+ RCT_EXPORT_METHOD(getRealtimeHeartbeats
727
+ : (nullable NSNumber *)periodSec resolver
728
+ : (RCTPromiseResolveBlock)resolve rejecter
729
+ : (RCTPromiseRejectBlock)reject) {
730
+ NSArray<Heartbeat *> *heartbeats = [ShenaiSDK getRealtimeHeartbeats:periodSec];
731
+ NSMutableArray *resultArray = [NSMutableArray array];
732
+ for (Heartbeat *heartbeat in heartbeats) {
733
+ [resultArray addObject:@{
734
+ @"startLocationSec" : @(heartbeat.startLocationSec),
735
+ @"endLocationSec" : @(heartbeat.endLocationSec),
736
+ @"durationMs" : @(heartbeat.durationMs)
737
+ }];
738
+ }
739
+ resolve(resultArray);
740
+ }
741
+
742
+ RCT_EXPORT_METHOD(getFullPpgSignal : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
743
+ NSArray<NSNumber *> *ppgSignal = [ShenaiSDK getFullPPGSignal];
744
+ resolve(ppgSignal);
745
+ }
746
+
747
+ // SDK Recording
748
+
749
+ RCT_EXPORT_METHOD(setRecordingEnabled : (BOOL)enabled) { [ShenaiSDK setRecordingEnabled:enabled]; }
750
+
751
+ RCT_EXPORT_METHOD(getRecordingEnabled : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
752
+ BOOL isEnabled = [ShenaiSDK isRecordingEnabled];
753
+ resolve(@(isEnabled));
754
+ }
755
+
756
+ // SDK Quality Control
757
+
758
+ RCT_EXPORT_METHOD(getTotalBadSignalSeconds : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
759
+ float totalBadSeconds = [ShenaiSDK getTotalBadSignalSeconds];
760
+ resolve(@(totalBadSeconds));
761
+ }
762
+
763
+ RCT_EXPORT_METHOD(getCurrentSignalQualityMetric
764
+ : (RCTPromiseResolveBlock)resolve rejecter
765
+ : (RCTPromiseRejectBlock)reject) {
766
+ float signalQuality = [ShenaiSDK getCurrentSignalQualityMetric];
767
+ resolve(@(signalQuality));
768
+ }
769
+
770
+ // SDK Visualizations
771
+
772
+ RCT_EXPORT_METHOD(getSignalQualityMapPng : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
773
+ NSData *data = [ShenaiSDK getSignalQualityMapPng];
774
+ if (!data || data.length == 0) {
775
+ resolve([NSNull null]);
776
+ return;
777
+ }
778
+
779
+ const UInt8 *buffer = (const UInt8 *)data.bytes;
780
+ NSUInteger length = data.length;
781
+ NSMutableArray *array = [NSMutableArray arrayWithCapacity:length];
782
+ for (NSUInteger i = 0; i < length; i++) {
783
+ [array addObject:@(buffer[i])];
784
+ }
785
+ resolve(array);
786
+ }
787
+
788
+ RCT_EXPORT_METHOD(getFaceTexturePng : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
789
+ NSData *data = [ShenaiSDK getFaceTexturePng];
790
+ if (!data || data.length == 0) {
791
+ resolve([NSNull null]);
792
+ return;
793
+ }
794
+
795
+ const UInt8 *buffer = (const UInt8 *)data.bytes;
796
+ NSUInteger length = data.length;
797
+ NSMutableArray *array = [NSMutableArray arrayWithCapacity:length];
798
+ for (NSUInteger i = 0; i < length; i++) {
799
+ [array addObject:@(buffer[i])];
800
+ }
801
+ resolve(array);
802
+ }
803
+
804
+ RCT_EXPORT_METHOD(setLanguage : (NSString *)language) { [ShenaiSDK setLanguage:language]; }
805
+
806
+ // Utility method to convert RisksFactors JS object to RisksFactors Objective-C object
807
+ - (RisksFactors *)risksFactorsFromDictionary:(NSDictionary *)dict {
808
+ RisksFactors *factors = [[RisksFactors alloc] initWithAge:dict[@"age"]
809
+ cholesterol:dict[@"cholesterol"]
810
+ cholesterolHDL:dict[@"cholesterolHdl"]
811
+ sbp:dict[@"sbp"]
812
+ dbp:dict[@"dbp"]
813
+ isSmoker:dict[@"isSmoker"]
814
+ hypertensionTreatment:[dict[@"hypertensionTreatment"] integerValue]
815
+ hasDiabetes:dict[@"hasDiabetes"]
816
+ bodyHeight:dict[@"bodyHeight"]
817
+ bodyWeight:dict[@"bodyWeight"]
818
+ waistCircumference:dict[@"waistCircumference"]
819
+ neckCircumference:dict[@"neckCircumference"]
820
+ hipCircumference:dict[@"hipCircumference"]
821
+ gender:[dict[@"gender"] integerValue]
822
+ physicalActivity:[dict[@"physicalActivity"] integerValue]
823
+ country:dict[@"country"]
824
+ race:[dict[@"race"] integerValue]
825
+ vegetableFruitDiet:dict[@"vegetableFruitDiet"]
826
+ historyOfHighGlucose:dict[@"historyOfHighGlucose"]
827
+ historyOfHypertension:dict[@"historyOfHypertension"]
828
+ triglyceride:dict[@"triglyceride"]
829
+ fastingGlucose:dict[@"fastingGlucose"]
830
+ familyDiabetes:[dict[@"familyDiabetes"] integerValue]
831
+ parentalHypertension:[dict[@"parentalHypertension"] integerValue]];
832
+ return factors;
833
+ }
834
+
835
+ - (NSDictionary *)dictionaryFromRisksFactors:(RisksFactors *)factors {
836
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
837
+
838
+ if (factors.age != nil) dict[@"age"] = factors.age;
839
+ if (factors.cholesterol != nil) dict[@"cholesterol"] = factors.cholesterol;
840
+ if (factors.cholesterolHDL != nil) dict[@"cholesterolHdl"] = factors.cholesterolHDL;
841
+ if (factors.sbp != nil) dict[@"sbp"] = factors.sbp;
842
+ if (factors.dbp != nil) dict[@"dbp"] = factors.dbp;
843
+ if (factors.isSmoker != nil) dict[@"isSmoker"] = factors.isSmoker;
844
+ if (factors.hypertensionTreatment != nil) dict[@"hypertensionTreatment"] = @(factors.hypertensionTreatment);
845
+ if (factors.hasDiabetes != nil) dict[@"hasDiabetes"] = factors.hasDiabetes;
846
+ if (factors.bodyHeight != nil) dict[@"bodyHeight"] = factors.bodyHeight;
847
+ if (factors.bodyWeight != nil) dict[@"bodyWeight"] = factors.bodyWeight;
848
+ if (factors.waistCircumference != nil) dict[@"waistCircumference"] = factors.waistCircumference;
849
+ if (factors.neckCircumference != nil) dict[@"neckCircumference"] = factors.neckCircumference;
850
+ if (factors.hipCircumference != nil) dict[@"hipCircumference"] = factors.hipCircumference;
851
+ if (factors.gender != nil) dict[@"gender"] = @(factors.gender);
852
+ if (factors.physicalActivity != nil) dict[@"physicalActivity"] = @(factors.physicalActivity);
853
+ if (factors.country != nil) dict[@"country"] = factors.country;
854
+ if (factors.race != nil) dict[@"race"] = @(factors.race);
855
+ if (factors.vegetableFruitDiet != nil) dict[@"vegetableFruitDiet"] = factors.vegetableFruitDiet;
856
+ if (factors.historyOfHighGlucose != nil) dict[@"historyOfHighGlucose"] = factors.historyOfHighGlucose;
857
+ if (factors.historyOfHypertension != nil) dict[@"historyOfHypertension"] = factors.historyOfHypertension;
858
+ if (factors.fastingGlucose != nil) dict[@"fastingGlucose"] = factors.fastingGlucose;
859
+ if (factors.triglyceride != nil) dict[@"triglyceride"] = factors.triglyceride;
860
+ if (factors.parentalHypertension != nil) dict[@"parentalHypertension"] = @(factors.parentalHypertension);
861
+ if (factors.familyDiabetes != nil) dict[@"familyDiabetes"] = @(factors.familyDiabetes);
862
+
863
+ return [dict copy];
864
+ }
865
+
866
+ // Convert HealthRisks object to NSDictionary for JS
867
+ - (NSDictionary *)dictionaryFromHealthRisks:(HealthRisks *)risks {
868
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
869
+
870
+ // HardAndFatalEventsRisks conversion
871
+ NSMutableDictionary *hardAndFatalEventsDict = [NSMutableDictionary dictionary];
872
+ if (risks.hardAndFatalEvents.coronaryDeathEventRisk != nil) {
873
+ hardAndFatalEventsDict[@"coronaryDeathEventRisk"] = risks.hardAndFatalEvents.coronaryDeathEventRisk;
874
+ }
875
+ if (risks.hardAndFatalEvents.fatalStrokeEventRisk != nil) {
876
+ hardAndFatalEventsDict[@"fatalStrokeEventRisk"] = risks.hardAndFatalEvents.fatalStrokeEventRisk;
877
+ }
878
+ if (risks.hardAndFatalEvents.totalCvMortalityRisk != nil) {
879
+ hardAndFatalEventsDict[@"totalCvMortalityRisk"] = risks.hardAndFatalEvents.totalCvMortalityRisk;
880
+ }
881
+ if (risks.hardAndFatalEvents.hardCvEventRisk != nil) {
882
+ hardAndFatalEventsDict[@"hardCvEventRisk"] = risks.hardAndFatalEvents.hardCvEventRisk;
883
+ }
884
+ dict[@"hardAndFatalEvents"] = [hardAndFatalEventsDict copy];
885
+
886
+ // CVDiseasesRisks conversion
887
+ NSMutableDictionary *cvDiseasesDict = [NSMutableDictionary dictionary];
888
+ if (risks.cvDiseases.overallRisk != nil) {
889
+ cvDiseasesDict[@"overallRisk"] = risks.cvDiseases.overallRisk;
890
+ }
891
+ if (risks.cvDiseases.coronaryHeartDiseaseRisk != nil) {
892
+ cvDiseasesDict[@"coronaryHeartDiseaseRisk"] = risks.cvDiseases.coronaryHeartDiseaseRisk;
893
+ }
894
+ if (risks.cvDiseases.strokeRisk != nil) {
895
+ cvDiseasesDict[@"strokeRisk"] = risks.cvDiseases.strokeRisk;
896
+ }
897
+ if (risks.cvDiseases.heartFailureRisk != nil) {
898
+ cvDiseasesDict[@"heartFailureRisk"] = risks.cvDiseases.heartFailureRisk;
899
+ }
900
+ if (risks.cvDiseases.peripheralVascularDiseaseRisk != nil) {
901
+ cvDiseasesDict[@"peripheralVascularDiseaseRisk"] = risks.cvDiseases.peripheralVascularDiseaseRisk;
902
+ }
903
+ dict[@"cvDiseases"] = [cvDiseasesDict copy];
904
+
905
+ // RisksFactorsScores conversion
906
+ NSMutableDictionary *risksFactorsScoresDict = [NSMutableDictionary dictionary];
907
+ if (risks.scores.ageScore != nil) {
908
+ risksFactorsScoresDict[@"ageScore"] = risks.scores.ageScore;
909
+ }
910
+ if (risks.scores.sbpScore != nil) {
911
+ risksFactorsScoresDict[@"sbpScore"] = risks.scores.sbpScore;
912
+ }
913
+ if (risks.scores.smokingScore != nil) {
914
+ risksFactorsScoresDict[@"smokingScore"] = risks.scores.smokingScore;
915
+ }
916
+ if (risks.scores.diabetesScore != nil) {
917
+ risksFactorsScoresDict[@"diabetesScore"] = risks.scores.diabetesScore;
918
+ }
919
+ if (risks.scores.bmiScore != nil) {
920
+ risksFactorsScoresDict[@"bmiScore"] = risks.scores.bmiScore;
921
+ }
922
+ if (risks.scores.cholesterolScore != nil) {
923
+ risksFactorsScoresDict[@"cholesterolScore"] = risks.scores.cholesterolScore;
924
+ }
925
+ if (risks.scores.cholesterolHdlScore != nil) {
926
+ risksFactorsScoresDict[@"cholesterolHdlScore"] = risks.scores.cholesterolHdlScore;
927
+ }
928
+ if (risks.scores.totalScore != nil) {
929
+ risksFactorsScoresDict[@"totalScore"] = risks.scores.totalScore;
930
+ }
931
+ dict[@"scores"] = [risksFactorsScoresDict copy];
932
+
933
+ // Vascular Age
934
+ if (risks.vascularAge != nil) {
935
+ dict[@"vascularAge"] = risks.vascularAge;
936
+ }
937
+
938
+ // Wellness Score
939
+ if (risks.wellnessScore != nil) {
940
+ dict[@"wellnessScore"] = risks.wellnessScore;
941
+ }
942
+
943
+ // WHtR, BFP, BMR
944
+ if (risks.waistToHeightRatio != nil) {
945
+ dict[@"waistToHeightRatio"] = risks.waistToHeightRatio;
946
+ }
947
+ if (risks.bodyFatPercentage != nil) {
948
+ dict[@"bodyFatPercentage"] = risks.bodyFatPercentage;
949
+ }
950
+ if (risks.basalMetabolicRate != nil) {
951
+ dict[@"basalMetabolicRate"] = risks.basalMetabolicRate;
952
+ }
953
+
954
+ // ABSI, BRI, CI, TDEE
955
+ if (risks.aBodyShapeIndex != nil) {
956
+ dict[@"aBodyShapeIndex"] = risks.aBodyShapeIndex;
957
+ }
958
+ if (risks.bodyRoundnessIndex != nil) {
959
+ dict[@"bodyRoundnessIndex"] = risks.bodyRoundnessIndex;
960
+ }
961
+ if (risks.conicityIndex != nil) {
962
+ dict[@"conicityIndex"] = risks.conicityIndex;
963
+ }
964
+ if (risks.totalDailyEnergyExpenditure != nil) {
965
+ dict[@"totalDailyEnergyExpenditure"] = risks.totalDailyEnergyExpenditure;
966
+ }
967
+
968
+ // HR, DR, NAFLDR
969
+ if (risks.hypertensionRisk != nil) {
970
+ dict[@"hypertensionRisk"] = risks.hypertensionRisk;
971
+ }
972
+ if (risks.diabetesRisk != nil) {
973
+ dict[@"diabetesRisk"] = risks.diabetesRisk;
974
+ }
975
+ if (risks.nonAlcoholicFattyLiverDiseaseRisk != nil) {
976
+ dict[@"nonAlcoholicFattyLiverDiseaseRisk"] = @(risks.nonAlcoholicFattyLiverDiseaseRisk);
977
+ }
978
+ return [dict copy];
979
+ }
980
+
981
+ RCT_EXPORT_METHOD(getHealthRisksFactors : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
982
+ RisksFactors *factors = [ShenaiHealthRisks getHealthRisksFactors];
983
+ NSDictionary *resultsDict = [self dictionaryFromRisksFactors:factors];
984
+ resolve(resultsDict);
985
+ }
986
+
987
+ RCT_EXPORT_METHOD(clearHealthRisksFactors : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
988
+ [ShenaiHealthRisks clearHealthRisksFactors];
989
+ resolve(nil);
990
+ }
991
+
992
+ RCT_EXPORT_METHOD(getHealthRisks : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
993
+ HealthRisks *risks = [ShenaiHealthRisks getHealthRisks];
994
+ NSDictionary *resultsDict = [self dictionaryFromHealthRisks:risks];
995
+ resolve(resultsDict);
996
+ }
997
+
998
+ RCT_EXPORT_METHOD(computeHealthRisks
999
+ : (NSDictionary *)factorsDict resolver
1000
+ : (RCTPromiseResolveBlock)resolve rejecter
1001
+ : (RCTPromiseRejectBlock)reject) {
1002
+ RisksFactors *factors = [self risksFactorsFromDictionary:factorsDict];
1003
+ HealthRisks *risks = [ShenaiHealthRisks computeHealthRisks:factors];
1004
+ NSDictionary *resultsDict = [self dictionaryFromHealthRisks:risks];
1005
+ resolve(resultsDict);
1006
+ }
1007
+
1008
+ RCT_EXPORT_METHOD(getMaximalRisks
1009
+ : (NSDictionary *)factorsDict resolver
1010
+ : (RCTPromiseResolveBlock)resolve rejecter
1011
+ : (RCTPromiseRejectBlock)reject) {
1012
+ RisksFactors *factors = [self risksFactorsFromDictionary:factorsDict];
1013
+ HealthRisks *risks = [ShenaiHealthRisks getMaximalRisks:factors];
1014
+ NSDictionary *resultsDict = [self dictionaryFromHealthRisks:risks];
1015
+ resolve(resultsDict);
1016
+ }
1017
+
1018
+ RCT_EXPORT_METHOD(getMinimalRisks
1019
+ : (NSDictionary *)factorsDict resolver
1020
+ : (RCTPromiseResolveBlock)resolve rejecter
1021
+ : (RCTPromiseRejectBlock)reject) {
1022
+ RisksFactors *factors = [self risksFactorsFromDictionary:factorsDict];
1023
+ HealthRisks *risks = [ShenaiHealthRisks getMinimalRisks:factors];
1024
+ NSDictionary *resultsDict = [self dictionaryFromHealthRisks:risks];
1025
+ resolve(resultsDict);
1026
+ }
1027
+
1028
+ RCT_EXPORT_METHOD(getReferenceRisks
1029
+ : (NSDictionary *)factorsDict resolver
1030
+ : (RCTPromiseResolveBlock)resolve rejecter
1031
+ : (RCTPromiseRejectBlock)reject) {
1032
+ RisksFactors *factors = [self risksFactorsFromDictionary:factorsDict];
1033
+ HealthRisks *risks = [ShenaiHealthRisks getReferenceRisks:factors];
1034
+ NSDictionary *resultsDict = [self dictionaryFromHealthRisks:risks];
1035
+ resolve(resultsDict);
1036
+ }
1037
+
1038
+ RCT_EXPORT_METHOD(openMeasurementResultsPdfInBrowser
1039
+ : (RCTPromiseResolveBlock)resolve rejecter
1040
+ : (RCTPromiseRejectBlock)reject) {
1041
+ [ShenaiSDK openMeasurementResultsPdfInBrowser];
1042
+ resolve(@(YES));
1043
+ }
1044
+
1045
+ RCT_EXPORT_METHOD(sendMeasurementResultsPdfToEmail
1046
+ : (NSString *)email resolver
1047
+ : (RCTPromiseResolveBlock)resolve rejecter
1048
+ : (RCTPromiseRejectBlock)reject) {
1049
+ [ShenaiSDK sendMeasurementResultsPdfToEmail:email];
1050
+ resolve(@(YES));
1051
+ }
1052
+
1053
+ RCT_EXPORT_METHOD(requestMeasurementResultsPdfUrl
1054
+ : (RCTPromiseResolveBlock)resolve rejecter
1055
+ : (RCTPromiseRejectBlock)reject) {
1056
+ [ShenaiSDK requestMeasurementResultsPdfUrl];
1057
+ resolve(@(YES));
1058
+ }
1059
+
1060
+ RCT_EXPORT_METHOD(getMeasurementResultsPdfUrl
1061
+ : (RCTPromiseResolveBlock)resolve rejecter
1062
+ : (RCTPromiseRejectBlock)reject) {
1063
+ NSString *url = [ShenaiSDK getMeasurementResultsPdfUrl];
1064
+ if (url && url.length > 0) {
1065
+ resolve(url);
1066
+ } else {
1067
+ resolve([NSNull null]);
1068
+ }
1069
+ }
1070
+
1071
+ RCT_EXPORT_METHOD(requestMeasurementResultsPdfBytes
1072
+ : (RCTPromiseResolveBlock)resolve rejecter
1073
+ : (RCTPromiseRejectBlock)reject) {
1074
+ [ShenaiSDK requestMeasurementResultsPdfBytes];
1075
+ resolve(@(YES));
1076
+ }
1077
+
1078
+ RCT_EXPORT_METHOD(getMeasurementResultsPdfBytes
1079
+ : (RCTPromiseResolveBlock)resolve rejecter
1080
+ : (RCTPromiseRejectBlock)reject) {
1081
+ NSData *data = [ShenaiSDK getMeasurementResultsPdfBytes];
1082
+ if (!data) {
1083
+ resolve([NSNull null]);
1084
+ return;
1085
+ }
1086
+
1087
+ const UInt8 *buffer = (const UInt8 *)data.bytes;
1088
+ NSUInteger length = data.length;
1089
+ NSMutableArray *array = [NSMutableArray arrayWithCapacity:length];
1090
+ for (NSUInteger i = 0; i < length; i++) {
1091
+ [array addObject:@(buffer[i])]; // wrap each byte as NSNumber
1092
+ }
1093
+ resolve(array);
1094
+ }
1095
+
1096
+ RCT_EXPORT_METHOD(getResultAsFhirObservation
1097
+ : (RCTPromiseResolveBlock)resolve rejecter
1098
+ : (RCTPromiseRejectBlock)reject) {
1099
+ NSString *observation = [ShenaiSDK getResultAsFhirObservation];
1100
+ if (observation && observation.length > 0) {
1101
+ resolve(observation);
1102
+ } else {
1103
+ resolve([NSNull null]);
1104
+ }
1105
+ }
1106
+
1107
+ RCT_EXPORT_METHOD(sendResultFhirObservation
1108
+ : (NSString *)url resolver
1109
+ : (RCTPromiseResolveBlock)resolve rejecter
1110
+ : (RCTPromiseRejectBlock)reject) {
1111
+ [ShenaiSDK sendResultFhirObservation:url
1112
+ completion:^(NSString *_Nullable response) {
1113
+ if (response && response.length > 0) {
1114
+ resolve(response);
1115
+ } else {
1116
+ resolve([NSNull null]);
1117
+ }
1118
+ }];
1119
+ }
1120
+
1121
+ @end