expo 53.0.13 → 53.0.15
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.
- package/android/build.gradle +4 -2
- package/android/src/main/java/expo/modules/ReactActivityDelegateWrapper.kt +155 -76
- package/android/src/main/java/expo/modules/fetch/FetchExceptions.kt +2 -2
- package/android/src/main/java/expo/modules/fetch/NativeRequest.kt +1 -1
- package/android/src/main/java/expo/modules/fetch/NativeResponse.kt +10 -9
- package/android/src/main/java/expo/modules/fetch/ResponseState.kt +1 -1
- package/android/src/test/java/expo/modules/ReactActivityDelegateWrapperDelayLoadTest.kt +336 -0
- package/bundledNativeModules.json +27 -27
- package/package.json +10 -10
package/android/build.gradle
CHANGED
|
@@ -32,7 +32,7 @@ buildscript {
|
|
|
32
32
|
def reactNativeVersion = project.extensions.getByType(ExpoModuleExtension).reactNativeVersion
|
|
33
33
|
|
|
34
34
|
group = 'host.exp.exponent'
|
|
35
|
-
version = '53.0.
|
|
35
|
+
version = '53.0.15'
|
|
36
36
|
|
|
37
37
|
expoModule {
|
|
38
38
|
// We can't prebuild the module because it depends on the generated files.
|
|
@@ -43,7 +43,7 @@ android {
|
|
|
43
43
|
namespace "expo.core"
|
|
44
44
|
defaultConfig {
|
|
45
45
|
versionCode 1
|
|
46
|
-
versionName "53.0.
|
|
46
|
+
versionName "53.0.15"
|
|
47
47
|
consumerProguardFiles("proguard-rules.pro")
|
|
48
48
|
}
|
|
49
49
|
testOptions {
|
|
@@ -77,6 +77,8 @@ dependencies { dependencyHandler ->
|
|
|
77
77
|
testImplementation 'androidx.test:core:1.5.0'
|
|
78
78
|
testImplementation "com.google.truth:truth:1.1.2"
|
|
79
79
|
testImplementation 'io.mockk:mockk:1.13.5'
|
|
80
|
+
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2'
|
|
81
|
+
testImplementation 'org.robolectric:robolectric:4.15.1'
|
|
80
82
|
|
|
81
83
|
if (useLegacyAutolinking) {
|
|
82
84
|
// Link expo modules as dependencies of the adapter. It uses `api` configuration so they all will be visible for the app as well.
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
package expo.modules
|
|
2
2
|
|
|
3
|
+
import android.annotation.SuppressLint
|
|
3
4
|
import android.app.Activity
|
|
4
5
|
import android.content.Context
|
|
5
6
|
import android.content.Intent
|
|
7
|
+
import android.content.pm.ActivityInfo
|
|
6
8
|
import android.content.res.Configuration
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.Build.VERSION
|
|
7
11
|
import android.os.Bundle
|
|
12
|
+
import android.util.Log
|
|
8
13
|
import android.view.KeyEvent
|
|
9
14
|
import android.view.ViewGroup
|
|
15
|
+
import androidx.annotation.VisibleForTesting
|
|
10
16
|
import androidx.collection.ArrayMap
|
|
17
|
+
import androidx.lifecycle.lifecycleScope
|
|
11
18
|
import com.facebook.react.ReactActivity
|
|
12
19
|
import com.facebook.react.ReactActivityDelegate
|
|
13
20
|
import com.facebook.react.ReactDelegate
|
|
@@ -18,17 +25,22 @@ import com.facebook.react.ReactNativeHost
|
|
|
18
25
|
import com.facebook.react.ReactRootView
|
|
19
26
|
import com.facebook.react.bridge.ReactContext
|
|
20
27
|
import com.facebook.react.modules.core.PermissionListener
|
|
28
|
+
import expo.modules.core.interfaces.ReactActivityHandler.DelayLoadAppHandler
|
|
21
29
|
import expo.modules.core.interfaces.ReactActivityLifecycleListener
|
|
22
30
|
import expo.modules.kotlin.Utils
|
|
23
31
|
import expo.modules.rncompatibility.ReactNativeFeatureFlags
|
|
32
|
+
import kotlinx.coroutines.CoroutineStart
|
|
33
|
+
import kotlinx.coroutines.launch
|
|
24
34
|
import java.lang.reflect.Field
|
|
25
35
|
import java.lang.reflect.Method
|
|
26
36
|
import java.lang.reflect.Modifier
|
|
37
|
+
import kotlin.coroutines.resume
|
|
38
|
+
import kotlin.coroutines.suspendCoroutine
|
|
27
39
|
|
|
28
40
|
class ReactActivityDelegateWrapper(
|
|
29
41
|
private val activity: ReactActivity,
|
|
30
42
|
private val isNewArchitectureEnabled: Boolean,
|
|
31
|
-
|
|
43
|
+
@get:VisibleForTesting internal var delegate: ReactActivityDelegate
|
|
32
44
|
) : ReactActivityDelegate(activity, null) {
|
|
33
45
|
constructor(activity: ReactActivity, delegate: ReactActivityDelegate) :
|
|
34
46
|
this(activity, false, delegate)
|
|
@@ -44,9 +56,14 @@ class ReactActivityDelegateWrapper(
|
|
|
44
56
|
private val _reactHost: ReactHost? by lazy {
|
|
45
57
|
delegate.reactHost
|
|
46
58
|
}
|
|
59
|
+
private val delayLoadAppHandler: DelayLoadAppHandler? by lazy {
|
|
60
|
+
reactActivityHandlers.asSequence()
|
|
61
|
+
.mapNotNull { it.getDelayLoadAppHandler(activity, reactNativeHost) }
|
|
62
|
+
.firstOrNull()
|
|
63
|
+
}
|
|
47
64
|
|
|
48
65
|
/**
|
|
49
|
-
* When the app delay for `loadApp`, the
|
|
66
|
+
* When the app delay for `loadApp`, the React Native lifecycle will be disrupted.
|
|
50
67
|
* This flag indicates we should emit `onResume` after `loadApp`.
|
|
51
68
|
*/
|
|
52
69
|
private var shouldEmitPendingResume = false
|
|
@@ -82,53 +99,12 @@ class ReactActivityDelegateWrapper(
|
|
|
82
99
|
}
|
|
83
100
|
|
|
84
101
|
override fun loadApp(appKey: String?) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// the new container view instead.
|
|
88
|
-
val rootViewContainer = reactActivityHandlers.asSequence()
|
|
89
|
-
.mapNotNull { it.createReactRootViewContainer(activity) }
|
|
90
|
-
.firstOrNull()
|
|
91
|
-
if (rootViewContainer != null) {
|
|
92
|
-
val mReactDelegate = ReactActivityDelegate::class.java.getDeclaredField("mReactDelegate")
|
|
93
|
-
mReactDelegate.isAccessible = true
|
|
94
|
-
val reactDelegate = mReactDelegate[delegate] as ReactDelegate
|
|
95
|
-
|
|
96
|
-
reactDelegate.loadApp(appKey)
|
|
97
|
-
val reactRootView = reactDelegate.reactRootView
|
|
98
|
-
(reactRootView?.parent as? ViewGroup)?.removeView(reactRootView)
|
|
99
|
-
rootViewContainer.addView(reactRootView, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
100
|
-
activity.setContentView(rootViewContainer)
|
|
101
|
-
reactActivityLifecycleListeners.forEach { listener ->
|
|
102
|
-
listener.onContentChanged(activity)
|
|
103
|
-
}
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
val delayLoadAppHandler = reactActivityHandlers.asSequence()
|
|
108
|
-
.mapNotNull { it.getDelayLoadAppHandler(activity, reactNativeHost) }
|
|
109
|
-
.firstOrNull()
|
|
110
|
-
if (delayLoadAppHandler != null) {
|
|
111
|
-
shouldEmitPendingResume = true
|
|
112
|
-
delayLoadAppHandler.whenReady {
|
|
113
|
-
Utils.assertMainThread()
|
|
114
|
-
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
|
|
115
|
-
reactActivityLifecycleListeners.forEach { listener ->
|
|
116
|
-
listener.onContentChanged(activity)
|
|
117
|
-
}
|
|
118
|
-
if (shouldEmitPendingResume) {
|
|
119
|
-
shouldEmitPendingResume = false
|
|
120
|
-
onResume()
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
|
|
127
|
-
reactActivityLifecycleListeners.forEach { listener ->
|
|
128
|
-
listener.onContentChanged(activity)
|
|
102
|
+
activity.lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
|
|
103
|
+
loadAppImpl(appKey, supportsDelayLoad = true)
|
|
129
104
|
}
|
|
130
105
|
}
|
|
131
106
|
|
|
107
|
+
@SuppressLint("DiscouragedPrivateApi")
|
|
132
108
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
133
109
|
// Give handlers a chance as early as possible to replace the wrapped delegate object.
|
|
134
110
|
// If they do, we call the new wrapped delegate's `onCreate` instead of overriding it here.
|
|
@@ -144,38 +120,49 @@ class ReactActivityDelegateWrapper(
|
|
|
144
120
|
mDelegateField.set(activity, newDelegate)
|
|
145
121
|
delegate = newDelegate
|
|
146
122
|
|
|
147
|
-
|
|
123
|
+
delegate.onCreate(savedInstanceState)
|
|
148
124
|
} else {
|
|
149
125
|
// Since we just wrap `ReactActivityDelegate` but not inherit it, in its `onCreate`,
|
|
150
126
|
// the calls to `createRootView()` or `getMainComponentName()` have no chances to be our wrapped methods.
|
|
151
127
|
// Instead we intercept `ReactActivityDelegate.onCreate` and replace the `mReactDelegate` with our version.
|
|
152
128
|
// That's not ideal but works.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
129
|
+
|
|
130
|
+
activity.lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
|
|
131
|
+
awaitDelayLoadAppWhenReady(delayLoadAppHandler)
|
|
132
|
+
|
|
133
|
+
if (VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled) {
|
|
134
|
+
activity.window.colorMode = ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
val launchOptions = composeLaunchOptions()
|
|
138
|
+
val reactDelegate: ReactDelegate
|
|
139
|
+
if (ReactNativeFeatureFlags.enableBridgelessArchitecture) {
|
|
140
|
+
reactDelegate = ReactDelegate(
|
|
141
|
+
plainActivity,
|
|
142
|
+
reactHost,
|
|
143
|
+
mainComponentName,
|
|
144
|
+
launchOptions
|
|
145
|
+
)
|
|
146
|
+
} else {
|
|
147
|
+
reactDelegate = object : ReactDelegate(
|
|
148
|
+
plainActivity,
|
|
149
|
+
reactNativeHost,
|
|
150
|
+
mainComponentName,
|
|
151
|
+
launchOptions,
|
|
152
|
+
isFabricEnabled
|
|
153
|
+
) {
|
|
154
|
+
override fun createRootView(): ReactRootView {
|
|
155
|
+
return this@ReactActivityDelegateWrapper.createRootView() ?: super.createRootView()
|
|
156
|
+
}
|
|
171
157
|
}
|
|
172
158
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
159
|
+
|
|
160
|
+
val mReactDelegate = ReactActivityDelegate::class.java.getDeclaredField("mReactDelegate")
|
|
161
|
+
mReactDelegate.isAccessible = true
|
|
162
|
+
mReactDelegate.set(delegate, reactDelegate)
|
|
163
|
+
if (mainComponentName != null) {
|
|
164
|
+
loadAppImpl(mainComponentName, supportsDelayLoad = false)
|
|
165
|
+
}
|
|
179
166
|
}
|
|
180
167
|
}
|
|
181
168
|
|
|
@@ -188,7 +175,7 @@ class ReactActivityDelegateWrapper(
|
|
|
188
175
|
if (shouldEmitPendingResume) {
|
|
189
176
|
return
|
|
190
177
|
}
|
|
191
|
-
|
|
178
|
+
delegate.onResume()
|
|
192
179
|
reactActivityLifecycleListeners.forEach { listener ->
|
|
193
180
|
listener.onResume(activity)
|
|
194
181
|
}
|
|
@@ -204,14 +191,26 @@ class ReactActivityDelegateWrapper(
|
|
|
204
191
|
reactActivityLifecycleListeners.forEach { listener ->
|
|
205
192
|
listener.onPause(activity)
|
|
206
193
|
}
|
|
207
|
-
|
|
194
|
+
if (delayLoadAppHandler != null) {
|
|
195
|
+
try {
|
|
196
|
+
// For the delay load case, we may enter a different call flow than react-native.
|
|
197
|
+
// For example, Activity stopped before delay load finished.
|
|
198
|
+
// We stop before the ReactActivityDelegate gets a chance to set up.
|
|
199
|
+
// In this case, we should catch the exceptions.
|
|
200
|
+
delegate.onPause()
|
|
201
|
+
} catch (e: Exception) {
|
|
202
|
+
Log.e(TAG, "Exception occurred during onPause with delayed app loading", e)
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
delegate.onPause()
|
|
206
|
+
}
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
override fun onUserLeaveHint() {
|
|
211
210
|
reactActivityLifecycleListeners.forEach { listener ->
|
|
212
211
|
listener.onUserLeaveHint(activity)
|
|
213
212
|
}
|
|
214
|
-
|
|
213
|
+
delegate.onUserLeaveHint()
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
override fun onDestroy() {
|
|
@@ -224,7 +223,19 @@ class ReactActivityDelegateWrapper(
|
|
|
224
223
|
reactActivityLifecycleListeners.forEach { listener ->
|
|
225
224
|
listener.onDestroy(activity)
|
|
226
225
|
}
|
|
227
|
-
|
|
226
|
+
if (delayLoadAppHandler != null) {
|
|
227
|
+
try {
|
|
228
|
+
// For the delay load case, we may enter a different call flow than react-native.
|
|
229
|
+
// For example, Activity stopped before delay load finished.
|
|
230
|
+
// We stop before the ReactActivityDelegate gets a chance to set up.
|
|
231
|
+
// In this case, we should catch the exceptions.
|
|
232
|
+
delegate.onDestroy()
|
|
233
|
+
} catch (e: Exception) {
|
|
234
|
+
Log.e(TAG, "Exception occurred during onDestroy with delayed app loading", e)
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
delegate.onDestroy()
|
|
238
|
+
}
|
|
228
239
|
}
|
|
229
240
|
|
|
230
241
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
@@ -318,6 +329,10 @@ class ReactActivityDelegateWrapper(
|
|
|
318
329
|
return invokeDelegateMethod("isFabricEnabled")
|
|
319
330
|
}
|
|
320
331
|
|
|
332
|
+
override fun isWideColorGamutEnabled(): Boolean {
|
|
333
|
+
return invokeDelegateMethod("isWideColorGamutEnabled")
|
|
334
|
+
}
|
|
335
|
+
|
|
321
336
|
override fun composeLaunchOptions(): Bundle? {
|
|
322
337
|
return invokeDelegateMethod("composeLaunchOptions")
|
|
323
338
|
}
|
|
@@ -341,8 +356,9 @@ class ReactActivityDelegateWrapper(
|
|
|
341
356
|
return method!!.invoke(delegate) as T
|
|
342
357
|
}
|
|
343
358
|
|
|
359
|
+
@VisibleForTesting
|
|
344
360
|
@Suppress("UNCHECKED_CAST")
|
|
345
|
-
|
|
361
|
+
internal fun <T, A> invokeDelegateMethod(
|
|
346
362
|
name: String,
|
|
347
363
|
argTypes: Array<Class<*>>,
|
|
348
364
|
args: Array<A>
|
|
@@ -356,5 +372,68 @@ class ReactActivityDelegateWrapper(
|
|
|
356
372
|
return method!!.invoke(delegate, *args) as T
|
|
357
373
|
}
|
|
358
374
|
|
|
375
|
+
private suspend fun loadAppImpl(appKey: String?, supportsDelayLoad: Boolean) {
|
|
376
|
+
// Give modules a chance to wrap the ReactRootView in a container ViewGroup. If some module
|
|
377
|
+
// wants to do this, we override the functionality of `loadApp` and call `setContentView` with
|
|
378
|
+
// the new container view instead.
|
|
379
|
+
val rootViewContainer = reactActivityHandlers.asSequence()
|
|
380
|
+
.mapNotNull { it.createReactRootViewContainer(activity) }
|
|
381
|
+
.firstOrNull()
|
|
382
|
+
if (rootViewContainer != null) {
|
|
383
|
+
val mReactDelegate = ReactActivityDelegate::class.java.getDeclaredField("mReactDelegate")
|
|
384
|
+
mReactDelegate.isAccessible = true
|
|
385
|
+
val reactDelegate = mReactDelegate[delegate] as ReactDelegate
|
|
386
|
+
|
|
387
|
+
reactDelegate.loadApp(appKey)
|
|
388
|
+
val reactRootView = reactDelegate.reactRootView
|
|
389
|
+
(reactRootView?.parent as? ViewGroup)?.removeView(reactRootView)
|
|
390
|
+
rootViewContainer.addView(reactRootView, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
391
|
+
activity.setContentView(rootViewContainer)
|
|
392
|
+
reactActivityLifecycleListeners.forEach { listener ->
|
|
393
|
+
listener.onContentChanged(activity)
|
|
394
|
+
}
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (supportsDelayLoad) {
|
|
399
|
+
awaitDelayLoadAppWhenReady(delayLoadAppHandler)
|
|
400
|
+
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
|
|
401
|
+
reactActivityLifecycleListeners.forEach { listener ->
|
|
402
|
+
listener.onContentChanged(activity)
|
|
403
|
+
}
|
|
404
|
+
if (shouldEmitPendingResume) {
|
|
405
|
+
shouldEmitPendingResume = false
|
|
406
|
+
onResume()
|
|
407
|
+
}
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
|
|
412
|
+
reactActivityLifecycleListeners.forEach { listener ->
|
|
413
|
+
listener.onContentChanged(activity)
|
|
414
|
+
}
|
|
415
|
+
if (shouldEmitPendingResume) {
|
|
416
|
+
shouldEmitPendingResume = false
|
|
417
|
+
onResume()
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private suspend fun awaitDelayLoadAppWhenReady(delayLoadAppHandler: DelayLoadAppHandler?) {
|
|
422
|
+
if (delayLoadAppHandler == null) {
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
shouldEmitPendingResume = true
|
|
426
|
+
suspendCoroutine { continuation ->
|
|
427
|
+
delayLoadAppHandler.whenReady {
|
|
428
|
+
Utils.assertMainThread()
|
|
429
|
+
continuation.resume(Unit)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
359
434
|
//endregion
|
|
435
|
+
|
|
436
|
+
companion object {
|
|
437
|
+
private val TAG = ReactActivityDelegate::class.simpleName
|
|
438
|
+
}
|
|
360
439
|
}
|
|
@@ -7,8 +7,8 @@ import expo.modules.kotlin.exception.CodedException
|
|
|
7
7
|
internal class FetchUnknownException :
|
|
8
8
|
CodedException("Unknown error")
|
|
9
9
|
|
|
10
|
-
internal class
|
|
11
|
-
CodedException("
|
|
10
|
+
internal class FetchRequestCanceledException :
|
|
11
|
+
CodedException("Fetch request has been canceled")
|
|
12
12
|
|
|
13
13
|
internal class FetchAndroidContextLostException :
|
|
14
14
|
CodedException("The Android context has been lost")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
package expo.modules.fetch
|
|
4
4
|
|
|
5
5
|
import android.util.Log
|
|
6
|
+
import expo.modules.core.logging.localizedMessageWithCauseLocalizedMessage
|
|
6
7
|
import expo.modules.kotlin.AppContext
|
|
7
8
|
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
8
9
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -68,14 +69,14 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
68
69
|
if (isInvalidState(ResponseState.BODY_STREAMING_STARTED)) {
|
|
69
70
|
return
|
|
70
71
|
}
|
|
71
|
-
state = ResponseState.
|
|
72
|
+
state = ResponseState.BODY_STREAMING_CANCELED
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
fun
|
|
75
|
-
val error =
|
|
75
|
+
fun emitRequestCanceled() {
|
|
76
|
+
val error = FetchRequestCanceledException()
|
|
76
77
|
this.error = error
|
|
77
78
|
if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
78
|
-
emit("didFailWithError", error)
|
|
79
|
+
emit("didFailWithError", error.localizedMessageWithCauseLocalizedMessage())
|
|
79
80
|
}
|
|
80
81
|
state = ResponseState.ERROR_RECEIVED
|
|
81
82
|
}
|
|
@@ -97,7 +98,7 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
97
98
|
//region Callback implementations
|
|
98
99
|
|
|
99
100
|
override fun onFailure(call: Call, e: IOException) {
|
|
100
|
-
// Canceled request should be handled by
|
|
101
|
+
// Canceled request should be handled by emitRequestCanceled
|
|
101
102
|
if (e.message === "Canceled") {
|
|
102
103
|
return
|
|
103
104
|
}
|
|
@@ -106,14 +107,14 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
106
107
|
ResponseState.STARTED,
|
|
107
108
|
ResponseState.RESPONSE_RECEIVED,
|
|
108
109
|
ResponseState.BODY_STREAMING_STARTED,
|
|
109
|
-
ResponseState.
|
|
110
|
+
ResponseState.BODY_STREAMING_CANCELED
|
|
110
111
|
)
|
|
111
112
|
) {
|
|
112
113
|
return
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
116
|
-
emit("didFailWithError", e)
|
|
117
|
+
emit("didFailWithError", e.localizedMessageWithCauseLocalizedMessage())
|
|
117
118
|
}
|
|
118
119
|
error = e
|
|
119
120
|
state = ResponseState.ERROR_RECEIVED
|
|
@@ -174,7 +175,7 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
174
175
|
if (isInvalidState(
|
|
175
176
|
ResponseState.RESPONSE_RECEIVED,
|
|
176
177
|
ResponseState.BODY_STREAMING_STARTED,
|
|
177
|
-
ResponseState.
|
|
178
|
+
ResponseState.BODY_STREAMING_CANCELED
|
|
178
179
|
)
|
|
179
180
|
) {
|
|
180
181
|
break
|
|
@@ -190,7 +191,7 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
190
191
|
} catch (e: IOException) {
|
|
191
192
|
this.error = e
|
|
192
193
|
if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
193
|
-
emit("didFailWithError", e)
|
|
194
|
+
emit("didFailWithError", e.localizedMessageWithCauseLocalizedMessage())
|
|
194
195
|
}
|
|
195
196
|
state = ResponseState.ERROR_RECEIVED
|
|
196
197
|
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
package expo.modules
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import com.facebook.react.ReactActivity
|
|
7
|
+
import com.facebook.react.ReactActivityDelegate
|
|
8
|
+
import com.facebook.react.ReactApplication
|
|
9
|
+
import com.facebook.react.ReactHost
|
|
10
|
+
import com.facebook.react.ReactNativeHost
|
|
11
|
+
import com.facebook.react.ReactRootView
|
|
12
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
13
|
+
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
14
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
15
|
+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
|
16
|
+
import com.facebook.soloader.SoLoader
|
|
17
|
+
import expo.modules.core.interfaces.Package
|
|
18
|
+
import expo.modules.core.interfaces.ReactActivityHandler
|
|
19
|
+
import expo.modules.core.interfaces.ReactActivityHandler.DelayLoadAppHandler
|
|
20
|
+
import expo.modules.core.interfaces.ReactActivityLifecycleListener
|
|
21
|
+
import io.mockk.MockKAnnotations
|
|
22
|
+
import io.mockk.every
|
|
23
|
+
import io.mockk.impl.annotations.RelaxedMockK
|
|
24
|
+
import io.mockk.mockk
|
|
25
|
+
import io.mockk.mockkObject
|
|
26
|
+
import io.mockk.mockkStatic
|
|
27
|
+
import io.mockk.slot
|
|
28
|
+
import io.mockk.spyk
|
|
29
|
+
import io.mockk.unmockkAll
|
|
30
|
+
import io.mockk.verify
|
|
31
|
+
import kotlinx.coroutines.Dispatchers
|
|
32
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
33
|
+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
34
|
+
import kotlinx.coroutines.test.runTest
|
|
35
|
+
import kotlinx.coroutines.test.setMain
|
|
36
|
+
import org.junit.After
|
|
37
|
+
import org.junit.Before
|
|
38
|
+
import org.junit.Test
|
|
39
|
+
import org.junit.runner.RunWith
|
|
40
|
+
import org.robolectric.Robolectric
|
|
41
|
+
import org.robolectric.RobolectricTestRunner
|
|
42
|
+
import org.robolectric.android.controller.ActivityController
|
|
43
|
+
import org.robolectric.annotation.Config
|
|
44
|
+
|
|
45
|
+
@RunWith(RobolectricTestRunner::class)
|
|
46
|
+
@Config(application = MockApplication::class)
|
|
47
|
+
internal class ReactActivityDelegateWrapperDelayLoadTest {
|
|
48
|
+
@RelaxedMockK
|
|
49
|
+
private lateinit var delayLoadAppHandler: DelayLoadAppHandler
|
|
50
|
+
|
|
51
|
+
private lateinit var mockPackageWithDelay: MockPackageWithDelayHandler
|
|
52
|
+
private lateinit var mockPackageWithoutDelay: MockPackageWithoutDelayHandler
|
|
53
|
+
private lateinit var activityController: ActivityController<MockActivity>
|
|
54
|
+
private val activity: MockActivity
|
|
55
|
+
get() = activityController.get()
|
|
56
|
+
|
|
57
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
58
|
+
@Before
|
|
59
|
+
fun setUp() {
|
|
60
|
+
SoLoader.setInTestMode()
|
|
61
|
+
mockkObject(ExpoModulesPackage.Companion)
|
|
62
|
+
mockkStatic(ReactNativeFeatureFlags::class)
|
|
63
|
+
every { ReactNativeFeatureFlags.enableBridgelessArchitecture() } returns true
|
|
64
|
+
every { ReactNativeFeatureFlags.enableFabricRenderer() } returns true
|
|
65
|
+
Dispatchers.setMain(UnconfinedTestDispatcher())
|
|
66
|
+
|
|
67
|
+
MockKAnnotations.init(this)
|
|
68
|
+
|
|
69
|
+
mockPackageWithDelay = MockPackageWithDelayHandler(delayLoadAppHandler)
|
|
70
|
+
mockPackageWithoutDelay = MockPackageWithoutDelayHandler()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@After
|
|
74
|
+
fun tearDown() {
|
|
75
|
+
unmockkAll()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Test
|
|
79
|
+
fun `should proceed loadApp immediately when no delayLoadAppHandler`() = runTest {
|
|
80
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithoutDelay)
|
|
81
|
+
|
|
82
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
83
|
+
.also {
|
|
84
|
+
val activity = it.get()
|
|
85
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
86
|
+
}
|
|
87
|
+
.setup()
|
|
88
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
89
|
+
verify { spyDelegateWrapper.invokeDelegateMethod("loadApp", arrayOf(String::class.java), arrayOf("main")) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Test
|
|
93
|
+
fun `should block loadApp until delayLoadAppHandler finished`() = runTest {
|
|
94
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithDelay)
|
|
95
|
+
|
|
96
|
+
val callbackSlot = slot<Runnable>()
|
|
97
|
+
every { delayLoadAppHandler.whenReady(capture(callbackSlot)) } answers {
|
|
98
|
+
// Don't call the callback immediately to simulate delay
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
102
|
+
.also {
|
|
103
|
+
val activity = it.get()
|
|
104
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
105
|
+
}
|
|
106
|
+
.setup()
|
|
107
|
+
|
|
108
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
109
|
+
|
|
110
|
+
verify(exactly = 0) { spyDelegateWrapper.invokeDelegateMethod("loadApp", arrayOf(String::class.java), arrayOf("main")) }
|
|
111
|
+
callbackSlot.captured.run()
|
|
112
|
+
verify(exactly = 1) { spyDelegateWrapper.invokeDelegateMethod("loadApp", arrayOf(String::class.java), arrayOf("main")) }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Test
|
|
116
|
+
fun `should call lifecycle methods in correct order with delay load`() = runTest {
|
|
117
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithDelay)
|
|
118
|
+
|
|
119
|
+
val callbackSlot = slot<Runnable>()
|
|
120
|
+
every { delayLoadAppHandler.whenReady(capture(callbackSlot)) } answers {
|
|
121
|
+
// Don't call the callback immediately to simulate delay
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
125
|
+
.also {
|
|
126
|
+
val activity = it.get()
|
|
127
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
128
|
+
}
|
|
129
|
+
.setup()
|
|
130
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
131
|
+
val spyDelegate = spyDelegateWrapper.delegate
|
|
132
|
+
|
|
133
|
+
verify(exactly = 1) { spyDelegateWrapper.onCreate(any()) }
|
|
134
|
+
verify(exactly = 1) { spyDelegateWrapper.onResume() }
|
|
135
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
136
|
+
|
|
137
|
+
callbackSlot.captured.run()
|
|
138
|
+
verify(exactly = 2) { spyDelegateWrapper.onResume() }
|
|
139
|
+
verify(exactly = 1) { spyDelegate.onResume() }
|
|
140
|
+
verify(exactly = 0) { spyDelegateWrapper.onPause() }
|
|
141
|
+
verify(exactly = 0) { spyDelegateWrapper.onDestroy() }
|
|
142
|
+
verify(exactly = 0) { spyDelegate.onPause() }
|
|
143
|
+
verify(exactly = 0) { spyDelegate.onDestroy() }
|
|
144
|
+
|
|
145
|
+
activityController.pause().stop().destroy()
|
|
146
|
+
verify(exactly = 1) { spyDelegateWrapper.onPause() }
|
|
147
|
+
verify(exactly = 1) { spyDelegateWrapper.onDestroy() }
|
|
148
|
+
verify(exactly = 1) { spyDelegate.onPause() }
|
|
149
|
+
verify(exactly = 1) { spyDelegate.onDestroy() }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Test
|
|
153
|
+
fun `should have normal lifecycle when no delayLoadHandler`() = runTest {
|
|
154
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithoutDelay)
|
|
155
|
+
|
|
156
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
157
|
+
.also {
|
|
158
|
+
val activity = it.get()
|
|
159
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
160
|
+
}
|
|
161
|
+
.setup()
|
|
162
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
163
|
+
val spyDelegate = spyDelegateWrapper.delegate
|
|
164
|
+
|
|
165
|
+
verify(exactly = 1) { spyDelegateWrapper.onCreate(any()) }
|
|
166
|
+
verify(exactly = 1) { spyDelegateWrapper.onResume() }
|
|
167
|
+
verify(exactly = 1) { spyDelegate.onResume() }
|
|
168
|
+
|
|
169
|
+
activityController.pause().stop().destroy()
|
|
170
|
+
verify(exactly = 1) { spyDelegateWrapper.onPause() }
|
|
171
|
+
verify(exactly = 1) { spyDelegateWrapper.onDestroy() }
|
|
172
|
+
verify(exactly = 1) { spyDelegate.onPause() }
|
|
173
|
+
verify(exactly = 1) { spyDelegate.onDestroy() }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun `should cancel pending resume if activity pause before delay load finished`() = runTest {
|
|
178
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithDelay)
|
|
179
|
+
|
|
180
|
+
val callbackSlot = slot<Runnable>()
|
|
181
|
+
every { delayLoadAppHandler.whenReady(capture(callbackSlot)) } answers {
|
|
182
|
+
// Don't call the callback immediately to simulate delay
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
186
|
+
.also {
|
|
187
|
+
val activity = it.get()
|
|
188
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
189
|
+
}
|
|
190
|
+
.setup()
|
|
191
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
192
|
+
val spyDelegate = spyDelegateWrapper.delegate
|
|
193
|
+
|
|
194
|
+
verify(exactly = 1) { spyDelegateWrapper.onCreate(any()) }
|
|
195
|
+
verify(exactly = 1) { spyDelegateWrapper.onResume() }
|
|
196
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
197
|
+
|
|
198
|
+
activityController.pause()
|
|
199
|
+
callbackSlot.captured.run()
|
|
200
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Test
|
|
204
|
+
fun `should cancel pending resume if activity stop before delay load finished`() = runTest {
|
|
205
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithDelay)
|
|
206
|
+
|
|
207
|
+
val callbackSlot = slot<Runnable>()
|
|
208
|
+
every { delayLoadAppHandler.whenReady(capture(callbackSlot)) } answers {
|
|
209
|
+
// Don't call the callback immediately to simulate delay
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
213
|
+
.also {
|
|
214
|
+
val activity = it.get()
|
|
215
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
216
|
+
}
|
|
217
|
+
.setup()
|
|
218
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
219
|
+
val spyDelegate = spyDelegateWrapper.delegate
|
|
220
|
+
|
|
221
|
+
verify(exactly = 1) { spyDelegateWrapper.onCreate(any()) }
|
|
222
|
+
verify(exactly = 1) { spyDelegateWrapper.onResume() }
|
|
223
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
224
|
+
|
|
225
|
+
activityController.pause().stop()
|
|
226
|
+
callbackSlot.captured.run()
|
|
227
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@Test
|
|
231
|
+
fun `should cancel pending resume if activity destroy before delay load finished`() = runTest {
|
|
232
|
+
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackageWithDelay)
|
|
233
|
+
|
|
234
|
+
val callbackSlot = slot<Runnable>()
|
|
235
|
+
every { delayLoadAppHandler.whenReady(capture(callbackSlot)) } answers {
|
|
236
|
+
// Don't call the callback immediately to simulate delay
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
activityController = Robolectric.buildActivity(MockActivity::class.java)
|
|
240
|
+
.also {
|
|
241
|
+
val activity = it.get()
|
|
242
|
+
(activity.application as MockApplication).bindCurrentActivity(activity)
|
|
243
|
+
}
|
|
244
|
+
.setup()
|
|
245
|
+
val spyDelegateWrapper = activity.reactActivityDelegate as ReactActivityDelegateWrapper
|
|
246
|
+
val spyDelegate = spyDelegateWrapper.delegate
|
|
247
|
+
|
|
248
|
+
verify(exactly = 1) { spyDelegateWrapper.onCreate(any()) }
|
|
249
|
+
verify(exactly = 1) { spyDelegateWrapper.onResume() }
|
|
250
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
251
|
+
|
|
252
|
+
activityController.pause().stop().destroy()
|
|
253
|
+
callbackSlot.captured.run()
|
|
254
|
+
verify(exactly = 0) { spyDelegate.onResume() }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
internal class MockPackageWithDelayHandler(delayHandler: DelayLoadAppHandler) : Package {
|
|
259
|
+
private val reactActivityLifecycleListener = mockk<ReactActivityLifecycleListener>(relaxed = true)
|
|
260
|
+
private val reactActivityHandler = mockk<ReactActivityHandler>(relaxed = true)
|
|
261
|
+
|
|
262
|
+
init {
|
|
263
|
+
every { reactActivityHandler.createReactRootViewContainer(any()) } returns null
|
|
264
|
+
every { reactActivityHandler.onDidCreateReactActivityDelegate(any(), any()) } returns null
|
|
265
|
+
every { reactActivityHandler.getDelayLoadAppHandler(any(), any()) } returns delayHandler
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
override fun createReactActivityLifecycleListeners(activityContext: Context?): List<ReactActivityLifecycleListener> {
|
|
269
|
+
return listOf(reactActivityLifecycleListener)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
override fun createReactActivityHandlers(activityContext: Context?): List<ReactActivityHandler> {
|
|
273
|
+
return listOf(reactActivityHandler)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
internal class MockPackageWithoutDelayHandler : Package {
|
|
278
|
+
private val reactActivityLifecycleListener = mockk<ReactActivityLifecycleListener>(relaxed = true)
|
|
279
|
+
private val reactActivityHandler = mockk<ReactActivityHandler>(relaxed = true)
|
|
280
|
+
|
|
281
|
+
init {
|
|
282
|
+
every { reactActivityHandler.createReactRootViewContainer(any()) } returns null
|
|
283
|
+
every { reactActivityHandler.onDidCreateReactActivityDelegate(any(), any()) } returns null
|
|
284
|
+
every { reactActivityHandler.getDelayLoadAppHandler(any(), any()) } returns null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
override fun createReactActivityLifecycleListeners(activityContext: Context?): List<ReactActivityLifecycleListener> {
|
|
288
|
+
return listOf(reactActivityLifecycleListener)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
override fun createReactActivityHandlers(activityContext: Context?): List<ReactActivityHandler> {
|
|
292
|
+
return listOf(reactActivityHandler)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
internal class MockApplication : Application(), ReactApplication {
|
|
297
|
+
private var currentActivity: Activity? = null
|
|
298
|
+
|
|
299
|
+
override fun onCreate() {
|
|
300
|
+
super.onCreate()
|
|
301
|
+
setTheme(androidx.appcompat.R.style.Theme_AppCompat)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
internal fun bindCurrentActivity(activity: Activity?) {
|
|
305
|
+
currentActivity = activity
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
override val reactNativeHost: ReactNativeHost = mockk<ReactNativeHost>(relaxed = true)
|
|
309
|
+
|
|
310
|
+
override val reactHost: ReactHost by lazy {
|
|
311
|
+
mockk<ReactHost>(relaxed = true)
|
|
312
|
+
.also {
|
|
313
|
+
val mockReactSurface = mockk<ReactSurface>(relaxed = true)
|
|
314
|
+
every { mockReactSurface.view } returns ReactRootView(currentActivity)
|
|
315
|
+
every { it.createSurface(any(), any(), any()) } returns mockReactSurface
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
internal class MockActivity : ReactActivity() {
|
|
321
|
+
override fun getMainComponentName(): String = "main"
|
|
322
|
+
|
|
323
|
+
override fun createReactActivityDelegate(): ReactActivityDelegate = spyk(
|
|
324
|
+
ReactActivityDelegateWrapper(
|
|
325
|
+
this,
|
|
326
|
+
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
|
|
327
|
+
spyk(
|
|
328
|
+
object : DefaultReactActivityDelegate(
|
|
329
|
+
this,
|
|
330
|
+
mainComponentName,
|
|
331
|
+
fabricEnabled
|
|
332
|
+
) {}
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@expo/fingerprint": "~0.13.
|
|
2
|
+
"@expo/fingerprint": "~0.13.3",
|
|
3
3
|
"@expo/metro-runtime": "~5.0.4",
|
|
4
4
|
"@expo/vector-icons": "^14.1.0",
|
|
5
|
-
"@expo/ui": "~0.1.1-alpha.
|
|
5
|
+
"@expo/ui": "~0.1.1-alpha.10",
|
|
6
6
|
"@react-native-async-storage/async-storage": "2.1.2",
|
|
7
7
|
"@react-native-community/datetimepicker": "8.4.1",
|
|
8
8
|
"@react-native-masked-view/masked-view": "0.3.2",
|
|
@@ -17,34 +17,34 @@
|
|
|
17
17
|
"expo-app-auth": "~11.1.0",
|
|
18
18
|
"expo-app-loader-provider": "~8.0.0",
|
|
19
19
|
"expo-apple-authentication": "~7.2.4",
|
|
20
|
-
"expo-application": "~6.1.
|
|
21
|
-
"expo-asset": "~11.1.
|
|
20
|
+
"expo-application": "~6.1.5",
|
|
21
|
+
"expo-asset": "~11.1.6",
|
|
22
22
|
"expo-audio": "~0.4.7",
|
|
23
23
|
"expo-auth-session": "~6.2.0",
|
|
24
|
-
"expo-av": "~15.1.
|
|
25
|
-
"expo-background-fetch": "~13.1.
|
|
26
|
-
"expo-background-task": "~0.2.
|
|
24
|
+
"expo-av": "~15.1.7",
|
|
25
|
+
"expo-background-fetch": "~13.1.6",
|
|
26
|
+
"expo-background-task": "~0.2.8",
|
|
27
27
|
"expo-battery": "~9.1.4",
|
|
28
28
|
"expo-blur": "~14.1.5",
|
|
29
29
|
"expo-brightness": "~13.1.4",
|
|
30
|
-
"expo-build-properties": "~0.14.
|
|
30
|
+
"expo-build-properties": "~0.14.8",
|
|
31
31
|
"expo-calendar": "~14.1.4",
|
|
32
|
-
"expo-camera": "~16.1.
|
|
33
|
-
"expo-cellular": "~7.1.
|
|
32
|
+
"expo-camera": "~16.1.10",
|
|
33
|
+
"expo-cellular": "~7.1.5",
|
|
34
34
|
"expo-checkbox": "~4.1.4",
|
|
35
|
-
"expo-clipboard": "~7.1.
|
|
35
|
+
"expo-clipboard": "~7.1.5",
|
|
36
36
|
"expo-constants": "~17.1.6",
|
|
37
37
|
"expo-contacts": "~14.2.5",
|
|
38
38
|
"expo-crypto": "~14.1.5",
|
|
39
|
-
"expo-dev-client": "~5.2.
|
|
39
|
+
"expo-dev-client": "~5.2.2",
|
|
40
40
|
"expo-device": "~7.1.4",
|
|
41
41
|
"expo-document-picker": "~13.1.6",
|
|
42
|
-
"expo-file-system": "~18.1.
|
|
43
|
-
"expo-font": "~13.3.
|
|
44
|
-
"expo-gl": "~15.1.
|
|
42
|
+
"expo-file-system": "~18.1.11",
|
|
43
|
+
"expo-font": "~13.3.2",
|
|
44
|
+
"expo-gl": "~15.1.7",
|
|
45
45
|
"expo-google-app-auth": "~8.3.0",
|
|
46
46
|
"expo-haptics": "~14.1.4",
|
|
47
|
-
"expo-image": "~2.3.
|
|
47
|
+
"expo-image": "~2.3.1",
|
|
48
48
|
"expo-image-loader": "~5.1.0",
|
|
49
49
|
"expo-image-manipulator": "~13.1.7",
|
|
50
50
|
"expo-image-picker": "~16.1.4",
|
|
@@ -52,24 +52,24 @@
|
|
|
52
52
|
"expo-insights": "~0.9.3",
|
|
53
53
|
"expo-keep-awake": "~14.1.4",
|
|
54
54
|
"expo-linear-gradient": "~14.1.5",
|
|
55
|
-
"expo-linking": "~7.1.
|
|
56
|
-
"expo-local-authentication": "~16.0.
|
|
57
|
-
"expo-localization": "~16.1.
|
|
58
|
-
"expo-location": "~18.1.
|
|
59
|
-
"expo-mail-composer": "~14.1.
|
|
55
|
+
"expo-linking": "~7.1.6",
|
|
56
|
+
"expo-local-authentication": "~16.0.5",
|
|
57
|
+
"expo-localization": "~16.1.6",
|
|
58
|
+
"expo-location": "~18.1.6",
|
|
59
|
+
"expo-mail-composer": "~14.1.5",
|
|
60
60
|
"expo-manifests": "~0.16.5",
|
|
61
61
|
"expo-maps": "~0.11.0",
|
|
62
62
|
"expo-media-library": "~17.1.7",
|
|
63
63
|
"expo-mesh-gradient": "~0.3.4",
|
|
64
64
|
"expo-module-template": "~10.16.6",
|
|
65
|
-
"expo-modules-core": "~2.4.
|
|
65
|
+
"expo-modules-core": "~2.4.1",
|
|
66
66
|
"expo-navigation-bar": "~4.2.6",
|
|
67
67
|
"expo-network": "~7.1.5",
|
|
68
68
|
"expo-notifications": "~0.31.3",
|
|
69
69
|
"expo-print": "~14.1.4",
|
|
70
70
|
"expo-live-photo": "~0.1.4",
|
|
71
|
-
"expo-router": "~5.1.
|
|
72
|
-
"expo-screen-capture": "~7.1.
|
|
71
|
+
"expo-router": "~5.1.2",
|
|
72
|
+
"expo-screen-capture": "~7.1.5",
|
|
73
73
|
"expo-screen-orientation": "~8.1.7",
|
|
74
74
|
"expo-secure-store": "~14.2.3",
|
|
75
75
|
"expo-sensors": "~14.1.4",
|
|
@@ -77,18 +77,18 @@
|
|
|
77
77
|
"expo-sms": "~13.1.4",
|
|
78
78
|
"expo-speech": "~13.1.7",
|
|
79
79
|
"expo-splash-screen": "~0.30.9",
|
|
80
|
-
"expo-sqlite": "~15.2.
|
|
80
|
+
"expo-sqlite": "~15.2.13",
|
|
81
81
|
"expo-status-bar": "~2.2.3",
|
|
82
82
|
"expo-store-review": "~8.1.5",
|
|
83
83
|
"expo-symbols": "~0.4.5",
|
|
84
84
|
"expo-system-ui": "~5.0.9",
|
|
85
|
-
"expo-task-manager": "~13.1.
|
|
85
|
+
"expo-task-manager": "~13.1.6",
|
|
86
86
|
"expo-tracking-transparency": "~5.2.4",
|
|
87
87
|
"expo-updates": "~0.28.15",
|
|
88
88
|
"expo-video-thumbnails": "~9.1.3",
|
|
89
89
|
"expo-video": "~2.2.2",
|
|
90
90
|
"expo-web-browser": "~14.2.0",
|
|
91
|
-
"jest-expo": "~53.0.
|
|
91
|
+
"jest-expo": "~53.0.8",
|
|
92
92
|
"lottie-react-native": "7.2.2",
|
|
93
93
|
"react": "19.0.0",
|
|
94
94
|
"react-dom": "19.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo",
|
|
3
|
-
"version": "53.0.
|
|
3
|
+
"version": "53.0.15",
|
|
4
4
|
"description": "The Expo SDK",
|
|
5
5
|
"main": "src/Expo.ts",
|
|
6
6
|
"module": "src/Expo.ts",
|
|
@@ -73,20 +73,20 @@
|
|
|
73
73
|
"homepage": "https://github.com/expo/expo/tree/main/packages/expo",
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@babel/runtime": "^7.20.0",
|
|
76
|
-
"@expo/cli": "0.24.
|
|
77
|
-
"@expo/config": "~11.0.
|
|
76
|
+
"@expo/cli": "0.24.16",
|
|
77
|
+
"@expo/config": "~11.0.11",
|
|
78
78
|
"@expo/config-plugins": "~10.0.3",
|
|
79
|
-
"@expo/fingerprint": "0.13.
|
|
79
|
+
"@expo/fingerprint": "0.13.3",
|
|
80
80
|
"@expo/metro-config": "0.20.15",
|
|
81
81
|
"@expo/vector-icons": "^14.0.0",
|
|
82
82
|
"babel-preset-expo": "~13.2.1",
|
|
83
|
-
"expo-asset": "~11.1.
|
|
83
|
+
"expo-asset": "~11.1.6",
|
|
84
84
|
"expo-constants": "~17.1.6",
|
|
85
|
-
"expo-file-system": "~18.1.
|
|
86
|
-
"expo-font": "~13.3.
|
|
85
|
+
"expo-file-system": "~18.1.11",
|
|
86
|
+
"expo-font": "~13.3.2",
|
|
87
87
|
"expo-keep-awake": "~14.1.4",
|
|
88
|
-
"expo-modules-autolinking": "2.1.
|
|
89
|
-
"expo-modules-core": "2.4.
|
|
88
|
+
"expo-modules-autolinking": "2.1.13",
|
|
89
|
+
"expo-modules-core": "2.4.1",
|
|
90
90
|
"react-native-edge-to-edge": "1.6.0",
|
|
91
91
|
"whatwg-url-without-unicode": "8.0.0-3"
|
|
92
92
|
},
|
|
@@ -119,5 +119,5 @@
|
|
|
119
119
|
"optional": true
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
|
-
"gitHead": "
|
|
122
|
+
"gitHead": "ceb57fc0dd75de75dc7006e20792b29e947226f3"
|
|
123
123
|
}
|