expo-dev-launcher 1.2.0 → 1.3.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.
- package/CHANGELOG.md +23 -0
- package/android/build.gradle +1 -1
- package/android/src/debug/assets/expo_dev_launcher_android.bundle +22 -21
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/expo/modules/devlauncher/helpers/DevLauncherReactUtils.kt +3 -1
- package/android/src/main/java/expo/modules/devlauncher/launcher/errors/DevLauncherUncaughtExceptionHandler.kt +6 -9
- package/android/src/main/java/expo/modules/devlauncher/logs/DevLauncherRemoteLog.kt +13 -37
- package/android/src/main/java/expo/modules/devlauncher/logs/DevLauncherRemoteLogManager.kt +27 -31
- package/android/src/react-native-64/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-65/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-66/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-67/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/android/src/react-native-69/expo/modules/devlauncher/rncompatibility/DevLauncherReactNativeHostHandler.kt +5 -0
- package/bundle/components/AppHeader.tsx +7 -1
- package/bundle/components/ScreenContainer.tsx +8 -0
- package/bundle/native-modules/DevLauncherAuth.ts +21 -10
- package/bundle/screens/BranchesScreen.tsx +18 -15
- package/bundle/screens/ExtensionsScreen.tsx +37 -38
- package/bundle/screens/ExtensionsStack.tsx +1 -0
- package/bundle/screens/HomeScreen.tsx +9 -2
- package/bundle/screens/SettingsScreen.tsx +113 -110
- package/bundle/screens/UpdatesScreen.tsx +44 -39
- package/ios/EXDevLauncherController.m +3 -0
- package/ios/EXDevLauncherURLHelper.swift +30 -19
- package/ios/Errors/EXDevLauncherUncaughtExceptionHandler.swift +14 -10
- package/ios/Logs/EXDevLauncherRemoteLogsManager.swift +39 -34
- package/ios/ReactDelegateHandler/ExpoDevLauncherReactDelegateHandler.swift +13 -1
- package/ios/main.jsbundle +22 -21
- package/package.json +4 -4
|
@@ -9,13 +9,15 @@ import com.facebook.react.bridge.JSBundleLoader
|
|
|
9
9
|
import expo.interfaces.devmenu.annotations.ContainsDevMenuExtension
|
|
10
10
|
import expo.modules.devlauncher.react.DevLauncherDevSupportManagerSwapper
|
|
11
11
|
import expo.modules.devlauncher.react.DevLauncherInternalSettings
|
|
12
|
+
import okhttp3.HttpUrl
|
|
12
13
|
|
|
13
14
|
fun injectReactInterceptor(
|
|
14
15
|
context: Context,
|
|
15
16
|
reactNativeHost: ReactNativeHost,
|
|
16
17
|
url: Uri
|
|
17
18
|
): Boolean {
|
|
18
|
-
val
|
|
19
|
+
val port = if (url.port != -1) url.port else HttpUrl.defaultPort(url.scheme)
|
|
20
|
+
val debugServerHost = url.host + ":" + port
|
|
19
21
|
// We need to remove "/" which is added to begin of the path by the Uri
|
|
20
22
|
// and the bundle type
|
|
21
23
|
val appBundleName = if (url.path.isNullOrEmpty()) {
|
|
@@ -109,28 +109,25 @@ class DevLauncherUncaughtExceptionHandler(
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
try {
|
|
112
|
-
val url =
|
|
112
|
+
val url = getWebSocketUrl()
|
|
113
113
|
val remoteLogManager = DevLauncherRemoteLogManager(DevLauncherKoinContext.app.koin.get(), url)
|
|
114
114
|
.apply {
|
|
115
115
|
deferError("Your app just crashed. See the error below.")
|
|
116
116
|
deferError(exception)
|
|
117
117
|
}
|
|
118
|
-
remoteLogManager.
|
|
118
|
+
remoteLogManager.sendViaWebSocket()
|
|
119
119
|
} catch (e: Throwable) {
|
|
120
120
|
Log.e("DevLauncher", "Couldn't send an exception to bundler. $e", e)
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
private fun
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return Uri.parse(logsUrlFromManifest)
|
|
128
|
-
}
|
|
129
|
-
|
|
124
|
+
private fun getWebSocketUrl(): Uri {
|
|
125
|
+
// URL structure replicates
|
|
126
|
+
// https://github.com/facebook/react-native/blob/0.69-stable/Libraries/Utilities/HMRClient.js#L164
|
|
130
127
|
return Uri
|
|
131
128
|
.parse(controller.appHost.reactInstanceManager.devSupportManager.sourceUrl)
|
|
132
129
|
.buildUpon()
|
|
133
|
-
.path("
|
|
130
|
+
.path("hot")
|
|
134
131
|
.clearQuery()
|
|
135
132
|
.build()
|
|
136
133
|
}
|
|
@@ -1,39 +1,27 @@
|
|
|
1
1
|
package expo.modules.devlauncher.logs
|
|
2
2
|
|
|
3
|
-
import com.google.gson.Gson
|
|
4
3
|
import com.google.gson.GsonBuilder
|
|
5
4
|
import com.google.gson.annotations.Expose
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
override fun toString(): String
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
internal class DevLauncherSimpleRemoteLogBody(override val message: String) : DevLauncherRemoteLogBody {
|
|
15
|
-
override val stack: String? = null
|
|
16
|
-
|
|
17
|
-
override fun toString(): String = message
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
internal class DevLauncherExceptionRemoteLogBody(exception: Throwable) : DevLauncherRemoteLogBody {
|
|
21
|
-
override val message: String = exception.toString()
|
|
22
|
-
override val stack: String = exception.stackTraceToRemoteLogString()
|
|
23
|
-
|
|
24
|
-
override fun toString(): String = Gson().toJson(this)
|
|
25
|
-
}
|
|
26
|
-
|
|
6
|
+
/**
|
|
7
|
+
* object format comes from
|
|
8
|
+
* https://github.com/facebook/react-native/blob/0.69-stable/Libraries/Utilities/HMRClient.js#L119-L134
|
|
9
|
+
*/
|
|
27
10
|
@Suppress("UNUSED")
|
|
28
11
|
internal data class DevLauncherRemoteLog(
|
|
29
|
-
val
|
|
30
|
-
@Expose val level: String = "error"
|
|
12
|
+
val messages: List<String>,
|
|
13
|
+
@Expose val level: String = "error",
|
|
14
|
+
@Expose val mode: String = "BRIDGE"
|
|
31
15
|
) {
|
|
16
|
+
/**
|
|
17
|
+
* `data` is an array whose members are simply concatenated with a space before printing to the
|
|
18
|
+
* console, so we join messages with a newline and send an array consisting of just a single item.
|
|
19
|
+
*/
|
|
32
20
|
@Expose
|
|
33
|
-
val
|
|
21
|
+
private val data = arrayOf(messages.joinToString("\n"))
|
|
34
22
|
|
|
35
23
|
@Expose
|
|
36
|
-
private val
|
|
24
|
+
private val type = "log"
|
|
37
25
|
|
|
38
26
|
fun toJson(): String {
|
|
39
27
|
return GsonBuilder()
|
|
@@ -42,15 +30,3 @@ internal data class DevLauncherRemoteLog(
|
|
|
42
30
|
.toJson(this)
|
|
43
31
|
}
|
|
44
32
|
}
|
|
45
|
-
|
|
46
|
-
internal fun Throwable.stackTraceToRemoteLogString(): String {
|
|
47
|
-
val baseTrace = stackTrace.joinToString(separator = "\n") {
|
|
48
|
-
it.toString()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
cause?.let {
|
|
52
|
-
return baseTrace + "\nCaused By ${it.stackTraceToRemoteLogString()}"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return baseTrace
|
|
56
|
-
}
|
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
package expo.modules.devlauncher.logs
|
|
2
2
|
|
|
3
3
|
import android.net.Uri
|
|
4
|
-
import android.os.Build
|
|
5
|
-
import expo.modules.devlauncher.helpers.await
|
|
6
|
-
import expo.modules.devlauncher.helpers.post
|
|
7
|
-
import kotlinx.coroutines.runBlocking
|
|
8
|
-
import okhttp3.MediaType
|
|
9
4
|
import okhttp3.OkHttpClient
|
|
10
|
-
import okhttp3.
|
|
5
|
+
import okhttp3.Request
|
|
6
|
+
import okhttp3.Response
|
|
7
|
+
import okhttp3.WebSocket
|
|
8
|
+
import okhttp3.WebSocketListener
|
|
11
9
|
|
|
12
|
-
class DevLauncherRemoteLogManager(private val httpClient: OkHttpClient, private val url: Uri) {
|
|
13
|
-
private val batch: MutableList<
|
|
10
|
+
class DevLauncherRemoteLogManager(private val httpClient: OkHttpClient, private val url: Uri) : WebSocketListener() {
|
|
11
|
+
private val batch: MutableList<String> = mutableListOf()
|
|
14
12
|
|
|
15
13
|
fun deferError(throwable: Throwable) {
|
|
16
|
-
|
|
17
|
-
DevLauncherRemoteLog(
|
|
18
|
-
DevLauncherExceptionRemoteLogBody(throwable)
|
|
19
|
-
)
|
|
20
|
-
)
|
|
14
|
+
batch.add(throwable.toRemoteLogString())
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
fun deferError(message: String) {
|
|
24
|
-
|
|
25
|
-
DevLauncherRemoteLog(
|
|
26
|
-
DevLauncherSimpleRemoteLogBody(message)
|
|
27
|
-
)
|
|
28
|
-
)
|
|
18
|
+
batch.add(message)
|
|
29
19
|
}
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
fun sendViaWebSocket() {
|
|
22
|
+
val request = Request.Builder().url(url.toString()).build()
|
|
23
|
+
httpClient.newWebSocket(request, this)
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
fun
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
override fun onOpen(webSocket: WebSocket, response: Response) {
|
|
27
|
+
webSocket.send(DevLauncherRemoteLog(batch).toJson())
|
|
28
|
+
webSocket.close(1000, null)
|
|
29
|
+
batch.clear()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
postRequest.await(httpClient)
|
|
33
|
+
internal fun Throwable.toRemoteLogString(): String {
|
|
34
|
+
val separator = "\n "
|
|
35
|
+
val baseTrace = stackTrace.joinToString(separator) {
|
|
36
|
+
it.toString()
|
|
37
|
+
}
|
|
38
|
+
val remoteLogString = "$this$separator$baseTrace"
|
|
46
39
|
|
|
47
|
-
|
|
40
|
+
cause?.let {
|
|
41
|
+
return "$remoteLogString\nCaused by ${it.toRemoteLogString()}"
|
|
48
42
|
}
|
|
43
|
+
|
|
44
|
+
return remoteLogString
|
|
49
45
|
}
|
|
@@ -25,6 +25,11 @@ class DevLauncherReactNativeHostHandler(context: Context) : ReactNativeHostHandl
|
|
|
25
25
|
val applicationContext = context.applicationContext
|
|
26
26
|
|
|
27
27
|
SoLoader.init(applicationContext, /* native exopackage */ false)
|
|
28
|
+
if (SoLoader.getLibraryPath("libv8android.so") != null) {
|
|
29
|
+
// Assuming V8 overrides the `getJavaScriptExecutorFactory` in the main ReactNativeHost,
|
|
30
|
+
// return null here to use the default value.
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
28
33
|
if (SoLoader.getLibraryPath("libjsc.so") != null) {
|
|
29
34
|
return JSCExecutorFactory(applicationContext.packageName, AndroidInfoHelpers.getFriendlyDeviceName())
|
|
30
35
|
}
|
|
@@ -25,6 +25,11 @@ class DevLauncherReactNativeHostHandler(context: Context) : ReactNativeHostHandl
|
|
|
25
25
|
val applicationContext = context.applicationContext
|
|
26
26
|
|
|
27
27
|
SoLoader.init(applicationContext, /* native exopackage */ false)
|
|
28
|
+
if (SoLoader.getLibraryPath("libv8android.so") != null) {
|
|
29
|
+
// Assuming V8 overrides the `getJavaScriptExecutorFactory` in the main ReactNativeHost,
|
|
30
|
+
// return null here to use the default value.
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
28
33
|
if (SoLoader.getLibraryPath("libjsc.so") != null) {
|
|
29
34
|
return JSCExecutorFactory(applicationContext.packageName, AndroidInfoHelpers.getFriendlyDeviceName())
|
|
30
35
|
}
|
|
@@ -25,6 +25,11 @@ class DevLauncherReactNativeHostHandler(context: Context) : ReactNativeHostHandl
|
|
|
25
25
|
val applicationContext = context.applicationContext
|
|
26
26
|
|
|
27
27
|
SoLoader.init(applicationContext, /* native exopackage */ false)
|
|
28
|
+
if (SoLoader.getLibraryPath("libv8android.so") != null) {
|
|
29
|
+
// Assuming V8 overrides the `getJavaScriptExecutorFactory` in the main ReactNativeHost,
|
|
30
|
+
// return null here to use the default value.
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
28
33
|
if (SoLoader.getLibraryPath("libjsc.so") != null) {
|
|
29
34
|
return JSCExecutorFactory(applicationContext.packageName, AndroidInfoHelpers.getFriendlyDeviceName())
|
|
30
35
|
}
|
|
@@ -26,6 +26,11 @@ class DevLauncherReactNativeHostHandler(context: Context) : ReactNativeHostHandl
|
|
|
26
26
|
val applicationContext = context.applicationContext
|
|
27
27
|
|
|
28
28
|
SoLoader.init(applicationContext, /* native exopackage */ false)
|
|
29
|
+
if (SoLoader.getLibraryPath("libv8android.so") != null) {
|
|
30
|
+
// Assuming V8 overrides the `getJavaScriptExecutorFactory` in the main ReactNativeHost,
|
|
31
|
+
// return null here to use the default value.
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
29
34
|
if (SoLoader.getLibraryPath("libjsc.so") != null) {
|
|
30
35
|
return JSCExecutorFactory(applicationContext.packageName, AndroidInfoHelpers.getFriendlyDeviceName())
|
|
31
36
|
}
|
|
@@ -26,6 +26,11 @@ class DevLauncherReactNativeHostHandler(context: Context) : ReactNativeHostHandl
|
|
|
26
26
|
val applicationContext = context.applicationContext
|
|
27
27
|
|
|
28
28
|
SoLoader.init(applicationContext, /* native exopackage */ false)
|
|
29
|
+
if (SoLoader.getLibraryPath("libv8android.so") != null) {
|
|
30
|
+
// Assuming V8 overrides the `getJavaScriptExecutorFactory` in the main ReactNativeHost,
|
|
31
|
+
// return null here to use the default value.
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
29
34
|
if (SoLoader.getLibraryPath("libjsc.so") != null) {
|
|
30
35
|
return JSCExecutorFactory(applicationContext.packageName, AndroidInfoHelpers.getFriendlyDeviceName())
|
|
31
36
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
scale,
|
|
12
12
|
} from 'expo-dev-client-components';
|
|
13
13
|
import * as React from 'react';
|
|
14
|
+
import { useWindowDimensions } from 'react-native';
|
|
14
15
|
|
|
15
16
|
import { SafeAreaTop } from '../components/SafeAreaTop';
|
|
16
17
|
import { useBuildInfo } from '../providers/BuildInfoProvider';
|
|
@@ -18,6 +19,7 @@ import { useUser } from '../providers/UserContextProvider';
|
|
|
18
19
|
|
|
19
20
|
export function AppHeader({ navigation }) {
|
|
20
21
|
const buildInfo = useBuildInfo();
|
|
22
|
+
const { width } = useWindowDimensions();
|
|
21
23
|
const { appName, appIcon } = buildInfo;
|
|
22
24
|
|
|
23
25
|
const { userData, selectedAccount } = useUser();
|
|
@@ -31,7 +33,11 @@ export function AppHeader({ navigation }) {
|
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
35
|
<>
|
|
34
|
-
<View
|
|
36
|
+
<View
|
|
37
|
+
bg="default"
|
|
38
|
+
pt="small"
|
|
39
|
+
pb="small"
|
|
40
|
+
style={{ paddingHorizontal: width > 650 ? scale[14] : 0 }}>
|
|
35
41
|
<SafeAreaTop />
|
|
36
42
|
<Row align="center">
|
|
37
43
|
<Spacer.Horizontal size="medium" />
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { scale, View } from 'expo-dev-client-components';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useWindowDimensions } from 'react-native';
|
|
4
|
+
|
|
5
|
+
export function ScreenContainer({ children }: { children: React.ReactNode }) {
|
|
6
|
+
const { width } = useWindowDimensions();
|
|
7
|
+
return <View style={{ flex: 1, marginHorizontal: width > 650 ? scale[14] : 0 }}>{children}</View>;
|
|
8
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AppState,
|
|
3
|
+
EmitterSubscription,
|
|
4
|
+
Linking,
|
|
5
|
+
Platform,
|
|
6
|
+
NativeEventSubscription,
|
|
7
|
+
NativeModules,
|
|
8
|
+
} from 'react-native';
|
|
2
9
|
|
|
3
10
|
const DevLauncherAuth = NativeModules.EXDevLauncherAuth;
|
|
4
11
|
|
|
5
|
-
let
|
|
12
|
+
let appStateSubscription: NativeEventSubscription | null = null;
|
|
13
|
+
let redirectSubscription: EmitterSubscription | null = null;
|
|
6
14
|
let onWebBrowserCloseAndroid: null | (() => void) = null;
|
|
7
15
|
let isAppStateAvailable: boolean = AppState.currentState !== null;
|
|
8
16
|
|
|
@@ -18,20 +26,20 @@ function onAppStateChangeAndroid(state: any) {
|
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
function stopWaitingForRedirect() {
|
|
21
|
-
if (!
|
|
29
|
+
if (!redirectSubscription) {
|
|
22
30
|
throw new Error(
|
|
23
31
|
`The WebBrowser auth session is in an invalid state with no redirect handler when one should be set`
|
|
24
32
|
);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
redirectSubscription.remove();
|
|
36
|
+
redirectSubscription = null;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
async function openBrowserAndWaitAndroidAsync(startUrl: string): Promise<any> {
|
|
32
40
|
const appStateChangedToActive = new Promise<void>((resolve) => {
|
|
33
41
|
onWebBrowserCloseAndroid = resolve;
|
|
34
|
-
AppState.addEventListener('change', onAppStateChangeAndroid);
|
|
42
|
+
appStateSubscription = AppState.addEventListener('change', onAppStateChangeAndroid);
|
|
35
43
|
});
|
|
36
44
|
|
|
37
45
|
let result = { type: 'cancel' };
|
|
@@ -43,25 +51,28 @@ async function openBrowserAndWaitAndroidAsync(startUrl: string): Promise<any> {
|
|
|
43
51
|
result = { type: 'dismiss' };
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
if (appStateSubscription != null) {
|
|
55
|
+
appStateSubscription.remove();
|
|
56
|
+
appStateSubscription = null;
|
|
57
|
+
}
|
|
47
58
|
onWebBrowserCloseAndroid = null;
|
|
48
59
|
return result;
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
function waitForRedirectAsync(returnUrl: string): Promise<any> {
|
|
52
63
|
return new Promise((resolve) => {
|
|
53
|
-
redirectHandler = (event: any) => {
|
|
64
|
+
const redirectHandler = (event: any) => {
|
|
54
65
|
if (event.url.startsWith(returnUrl)) {
|
|
55
66
|
resolve({ url: event.url, type: 'success' });
|
|
56
67
|
}
|
|
57
68
|
};
|
|
58
69
|
|
|
59
|
-
Linking.addEventListener('url', redirectHandler);
|
|
70
|
+
redirectSubscription = Linking.addEventListener('url', redirectHandler);
|
|
60
71
|
});
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
async function openAuthSessionPolyfillAsync(startUrl: string, returnUrl: string): Promise<any> {
|
|
64
|
-
if (
|
|
75
|
+
if (redirectSubscription) {
|
|
65
76
|
throw new Error(
|
|
66
77
|
`The WebBrowser's auth session is in an invalid state with a redirect handler set when it should not be`
|
|
67
78
|
);
|
|
@@ -6,6 +6,7 @@ import { BasicButton } from '../components/BasicButton';
|
|
|
6
6
|
import { EASBranchRow, EASEmptyBranchRow } from '../components/EASUpdatesRows';
|
|
7
7
|
import { EmptyBranchesMessage } from '../components/EmptyBranchesMessage';
|
|
8
8
|
import { FlatList } from '../components/FlatList';
|
|
9
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
9
10
|
import { getRecentRuntime } from '../functions/getRecentRuntime';
|
|
10
11
|
import { useOnUpdatePress } from '../hooks/useOnUpdatePress';
|
|
11
12
|
import { useUpdatesConfig } from '../providers/UpdatesConfigProvider';
|
|
@@ -127,21 +128,23 @@ export function BranchesScreen({ navigation }: BranchesScreenProps) {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
return (
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
<ScreenContainer>
|
|
132
|
+
<View flex="1" px="medium">
|
|
133
|
+
<FlatList
|
|
134
|
+
isLoading={isLoading}
|
|
135
|
+
isRefreshing={isRefreshing}
|
|
136
|
+
onRefresh={() => refetch()}
|
|
137
|
+
ListHeaderComponent={Header}
|
|
138
|
+
extraData={{ length: branches.length, hasNextPage, loadingUpdateId }}
|
|
139
|
+
data={branches}
|
|
140
|
+
ItemSeparatorComponent={Divider}
|
|
141
|
+
renderItem={renderBranch}
|
|
142
|
+
keyExtractor={(item) => item?.id}
|
|
143
|
+
ListFooterComponent={Footer}
|
|
144
|
+
ListEmptyComponent={EmptyList}
|
|
145
|
+
/>
|
|
146
|
+
</View>
|
|
147
|
+
</ScreenContainer>
|
|
145
148
|
);
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -20,6 +20,7 @@ import { AppHeader } from '../components/AppHeader';
|
|
|
20
20
|
import { EASBranchRow, EASEmptyBranchRow } from '../components/EASUpdatesRows';
|
|
21
21
|
import { EmptyBranchesMessage } from '../components/EmptyBranchesMessage';
|
|
22
22
|
import { ListButton } from '../components/ListButton';
|
|
23
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
23
24
|
import { useThrottle } from '../hooks/useDebounce';
|
|
24
25
|
import { useOnUpdatePress } from '../hooks/useOnUpdatePress';
|
|
25
26
|
import { useUpdatesConfig } from '../providers/UpdatesConfigProvider';
|
|
@@ -72,7 +73,7 @@ export function ExtensionsScreen({ navigation }: ExtensionsScreenProps) {
|
|
|
72
73
|
<RefreshControl refreshing={throttledRefreshing} onRefresh={() => refetch()} />
|
|
73
74
|
) : null
|
|
74
75
|
}>
|
|
75
|
-
<
|
|
76
|
+
<ScreenContainer>
|
|
76
77
|
{compatibleExtensions.length === 0 && (
|
|
77
78
|
<>
|
|
78
79
|
<Spacer.Vertical size="medium" />
|
|
@@ -208,7 +209,7 @@ export function ExtensionsScreen({ navigation }: ExtensionsScreenProps) {
|
|
|
208
209
|
<ActivityIndicator />
|
|
209
210
|
</View>
|
|
210
211
|
)}
|
|
211
|
-
</
|
|
212
|
+
</ScreenContainer>
|
|
212
213
|
</ScrollView>
|
|
213
214
|
</View>
|
|
214
215
|
);
|
|
@@ -298,43 +299,41 @@ function EASUpdatesPreview({
|
|
|
298
299
|
|
|
299
300
|
// some compatible branches, possible some empty branches
|
|
300
301
|
return (
|
|
301
|
-
<View>
|
|
302
|
-
<View
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
</Heading>
|
|
307
|
-
</View>
|
|
308
|
-
{branches?.slice(0, 2).map((branch, index, arr) => {
|
|
309
|
-
const isFirst = index === 0;
|
|
310
|
-
const isLast = index === arr.length - 1 && branchCount <= 1;
|
|
311
|
-
const isLoading = branch.updates[0]?.id === loadingUpdateId;
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<View key={branch.name}>
|
|
315
|
-
<EASBranchRow
|
|
316
|
-
branch={branch}
|
|
317
|
-
isFirst={isFirst}
|
|
318
|
-
isLast={isLast}
|
|
319
|
-
navigation={navigation}
|
|
320
|
-
isLoading={isLoading}
|
|
321
|
-
onUpdatePress={onUpdatePress}
|
|
322
|
-
/>
|
|
323
|
-
<Divider />
|
|
324
|
-
</View>
|
|
325
|
-
);
|
|
326
|
-
})}
|
|
327
|
-
|
|
328
|
-
{branchCount > 1 && (
|
|
329
|
-
<ListButton onPress={onSeeAllBranchesPress} isLast>
|
|
330
|
-
<Row>
|
|
331
|
-
<Text size="medium">See all branches</Text>
|
|
332
|
-
<Spacer.Horizontal />
|
|
333
|
-
<ChevronRightIcon />
|
|
334
|
-
</Row>
|
|
335
|
-
</ListButton>
|
|
336
|
-
)}
|
|
302
|
+
<View mx="medium">
|
|
303
|
+
<View py="small" px="small">
|
|
304
|
+
<Heading size="small" color="secondary">
|
|
305
|
+
EAS Updates
|
|
306
|
+
</Heading>
|
|
337
307
|
</View>
|
|
308
|
+
{branches?.slice(0, 2).map((branch, index, arr) => {
|
|
309
|
+
const isFirst = index === 0;
|
|
310
|
+
const isLast = index === arr.length - 1 && branchCount <= 1;
|
|
311
|
+
const isLoading = branch.updates[0]?.id === loadingUpdateId;
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<View key={branch.name}>
|
|
315
|
+
<EASBranchRow
|
|
316
|
+
branch={branch}
|
|
317
|
+
isFirst={isFirst}
|
|
318
|
+
isLast={isLast}
|
|
319
|
+
navigation={navigation}
|
|
320
|
+
isLoading={isLoading}
|
|
321
|
+
onUpdatePress={onUpdatePress}
|
|
322
|
+
/>
|
|
323
|
+
<Divider />
|
|
324
|
+
</View>
|
|
325
|
+
);
|
|
326
|
+
})}
|
|
327
|
+
|
|
328
|
+
{branchCount > 1 && (
|
|
329
|
+
<ListButton onPress={onSeeAllBranchesPress} isLast>
|
|
330
|
+
<Row>
|
|
331
|
+
<Text size="medium">See all branches</Text>
|
|
332
|
+
<Spacer.Horizontal />
|
|
333
|
+
<ChevronRightIcon />
|
|
334
|
+
</Row>
|
|
335
|
+
</ListButton>
|
|
336
|
+
)}
|
|
338
337
|
</View>
|
|
339
338
|
);
|
|
340
339
|
}
|
|
@@ -17,13 +17,14 @@ import {
|
|
|
17
17
|
BranchIcon,
|
|
18
18
|
} from 'expo-dev-client-components';
|
|
19
19
|
import * as React from 'react';
|
|
20
|
-
import { Animated, ScrollView } from 'react-native';
|
|
20
|
+
import { Animated, ScrollView, useWindowDimensions } from 'react-native';
|
|
21
21
|
|
|
22
22
|
import { AppHeader } from '../components/AppHeader';
|
|
23
23
|
import { DevServerExplainerModal } from '../components/DevServerExplainerModal';
|
|
24
24
|
import { useLoadingContainerStyle } from '../components/EASUpdatesRows';
|
|
25
25
|
import { LoadAppErrorModal } from '../components/LoadAppErrorModal';
|
|
26
26
|
import { PulseIndicator } from '../components/PulseIndicator';
|
|
27
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
27
28
|
import { Toasts } from '../components/Toasts';
|
|
28
29
|
import { UrlDropdown } from '../components/UrlDropdown';
|
|
29
30
|
import { formatUpdateUrl } from '../functions/formatUpdateUrl';
|
|
@@ -114,7 +115,12 @@ export function HomeScreen({
|
|
|
114
115
|
return (
|
|
115
116
|
<View testID="DevLauncherMainScreen">
|
|
116
117
|
<AppHeader navigation={navigation} />
|
|
117
|
-
<ScrollView
|
|
118
|
+
<ScrollView
|
|
119
|
+
style={{}}
|
|
120
|
+
contentContainerStyle={{
|
|
121
|
+
paddingBottom: scale['48'],
|
|
122
|
+
}}>
|
|
123
|
+
<ScreenContainer>
|
|
118
124
|
{crashReport && (
|
|
119
125
|
<View px="medium" py="small" mt="small">
|
|
120
126
|
<Button.ScaleOnPressContainer onPress={onCrashReportPress} bg="default" rounded="large">
|
|
@@ -197,6 +203,7 @@ export function HomeScreen({
|
|
|
197
203
|
|
|
198
204
|
<RecentlyOpenedApps onRecentAppPress={onRecentAppPress} loadingUrl={loadingUrl} />
|
|
199
205
|
</View>
|
|
206
|
+
</ScreenContainer>
|
|
200
207
|
</ScrollView>
|
|
201
208
|
</View>
|
|
202
209
|
);
|