@sency/react-native-smkit-ui 2.2.1 → 2.2.2

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 (30) hide show
  1. package/README.md +218 -65
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/java/com/smkituilibrary/SmkitUiLibraryModule.kt +237 -3
  4. package/android/src/main/java/com/smkituilibrary/mapper/SMMapper.kt +70 -10
  5. package/android/src/main/java/com/smkituilibrary/model/SMKitExercise.kt +40 -0
  6. package/android/src/main/java/com/smkituilibrary/model/SMKitWorkout.kt +8 -0
  7. package/android/src/main/java/com/smkituilibrary/model/SMKitWorkoutConfig.kt +2 -0
  8. package/android/src/main/java/com/smkituilibrary/model/SMUserData.kt +5 -1
  9. package/ios/SMKitUIManager.mm +33 -11
  10. package/ios/SMKitUIManager.swift +237 -36
  11. package/lib/commonjs/SMWorkout.js +239 -177
  12. package/lib/commonjs/SMWorkout.js.map +1 -1
  13. package/lib/commonjs/index.js +187 -5
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/module/SMWorkout.js +232 -181
  16. package/lib/module/SMWorkout.js.map +1 -1
  17. package/lib/module/index.js +165 -5
  18. package/lib/module/index.js.map +1 -1
  19. package/package.json +2 -2
  20. package/react-native-smkit-ui.podspec +2 -2
  21. package/src/SMWorkout.tsx +353 -176
  22. package/src/index.tsx +167 -8
  23. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  24. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  25. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  26. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  27. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  28. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  29. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  30. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
@@ -18,6 +18,7 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
18
18
 
19
19
  var summaryData: WorkoutSummaryData?
20
20
  var didCompleteWorkout = false
21
+ var startTimerOnFirstActivityOverride: Bool?
21
22
 
22
23
  var didReciveSummary = false {
23
24
  didSet {
@@ -65,7 +66,7 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
65
66
  var isConfigDone = false
66
67
  var didFail = false
67
68
  // RN SDK policy: do not wait for first activity before starting exercise timer.
68
- SMKitUIModel.startTimerOnFirstActivity = false
69
+ SMKitUIModel.startTimerOnFirstActivity = self.startTimerOnFirstActivityOverride ?? false
69
70
  // RN SDK policy: keep Jinni wake-word / orb features internal — never enabled from RN.
70
71
  SMKitUIModel.alwaysOnJinniWakeWordDuringWorkout = false
71
72
  SMKitUIModel.configure(authKey: "\(key)", includesHighlights: false, onSuccess: {
@@ -153,7 +154,6 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
153
154
  do {
154
155
  let json = try self.stringJsonToDic(stringJSON: rawJson)
155
156
  let workout = try SMWorkout(FromJson: json)
156
- self.sanitizeInternalFlags(workout)
157
157
  let (finalModifications, showPhoneCalibration) = self.processModifications(modifications)
158
158
  try SMKitUIModel.startWorkout(viewController: smkitUIViewController, workout: workout, delegate: self, modifications: finalModifications, showPhoneCalibration: showPhoneCalibration)
159
159
  } catch {
@@ -177,7 +177,6 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
177
177
  let user = try self.getUserData(rawJson: userData)
178
178
  let json = try self.stringJsonToDic(stringJSON: rawJson)
179
179
  let assessment = try SMWorkoutAssessment.initFromJSON(json)
180
- self.sanitizeInternalFlags(assessment)
181
180
  let (finalModifications, showPhoneCalibration) = self.processModifications(modifications)
182
181
  try SMKitUIModel.startCustomAssessment(viewController: smkitUIViewController, assessment: assessment, userData: user, forceShowUserDataScreen: forceShowUserDataScreen, showSummary: showSummary, delegate: self, onFailure: { error in
183
182
  onWorkoutFailed("startCustomAssessment Failed", error.localizedDescription, error)
@@ -233,7 +232,8 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
233
232
  workoutDuration: workoutDuration,
234
233
  language: language,
235
234
  programID: programID,
236
- shortIntro: false
235
+ phonePosition: self.resolvePhonePosition(json["phonePosition"] as? String),
236
+ shortIntro: json["shortIntro"] as? Bool ?? false
237
237
  )
238
238
 
239
239
  SMKitUIModel.startWorkoutFromProgram(viewController: smkitUIViewController, workoutConfig: config, delegate: self, onFailure: { error in
@@ -242,17 +242,19 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
242
242
  }
243
243
  }
244
244
 
245
- @objc(setSessionLanguage:)
246
- func setSessionLanguage(language: String) {
245
+ @objc(setSessionLanguage:onSuccess:onFailure:)
246
+ func setSessionLanguage(language: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
247
247
  let lang = resolveLanguage(language)
248
248
  SMKitUIModel.setSessionLanguage(language: lang)
249
249
  // Auto-sync phone calibration language so callers don't need to set it separately
250
250
  SMKitUIModel.setPhoneCalibrationLanguage(language: lang)
251
+ onSuccess(nil)
251
252
  }
252
253
 
253
- @objc(setPhoneCalibrationLanguage:)
254
- func setPhoneCalibrationLanguage(language: String) {
254
+ @objc(setPhoneCalibrationLanguage:onSuccess:onFailure:)
255
+ func setPhoneCalibrationLanguage(language: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
255
256
  SMKitUIModel.setPhoneCalibrationLanguage(language: resolveLanguage(language))
257
+ onSuccess(nil)
256
258
  }
257
259
 
258
260
  private func resolveLanguage(_ language: String) -> SencySupportedLanguage {
@@ -263,29 +265,40 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
263
265
  }
264
266
  }
265
267
 
266
- @objc(setEndExercisePreferences:)
267
- func setEndExercisePreferences(preferencesString: String) {
268
+ private func resolvePhonePosition(_ position: String?) -> PhonePosition {
269
+ switch position?.lowercased() {
270
+ case "elevated": return .Elevated
271
+ default: return .Floor
272
+ }
273
+ }
274
+
275
+ @objc(setEndExercisePreferences:onSuccess:onFailure:)
276
+ func setEndExercisePreferences(preferencesString: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
268
277
  let target = EndExercisePreferences(rawValue: preferencesString) ?? .Default
269
278
  SMKitUIModel.setEndExercisePreferences(endExercisePreferences: target)
279
+ onSuccess(nil)
270
280
  }
271
281
 
272
- @objc(setCounterPreferences:)
273
- func setCounterPreferences(preferencesString: String) {
282
+ @objc(setCounterPreferences:onSuccess:onFailure:)
283
+ func setCounterPreferences(preferencesString: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
274
284
  let preferences = CounterPreferences(rawValue: preferencesString) ?? .Default
275
285
  SMKitUIModel.setCounterPreferences(counterPreferences: preferences)
286
+ onSuccess(nil)
276
287
  }
277
288
 
278
- @objc(setIntelligenceRestEnabled:)
279
- func setIntelligenceRestEnabled(_ enabled: Bool) {
289
+ @objc(setIntelligenceRestEnabled:onSuccess:onFailure:)
290
+ func setIntelligenceRestEnabled(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
280
291
  SMKitUIModel.enableIntelligenceRest = enabled
292
+ onSuccess(nil)
281
293
  }
282
294
 
283
- @objc(setSkeletonSettings:)
284
- func setSkeletonSettings(_ configString: String) {
295
+ @objc(setSkeletonSettings:onSuccess:onFailure:)
296
+ func setSkeletonSettings(_ configString: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
285
297
  guard let data = configString.data(using: .utf8),
286
298
  let rawConfig = try? JSONSerialization.jsonObject(with: data),
287
299
  let config = rawConfig as? [String: Any] else {
288
300
  print("setSkeletonSettings: invalid JSON")
301
+ onFailure("setSkeletonSettings Failed", "Invalid JSON", nil)
289
302
  return
290
303
  }
291
304
  if let v = config["hidden"] as? Bool { SMKitUIModel.skeletonHidden = v }
@@ -304,37 +317,49 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
304
317
  if let v = config["dotsOuterColor"] as? String, let c = skeletonColor(from: v) { SMKitUIModel.skeletonDotsOuterColorOption = c }
305
318
  if let v = config["connectionsInnerColor"] as? String, let c = skeletonColor(from: v) { SMKitUIModel.skeletonConnectionsInnerColorOption = c }
306
319
  if let v = config["connectionsOuterColor"] as? String, let c = skeletonColor(from: v) { SMKitUIModel.skeletonConnectionsOuterColorOption = c }
320
+ onSuccess(nil)
307
321
  }
308
322
 
309
- @objc(setPauseTypes:)
310
- func setPauseTypes(_ typesJson: String) {
323
+ @objc(setPauseTypes:onSuccess:onFailure:)
324
+ func setPauseTypes(_ typesJson: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
311
325
  guard let data = typesJson.data(using: .utf8),
312
326
  let rawArray = try? JSONSerialization.jsonObject(with: data),
313
- let arr = rawArray as? [String] else { return }
327
+ let arr = rawArray as? [String] else {
328
+ onFailure("setPauseTypes Failed", "Invalid JSON", nil)
329
+ return
330
+ }
314
331
  let types = arr.compactMap { pauseAlertType(from: $0) }
315
- try? SMKitUIModel.setAllowedPauseTypes(types: types)
332
+ do {
333
+ try SMKitUIModel.setAllowedPauseTypes(types: types)
334
+ onSuccess(nil)
335
+ } catch {
336
+ onFailure("setPauseTypes Failed", error.localizedDescription, error)
337
+ }
316
338
  }
317
339
 
318
- @objc(setAllowAudioMixing:)
319
- func setAllowAudioMixing(_ enabled: Bool) {
340
+ @objc(setAllowAudioMixing:onSuccess:onFailure:)
341
+ func setAllowAudioMixing(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
320
342
  SMKitUIModel.allowAudioMixing = enabled
343
+ onSuccess(nil)
321
344
  }
322
345
 
323
- @objc(setShowExternalAudioControl:)
324
- func setShowExternalAudioControl(_ enabled: Bool) {
346
+ @objc(setShowExternalAudioControl:onSuccess:onFailure:)
347
+ func setShowExternalAudioControl(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
325
348
  SMKitUIModel.showExternalAudioControl = enabled
349
+ onSuccess(nil)
326
350
  }
327
351
 
328
- @objc(setAccuratePoseEstimation:)
329
- func setAccuratePoseEstimation(_ enabled: Bool) {
352
+ @objc(setAccuratePoseEstimation:onSuccess:onFailure:)
353
+ func setAccuratePoseEstimation(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
330
354
  SMKitUIModel.accuratePoseEstimation = enabled
355
+ onSuccess(nil)
331
356
  }
332
357
 
333
358
  // Instruction video config (align with ../smkit_android ConfigureViewModel).
334
359
  // displayMode: "default" | "mediumCycle". mediumCycle: video at 75% for first N reps, then 50%.
335
360
  // mediumSizeCycles: 1–5, only used when displayMode is mediumCycle.
336
- @objc(setInstructionVideoConfig:)
337
- func setInstructionVideoConfig(_ configString: String) {
361
+ @objc(setInstructionVideoConfig:onSuccess:onFailure:)
362
+ func setInstructionVideoConfig(_ configString: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
338
363
  do {
339
364
  let configDict = try stringJsonToDic(stringJSON: configString)
340
365
 
@@ -349,9 +374,175 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
349
374
  SMKitUIModel.instructionVideoConfig = config
350
375
 
351
376
  print("SMKitUIManager: Set InstructionVideoConfig - mode: \(displayModeStr), cycles: \(mediumSizeCycles)")
377
+ onSuccess(nil)
352
378
  } catch {
353
379
  print("SMKitUIManager: Error parsing InstructionVideoConfig - \(error.localizedDescription)")
380
+ onFailure("setInstructionVideoConfig Failed", error.localizedDescription, error)
381
+ }
382
+ }
383
+
384
+ @objc(setColorTheme:onSuccess:onFailure:)
385
+ func setColorTheme(_ theme: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
386
+ SMKitUIModel.colorTheme = mapColorToTheme(primaryColor: theme)
387
+ onSuccess(nil)
388
+ }
389
+
390
+ @objc(setPlayPhoneCalibrationAudio:onSuccess:onFailure:)
391
+ func setPlayPhoneCalibrationAudio(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
392
+ SMKitUIModel.playPhoneCalibrationAudio = enabled
393
+ onSuccess(nil)
394
+ }
395
+
396
+ @objc(setPlayBodyCalibrationAudio:onSuccess:onFailure:)
397
+ func setPlayBodyCalibrationAudio(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
398
+ SMKitUIModel.playBodyCalibrationAudio = enabled
399
+ onSuccess(nil)
400
+ }
401
+
402
+ @objc(setStartTimerOnFirstActivity:onSuccess:onFailure:)
403
+ func setStartTimerOnFirstActivity(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
404
+ startTimerOnFirstActivityOverride = enabled
405
+ SMKitUIModel.startTimerOnFirstActivity = enabled
406
+ onSuccess(nil)
407
+ }
408
+
409
+ @objc(setWorkoutContinuationTimerDuration:onSuccess:onFailure:)
410
+ func setWorkoutContinuationTimerDuration(_ seconds: NSNumber, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
411
+ SMKitUIModel.workoutContinuationTimerDuration = max(1.0, seconds.doubleValue)
412
+ onSuccess(nil)
413
+ }
414
+
415
+ @objc(setPhoneMovementCountPreventionEnabled:onSuccess:onFailure:)
416
+ func setPhoneMovementCountPreventionEnabled(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
417
+ SMKitUIModel.enablePhoneMovementCountPrevention = enabled
418
+ onSuccess(nil)
419
+ }
420
+
421
+ @objc(setVariationMismatchFeedbackEnabled:onSuccess:onFailure:)
422
+ func setVariationMismatchFeedbackEnabled(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
423
+ SMKitUIModel.enableVariationMismatchFeedback = enabled
424
+ onSuccess(nil)
425
+ }
426
+
427
+ @objc(setUseDefaultGuidanceMode:onSuccess:onFailure:)
428
+ func setUseDefaultGuidanceMode(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
429
+ SMKitFlowManager.useDefaultGuidanceMode = enabled
430
+ onSuccess(nil)
431
+ }
432
+
433
+ @objc(setGuidanceDebugLogging:onSuccess:onFailure:)
434
+ func setGuidanceDebugLogging(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
435
+ UserDefaults.standard.set(enabled, forKey: "guidanceDebugLogging")
436
+ onSuccess(nil)
437
+ }
438
+
439
+ @objc(setEnableButtonTutorial:onSuccess:onFailure:)
440
+ func setEnableButtonTutorial(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
441
+ SMKitUIModel.enableButtonTutorial = enabled
442
+ onSuccess(nil)
443
+ }
444
+
445
+ @objc(setButtonTutorialCompletionAudioUri:onSuccess:onFailure:)
446
+ func setButtonTutorialCompletionAudioUri(_ uri: NSString?, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
447
+ guard let raw = uri as? String, !raw.isEmpty else {
448
+ SMKitUIModel.buttonTutorialCompletionAudioURL = nil
449
+ onSuccess(nil)
450
+ return
354
451
  }
452
+ SMKitUIModel.buttonTutorialCompletionAudioURL = URL(string: raw)
453
+ onSuccess(nil)
454
+ }
455
+
456
+ @objc(setShowRowingPhoneCalibration:onSuccess:onFailure:)
457
+ func setShowRowingPhoneCalibration(_ enabled: Bool, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
458
+ SMKitUIModel.showRowingPhoneCalibration = enabled
459
+ onSuccess(nil)
460
+ }
461
+
462
+ @objc(setFeedbacksUIToExclude:onSuccess:onFailure:)
463
+ func setFeedbacksUIToExclude(_ feedbacksJson: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
464
+ do {
465
+ let feedbacks = try parseFeedbackTypes(feedbacksJson)
466
+ SMKitUIModel.setFeedbacksUIToExclude(feedbacksUIToExclude: feedbacks)
467
+ onSuccess(nil)
468
+ } catch {
469
+ onFailure("setFeedbacksUIToExclude Failed", error.localizedDescription, error)
470
+ }
471
+ }
472
+
473
+ @objc(setExcludedFeedbacks:onSuccess:onFailure:)
474
+ func setExcludedFeedbacks(_ feedbacksJson: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
475
+ do {
476
+ let feedbacks = try parseFeedbackTypes(feedbacksJson)
477
+ SMKitUIModel.setExcludedFeedbacks(excludedFeedbacks: feedbacks)
478
+ onSuccess(nil)
479
+ } catch {
480
+ onFailure("setExcludedFeedbacks Failed", error.localizedDescription, error)
481
+ }
482
+ }
483
+
484
+ @objc(setConfigString:onSuccess:onFailure:)
485
+ func setConfigString(_ configString: NSString?, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
486
+ // The iOS SMKitUI public surface does not expose a global config-string setter.
487
+ // Exercise modifications remain supported through the start* APIs.
488
+ onSuccess(nil)
489
+ }
490
+
491
+ @objc(clearAdaptiveRomCache:onFailure:)
492
+ func clearAdaptiveRomCache(onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
493
+ SMKitFlowManager.clearAdaptiveRomPilotCache()
494
+ onSuccess(nil)
495
+ }
496
+
497
+ @objc(quitWorkout:onFailure:)
498
+ func quitWorkout(onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
499
+ SMKitUIModel.exitSDK()
500
+ onSuccess(nil)
501
+ }
502
+
503
+ @objc(pauseSDK:onFailure:)
504
+ func pauseSDK(onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
505
+ do {
506
+ try SMKitUIModel.pauseSDK()
507
+ onSuccess(nil)
508
+ } catch {
509
+ onFailure("pauseSDK Failed", error.localizedDescription, error)
510
+ }
511
+ }
512
+
513
+ @objc(resumeSDK:onFailure:)
514
+ func resumeSDK(onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
515
+ do {
516
+ try SMKitUIModel.resumeSDK()
517
+ onSuccess(nil)
518
+ } catch {
519
+ onFailure("resumeSDK Failed", error.localizedDescription, error)
520
+ }
521
+ }
522
+
523
+ @objc(getSupportedMovements:onFailure:)
524
+ func getSupportedMovements(onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
525
+ onSuccess(SMKitUIModel.getSupportedMovements() ?? [])
526
+ }
527
+
528
+ @objc(getExerciseType:onSuccess:onFailure:)
529
+ func getExerciseType(_ detector: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
530
+ do {
531
+ let exerciseType = try SMKitUIModel.getExerciseType(type: detector)
532
+ onSuccess(String(describing: exerciseType))
533
+ } catch {
534
+ onFailure("getExerciseType Failed", error.localizedDescription, error)
535
+ }
536
+ }
537
+
538
+ @objc(setPoseModelChoice:onSuccess:onFailure:)
539
+ func setPoseModelChoice(_ choice: String, onSuccess: @escaping RCTPromiseResolveBlock, onFailure: @escaping RCTPromiseRejectBlock) {
540
+ guard ["AdaptiveChoice", "Prime", "Pro", "Lite", "UltraLite", "Basic"].contains(choice) else {
541
+ onFailure("setPoseModelChoice Failed", "Invalid pose model choice: \(choice)", nil)
542
+ return
543
+ }
544
+ // Android-only. Resolve for API parity.
545
+ onSuccess(nil)
355
546
  }
356
547
 
357
548
  private func pauseAlertType(from s: String) -> PauseAlertType? {
@@ -460,14 +651,24 @@ class SMKitUIManager: NSObject, RCTBridgeModule {
460
651
  }
461
652
  }
462
653
 
463
- /// Force-disable SDK features that are intentionally not exposed through the RN bridge.
464
- /// Applies to any per-exercise flags that consumers might smuggle in through raw workout JSON.
465
- private func sanitizeInternalFlags(_ workout: SMWorkout) {
466
- workout.exercises?.forEach { ex in
467
- ex.shortIntro = false
468
- ex.adaptiveRomFeedbackEnabled = false
469
- ex.adaptiveRomWarmupReps = 2
470
- ex.guidanceVideoSegments = nil
654
+ private func parseFeedbackTypes(_ feedbacksJson: String) throws -> [FormFeedbackTypeBr] {
655
+ guard let data = feedbacksJson.data(using: .utf8),
656
+ let rawArray = try JSONSerialization.jsonObject(with: data) as? [String] else {
657
+ throw NSError(
658
+ domain: "SMKitUIManager",
659
+ code: 0,
660
+ userInfo: [NSLocalizedDescriptionKey: "Expected a JSON array of feedback type names"]
661
+ )
662
+ }
663
+ return try rawArray.map { raw in
664
+ guard let feedback = FormFeedbackTypeBr(rawValue: raw) else {
665
+ throw NSError(
666
+ domain: "SMKitUIManager",
667
+ code: 0,
668
+ userInfo: [NSLocalizedDescriptionKey: "Invalid feedback type: \(raw)"]
669
+ )
670
+ }
671
+ return feedback
471
672
  }
472
673
  }
473
674