expo-dev-launcher 6.0.5 → 6.0.6
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 +4 -0
- package/android/build.gradle +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/compose/DevLauncherBottomTabsNavigator.kt +16 -4
- package/android/src/debug/java/expo/modules/devlauncher/compose/primitives/Accordion.kt +47 -40
- package/android/src/debug/java/expo/modules/devlauncher/compose/routes/DevelopmentServers.kt +51 -0
- package/android/src/debug/java/expo/modules/devlauncher/compose/routes/Home.kt +4 -2
- package/android/src/debug/java/expo/modules/devlauncher/compose/routes/Profile.kt +2 -2
- package/android/src/debug/java/expo/modules/devlauncher/compose/screens/BranchesScreen.kt +121 -128
- package/android/src/debug/java/expo/modules/devlauncher/compose/screens/HomeScreen.kt +29 -201
- package/android/src/debug/java/expo/modules/devlauncher/compose/ui/DevelopmentSession.kt +122 -0
- package/android/src/debug/java/expo/modules/devlauncher/compose/ui/FetchDevelopmentServersButton.kt +118 -0
- package/android/src/debug/java/expo/modules/devlauncher/compose/ui/ScanQRCodeButton.kt +62 -0
- package/android/src/debug/java/expo/modules/devlauncher/compose/ui/ServerUrlInput.kt +12 -14
- package/android/src/main/res/drawable/download.xml +13 -0
- package/android/src/main/res/drawable/plus.xml +9 -0
- package/package.json +4 -4
- package/android/src/debug/java/expo/modules/devlauncher/compose/ui/DevelopmentServerHelp.kt +0 -55
|
@@ -1,110 +1,40 @@
|
|
|
1
1
|
package expo.modules.devlauncher.compose.screens
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.background
|
|
4
3
|
import androidx.compose.foundation.clickable
|
|
5
4
|
import androidx.compose.foundation.layout.Arrangement
|
|
6
5
|
import androidx.compose.foundation.layout.Column
|
|
7
6
|
import androidx.compose.foundation.layout.Row
|
|
8
|
-
import androidx.compose.foundation.layout.displayCutoutPadding
|
|
9
7
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
10
8
|
import androidx.compose.foundation.layout.padding
|
|
11
|
-
import androidx.compose.foundation.layout.size
|
|
12
|
-
import androidx.compose.foundation.layout.systemBarsPadding
|
|
13
9
|
import androidx.compose.foundation.rememberScrollState
|
|
14
|
-
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
15
10
|
import androidx.compose.foundation.verticalScroll
|
|
16
11
|
import androidx.compose.runtime.Composable
|
|
17
12
|
import androidx.compose.runtime.LaunchedEffect
|
|
18
|
-
import androidx.compose.runtime.getValue
|
|
19
|
-
import androidx.compose.runtime.mutableStateOf
|
|
20
|
-
import androidx.compose.runtime.remember
|
|
21
|
-
import androidx.compose.runtime.setValue
|
|
22
|
-
import androidx.compose.ui.Alignment
|
|
23
13
|
import androidx.compose.ui.Modifier
|
|
24
|
-
import androidx.compose.ui.draw.clip
|
|
25
|
-
import androidx.compose.ui.graphics.SolidColor
|
|
26
|
-
import androidx.compose.ui.res.painterResource
|
|
27
14
|
import androidx.compose.ui.text.font.FontWeight
|
|
28
15
|
import androidx.compose.ui.tooling.preview.Preview
|
|
29
|
-
import androidx.compose.ui.unit.dp
|
|
30
|
-
import com.composables.core.Dialog
|
|
31
|
-
import com.composables.core.DialogPanel
|
|
32
|
-
import com.composables.core.DialogState
|
|
33
|
-
import com.composables.core.Scrim
|
|
34
16
|
import com.composables.core.rememberDialogState
|
|
35
17
|
import com.composeunstyled.Button
|
|
36
|
-
import com.composeunstyled.Icon
|
|
37
|
-
import expo.modules.core.utilities.EmulatorUtilities
|
|
38
|
-
import expo.modules.devlauncher.R
|
|
39
18
|
import expo.modules.devlauncher.compose.DefaultScreenContainer
|
|
40
19
|
import expo.modules.devlauncher.compose.models.HomeAction
|
|
41
20
|
import expo.modules.devlauncher.compose.models.HomeState
|
|
42
21
|
import expo.modules.devlauncher.compose.primitives.Accordion
|
|
43
22
|
import expo.modules.devlauncher.compose.ui.AppHeader
|
|
44
23
|
import expo.modules.devlauncher.compose.ui.AppLoadingErrorDialog
|
|
45
|
-
import expo.modules.devlauncher.compose.ui.
|
|
24
|
+
import expo.modules.devlauncher.compose.ui.DevelopmentSessionActions
|
|
25
|
+
import expo.modules.devlauncher.compose.ui.DevelopmentSessionSection
|
|
46
26
|
import expo.modules.devlauncher.compose.ui.RunningAppCard
|
|
47
|
-
import expo.modules.devlauncher.compose.ui.ServerUrlInput
|
|
48
27
|
import expo.modules.devlauncher.launcher.DevLauncherAppEntry
|
|
49
28
|
import expo.modules.devlauncher.launcher.errors.DevLauncherErrorInstance
|
|
50
29
|
import expo.modules.devlauncher.services.PackagerInfo
|
|
51
30
|
import expo.modules.devmenu.compose.newtheme.NewAppTheme
|
|
52
|
-
import expo.modules.devmenu.compose.primitives.DayNighIcon
|
|
53
|
-
import expo.modules.devmenu.compose.primitives.Divider
|
|
54
|
-
import expo.modules.devmenu.compose.primitives.Heading
|
|
55
31
|
import expo.modules.devmenu.compose.primitives.NewText
|
|
56
|
-
import expo.modules.devmenu.compose.primitives.RowLayout
|
|
57
32
|
import expo.modules.devmenu.compose.primitives.Spacer
|
|
58
|
-
import expo.modules.devmenu.compose.primitives.pulseEffect
|
|
59
33
|
import expo.modules.devmenu.compose.ui.Warning
|
|
60
|
-
import kotlinx.coroutines.delay
|
|
61
|
-
import kotlin.time.Clock
|
|
62
|
-
import kotlin.time.Duration.Companion.seconds
|
|
63
34
|
import kotlin.time.ExperimentalTime
|
|
64
|
-
import kotlin.time.Instant
|
|
65
35
|
|
|
66
36
|
@Composable
|
|
67
|
-
fun
|
|
68
|
-
Dialog(state = dialogState) {
|
|
69
|
-
Scrim()
|
|
70
|
-
|
|
71
|
-
DialogPanel(
|
|
72
|
-
modifier = Modifier
|
|
73
|
-
.displayCutoutPadding()
|
|
74
|
-
.systemBarsPadding()
|
|
75
|
-
.padding(horizontal = NewAppTheme.spacing.`3`)
|
|
76
|
-
.clip(RoundedCornerShape(NewAppTheme.borderRadius.xl))
|
|
77
|
-
.background(NewAppTheme.colors.background.default)
|
|
78
|
-
) {
|
|
79
|
-
Column {
|
|
80
|
-
RowLayout(
|
|
81
|
-
rightComponent = {
|
|
82
|
-
Button(onClick = {
|
|
83
|
-
dialogState.visible = false
|
|
84
|
-
}) {
|
|
85
|
-
DayNighIcon(
|
|
86
|
-
id = R.drawable.x_icon,
|
|
87
|
-
contentDescription = "Close dialog"
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
modifier = Modifier.padding(NewAppTheme.spacing.`3`)
|
|
92
|
-
) {
|
|
93
|
-
Heading("Development servers")
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Divider()
|
|
97
|
-
|
|
98
|
-
Row(modifier = Modifier.padding(NewAppTheme.spacing.`3`)) {
|
|
99
|
-
DevelopmentSessionHelper()
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@Composable
|
|
107
|
-
fun CrashReport(
|
|
37
|
+
private fun CrashReport(
|
|
108
38
|
crashReport: DevLauncherErrorInstance?,
|
|
109
39
|
onClick: (report: DevLauncherErrorInstance) -> Unit = {}
|
|
110
40
|
) {
|
|
@@ -128,10 +58,10 @@ fun CrashReport(
|
|
|
128
58
|
fun HomeScreen(
|
|
129
59
|
state: HomeState,
|
|
130
60
|
onAction: (HomeAction) -> Unit,
|
|
131
|
-
onProfileClick: () -> Unit
|
|
61
|
+
onProfileClick: () -> Unit,
|
|
62
|
+
onDevServersClick: () -> Unit
|
|
132
63
|
) {
|
|
133
64
|
val hasPackager = state.runningPackagers.isNotEmpty()
|
|
134
|
-
val howToStartDevelopmentDialogState = rememberDialogState(initiallyVisible = false)
|
|
135
65
|
val errorDialogState = rememberDialogState(initiallyVisible = false)
|
|
136
66
|
val scrollState = rememberScrollState()
|
|
137
67
|
|
|
@@ -147,8 +77,6 @@ fun HomeScreen(
|
|
|
147
77
|
}
|
|
148
78
|
}
|
|
149
79
|
|
|
150
|
-
HowToStartDevelopmentServerDialog(howToStartDevelopmentDialogState)
|
|
151
|
-
|
|
152
80
|
AppLoadingErrorDialog(
|
|
153
81
|
errorDialogState,
|
|
154
82
|
currentError = state.loadingError
|
|
@@ -195,9 +123,13 @@ fun HomeScreen(
|
|
|
195
123
|
fontFamily = NewAppTheme.font.mono
|
|
196
124
|
),
|
|
197
125
|
color = NewAppTheme.colors.text.link,
|
|
198
|
-
modifier = Modifier.clickable
|
|
199
|
-
|
|
200
|
-
|
|
126
|
+
modifier = Modifier.clickable(
|
|
127
|
+
interactionSource = null,
|
|
128
|
+
indication = null,
|
|
129
|
+
onClick = {
|
|
130
|
+
onDevServersClick()
|
|
131
|
+
}
|
|
132
|
+
)
|
|
201
133
|
)
|
|
202
134
|
}
|
|
203
135
|
|
|
@@ -205,7 +137,7 @@ fun HomeScreen(
|
|
|
205
137
|
|
|
206
138
|
if (hasPackager) {
|
|
207
139
|
Column(
|
|
208
|
-
verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`
|
|
140
|
+
verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`2`)
|
|
209
141
|
) {
|
|
210
142
|
for (packager in state.runningPackagers) {
|
|
211
143
|
RunningAppCard(
|
|
@@ -216,128 +148,19 @@ fun HomeScreen(
|
|
|
216
148
|
}
|
|
217
149
|
}
|
|
218
150
|
}
|
|
219
|
-
} else {
|
|
220
|
-
DevelopmentSessionHelper()
|
|
221
|
-
Spacer(NewAppTheme.spacing.`2`)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
val isFetching = state.isFetchingPackagers
|
|
225
|
-
var isFetchingUIState by remember { mutableStateOf(isFetching) }
|
|
226
|
-
var fetchStartTime by remember { mutableStateOf<Instant?>(null) }
|
|
227
|
-
|
|
228
|
-
LaunchedEffect(isFetching) {
|
|
229
|
-
if (isFetching) {
|
|
230
|
-
isFetchingUIState = true
|
|
231
|
-
fetchStartTime = Clock.System.now()
|
|
232
|
-
return@LaunchedEffect
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!isFetchingUIState) {
|
|
236
|
-
return@LaunchedEffect
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
val startTime = fetchStartTime
|
|
240
|
-
if (startTime == null) {
|
|
241
|
-
isFetchingUIState = false
|
|
242
|
-
return@LaunchedEffect
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
val elapsedTime = startTime - Clock.System.now()
|
|
246
|
-
val remainingTime = 2.seconds - elapsedTime
|
|
247
151
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
isFetchingUIState = state.isFetchingPackagers
|
|
251
|
-
}
|
|
152
|
+
Spacer(NewAppTheme.spacing.`2`)
|
|
252
153
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
},
|
|
257
|
-
enabled = !isFetchingUIState
|
|
258
|
-
) {
|
|
259
|
-
Row(
|
|
260
|
-
horizontalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`2`),
|
|
261
|
-
verticalAlignment = Alignment.CenterVertically,
|
|
154
|
+
Accordion(
|
|
155
|
+
"New development server",
|
|
156
|
+
initialState = false,
|
|
262
157
|
modifier = Modifier
|
|
263
158
|
.fillMaxWidth()
|
|
264
|
-
.padding(vertical = 12.dp)
|
|
265
159
|
) {
|
|
266
|
-
|
|
267
|
-
painter = painterResource(R.drawable.signal),
|
|
268
|
-
contentDescription = "Signal Icon",
|
|
269
|
-
tint = NewAppTheme.colors.text.link,
|
|
270
|
-
modifier = Modifier
|
|
271
|
-
.size(16.dp)
|
|
272
|
-
.then(
|
|
273
|
-
if (isFetchingUIState) {
|
|
274
|
-
Modifier.pulseEffect(
|
|
275
|
-
initialScale = 0.2f,
|
|
276
|
-
brush = SolidColor(NewAppTheme.colors.text.link.copy(alpha = 0.4f))
|
|
277
|
-
)
|
|
278
|
-
} else {
|
|
279
|
-
Modifier
|
|
280
|
-
}
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
NewText(
|
|
285
|
-
if (isFetchingUIState) {
|
|
286
|
-
"Searching for development servers..."
|
|
287
|
-
} else {
|
|
288
|
-
"Fetch development servers"
|
|
289
|
-
},
|
|
290
|
-
style = NewAppTheme.font.sm,
|
|
291
|
-
color = NewAppTheme.colors.text.link
|
|
292
|
-
)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (!EmulatorUtilities.isRunningOnEmulator()) {
|
|
297
|
-
Button(
|
|
298
|
-
onClick = {
|
|
299
|
-
onAction(HomeAction.ScanQRCode)
|
|
300
|
-
}
|
|
301
|
-
) {
|
|
302
|
-
Row(
|
|
303
|
-
horizontalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`2`),
|
|
304
|
-
verticalAlignment = Alignment.CenterVertically,
|
|
305
|
-
modifier = Modifier
|
|
306
|
-
.fillMaxWidth()
|
|
307
|
-
.padding(vertical = 12.dp)
|
|
308
|
-
) {
|
|
309
|
-
Icon(
|
|
310
|
-
painter = painterResource(R.drawable.scan),
|
|
311
|
-
contentDescription = "QR code",
|
|
312
|
-
tint = NewAppTheme.colors.text.link,
|
|
313
|
-
modifier = Modifier
|
|
314
|
-
.size(16.dp)
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
NewText(
|
|
318
|
-
"Scan QR code",
|
|
319
|
-
style = NewAppTheme.font.sm,
|
|
320
|
-
color = NewAppTheme.colors.text.link
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
Accordion(
|
|
327
|
-
"Enter URL manually",
|
|
328
|
-
initialState = false,
|
|
329
|
-
modifier = Modifier
|
|
330
|
-
.fillMaxWidth()
|
|
331
|
-
.padding(vertical = 12.dp)
|
|
332
|
-
) {
|
|
333
|
-
Column {
|
|
334
|
-
Spacer(NewAppTheme.spacing.`1`)
|
|
335
|
-
ServerUrlInput(
|
|
336
|
-
openApp = { urlValue ->
|
|
337
|
-
onAction(HomeAction.OpenApp(urlValue))
|
|
338
|
-
}
|
|
339
|
-
)
|
|
160
|
+
DevelopmentSessionActions(state.isFetchingPackagers, onAction)
|
|
340
161
|
}
|
|
162
|
+
} else {
|
|
163
|
+
DevelopmentSessionSection(state.isFetchingPackagers, onAction)
|
|
341
164
|
}
|
|
342
165
|
|
|
343
166
|
if (state.recentlyOpenedApps.isNotEmpty()) {
|
|
@@ -363,9 +186,13 @@ fun HomeScreen(
|
|
|
363
186
|
fontFamily = NewAppTheme.font.mono
|
|
364
187
|
),
|
|
365
188
|
color = NewAppTheme.colors.text.link,
|
|
366
|
-
modifier = Modifier.clickable
|
|
367
|
-
|
|
368
|
-
|
|
189
|
+
modifier = Modifier.clickable(
|
|
190
|
+
interactionSource = null,
|
|
191
|
+
indication = null,
|
|
192
|
+
onClick = {
|
|
193
|
+
onAction(HomeAction.ResetRecentlyOpenedApps)
|
|
194
|
+
}
|
|
195
|
+
)
|
|
369
196
|
)
|
|
370
197
|
}
|
|
371
198
|
|
|
@@ -418,7 +245,8 @@ fun HomeScreenPreview() {
|
|
|
418
245
|
)
|
|
419
246
|
),
|
|
420
247
|
onAction = {},
|
|
421
|
-
onProfileClick = {}
|
|
248
|
+
onProfileClick = {},
|
|
249
|
+
onDevServersClick = {}
|
|
422
250
|
)
|
|
423
251
|
}
|
|
424
252
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
package expo.modules.devlauncher.compose.ui
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.BorderStroke
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
8
|
+
import androidx.compose.foundation.layout.padding
|
|
9
|
+
import androidx.compose.runtime.Composable
|
|
10
|
+
import androidx.compose.ui.Modifier
|
|
11
|
+
import androidx.compose.ui.text.style.TextAlign
|
|
12
|
+
import androidx.compose.ui.tooling.preview.Preview
|
|
13
|
+
import androidx.compose.ui.unit.dp
|
|
14
|
+
import expo.modules.core.utilities.EmulatorUtilities
|
|
15
|
+
import expo.modules.devlauncher.compose.models.HomeAction
|
|
16
|
+
import expo.modules.devmenu.compose.newtheme.NewAppTheme
|
|
17
|
+
import expo.modules.devmenu.compose.primitives.NewText
|
|
18
|
+
import expo.modules.devmenu.compose.primitives.RoundedSurface
|
|
19
|
+
|
|
20
|
+
@Composable
|
|
21
|
+
fun DevelopmentSessionSection(
|
|
22
|
+
isFetching: Boolean = false,
|
|
23
|
+
onAction: (HomeAction) -> Unit = {}
|
|
24
|
+
) {
|
|
25
|
+
RoundedSurface(
|
|
26
|
+
borderRadius = NewAppTheme.borderRadius.xl,
|
|
27
|
+
color = NewAppTheme.colors.background.subtle
|
|
28
|
+
) {
|
|
29
|
+
Column(
|
|
30
|
+
verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`3`),
|
|
31
|
+
modifier = Modifier.padding(NewAppTheme.spacing.`3`)
|
|
32
|
+
) {
|
|
33
|
+
DevelopmentSessionHelp()
|
|
34
|
+
|
|
35
|
+
DevelopmentSessionActions(isFetching, onAction)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Composable
|
|
41
|
+
fun DevelopmentSessionHelp() {
|
|
42
|
+
Column(
|
|
43
|
+
verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`3`)
|
|
44
|
+
) {
|
|
45
|
+
NewText(
|
|
46
|
+
"Start a local development server with:",
|
|
47
|
+
color = NewAppTheme.colors.text.secondary,
|
|
48
|
+
style = NewAppTheme.font.sm
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
RoundedSurface(
|
|
52
|
+
color = NewAppTheme.colors.background.element,
|
|
53
|
+
border = BorderStroke(
|
|
54
|
+
width = 1.dp,
|
|
55
|
+
color = NewAppTheme.colors.border.default
|
|
56
|
+
),
|
|
57
|
+
modifier = Modifier
|
|
58
|
+
.fillMaxWidth()
|
|
59
|
+
) {
|
|
60
|
+
Box(modifier = Modifier.padding(NewAppTheme.spacing.`3`)) {
|
|
61
|
+
NewText(
|
|
62
|
+
"npx expo start",
|
|
63
|
+
style = NewAppTheme.font.md.merge(
|
|
64
|
+
fontFamily = NewAppTheme.font.mono
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
NewText(
|
|
71
|
+
"Then, select the local server when it appears here.",
|
|
72
|
+
color = NewAppTheme.colors.text.secondary,
|
|
73
|
+
style = NewAppTheme.font.sm
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Composable
|
|
79
|
+
fun DevelopmentSessionActions(isFetching: Boolean, onAction: (HomeAction) -> Unit) {
|
|
80
|
+
Column(
|
|
81
|
+
verticalArrangement = Arrangement.spacedBy(NewAppTheme.spacing.`3`)
|
|
82
|
+
) {
|
|
83
|
+
ServerUrlInput(
|
|
84
|
+
openApp = { urlValue ->
|
|
85
|
+
onAction(HomeAction.OpenApp(urlValue))
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
NewText(
|
|
90
|
+
"Or",
|
|
91
|
+
color = NewAppTheme.colors.text.secondary,
|
|
92
|
+
style = NewAppTheme.font.sm.merge(
|
|
93
|
+
textAlign = TextAlign.Center
|
|
94
|
+
),
|
|
95
|
+
modifier = Modifier.fillMaxWidth()
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
FetchDevelopmentServersButton(
|
|
99
|
+
isFetching,
|
|
100
|
+
onAction
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if (!EmulatorUtilities.isRunningOnEmulator()) {
|
|
104
|
+
NewText(
|
|
105
|
+
"Or",
|
|
106
|
+
color = NewAppTheme.colors.text.secondary,
|
|
107
|
+
style = NewAppTheme.font.sm.merge(
|
|
108
|
+
textAlign = TextAlign.Center
|
|
109
|
+
),
|
|
110
|
+
modifier = Modifier.fillMaxWidth()
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
ScanQRCodeButton(onAction)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Preview(showBackground = true)
|
|
119
|
+
@Composable
|
|
120
|
+
fun DevelopmentSessionHelperPreview() {
|
|
121
|
+
DevelopmentSessionSection()
|
|
122
|
+
}
|
package/android/src/debug/java/expo/modules/devlauncher/compose/ui/FetchDevelopmentServersButton.kt
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
package expo.modules.devlauncher.compose.ui
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Row
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
6
|
+
import androidx.compose.foundation.layout.padding
|
|
7
|
+
import androidx.compose.foundation.layout.size
|
|
8
|
+
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
10
|
+
import androidx.compose.runtime.getValue
|
|
11
|
+
import androidx.compose.runtime.mutableStateOf
|
|
12
|
+
import androidx.compose.runtime.remember
|
|
13
|
+
import androidx.compose.runtime.setValue
|
|
14
|
+
import androidx.compose.ui.Alignment
|
|
15
|
+
import androidx.compose.ui.Modifier
|
|
16
|
+
import androidx.compose.ui.res.painterResource
|
|
17
|
+
import androidx.compose.ui.tooling.preview.Preview
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import com.composeunstyled.Button
|
|
20
|
+
import com.composeunstyled.Icon
|
|
21
|
+
import expo.modules.devlauncher.R
|
|
22
|
+
import expo.modules.devlauncher.compose.models.HomeAction
|
|
23
|
+
import expo.modules.devlauncher.compose.primitives.CircularProgressBar
|
|
24
|
+
import expo.modules.devmenu.compose.newtheme.NewAppTheme
|
|
25
|
+
import expo.modules.devmenu.compose.primitives.NewText
|
|
26
|
+
import expo.modules.devmenu.compose.primitives.RoundedSurface
|
|
27
|
+
import kotlinx.coroutines.delay
|
|
28
|
+
import kotlin.time.Clock
|
|
29
|
+
import kotlin.time.Duration.Companion.seconds
|
|
30
|
+
import kotlin.time.ExperimentalTime
|
|
31
|
+
import kotlin.time.Instant
|
|
32
|
+
|
|
33
|
+
@Composable
|
|
34
|
+
@OptIn(ExperimentalTime::class)
|
|
35
|
+
fun FetchDevelopmentServersButton(
|
|
36
|
+
isFetching: Boolean,
|
|
37
|
+
onAction: (HomeAction) -> Unit
|
|
38
|
+
) {
|
|
39
|
+
// Users might spam the button, so after debouncing we need to get the current state.
|
|
40
|
+
// We can't use `isFetching` directly in the `LaunchedEffect` as it would be captured in the lambda.
|
|
41
|
+
var getCurrentState by remember { mutableStateOf({ isFetching }) }
|
|
42
|
+
var isFetchingUIState by remember { mutableStateOf(isFetching) }
|
|
43
|
+
var fetchStartTime by remember { mutableStateOf<Instant?>(null) }
|
|
44
|
+
|
|
45
|
+
LaunchedEffect(isFetching) {
|
|
46
|
+
getCurrentState = { isFetching }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
LaunchedEffect(isFetching) {
|
|
50
|
+
if (isFetching) {
|
|
51
|
+
isFetchingUIState = true
|
|
52
|
+
fetchStartTime = Clock.System.now()
|
|
53
|
+
return@LaunchedEffect
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
val startTime = fetchStartTime
|
|
57
|
+
if (startTime == null) {
|
|
58
|
+
isFetchingUIState = false
|
|
59
|
+
return@LaunchedEffect
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
val elapsedTime = Clock.System.now() - startTime
|
|
63
|
+
val remainingTime = 2.seconds - elapsedTime
|
|
64
|
+
|
|
65
|
+
delay(remainingTime)
|
|
66
|
+
|
|
67
|
+
isFetchingUIState = getCurrentState()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
RoundedSurface(
|
|
71
|
+
color = NewAppTheme.colors.background.element,
|
|
72
|
+
borderRadius = NewAppTheme.borderRadius.xl
|
|
73
|
+
) {
|
|
74
|
+
Button(
|
|
75
|
+
onClick = {
|
|
76
|
+
onAction(HomeAction.RefetchRunningApps)
|
|
77
|
+
},
|
|
78
|
+
enabled = !isFetchingUIState
|
|
79
|
+
) {
|
|
80
|
+
Row(
|
|
81
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
82
|
+
verticalAlignment = Alignment.Companion.CenterVertically,
|
|
83
|
+
modifier = Modifier.Companion
|
|
84
|
+
.fillMaxWidth()
|
|
85
|
+
.padding(NewAppTheme.spacing.`3`)
|
|
86
|
+
) {
|
|
87
|
+
NewText(
|
|
88
|
+
if (isFetchingUIState) {
|
|
89
|
+
"Searching for development servers..."
|
|
90
|
+
} else {
|
|
91
|
+
"Fetch development servers"
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if (isFetchingUIState) {
|
|
96
|
+
CircularProgressBar(size = 20.dp)
|
|
97
|
+
} else {
|
|
98
|
+
Icon(
|
|
99
|
+
painter = painterResource(R.drawable.download),
|
|
100
|
+
contentDescription = "Fetch development servers icon",
|
|
101
|
+
tint = NewAppTheme.colors.icon.quaternary,
|
|
102
|
+
modifier = Modifier
|
|
103
|
+
.size(20.dp)
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Preview(widthDp = 400)
|
|
112
|
+
@Composable
|
|
113
|
+
fun FetchDevelopmentServersButtonPreview() {
|
|
114
|
+
FetchDevelopmentServersButton(
|
|
115
|
+
isFetching = false,
|
|
116
|
+
onAction = {}
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package expo.modules.devlauncher.compose.ui
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
4
|
+
import androidx.compose.foundation.layout.Row
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
6
|
+
import androidx.compose.foundation.layout.padding
|
|
7
|
+
import androidx.compose.foundation.layout.size
|
|
8
|
+
import androidx.compose.runtime.Composable
|
|
9
|
+
import androidx.compose.ui.Alignment
|
|
10
|
+
import androidx.compose.ui.Modifier
|
|
11
|
+
import androidx.compose.ui.res.painterResource
|
|
12
|
+
import androidx.compose.ui.tooling.preview.Preview
|
|
13
|
+
import androidx.compose.ui.unit.dp
|
|
14
|
+
import com.composeunstyled.Button
|
|
15
|
+
import com.composeunstyled.Icon
|
|
16
|
+
import expo.modules.devlauncher.R
|
|
17
|
+
import expo.modules.devlauncher.compose.models.HomeAction
|
|
18
|
+
import expo.modules.devmenu.compose.newtheme.NewAppTheme
|
|
19
|
+
import expo.modules.devmenu.compose.primitives.NewText
|
|
20
|
+
import expo.modules.devmenu.compose.primitives.RoundedSurface
|
|
21
|
+
|
|
22
|
+
@Composable
|
|
23
|
+
fun ScanQRCodeButton(
|
|
24
|
+
onAction: (HomeAction) -> Unit = {}
|
|
25
|
+
) {
|
|
26
|
+
RoundedSurface(
|
|
27
|
+
color = NewAppTheme.colors.background.element,
|
|
28
|
+
borderRadius = NewAppTheme.borderRadius.xl
|
|
29
|
+
) {
|
|
30
|
+
Button(
|
|
31
|
+
onClick = {
|
|
32
|
+
onAction(HomeAction.ScanQRCode)
|
|
33
|
+
}
|
|
34
|
+
) {
|
|
35
|
+
Row(
|
|
36
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
37
|
+
verticalAlignment = Alignment.Companion.CenterVertically,
|
|
38
|
+
modifier = Modifier.Companion
|
|
39
|
+
.fillMaxWidth()
|
|
40
|
+
.padding(NewAppTheme.spacing.`3`)
|
|
41
|
+
) {
|
|
42
|
+
NewText(
|
|
43
|
+
"Scan QR Code"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
Icon(
|
|
47
|
+
painter = painterResource(R.drawable.scan),
|
|
48
|
+
contentDescription = "Fetch development servers icon",
|
|
49
|
+
tint = NewAppTheme.colors.icon.quaternary,
|
|
50
|
+
modifier = Modifier
|
|
51
|
+
.size(20.dp)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Preview(widthDp = 300)
|
|
59
|
+
@Composable
|
|
60
|
+
fun ScanQRCodeButtonPreview() {
|
|
61
|
+
ScanQRCodeButton()
|
|
62
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package expo.modules.devlauncher.compose.ui
|
|
2
2
|
|
|
3
3
|
import android.widget.Toast
|
|
4
|
+
import androidx.compose.foundation.background
|
|
4
5
|
import androidx.compose.foundation.border
|
|
5
6
|
import androidx.compose.foundation.layout.Arrangement
|
|
6
7
|
import androidx.compose.foundation.layout.Column
|
|
@@ -14,6 +15,7 @@ import androidx.compose.runtime.mutableStateOf
|
|
|
14
15
|
import androidx.compose.runtime.remember
|
|
15
16
|
import androidx.compose.runtime.setValue
|
|
16
17
|
import androidx.compose.ui.Modifier
|
|
18
|
+
import androidx.compose.ui.draw.clip
|
|
17
19
|
import androidx.compose.ui.graphics.Color
|
|
18
20
|
import androidx.compose.ui.graphics.SolidColor
|
|
19
21
|
import androidx.compose.ui.platform.LocalContext
|
|
@@ -53,19 +55,18 @@ fun ServerUrlInput(
|
|
|
53
55
|
url = newUrl
|
|
54
56
|
},
|
|
55
57
|
textColor = NewAppTheme.colors.text.default,
|
|
56
|
-
textStyle = NewAppTheme.font.
|
|
57
|
-
fontFamily = NewAppTheme.font.mono,
|
|
58
|
-
fontWeight = FontWeight.Light
|
|
59
|
-
),
|
|
58
|
+
textStyle = NewAppTheme.font.md,
|
|
60
59
|
singleLine = true,
|
|
61
60
|
modifier = Modifier
|
|
62
61
|
.fillMaxWidth()
|
|
63
62
|
.border(
|
|
64
63
|
width = 1.dp,
|
|
65
|
-
shape = RoundedCornerShape(NewAppTheme.borderRadius.
|
|
64
|
+
shape = RoundedCornerShape(NewAppTheme.borderRadius.xl),
|
|
66
65
|
color = NewAppTheme.colors.border.default
|
|
67
66
|
)
|
|
68
|
-
.
|
|
67
|
+
.clip(RoundedCornerShape(NewAppTheme.borderRadius.xl))
|
|
68
|
+
.background(NewAppTheme.colors.background.element)
|
|
69
|
+
.padding(NewAppTheme.spacing.`3`),
|
|
69
70
|
keyboardOptions = KeyboardOptions(
|
|
70
71
|
capitalization = KeyboardCapitalization.None,
|
|
71
72
|
autoCorrectEnabled = false,
|
|
@@ -77,11 +78,8 @@ fun ServerUrlInput(
|
|
|
77
78
|
placeholder = {
|
|
78
79
|
NewText(
|
|
79
80
|
text = "http://localhost:8081",
|
|
80
|
-
style = NewAppTheme.font.
|
|
81
|
-
|
|
82
|
-
fontWeight = FontWeight.Light
|
|
83
|
-
),
|
|
84
|
-
color = NewAppTheme.colors.text.quaternary
|
|
81
|
+
style = NewAppTheme.font.md,
|
|
82
|
+
color = NewAppTheme.colors.text.secondary
|
|
85
83
|
)
|
|
86
84
|
}
|
|
87
85
|
)
|
|
@@ -91,9 +89,9 @@ fun ServerUrlInput(
|
|
|
91
89
|
text = "Connect",
|
|
92
90
|
foreground = Color.White,
|
|
93
91
|
background = Color.Black,
|
|
94
|
-
modifier = Modifier.padding(vertical = NewAppTheme.spacing.`
|
|
95
|
-
borderRadius = NewAppTheme.borderRadius.
|
|
96
|
-
textStyle = NewAppTheme.font.
|
|
92
|
+
modifier = Modifier.padding(vertical = NewAppTheme.spacing.`3`),
|
|
93
|
+
borderRadius = NewAppTheme.borderRadius.xl,
|
|
94
|
+
textStyle = NewAppTheme.font.md.merge(
|
|
97
95
|
fontWeight = FontWeight.SemiBold
|
|
98
96
|
),
|
|
99
97
|
onClick = {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
android:width="20dp"
|
|
3
|
+
android:height="20dp"
|
|
4
|
+
android:viewportWidth="20"
|
|
5
|
+
android:viewportHeight="20">
|
|
6
|
+
<path
|
|
7
|
+
android:pathData="M6.667,10L10,13.333M10,13.333L13.333,10M10,13.333V6.667M18.333,10C18.333,14.602 14.602,18.333 10,18.333C5.397,18.333 1.666,14.602 1.666,10C1.666,5.397 5.397,1.666 10,1.666C14.602,1.666 18.333,5.397 18.333,10Z"
|
|
8
|
+
android:strokeLineJoin="round"
|
|
9
|
+
android:strokeWidth="1.66667"
|
|
10
|
+
android:fillColor="#00000000"
|
|
11
|
+
android:strokeColor="#B9BBC6"
|
|
12
|
+
android:strokeLineCap="round"/>
|
|
13
|
+
</vector>
|