expo-dev-launcher 6.0.12 → 6.1.0-canary-20250919-7a31b96

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 CHANGED
@@ -6,19 +6,13 @@
6
6
 
7
7
  ### 🎉 New features
8
8
 
9
+ - Remove `ExpoAppDelegate` inheritance requirement ([#39417](https://github.com/expo/expo/pull/39417) by [@gabrieldonadel](https://github.com/gabrieldonadel))
10
+
9
11
  ### 🐛 Bug fixes
10
12
 
11
13
  ### 💡 Others
12
14
 
13
- ## 6.0.12 2025-09-22
14
-
15
- ### 🎉 New features
16
-
17
- - [Android] Adds loading state when connecting to a development server. ([#39873](https://github.com/expo/expo/pull/39873) by [@lukmccall](https://github.com/lukmccall))
18
-
19
- ### 🐛 Bug fixes
20
-
21
- - [expo-dev-launcher] Fix manual URL entry: decode percent-encoded URLs, enable return key submit, and support dark mode text. ([#39840](https://github.com/expo/expo/pull/39840) by [@blazejkustra](https://github.com/blazejkustra))
15
+ - [Android] Migrated from `kotlinOptions` to `compilerOptions` DSL. ([#39794](https://github.com/expo/expo/pull/39794) by [@huextrat](https://github.com/huextrat))
22
16
 
23
17
  ## 6.0.11 — 2025-09-11
24
18
 
@@ -20,13 +20,13 @@ expoModule {
20
20
  }
21
21
 
22
22
  group = "host.exp.exponent"
23
- version = "6.0.12"
23
+ version = "6.1.0-canary-20250919-7a31b96"
24
24
 
25
25
  android {
26
26
  namespace "expo.modules.devlauncher"
27
27
  defaultConfig {
28
28
  versionCode 9
29
- versionName "6.0.12"
29
+ versionName "6.1.0-canary-20250919-7a31b96"
30
30
  }
31
31
 
32
32
  buildTypes {
@@ -7,8 +7,6 @@ import android.net.Uri
7
7
  import android.os.Bundle
8
8
  import android.util.Log
9
9
  import androidx.annotation.UiThread
10
- import androidx.compose.runtime.State
11
- import androidx.compose.runtime.mutableStateOf
12
10
  import androidx.core.net.toUri
13
11
  import com.facebook.react.ReactActivity
14
12
  import com.facebook.react.ReactActivityDelegate
@@ -109,10 +107,6 @@ class DevLauncherController private constructor() :
109
107
 
110
108
  private var appIsLoading = false
111
109
 
112
- private val _isLoadingToBundler = mutableStateOf(false)
113
- val isLoadingToBundler: State<Boolean>
114
- get() = _isLoadingToBundler
115
-
116
110
  private var networkInterceptor: DevLauncherNetworkInterceptor? = null
117
111
  private var pendingIntentExtras: Bundle? = null
118
112
 
@@ -136,7 +130,6 @@ class DevLauncherController private constructor() :
136
130
  return
137
131
  }
138
132
  appIsLoading = true
139
- _isLoadingToBundler.value = true
140
133
  }
141
134
 
142
135
  try {
@@ -170,7 +163,6 @@ class DevLauncherController private constructor() :
170
163
  lifecycle.addListener(appLoaderListener)
171
164
  mode = Mode.APP
172
165
 
173
- _isLoadingToBundler.value = false
174
166
  // Note that `launch` method is a suspend one. So the execution will be stopped here until the method doesn't finish.
175
167
  if (appLoader.launch(appIntent)) {
176
168
  recentlyOpedAppsRegistry.appWasOpened(parsedUrl.toString(), devLauncherUrl.queryParams, manifest)
@@ -187,7 +179,6 @@ class DevLauncherController private constructor() :
187
179
  } catch (e: Exception) {
188
180
  synchronized(this) {
189
181
  appIsLoading = false
190
- _isLoadingToBundler.value = false
191
182
  }
192
183
  throw e
193
184
  }
@@ -243,7 +234,7 @@ class DevLauncherController private constructor() :
243
234
  // used by appetize for snack
244
235
  if (intent.getBooleanExtra("EXDevMenuDisableAutoLaunch", false)) {
245
236
  canLaunchDevMenuOnStart = false
246
- this.devMenuManager.setCanLaunchDevMenuOnStart(false)
237
+ this.devMenuManager.setCanLaunchDevMenuOnStart(canLaunchDevMenuOnStart)
247
238
  }
248
239
 
249
240
  if (!isDevLauncherUrl(uri)) {
@@ -1,40 +1,14 @@
1
1
  package expo.modules.devlauncher.compose
2
2
 
3
3
  import androidx.compose.animation.AnimatedContentTransitionScope
4
- import androidx.compose.animation.AnimatedVisibility
5
4
  import androidx.compose.animation.EnterTransition
6
5
  import androidx.compose.animation.ExitTransition
7
6
  import androidx.compose.animation.core.tween
8
- import androidx.compose.animation.fadeIn
9
- import androidx.compose.animation.fadeOut
10
- import androidx.compose.animation.slideIn
11
- import androidx.compose.animation.slideOut
12
- import androidx.compose.foundation.background
13
- import androidx.compose.foundation.clickable
14
- import androidx.compose.foundation.interaction.MutableInteractionSource
15
- import androidx.compose.foundation.layout.Arrangement
16
- import androidx.compose.foundation.layout.Box
17
- import androidx.compose.foundation.layout.Row
18
- import androidx.compose.foundation.layout.fillMaxSize
19
- import androidx.compose.foundation.layout.fillMaxWidth
20
- import androidx.compose.foundation.layout.navigationBarsPadding
21
- import androidx.compose.foundation.layout.padding
22
- import androidx.compose.foundation.shape.RoundedCornerShape
23
7
  import androidx.compose.runtime.Composable
24
- import androidx.compose.runtime.getValue
25
8
  import androidx.compose.runtime.remember
26
- import androidx.compose.ui.Alignment
27
- import androidx.compose.ui.Modifier
28
- import androidx.compose.ui.draw.clip
29
- import androidx.compose.ui.graphics.Color
30
- import androidx.compose.ui.text.font.FontWeight
31
- import androidx.compose.ui.unit.IntOffset
32
- import androidx.compose.ui.unit.dp
33
9
  import androidx.navigation.compose.NavHost
34
10
  import androidx.navigation.compose.composable
35
11
  import androidx.navigation.compose.rememberNavController
36
- import expo.modules.devlauncher.DevLauncherController
37
- import expo.modules.devlauncher.compose.primitives.CircularProgressBar
38
12
  import expo.modules.devlauncher.compose.primitives.DefaultScaffold
39
13
  import expo.modules.devlauncher.compose.routes.CrashReport
40
14
  import expo.modules.devlauncher.compose.routes.CrashReportRoute
@@ -47,8 +21,6 @@ import expo.modules.devlauncher.compose.routes.UpdatesRoute
47
21
  import expo.modules.devlauncher.compose.ui.BottomTabBar
48
22
  import expo.modules.devlauncher.compose.ui.Full
49
23
  import expo.modules.devlauncher.compose.ui.rememberBottomSheetState
50
- import expo.modules.devmenu.compose.newtheme.NewAppTheme
51
- import expo.modules.devmenu.compose.primitives.NewText
52
24
 
53
25
  @Composable
54
26
  fun DevLauncherBottomTabsNavigator() {
@@ -134,59 +106,4 @@ fun DevLauncherBottomTabsNavigator() {
134
106
  ProfileRoute(profileBottomSheetState)
135
107
 
136
108
  DevelopmentServersRoute(developmentServersBottomSheetState)
137
-
138
- val isVisible by (DevLauncherController.instance as DevLauncherController).isLoadingToBundler
139
-
140
- AnimatedVisibility(
141
- visible = isVisible,
142
- enter = fadeIn(animationSpec = tween(durationMillis = 300)),
143
- exit = fadeOut(animationSpec = tween(durationMillis = 300))
144
- ) {
145
- Box(
146
- modifier = Modifier
147
- .fillMaxSize()
148
- .background(Color.Black.copy(alpha = 0.6f))
149
- .clickable(
150
- interactionSource = remember { MutableInteractionSource() },
151
- indication = null,
152
- onClick = {
153
- // Captures all clicks to block interaction with the underlying screen
154
- }
155
- )
156
- ) {
157
- Row(
158
- horizontalArrangement = Arrangement.SpaceBetween,
159
- verticalAlignment = Alignment.CenterVertically,
160
- modifier = Modifier
161
- .align(Alignment.BottomStart)
162
- .animateEnterExit(
163
- enter = slideIn(
164
- animationSpec = tween(durationMillis = 300),
165
- initialOffset = { fullSize -> IntOffset(-fullSize.height, fullSize.width) }
166
- ),
167
- exit = slideOut(
168
- animationSpec = tween(durationMillis = 300),
169
- targetOffset = { fullSize -> IntOffset(fullSize.height, fullSize.width) }
170
- )
171
- )
172
- .clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
173
- .background(NewAppTheme.colors.background.element)
174
- .fillMaxWidth()
175
- .navigationBarsPadding()
176
- .padding(NewAppTheme.spacing.`3`)
177
- .padding(top = NewAppTheme.spacing.`1`)
178
- ) {
179
- NewText(
180
- "Connecting to the development server...",
181
- style = NewAppTheme.font.md.copy(
182
- fontWeight = FontWeight.SemiBold
183
- )
184
- )
185
-
186
- CircularProgressBar(
187
- size = 20.dp
188
- )
189
- }
190
- }
191
- }
192
109
  }
@@ -25,14 +25,13 @@ import kotlin.time.Duration.Companion.seconds
25
25
 
26
26
  @Composable
27
27
  fun CircularProgressBar(
28
- modifier: Modifier = Modifier,
29
28
  startAngle: Float = 270f,
30
29
  size: Dp = 96.dp,
31
30
  strokeWidth: Dp = size / 8,
32
31
  duration: Duration = 1.seconds
33
32
  ) {
34
33
  val backgroundColor = NewAppTheme.pallet.gray.`3`
35
- val progressColor = NewAppTheme.pallet.blue.`8`
34
+ val progressColor = NewAppTheme.pallet.blue.`5`
36
35
 
37
36
  val transition = rememberInfiniteTransition(label = "infiniteSpinningTransition")
38
37
 
@@ -45,7 +44,7 @@ fun CircularProgressBar(
45
44
  label = "Progress Animation"
46
45
  )
47
46
 
48
- Canvas(modifier = Modifier.size(size).then(modifier)) {
47
+ Canvas(modifier = Modifier.size(size)) {
49
48
  val strokeWidthPx = strokeWidth.toPx()
50
49
  val arcSize = size.toPx() - strokeWidthPx
51
50
  drawArc(
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
9
9
  import androidx.compose.foundation.layout.padding
10
10
  import androidx.compose.foundation.shape.RoundedCornerShape
11
11
  import androidx.compose.foundation.text.KeyboardOptions
12
- import androidx.compose.foundation.text.KeyboardActions
13
12
  import androidx.compose.runtime.Composable
14
13
  import androidx.compose.runtime.getValue
15
14
  import androidx.compose.runtime.mutableStateOf
@@ -27,7 +26,7 @@ import androidx.compose.ui.tooling.preview.Preview
27
26
  import androidx.compose.ui.unit.dp
28
27
  import com.composeunstyled.TextField
29
28
  import com.composeunstyled.TextInput
30
- import expo.modules.devlauncher.compose.utils.sanitizeUrlString
29
+ import expo.modules.devlauncher.compose.utils.validateUrl
31
30
  import expo.modules.devmenu.compose.newtheme.NewAppTheme
32
31
  import expo.modules.devmenu.compose.primitives.NewText
33
32
 
@@ -38,18 +37,6 @@ fun ServerUrlInput(
38
37
  var url by remember { mutableStateOf("") }
39
38
  val context = LocalContext.current
40
39
 
41
- fun connectToURL() {
42
- val sanitizedURL = sanitizeUrlString(url)
43
- if (sanitizedURL != null) {
44
- openApp(sanitizedURL)
45
- url = ""
46
- } else {
47
- Toast
48
- .makeText(context, "Invalid URL", Toast.LENGTH_SHORT)
49
- .show()
50
- }
51
- }
52
-
53
40
  Column(
54
41
  verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`2`)
55
42
  ) {
@@ -76,9 +63,6 @@ fun ServerUrlInput(
76
63
  autoCorrectEnabled = false,
77
64
  keyboardType = KeyboardType.Uri
78
65
  ),
79
- keyboardActions = KeyboardActions(
80
- onDone = { connectToURL() }
81
- ),
82
66
  cursorBrush = SolidColor(NewAppTheme.colors.text.default.copy(alpha = 0.9f))
83
67
  ) {
84
68
  TextInput(
@@ -101,7 +85,15 @@ fun ServerUrlInput(
101
85
  textStyle = NewAppTheme.font.md.merge(
102
86
  fontWeight = FontWeight.SemiBold
103
87
  ),
104
- onClick = { connectToURL() }
88
+ onClick = {
89
+ if (validateUrl(url)) {
90
+ openApp(url)
91
+ } else {
92
+ Toast
93
+ .makeText(context, "Invalid URL", Toast.LENGTH_SHORT)
94
+ .show()
95
+ }
96
+ }
105
97
  )
106
98
  }
107
99
  }
@@ -1,31 +1,12 @@
1
1
  package expo.modules.devlauncher.compose.utils
2
2
 
3
3
  import androidx.core.net.toUri
4
- import java.net.URLDecoder
5
- import java.nio.charset.StandardCharsets
6
-
7
- fun sanitizeUrlString(urlString: String): String? {
8
- var sanitizedUrl = urlString.trim()
9
-
10
- val decodedUrl = try {
11
- URLDecoder.decode(sanitizedUrl, StandardCharsets.UTF_8.toString())
12
- } catch (_: Exception) {
13
- sanitizedUrl
14
- }
15
- sanitizedUrl = decodedUrl
16
-
17
- if (!sanitizedUrl.contains("://")) {
18
- sanitizedUrl = "http://$sanitizedUrl"
19
- }
20
4
 
5
+ fun validateUrl(url: String): Boolean {
21
6
  return try {
22
- val uri = sanitizedUrl.toUri()
23
- if (uri.scheme != null && uri.host != null) {
24
- sanitizedUrl
25
- } else {
26
- null
27
- }
7
+ val uri = url.toUri()
8
+ uri.scheme != null && uri.host != null
28
9
  } catch (_: Throwable) {
29
- null
10
+ false
30
11
  }
31
12
  }
@@ -1,3 +1,4 @@
1
+ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
1
2
  import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2
3
 
3
4
  plugins {
@@ -22,8 +23,8 @@ java {
22
23
  }
23
24
 
24
25
  tasks.withType<KotlinCompile> {
25
- kotlinOptions {
26
- jvmTarget = JavaVersion.VERSION_11.toString()
26
+ compilerOptions {
27
+ jvmTarget.set(JvmTarget.JVM_11)
27
28
  }
28
29
  }
29
30
 
@@ -50,21 +50,21 @@ public class ExpoDevLauncherReactDelegateHandler: ExpoReactDelegateHandler, EXDe
50
50
  // MARK: EXDevelopmentClientControllerDelegate implementations
51
51
 
52
52
  public func devLauncherController(_ developmentClientController: EXDevLauncherController, didStartWithSuccess success: Bool) {
53
- guard let appDelegate = (UIApplication.shared.delegate as? (any ReactNativeFactoryProvider)) ??
54
- ((UIApplication.shared.delegate as? NSObject)?.value(forKey: "_expoAppDelegate") as? (any ReactNativeFactoryProvider)) else {
55
- fatalError("`UIApplication.shared.delegate` must be an `ExpoAppDelegate` or `EXAppDelegateWrapper`")
53
+ guard let reactDelegate = self.reactDelegate else {
54
+ fatalError("`reactDelegate` should not be nil")
56
55
  }
57
- self.reactNativeFactory = appDelegate.factory as? RCTReactNativeFactory
56
+
57
+ self.reactNativeFactory = reactDelegate.reactNativeFactory as? RCTReactNativeFactory
58
58
 
59
59
  // Reset rctAppDelegate so we can relaunch the app
60
- if self.reactNativeFactory?.delegate?.newArchEnabled() ?? false {
60
+ if RCTIsNewArchEnabled() {
61
61
  self.reactNativeFactory?.rootViewFactory.setValue(nil, forKey: "_reactHost")
62
62
  } else {
63
63
  self.reactNativeFactory?.bridge = nil
64
64
  self.reactNativeFactory?.rootViewFactory.bridge = nil
65
65
  }
66
66
 
67
- let rootView = appDelegate.recreateRootView(
67
+ let rootView = reactDelegate.reactNativeFactory.recreateRootView(
68
68
  withBundleURL: developmentClientController.sourceUrl(),
69
69
  moduleName: self.rootViewModuleName,
70
70
  initialProps: self.rootViewInitialProperties,
@@ -8,10 +8,6 @@ import Combine
8
8
  private func sanitizeUrlString(_ urlString: String) -> String? {
9
9
  var sanitizedUrl = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
10
10
 
11
- if let decodedUrl = sanitizedUrl.removingPercentEncoding {
12
- sanitizedUrl = decodedUrl
13
- }
14
-
15
11
  if !sanitizedUrl.contains("://") {
16
12
  sanitizedUrl = "http://" + sanitizedUrl
17
13
  }
@@ -30,19 +26,6 @@ struct DevServersView: View {
30
26
  @State private var urlText = ""
31
27
  @State private var cancellables = Set<AnyCancellable>()
32
28
 
33
- private func connectToURL() {
34
- if !urlText.isEmpty {
35
- let sanitizedURL = sanitizeUrlString(urlText)
36
- if let validURL = sanitizedURL {
37
- viewModel.openApp(url: validURL)
38
- withAnimation(.easeInOut(duration: 0.3)) {
39
- showingURLInput = false
40
- }
41
- urlText = ""
42
- }
43
- }
44
- }
45
-
46
29
  var body: some View {
47
30
  VStack(alignment: .leading, spacing: 12) {
48
31
  header
@@ -98,8 +81,7 @@ struct DevServersView: View {
98
81
  .disableAutocorrection(true)
99
82
  .padding(.horizontal, 16)
100
83
  .padding(.vertical, 12)
101
- .foregroundColor(.primary)
102
- .onSubmit(connectToURL)
84
+ .foregroundColor(.black)
103
85
  #if !os(tvOS)
104
86
  .overlay(
105
87
  RoundedRectangle(cornerRadius: 5)
@@ -142,7 +124,18 @@ struct DevServersView: View {
142
124
  }
143
125
 
144
126
  private var connectButton: some View {
145
- Button(action: connectToURL) {
127
+ Button {
128
+ if !urlText.isEmpty {
129
+ let sanitizedURL = sanitizeUrlString(urlText)
130
+ if let validURL = sanitizedURL {
131
+ viewModel.openApp(url: validURL)
132
+ withAnimation(.easeInOut(duration: 0.3)) {
133
+ showingURLInput = false
134
+ }
135
+ urlText = ""
136
+ }
137
+ }
138
+ } label: {
146
139
  Text("Connect")
147
140
  .font(.headline)
148
141
  .foregroundColor(.white)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "6.0.12",
4
+ "version": "6.1.0-canary-20250919-7a31b96",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,11 +15,10 @@
15
15
  "license": "MIT",
16
16
  "homepage": "https://docs.expo.dev",
17
17
  "dependencies": {
18
- "expo-dev-menu": "7.0.12",
19
- "expo-manifests": "~1.0.8"
18
+ "expo-dev-menu": "7.0.12-canary-20250919-7a31b96",
19
+ "expo-manifests": "1.0.9-canary-20250919-7a31b96"
20
20
  },
21
21
  "peerDependencies": {
22
- "expo": "*"
23
- },
24
- "gitHead": "6523053d0d997d2a21f580d2752b2f873c122038"
22
+ "expo": "55.0.0-canary-20250919-7a31b96"
23
+ }
25
24
  }