expo-modules-core 2.3.12 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/cpp/JSharedObject.cpp +1 -1
  4. package/android/src/main/java/expo/modules/kotlin/classcomponent/ClassComponentBuilder.kt +22 -11
  5. package/android/src/main/java/expo/modules/kotlin/events/EventsDefinition.kt +9 -1
  6. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +6 -0
  7. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionBuilder.kt +28 -24
  8. package/android/src/main/java/expo/modules/kotlin/jni/ExpectedType.kt +122 -5
  9. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +6 -1
  10. package/android/src/main/java/expo/modules/kotlin/modules/ModuleConvertersBuilder.kt +50 -0
  11. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +31 -7
  12. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +28 -30
  13. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionData.kt +15 -0
  14. package/android/src/main/java/expo/modules/kotlin/traits/SavableTrait.kt +57 -0
  15. package/android/src/main/java/expo/modules/kotlin/traits/Trait.kt +8 -0
  16. package/android/src/main/java/expo/modules/kotlin/types/AnyType.kt +9 -0
  17. package/android/src/main/java/expo/modules/kotlin/types/EitherTypeConverter.kt +3 -3
  18. package/android/src/main/java/expo/modules/kotlin/types/ExpoDynamic.kt +57 -0
  19. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterCollection.kt +81 -0
  20. package/android/src/main/java/expo/modules/kotlin/views/ConcreteViewProp.kt +18 -3
  21. package/android/src/main/java/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +40 -25
  22. package/ios/Api/Factories/ViewFactories.swift +16 -0
  23. package/ios/Core/Conversions.swift +51 -7
  24. package/ios/Core/Convertibles/Convertibles+Color.swift +1 -1
  25. package/ios/Core/DynamicTypes/DynamicSharedObjectType.swift +25 -14
  26. package/ios/Core/Functions/AsyncFunctionDefinition.swift +1 -1
  27. package/ios/Core/Promise.swift +0 -11
  28. package/ios/Core/Views/ConcreteViewProp.swift +16 -0
  29. package/ios/Core/Views/SwiftUI/Convertibles+SwiftUI.swift +62 -0
  30. package/ios/Core/Views/SwiftUI/SwiftUIHostingView.swift +7 -0
  31. package/ios/DevTools/ExpoRequestInterceptorProtocol.swift +40 -0
  32. package/ios/DevTools/ModuleDefinitionEncoder.swift +182 -0
  33. package/ios/DevTools/URLSessionSessionDelegateProxy.swift +17 -0
  34. package/ios/ReactDelegates/ExpoReactDelegate.swift +2 -2
  35. package/ios/ReactDelegates/ExpoReactDelegateHandler.swift +1 -1
  36. package/ios/Tests/FunctionSpec.swift +27 -1
  37. package/package.json +3 -3
@@ -19,6 +19,7 @@ import expo.modules.kotlin.functions.AsyncFunctionWithPromiseComponent
19
19
  import expo.modules.kotlin.functions.Queues
20
20
  import expo.modules.kotlin.functions.createAsyncFunctionComponent
21
21
  import expo.modules.kotlin.modules.DefinitionMarker
22
+ import expo.modules.kotlin.types.TypeConverterProvider
22
23
  import expo.modules.kotlin.types.enforceType
23
24
  import expo.modules.kotlin.types.toAnyType
24
25
  import expo.modules.kotlin.types.toArgsArray
@@ -28,7 +29,8 @@ import kotlin.reflect.KType
28
29
  @DefinitionMarker
29
30
  class ViewDefinitionBuilder<T : View>(
30
31
  @PublishedApi internal val viewClass: KClass<T>,
31
- @PublishedApi internal val viewType: KType
32
+ @PublishedApi internal val viewType: KType,
33
+ @PublishedApi internal val converters: TypeConverterProvider? = null
32
34
  ) {
33
35
  @PublishedApi
34
36
  internal var name = viewClass.simpleName
@@ -144,6 +146,23 @@ class ViewDefinitionBuilder<T : View>(
144
146
  )
145
147
  }
146
148
 
149
+ /**
150
+ * Creates a view prop that defines its name, default value and setter.
151
+ */
152
+ @JvmName("PropGeneric")
153
+ inline fun <reified ViewType : View, reified PropType> Prop(
154
+ name: String,
155
+ defaultValue: PropType,
156
+ noinline body: (view: ViewType, prop: PropType) -> Unit
157
+ ) {
158
+ props[name] = ConcreteViewPropWithDefault(
159
+ name,
160
+ toAnyType<PropType>(),
161
+ body,
162
+ defaultValue
163
+ )
164
+ }
165
+
147
166
  inline fun <reified ViewType : View, reified PropType, reified CustomValueType> PropGroup(
148
167
  vararg props: Pair<String, CustomValueType>,
149
168
  noinline body: (view: ViewType, value: CustomValueType, prop: PropType) -> Unit
@@ -229,7 +248,7 @@ class ViewDefinitionBuilder<T : View>(
229
248
  name: String,
230
249
  crossinline body: (p0: P0, p1: P1) -> R
231
250
  ): AsyncFunctionComponent {
232
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1>()) { (p0, p1) ->
251
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1>(converterProvider = converters)) { (p0, p1) ->
233
252
  enforceType<P0, P1>(p0, p1)
234
253
  body(p0, p1)
235
254
  }.also {
@@ -242,7 +261,7 @@ class ViewDefinitionBuilder<T : View>(
242
261
  name: String,
243
262
  crossinline body: (p0: P0, p1: Promise) -> R
244
263
  ): AsyncFunctionComponent {
245
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0>()) { (p0), promise ->
264
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0>(converterProvider = converters)) { (p0), promise ->
246
265
  enforceType<P0>(p0)
247
266
  body(p0, promise)
248
267
  }.also {
@@ -254,7 +273,7 @@ class ViewDefinitionBuilder<T : View>(
254
273
  name: String,
255
274
  crossinline body: (p0: P0, p1: P1, p2: P2) -> R
256
275
  ): AsyncFunctionComponent {
257
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2>()) { (p0, p1, p2) ->
276
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2>(converterProvider = converters)) { (p0, p1, p2) ->
258
277
  enforceType<P0, P1, P2>(p0, p1, p2)
259
278
  body(p0, p1, p2)
260
279
  }.also {
@@ -267,7 +286,7 @@ class ViewDefinitionBuilder<T : View>(
267
286
  name: String,
268
287
  crossinline body: (p0: P0, p1: P1, p2: Promise) -> R
269
288
  ): AsyncFunctionComponent {
270
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1>()) { (p0, p1), promise ->
289
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1>(converterProvider = converters)) { (p0, p1), promise ->
271
290
  enforceType<P0, P1>(p0, p1)
272
291
  body(p0, p1, promise)
273
292
  }.also {
@@ -279,7 +298,7 @@ class ViewDefinitionBuilder<T : View>(
279
298
  name: String,
280
299
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3) -> R
281
300
  ): AsyncFunctionComponent {
282
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3>()) { (p0, p1, p2, p3) ->
301
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3>(converterProvider = converters)) { (p0, p1, p2, p3) ->
283
302
  enforceType<P0, P1, P2, P3>(p0, p1, p2, p3)
284
303
  body(p0, p1, p2, p3)
285
304
  }.also {
@@ -292,7 +311,7 @@ class ViewDefinitionBuilder<T : View>(
292
311
  name: String,
293
312
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: Promise) -> R
294
313
  ): AsyncFunctionComponent {
295
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2>()) { (p0, p1, p2), promise ->
314
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2>(converterProvider = converters)) { (p0, p1, p2), promise ->
296
315
  enforceType<P0, P1, P2>(p0, p1, p2)
297
316
  body(p0, p1, p2, promise)
298
317
  }.also {
@@ -304,7 +323,7 @@ class ViewDefinitionBuilder<T : View>(
304
323
  name: String,
305
324
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) -> R
306
325
  ): AsyncFunctionComponent {
307
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4>()) { (p0, p1, p2, p3, p4) ->
326
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4>(converterProvider = converters)) { (p0, p1, p2, p3, p4) ->
308
327
  enforceType<P0, P1, P2, P3, P4>(p0, p1, p2, p3, p4)
309
328
  body(p0, p1, p2, p3, p4)
310
329
  }.also {
@@ -317,7 +336,7 @@ class ViewDefinitionBuilder<T : View>(
317
336
  name: String,
318
337
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: Promise) -> R
319
338
  ): AsyncFunctionComponent {
320
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3>()) { (p0, p1, p2, p3), promise ->
339
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3>(converterProvider = converters)) { (p0, p1, p2, p3), promise ->
321
340
  enforceType<P0, P1, P2, P3>(p0, p1, p2, p3)
322
341
  body(p0, p1, p2, p3, promise)
323
342
  }.also {
@@ -329,7 +348,7 @@ class ViewDefinitionBuilder<T : View>(
329
348
  name: String,
330
349
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) -> R
331
350
  ): AsyncFunctionComponent {
332
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5>()) { (p0, p1, p2, p3, p4, p5) ->
351
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5>(converterProvider = converters)) { (p0, p1, p2, p3, p4, p5) ->
333
352
  enforceType<P0, P1, P2, P3, P4, P5>(p0, p1, p2, p3, p4, p5)
334
353
  body(p0, p1, p2, p3, p4, p5)
335
354
  }.also {
@@ -342,7 +361,7 @@ class ViewDefinitionBuilder<T : View>(
342
361
  name: String,
343
362
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: Promise) -> R
344
363
  ): AsyncFunctionComponent {
345
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4>()) { (p0, p1, p2, p3, p4), promise ->
364
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4>(converterProvider = converters)) { (p0, p1, p2, p3, p4), promise ->
346
365
  enforceType<P0, P1, P2, P3, P4>(p0, p1, p2, p3, p4)
347
366
  body(p0, p1, p2, p3, p4, promise)
348
367
  }.also {
@@ -354,7 +373,7 @@ class ViewDefinitionBuilder<T : View>(
354
373
  name: String,
355
374
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) -> R
356
375
  ): AsyncFunctionComponent {
357
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6>()) { (p0, p1, p2, p3, p4, p5, p6) ->
376
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6>(converterProvider = converters)) { (p0, p1, p2, p3, p4, p5, p6) ->
358
377
  enforceType<P0, P1, P2, P3, P4, P5, P6>(p0, p1, p2, p3, p4, p5, p6)
359
378
  body(p0, p1, p2, p3, p4, p5, p6)
360
379
  }.also {
@@ -367,7 +386,7 @@ class ViewDefinitionBuilder<T : View>(
367
386
  name: String,
368
387
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: Promise) -> R
369
388
  ): AsyncFunctionComponent {
370
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5>()) { (p0, p1, p2, p3, p4, p5), promise ->
389
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5>(converterProvider = converters)) { (p0, p1, p2, p3, p4, p5), promise ->
371
390
  enforceType<P0, P1, P2, P3, P4, P5>(p0, p1, p2, p3, p4, p5)
372
391
  body(p0, p1, p2, p3, p4, p5, promise)
373
392
  }.also {
@@ -379,7 +398,7 @@ class ViewDefinitionBuilder<T : View>(
379
398
  name: String,
380
399
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) -> R
381
400
  ): AsyncFunctionComponent {
382
- return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6, P7>()) { (p0, p1, p2, p3, p4, p5, p6, p7) ->
401
+ return createAsyncFunctionComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6, P7>(converterProvider = converters)) { (p0, p1, p2, p3, p4, p5, p6, p7) ->
383
402
  enforceType<P0, P1, P2, P3, P4, P5, P6, P7>(p0, p1, p2, p3, p4, p5, p6, p7)
384
403
  body(p0, p1, p2, p3, p4, p5, p6, p7)
385
404
  }.also {
@@ -392,7 +411,7 @@ class ViewDefinitionBuilder<T : View>(
392
411
  name: String,
393
412
  crossinline body: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: Promise) -> R
394
413
  ): AsyncFunctionComponent {
395
- return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6>()) { (p0, p1, p2, p3, p4, p5, p6), promise ->
414
+ return AsyncFunctionWithPromiseComponent(name, toArgsArray<P0, P1, P2, P3, P4, P5, P6>(converterProvider = converters)) { (p0, p1, p2, p3, p4, p5, p6), promise ->
396
415
  enforceType<P0, P1, P2, P3, P4, P5, P6>(p0, p1, p2, p3, p4, p5, p6)
397
416
  body(p0, p1, p2, p3, p4, p5, p6, promise)
398
417
  }.also {
@@ -402,14 +421,14 @@ class ViewDefinitionBuilder<T : View>(
402
421
 
403
422
  fun AsyncFunction(
404
423
  name: String
405
- ) = AsyncFunctionBuilder(name).also { functionBuilders[name] = it }
424
+ ) = AsyncFunctionBuilder(name, converters).also { functionBuilders[name] = it }
406
425
 
407
426
  private fun createViewFactory(): (Context, AppContext) -> View =
408
427
  viewFactory@{ context: Context, appContext: AppContext ->
409
428
  val fullConstructor = try {
410
429
  // Try to use constructor with two arguments
411
430
  viewClass.java.getConstructor(Context::class.java, AppContext::class.java)
412
- } catch (e: NoSuchMethodException) {
431
+ } catch (_: NoSuchMethodException) {
413
432
  null
414
433
  }
415
434
 
@@ -424,7 +443,7 @@ class ViewDefinitionBuilder<T : View>(
424
443
  val contextConstructor = try {
425
444
  // Try to use constructor that use Android's context
426
445
  viewClass.java.getConstructor(Context::class.java)
427
- } catch (e: NoSuchMethodException) {
446
+ } catch (_: NoSuchMethodException) {
428
447
  null
429
448
  }
430
449
 
@@ -439,15 +458,11 @@ class ViewDefinitionBuilder<T : View>(
439
458
  throw IllegalStateException("Didn't find a correct constructor for $viewClass")
440
459
  }
441
460
 
442
- private fun handleFailureDuringViewCreation(context: Context, appContext: AppContext, e: Throwable): View {
443
- Log.e("ExpoModulesCore", "Couldn't create view of type $viewClass", e)
461
+ private fun handleFailureDuringViewCreation(context: Context, appContext: AppContext, error: Throwable): View {
462
+ Log.e("ExpoModulesCore", "Couldn't create view of type $viewClass", error)
444
463
 
445
464
  appContext.errorManager?.reportExceptionToLogBox(
446
- if (e is CodedException) {
447
- e
448
- } else {
449
- UnexpectedException(e)
450
- }
465
+ error as? CodedException ?: UnexpectedException(error)
451
466
  )
452
467
 
453
468
  return if (ViewGroup::class.java.isAssignableFrom(viewClass.java)) {
@@ -46,6 +46,22 @@ public func Prop<ViewType: UIView, PropType: AnyArgument>(
46
46
  )
47
47
  }
48
48
 
49
+ /**
50
+ Creates a view prop that defines its name, default value and setter.
51
+ */
52
+ public func Prop<ViewType: UIView, PropType: AnyArgument>(
53
+ _ name: String,
54
+ _ defaultValue: PropType,
55
+ @_implicitSelfCapture _ setter: @escaping (ViewType, PropType) -> Void
56
+ ) -> ConcreteViewProp<ViewType, PropType> {
57
+ return ConcreteViewProp(
58
+ name: name,
59
+ propType: ~PropType.self,
60
+ defaultValue: defaultValue,
61
+ setter: setter
62
+ )
63
+ }
64
+
49
65
  // MARK: - View lifecycle
50
66
 
51
67
  /**
@@ -77,11 +77,17 @@ public struct Conversions {
77
77
  }
78
78
 
79
79
  /**
80
- Converts hex string to `UIColor` or throws an exception if the string is corrupted.
80
+ Converts color string to `UIColor` or throws an exception if the string is corrupted.
81
81
  */
82
- static func toColor(hexString hex: String) throws -> UIColor {
83
- var hexStr = hex
84
- .trimmingCharacters(in: .whitespacesAndNewlines)
82
+ static func toColor(colorString: String) throws -> UIColor {
83
+ let input = colorString.trimmingCharacters(in: .whitespacesAndNewlines)
84
+
85
+ // Handle RGB format
86
+ if input.hasPrefix("rgb") {
87
+ return try fromRGBString(input)
88
+ }
89
+
90
+ var hexStr = input
85
91
  .replacingOccurrences(of: "#", with: "")
86
92
 
87
93
  // If just RGB, set alpha to maximum
@@ -103,11 +109,33 @@ public struct Conversions {
103
109
 
104
110
  guard hexStr.range(of: #"^[0-9a-fA-F]{8}$"#, options: .regularExpression) != nil,
105
111
  Scanner(string: hexStr).scanHexInt64(&rgba) else {
106
- throw InvalidHexColorException(hex)
112
+ throw InvalidHexColorException(input)
107
113
  }
108
114
  return try toColor(rgba: rgba)
109
115
  }
110
116
 
117
+ private static func fromRGBString(_ rgbString: String) throws -> UIColor {
118
+ let components = rgbString
119
+ .replacingOccurrences(of: "rgba(", with: "")
120
+ .replacingOccurrences(of: "rgb(", with: "")
121
+ .replacingOccurrences(of: ")", with: "")
122
+ .split(separator: ",")
123
+ .compactMap { Double($0.trimmingCharacters(in: .whitespaces)) }
124
+
125
+ guard components.count >= 3,
126
+ components[0] >= 0 && components[0] <= 255,
127
+ components[1] >= 0 && components[1] <= 255,
128
+ components[2] >= 0 && components[2] <= 255 else {
129
+ throw InvalidRGBColorException(rgbString)
130
+ }
131
+
132
+ let alpha = components.count > 3 ? Double(components[3]) : 1.0
133
+ return UIColor(
134
+ red: CGFloat(components[0]) / 255.0,
135
+ green: CGFloat(components[1]) / 255.0,
136
+ blue: CGFloat(components[2]) / 255.0,
137
+ alpha: alpha)
138
+ }
111
139
  /**
112
140
  Converts an integer for ARGB color to `UIColor`. Since the alpha channel is represented by first 8 bits,
113
141
  it's optional out of the box. React Native converts colors to such format.
@@ -156,8 +184,15 @@ public struct Conversions {
156
184
  appContext: AppContext? = nil,
157
185
  dynamicType: AnyDynamicType? = nil
158
186
  ) -> Any {
159
- if let appContext, let result = try? dynamicType?.convertResult(value as Any, appContext: appContext) {
160
- return result
187
+ if let appContext {
188
+ // Dynamic type is provided
189
+ if dynamicType as? DynamicVoidType == nil, let result = try? dynamicType?.convertResult(value as Any, appContext: appContext) {
190
+ return result
191
+ }
192
+ // Dynamic type can be obtained from the value
193
+ if let value = value as? AnyArgument, let result = try? type(of: value).getDynamicType().convertResult(value as Any, appContext: appContext) {
194
+ return result
195
+ }
161
196
  }
162
197
  return convertFunctionResultInRuntime(value, appContext: appContext)
163
198
  }
@@ -281,6 +316,15 @@ public struct Conversions {
281
316
  }
282
317
  }
283
318
 
319
+ /**
320
+ An exception used when the rgb color string is invalid.
321
+ */
322
+ internal class InvalidRGBColorException: GenericException<String> {
323
+ override var reason: String {
324
+ "Provided rgb color string '\(param)' is invalid"
325
+ }
326
+ }
327
+
284
328
  /**
285
329
  An exception used when the integer value of the color would result in an overflow of `UInt32`.
286
330
  */
@@ -7,7 +7,7 @@ extension UIColor: Convertible {
7
7
  if let namedColorComponents = namedColors[value] {
8
8
  return uiColorWithComponents(namedColorComponents.map { $0 / 255 }) as! Self
9
9
  }
10
- return try Conversions.toColor(hexString: value) as! Self
10
+ return try Conversions.toColor(colorString: value) as! Self
11
11
  }
12
12
  if let components = value as? [Double] {
13
13
  return uiColorWithComponents(components) as! Self
@@ -59,28 +59,39 @@ internal struct DynamicSharedObjectType: AnyDynamicType {
59
59
  }
60
60
 
61
61
  func convertResult<ResultType>(_ result: ResultType, appContext: AppContext) throws -> Any {
62
- // If the result is a native shared object, create its JS representation and add the pair to the registry of shared objects.
63
- if let sharedObject = result as? SharedObject {
64
- // If the JS object already exists, just return it.
65
- if let jsObject = sharedObject.getJavaScriptObject() {
66
- return jsObject
67
- }
68
- guard let jsObject = try? appContext.newObject(nativeClassId: typeIdentifier) else {
69
- log.warn("Unable to create a JS object for \(description)")
70
- return Optional<Any>.none as Any
71
- }
62
+ // Postpone object creation to execute on the JS thread.
63
+ JavaScriptSharedObjectBinding.init {
64
+ // If the result is a native shared object, create its JS representation and add the pair to the registry of shared objects.
65
+ if let sharedObject = result as? SharedObject {
66
+ // If the JS object already exists, just return it.
67
+ if let jsObject = sharedObject.getJavaScriptObject() {
68
+ return jsObject
69
+ }
70
+ guard let jsObject = try? appContext.newObject(nativeClassId: typeIdentifier) else {
71
+ // Throwing is not possible here due to swift-objC interop.
72
+ log.warn("Unable to create a JS object for \(description)")
73
+ return JavaScriptObject()
74
+ }
72
75
 
73
- // Add newly created objects to the registry.
74
- appContext.sharedObjectRegistry.add(native: sharedObject, javaScript: jsObject)
76
+ // Add newly created objects to the registry.
77
+ appContext.sharedObjectRegistry.add(native: sharedObject, javaScript: jsObject)
75
78
 
76
- return jsObject
79
+ return jsObject
80
+ }
81
+ return JavaScriptObject()
77
82
  }
78
- return result
79
83
  }
80
84
 
81
85
  var description: String {
82
86
  return "SharedObject<\(innerType)>"
83
87
  }
88
+
89
+ func castToJS<ValueType>(_ value: ValueType, appContext: AppContext) throws -> JavaScriptValue {
90
+ if let value = value as? JavaScriptSharedObjectBinding {
91
+ return try JavaScriptValue.from(value.get(), runtime: appContext.runtime)
92
+ }
93
+ throw NativeSharedObjectNotFoundException()
94
+ }
84
95
  }
85
96
 
86
97
  internal final class NativeSharedObjectNotFoundException: Exception {
@@ -65,7 +65,7 @@ public final class AsyncFunctionDefinition<Args, FirstArgType, ReturnType>: AnyA
65
65
 
66
66
  func call(by owner: AnyObject?, withArguments args: [Any], appContext: AppContext, callback: @escaping (FunctionCallResult) -> ()) {
67
67
  let promise = Promise(appContext: appContext) { value in
68
- callback(.success(Conversions.convertFunctionResult(value, appContext: appContext)))
68
+ callback(.success(Conversions.convertFunctionResult(value, appContext: appContext, dynamicType: ~ReturnType.self)))
69
69
  } rejecter: { exception in
70
70
  callback(.failure(exception))
71
71
  }
@@ -19,17 +19,6 @@ public struct Promise: AnyArgument {
19
19
  }
20
20
 
21
21
  public func resolve(_ value: Any? = nil) {
22
- if let value = value as? AnySharedObject {
23
- resolver(JavaScriptSharedObjectBinding.init {
24
- return Conversions.convertFunctionResult(
25
- value,
26
- appContext: appContext,
27
- dynamicType: type(of: value).getDynamicType()
28
- // swiftlint:disable:next force_cast
29
- ) as! JavaScriptObject
30
- })
31
- return
32
- }
33
22
  resolver(value)
34
23
  }
35
24
 
@@ -16,6 +16,11 @@ public final class ConcreteViewProp<ViewType: UIView, PropType: AnyArgument>: An
16
16
  */
17
17
  private let propType: AnyDynamicType
18
18
 
19
+ /**
20
+ Default prop value
21
+ */
22
+ private let defaultValue: PropType?
23
+
19
24
  /**
20
25
  Closure to call to set the actual property on the given view.
21
26
  */
@@ -24,6 +29,14 @@ public final class ConcreteViewProp<ViewType: UIView, PropType: AnyArgument>: An
24
29
  internal init(name: String, propType: AnyDynamicType, setter: @escaping SetterType) {
25
30
  self.name = name
26
31
  self.propType = propType
32
+ self.defaultValue = nil
33
+ self.setter = setter
34
+ }
35
+
36
+ internal init(name: String, propType: AnyDynamicType, defaultValue: PropType, setter: @escaping SetterType) {
37
+ self.name = name
38
+ self.propType = propType
39
+ self.defaultValue = defaultValue
27
40
  self.setter = setter
28
41
  }
29
42
 
@@ -36,6 +49,9 @@ public final class ConcreteViewProp<ViewType: UIView, PropType: AnyArgument>: An
36
49
  guard let view = view as? ViewType else {
37
50
  throw IncompatibleViewException((propName: name, viewType: ViewType.self))
38
51
  }
52
+ if Optional.isNil(value), let defaultValue {
53
+ return setter(view, defaultValue)
54
+ }
39
55
  guard let value = try propType.cast(value, appContext: appContext) as? PropType else {
40
56
  throw Conversions.CastingException<PropType>(value)
41
57
  }
@@ -8,8 +8,70 @@ extension Color: Convertible {
8
8
  if let uiColor = try? UIColor.convert(from: value, appContext: appContext) {
9
9
  return Color(uiColor)
10
10
  }
11
+ // Context-dependent colors
12
+ if let stringValue = value as? String, let color = colorFromName(stringValue) {
13
+ return color
14
+ }
11
15
  throw Conversions.ConvertingException<Color>(value)
12
16
  }
17
+
18
+ private static func colorFromName(_ name: String) -> Color? {
19
+ switch name {
20
+ case "primary":
21
+ return .primary
22
+ case "secondary":
23
+ return .secondary
24
+ case "red":
25
+ return .red
26
+ case "orange":
27
+ return .orange
28
+ case "yellow":
29
+ return .yellow
30
+ case "green":
31
+ return .green
32
+ case "blue":
33
+ return .blue
34
+ case "purple":
35
+ return .purple
36
+ case "pink":
37
+ return .pink
38
+ case "white":
39
+ return .white
40
+ case "gray":
41
+ return .gray
42
+ case "black":
43
+ return .black
44
+ case "clear":
45
+ return .clear
46
+ case "mint":
47
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
48
+ return .mint
49
+ }
50
+ return nil
51
+ case "teal":
52
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
53
+ return .teal
54
+ }
55
+ return nil
56
+ case "cyan":
57
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
58
+ return .cyan
59
+ }
60
+ return nil
61
+ case "indigo":
62
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
63
+ return .indigo
64
+ }
65
+ return nil
66
+ case "brown":
67
+ if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
68
+ return .brown
69
+ }
70
+ return nil
71
+ default:
72
+ return nil
73
+ }
74
+ }
13
75
  }
14
76
 
15
77
  extension UnitPoint: Convertible {
@@ -12,6 +12,13 @@ internal protocol AnyExpoSwiftUIHostingView {
12
12
  }
13
13
 
14
14
  extension ExpoSwiftUI {
15
+ /**
16
+ Checks if the child view is wrapped by a `UIViewHost` and matches the specified SwiftUI view type.
17
+ */
18
+ public static func isHostingView(_ view: any AnyChild) -> Bool {
19
+ return view is UIViewHost
20
+ }
21
+
15
22
  /**
16
23
  Checks if the child view is wrapped by a `UIViewHost` and matches the specified SwiftUI view type.
17
24
  */
@@ -149,6 +149,46 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
149
149
  client?.urlProtocol(self, didReceive: challengeWithSender)
150
150
  }
151
151
 
152
+ public func urlSession(
153
+ _ session: URLSession,
154
+ task: URLSessionTask,
155
+ didSendBodyData bytesSent: Int64,
156
+ totalBytesSent: Int64,
157
+ totalBytesExpectedToSend: Int64
158
+ ) {
159
+ // swiftlint:disable line_length
160
+ // Apple does not support sending upload progress from URLProtocol back to URLProtocolClient.
161
+ // > Similarly, there is no way for your NSURLProtocol subclass to call the NSURLConnection delegate's -connection:needNewBodyStream: or -connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: methods (<rdar://problem/9226155> and <rdar://problem/9226157>). The latter is not a serious concern--it just means that your clients don't get upload progress--but the former is a real issue. If you're in a situation where you might need a second copy of a request body, you will need your own logic to make that copy, including the case where the body is a stream.
162
+ // See: https://developer.apple.com/library/archive/samplecode/CustomHTTPProtocol/Listings/Read_Me_About_CustomHTTPProtocol_txt.html
163
+ //
164
+ // Workaround to get the original task's URLSessionDelegate through the internal property and send upload process
165
+ // Fixes https://github.com/expo/expo/issues/28269
166
+ // swiftlint:enable line_length
167
+ guard let dataTask = dataTask_ else {
168
+ return
169
+ }
170
+
171
+ // Prevent recursive delegate calls
172
+ if task === dataTask {
173
+ return
174
+ }
175
+
176
+ if #available(iOS 15.0, tvOS 15.0, macOS 12.0, *), let delegate = dataTask.delegate {
177
+ // For the case if the task has a dedicated delegate than the default delegate from its URLSession
178
+ delegate.urlSession?(
179
+ session, task: dataTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend
180
+ )
181
+ return
182
+ }
183
+ guard let session = task.value(forKey: "session") as? URLSession,
184
+ let delegate = session.delegate as? URLSessionTaskDelegate else {
185
+ return
186
+ }
187
+ delegate.urlSession?(
188
+ session, task: dataTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend
189
+ )
190
+ }
191
+
152
192
  /**
153
193
  Data structure to save the response for redirection
154
194
  */