expo-dev-launcher 5.2.0-canary-20250709-136b77f → 5.2.0-canary-20250722-599a28f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/android/build.gradle +3 -3
  2. package/android/src/debug/java/expo/modules/devlauncher/DevLauncherController.kt +22 -18
  3. package/android/src/debug/java/expo/modules/devlauncher/DevLauncherPackageDelegate.kt +1 -3
  4. package/android/src/debug/java/expo/modules/devlauncher/compose/AuthActivity.kt +2 -2
  5. package/android/src/debug/java/expo/modules/devlauncher/compose/DevLauncherBottomTabsNavigator.kt +59 -18
  6. package/android/src/debug/java/expo/modules/devlauncher/compose/models/CrashReportModel.kt +22 -0
  7. package/android/src/debug/java/expo/modules/devlauncher/compose/{HomeViewModel.kt → models/HomeViewModel.kt} +45 -16
  8. package/android/src/debug/java/expo/modules/devlauncher/compose/{ProfileViewModel.kt → models/ProfileViewModel.kt} +2 -1
  9. package/android/src/debug/java/expo/modules/devlauncher/compose/models/SettingsViewModel.kt +80 -0
  10. package/android/src/debug/java/expo/modules/devlauncher/compose/routes/CrashReport.kt +38 -0
  11. package/android/src/debug/java/expo/modules/devlauncher/compose/routes/Home.kt +16 -3
  12. package/android/src/debug/java/expo/modules/devlauncher/compose/routes/Profile.kt +2 -2
  13. package/android/src/debug/java/expo/modules/devlauncher/compose/routes/Settings.kt +7 -1
  14. package/android/src/debug/java/expo/modules/devlauncher/compose/screens/CrashReportScreen.kt +185 -0
  15. package/android/src/debug/java/expo/modules/devlauncher/compose/screens/HomeScreen.kt +289 -48
  16. package/android/src/debug/java/expo/modules/devlauncher/compose/screens/SettingsScreen.kt +63 -9
  17. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/AccountSelector.kt +5 -3
  18. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/ActionButton.kt +3 -1
  19. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/AppHeader.kt +0 -1
  20. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/BottomTabButton.kt +1 -1
  21. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/DevelopmentServerHelp.kt +52 -0
  22. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/RunningAppCard.kt +18 -26
  23. package/android/src/debug/java/expo/modules/devlauncher/compose/ui/ServerUrlInput.kt +78 -0
  24. package/android/src/debug/java/expo/modules/devlauncher/compose/utils/WithIsLast.kt +32 -0
  25. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherActivity.kt +11 -105
  26. package/android/src/debug/java/expo/modules/devlauncher/services/ApolloClientService.kt +5 -1
  27. package/android/src/debug/java/expo/modules/devlauncher/services/AppService.kt +82 -0
  28. package/android/src/debug/java/expo/modules/devlauncher/services/DependencyInjection.kt +19 -12
  29. package/android/src/debug/java/expo/modules/devlauncher/services/ErrorRegistryService.kt +13 -0
  30. package/android/src/debug/java/expo/modules/devlauncher/services/HttpClientService.kt +78 -3
  31. package/android/src/debug/java/expo/modules/devlauncher/services/PackagerService.kt +118 -45
  32. package/android/src/debug/java/expo/modules/devlauncher/services/SessionService.kt +4 -1
  33. package/android/src/main/java/com/facebook/react/devsupport/DevLauncherDevServerHelper.kt +3 -3
  34. package/android/src/main/java/expo/modules/devlauncher/launcher/DevLauncherControllerInterface.kt +0 -1
  35. package/android/src/main/java/expo/modules/devlauncher/launcher/DevLauncherRecentlyOpenedAppsRegistry.kt +10 -9
  36. package/android/src/main/java/expo/modules/devlauncher/launcher/errors/DevLauncherErrorRegistry.kt +7 -6
  37. package/android/src/release/java/expo/modules/devlauncher/DevLauncherController.kt +0 -3
  38. package/expo-module.config.json +0 -1
  39. package/package.json +4 -4
  40. package/android/src/debug/assets/expo_dev_launcher_android.bundle +0 -1418
  41. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherReactHost.kt +0 -104
  42. package/android/src/debug/java/expo/modules/devlauncher/launcher/DevLauncherReactNativeHost.kt +0 -65
  43. package/android/src/debug/java/expo/modules/devlauncher/modules/DevLauncherAuth.kt +0 -72
  44. package/android/src/main/java/expo/modules/devlauncher/modules/DevLauncherInternalModule.kt +0 -234
@@ -20,13 +20,13 @@ expoModule {
20
20
  }
21
21
 
22
22
  group = "host.exp.exponent"
23
- version = "5.2.0-canary-20250709-136b77f"
23
+ version = "5.2.0-canary-20250722-599a28f"
24
24
 
25
25
  android {
26
26
  namespace "expo.modules.devlauncher"
27
27
  defaultConfig {
28
28
  versionCode 9
29
- versionName "5.2.0-canary-20250709-136b77f"
29
+ versionName "5.2.0-canary-20250722-599a28f"
30
30
  }
31
31
 
32
32
  buildTypes {
@@ -100,7 +100,7 @@ dependencies {
100
100
 
101
101
  implementation("com.apollographql.apollo:apollo-runtime:4.3.1")
102
102
 
103
- implementation("com.composables:core:1.31.1")
103
+ implementation("com.composables:core:1.37.0")
104
104
 
105
105
  testImplementation 'androidx.test:core:1.4.0'
106
106
  testImplementation 'androidx.test:core-ktx:1.4.0'
@@ -6,6 +6,7 @@ import android.content.Intent
6
6
  import android.net.Uri
7
7
  import android.os.Bundle
8
8
  import androidx.annotation.UiThread
9
+ import androidx.core.net.toUri
9
10
  import com.facebook.react.ReactActivity
10
11
  import com.facebook.react.ReactActivityDelegate
11
12
  import com.facebook.react.ReactApplication
@@ -30,8 +31,6 @@ import expo.modules.devlauncher.launcher.DevLauncherIntentRegistryInterface
30
31
  import expo.modules.devlauncher.launcher.DevLauncherLifecycle
31
32
  import expo.modules.devlauncher.launcher.DevLauncherNetworkInterceptor
32
33
  import expo.modules.devlauncher.launcher.DevLauncherReactActivityDelegateSupplier
33
- import expo.modules.devlauncher.launcher.DevLauncherReactHost
34
- import expo.modules.devlauncher.launcher.DevLauncherReactNativeHost
35
34
  import expo.modules.devlauncher.launcher.DevLauncherRecentlyOpenedAppsRegistry
36
35
  import expo.modules.devlauncher.launcher.errors.DevLauncherAppError
37
36
  import expo.modules.devlauncher.launcher.errors.DevLauncherErrorActivity
@@ -40,6 +39,7 @@ import expo.modules.devlauncher.launcher.loaders.DevLauncherAppLoaderFactoryInte
40
39
  import expo.modules.devlauncher.launcher.manifest.DevLauncherManifestParser
41
40
  import expo.modules.devlauncher.react.activitydelegates.DevLauncherReactActivityNOPDelegate
42
41
  import expo.modules.devlauncher.react.activitydelegates.DevLauncherReactActivityRedirectDelegate
42
+ import expo.modules.devlauncher.services.DependencyInjection
43
43
  import expo.modules.devlauncher.tests.DevLauncherTestInterceptor
44
44
  import expo.modules.devmenu.DevMenuManager
45
45
  import expo.modules.manifests.core.Manifest
@@ -74,27 +74,30 @@ class DevLauncherController private constructor() :
74
74
  var devMenuManager: DevMenuManager = DevMenuManager
75
75
  override var updatesInterface: UpdatesInterface?
76
76
  get() = internalUpdatesInterface
77
- set(value) = DevLauncherKoinContext.app.koin.loadModules(
78
- listOf(
79
- module {
80
- single { value }
81
- }
77
+ set(value) = run {
78
+ if (value != null) {
79
+ DependencyInjection.appService.setUpUpdateInterface(value, context)
80
+ }
81
+
82
+ DevLauncherKoinContext.app.koin.loadModules(
83
+ listOf(
84
+ module {
85
+ single { value }
86
+ }
87
+ )
82
88
  )
83
- )
89
+ }
90
+
84
91
  override val coroutineScope = CoroutineScope(Dispatchers.Default)
85
92
 
86
- override val devClientHost by lazy {
87
- ReactHostWrapper(
88
- reactNativeHost = DevLauncherReactNativeHost(context as Application, DEV_LAUNCHER_HOST),
89
- reactHostProvider = { DevLauncherReactHost.create(context as Application, DEV_LAUNCHER_HOST) }
90
- )
91
- }
92
93
 
93
94
  private val recentlyOpedAppsRegistry = DevLauncherRecentlyOpenedAppsRegistry(context)
94
95
  override var manifest: Manifest? = null
95
96
  private set
97
+
96
98
  override var manifestURL: Uri? = null
97
99
  private set
100
+
98
101
  override var latestLoadedApp: Uri? = null
99
102
  override var useDeveloperSupport = true
100
103
  var canLaunchDevMenuOnStart = false
@@ -144,7 +147,7 @@ class DevLauncherController private constructor() :
144
147
  // default to the EXPO_UPDATE_URL value configured in AndroidManifest.xml when project url is unspecified for an EAS update
145
148
  if (isEASUpdate && projectUrl == null) {
146
149
  val projectUrlString = getMetadataValue(context, "expo.modules.updates.EXPO_UPDATE_URL")
147
- parsedProjectUrl = Uri.parse(projectUrlString)
150
+ parsedProjectUrl = projectUrlString.toUri()
148
151
  }
149
152
 
150
153
  val manifestParser = DevLauncherManifestParser(httpClient, parsedUrl, installationIDHelper.getOrCreateInstallationID(context))
@@ -206,7 +209,8 @@ class DevLauncherController private constructor() :
206
209
  }
207
210
  }
208
211
 
209
- override fun getRecentlyOpenedApps(): List<DevLauncherAppEntry> = recentlyOpedAppsRegistry.getRecentlyOpenedApps()
212
+ override fun getRecentlyOpenedApps(): List<DevLauncherAppEntry> =
213
+ recentlyOpedAppsRegistry.getRecentlyOpenedApps()
210
214
 
211
215
  override fun clearRecentlyOpenedApps() {
212
216
  recentlyOpedAppsRegistry.clearRegistry()
@@ -270,8 +274,8 @@ class DevLauncherController private constructor() :
270
274
  if (shouldTryToLaunchLastOpenedBundle && lastOpenedApp != null) {
271
275
  coroutineScope.launch {
272
276
  try {
273
- loadApp(Uri.parse(lastOpenedApp.url), activityToBeInvalidated)
274
- } catch (e: Throwable) {
277
+ loadApp(lastOpenedApp.url.toUri(), activityToBeInvalidated)
278
+ } catch (_: Throwable) {
275
279
  navigateToLauncher()
276
280
  }
277
281
  }
@@ -13,7 +13,6 @@ import expo.modules.core.interfaces.ReactActivityHandler
13
13
  import expo.modules.core.interfaces.ReactActivityLifecycleListener
14
14
  import expo.modules.devlauncher.launcher.DevLauncherReactActivityDelegateSupplier
15
15
  import expo.modules.devlauncher.modules.DevLauncherModule
16
- import expo.modules.devlauncher.modules.DevLauncherAuth
17
16
  import expo.modules.core.interfaces.ReactNativeHostHandler
18
17
  import expo.modules.devlauncher.modules.DevLauncherDevMenuExtension
19
18
  import expo.modules.devlauncher.react.DevLauncherReactNativeHostHandler
@@ -22,8 +21,7 @@ object DevLauncherPackageDelegate {
22
21
  fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
23
22
  listOf(
24
23
  DevLauncherModule(reactContext),
25
- DevLauncherDevMenuExtension(reactContext),
26
- DevLauncherAuth(reactContext)
24
+ DevLauncherDevMenuExtension(reactContext)
27
25
  )
28
26
 
29
27
  fun createApplicationLifecycleListeners(context: Context?): List<ApplicationLifecycleListener> =
@@ -35,10 +35,10 @@ sealed interface AuthResult {
35
35
 
36
36
  class AuthActivity : AppCompatActivity() {
37
37
  class Contract : ActivityResultContract<AuthRequestType, AuthResult>() {
38
- override fun createIntent(context: Context, type: AuthRequestType): Intent {
38
+ override fun createIntent(context: Context, input: AuthRequestType): Intent {
39
39
  return Intent(context, AuthActivity::class.java).apply {
40
40
  action = ACTION_VIEW
41
- putExtra(AUTH_REQUEST_TYPE_KEY, type.type)
41
+ putExtra(AUTH_REQUEST_TYPE_KEY, input.type)
42
42
  }
43
43
  }
44
44
 
@@ -1,12 +1,12 @@
1
1
  package expo.modules.devlauncher.compose
2
2
 
3
+ import androidx.compose.animation.AnimatedContentTransitionScope
3
4
  import androidx.compose.animation.EnterTransition
4
5
  import androidx.compose.animation.ExitTransition
6
+ import androidx.compose.animation.core.tween
5
7
  import androidx.compose.foundation.background
6
8
  import androidx.compose.foundation.layout.Box
7
9
  import androidx.compose.foundation.layout.fillMaxSize
8
- import androidx.compose.foundation.rememberScrollState
9
- import androidx.compose.foundation.verticalScroll
10
10
  import androidx.compose.runtime.Composable
11
11
  import androidx.compose.ui.Modifier
12
12
  import androidx.compose.ui.graphics.painter.Painter
@@ -14,6 +14,8 @@ import androidx.navigation.compose.NavHost
14
14
  import androidx.navigation.compose.composable
15
15
  import androidx.navigation.compose.rememberNavController
16
16
  import expo.modules.devlauncher.compose.primitives.DefaultScaffold
17
+ import expo.modules.devlauncher.compose.routes.CrashReport
18
+ import expo.modules.devlauncher.compose.routes.CrashReportRoute
17
19
  import expo.modules.devlauncher.compose.routes.Home
18
20
  import expo.modules.devlauncher.compose.routes.HomeRoute
19
21
  import expo.modules.devlauncher.compose.routes.ProfileRoute
@@ -23,17 +25,15 @@ import expo.modules.devlauncher.compose.ui.BottomTabBar
23
25
  import expo.modules.devlauncher.compose.ui.Full
24
26
  import expo.modules.devlauncher.compose.ui.rememberBottomSheetState
25
27
  import expo.modules.devmenu.compose.theme.Theme
28
+ import kotlinx.serialization.Serializable
26
29
 
27
30
  @Composable
28
31
  fun DefaultScreenContainer(
29
32
  content: @Composable () -> Unit
30
33
  ) {
31
- val scrollState = rememberScrollState()
32
-
33
34
  Box(
34
35
  modifier = Modifier
35
36
  .fillMaxSize()
36
- .verticalScroll(scrollState)
37
37
  .background(Theme.colors.background.secondary)
38
38
  ) {
39
39
  content()
@@ -46,30 +46,71 @@ data class Tab(
46
46
  val screen: Any
47
47
  )
48
48
 
49
+ @Serializable
50
+ object Main
51
+
49
52
  @Composable
50
53
  fun DevLauncherBottomTabsNavigator() {
51
- val navController = rememberNavController()
54
+ val mainNavController = rememberNavController()
55
+ val bottomTabsNavController = rememberNavController()
52
56
  val bottomSheetState = rememberBottomSheetState()
53
57
 
54
- DefaultScaffold(bottomTab = {
55
- BottomTabBar(navController)
56
- }) {
57
- NavHost(
58
- navController = navController,
59
- startDestination = Home,
58
+ NavHost(
59
+ navController = mainNavController,
60
+ startDestination = Main
61
+ ) {
62
+ composable<Main>(
60
63
  enterTransition = {
61
- EnterTransition.None
64
+ slideIntoContainer(
65
+ AnimatedContentTransitionScope.SlideDirection.Right,
66
+ animationSpec = tween(700)
67
+ )
62
68
  },
63
69
  exitTransition = {
64
- ExitTransition.None
70
+ slideOutOfContainer(
71
+ AnimatedContentTransitionScope.SlideDirection.Left,
72
+ animationSpec = tween(700)
73
+ )
65
74
  }
66
75
  ) {
67
- composable<Home> {
68
- HomeRoute(onProfileClick = { bottomSheetState.jumpTo(Full) })
76
+ DefaultScaffold(bottomTab = {
77
+ BottomTabBar(bottomTabsNavController)
78
+ }) {
79
+ NavHost(
80
+ navController = bottomTabsNavController,
81
+ startDestination = Home,
82
+ enterTransition = {
83
+ EnterTransition.None
84
+ },
85
+ exitTransition = {
86
+ ExitTransition.None
87
+ }
88
+ ) {
89
+ composable<Home> {
90
+ HomeRoute(navController = mainNavController, onProfileClick = { bottomSheetState.jumpTo(Full) })
91
+ }
92
+ composable<Settings> {
93
+ SettingsRoute()
94
+ }
95
+ }
69
96
  }
70
- composable<Settings> {
71
- SettingsRoute()
97
+ }
98
+
99
+ composable<CrashReport>(
100
+ enterTransition = {
101
+ slideIntoContainer(
102
+ AnimatedContentTransitionScope.SlideDirection.Left,
103
+ animationSpec = tween(700)
104
+ )
105
+ },
106
+ exitTransition = {
107
+ slideOutOfContainer(
108
+ AnimatedContentTransitionScope.SlideDirection.Right,
109
+ animationSpec = tween(700)
110
+ )
72
111
  }
112
+ ) {
113
+ CrashReportRoute()
73
114
  }
74
115
  }
75
116
 
@@ -0,0 +1,22 @@
1
+ package expo.modules.devlauncher.compose.models
2
+
3
+ import androidx.compose.runtime.mutableStateOf
4
+ import androidx.lifecycle.SavedStateHandle
5
+ import androidx.lifecycle.ViewModel
6
+ import androidx.navigation.toRoute
7
+ import expo.modules.devlauncher.compose.routes.CrashReport
8
+
9
+ data class CrashReportState(
10
+ val report: CrashReport
11
+ )
12
+
13
+ class CrashReportModel(
14
+ savedStateHandle: SavedStateHandle
15
+ ) : ViewModel() {
16
+ private val _state = mutableStateOf(
17
+ savedStateHandle.toRoute<CrashReport>()
18
+ )
19
+
20
+ val state
21
+ get() = _state.value
22
+ }
@@ -1,4 +1,4 @@
1
- package expo.modules.devlauncher.compose
1
+ package expo.modules.devlauncher.compose.models
2
2
 
3
3
  import android.util.Log
4
4
  import androidx.compose.runtime.mutableStateOf
@@ -7,7 +7,10 @@ import androidx.lifecycle.ViewModel
7
7
  import androidx.lifecycle.viewModelScope
8
8
  import expo.modules.devlauncher.DevLauncherController
9
9
  import expo.modules.devlauncher.MeQuery
10
- import expo.modules.devlauncher.services.HttpClientService
10
+ import expo.modules.devlauncher.launcher.DevLauncherAppEntry
11
+ import expo.modules.devlauncher.launcher.errors.DevLauncherErrorInstance
12
+ import expo.modules.devlauncher.services.AppService
13
+ import expo.modules.devlauncher.services.ErrorRegistryService
11
14
  import expo.modules.devlauncher.services.PackagerInfo
12
15
  import expo.modules.devlauncher.services.PackagerService
13
16
  import expo.modules.devlauncher.services.SessionService
@@ -15,34 +18,42 @@ import expo.modules.devlauncher.services.UserState
15
18
  import expo.modules.devlauncher.services.inject
16
19
  import kotlinx.coroutines.flow.launchIn
17
20
  import kotlinx.coroutines.flow.onEach
21
+ import kotlinx.coroutines.flow.onStart
18
22
  import kotlinx.coroutines.launch
19
23
 
20
24
  sealed interface HomeAction {
21
25
  class OpenApp(val url: String) : HomeAction
26
+ object RefetchRunningApps : HomeAction
27
+ object ResetRecentlyOpenedApps : HomeAction
28
+ class NavigateToCrashReport(val crashReport: DevLauncherErrorInstance) : HomeAction
22
29
  }
23
30
 
24
- typealias HomeActionHandler = (HomeAction) -> Unit
25
-
26
31
  data class HomeState(
27
- val appName: String = "BareExpo",
32
+ val appName: String = "Unknown App",
28
33
  val runningPackagers: Set<PackagerInfo> = emptySet(),
29
- val currentAccount: MeQuery.Account? = null
34
+ val isFetchingPackagers: Boolean = false,
35
+ val currentAccount: MeQuery.Account? = null,
36
+ val recentlyOpenedApps: List<DevLauncherAppEntry> = emptyList(),
37
+ val crashReport: DevLauncherErrorInstance? = null
30
38
  )
31
39
 
32
40
  class HomeViewModel() : ViewModel() {
33
- val httpClientService = inject<HttpClientService>()
34
41
  val devLauncherController = inject<DevLauncherController>()
35
42
  val sessionService = inject<SessionService>()
36
-
37
- val packagerService = PackagerService(httpClientService.httpClient, viewModelScope)
43
+ val packagerService = inject<PackagerService>()
44
+ val appService = inject<AppService>()
45
+ val errorRegistryService = inject<ErrorRegistryService>()
38
46
 
39
47
  private var _state = mutableStateOf(
40
48
  HomeState(
49
+ appName = appService.applicationInfo.appName,
41
50
  runningPackagers = packagerService.runningPackagers.value,
42
51
  currentAccount = when (val userState = sessionService.user.value) {
43
52
  UserState.Fetching, UserState.LoggedOut -> null
44
53
  is UserState.LoggedIn -> userState.selectedAccount
45
- }
54
+ },
55
+ recentlyOpenedApps = devLauncherController.getRecentlyOpenedApps(),
56
+ crashReport = errorRegistryService.consumeException()
46
57
  )
47
58
  )
48
59
 
@@ -50,11 +61,14 @@ class HomeViewModel() : ViewModel() {
50
61
  get() = _state.value
51
62
 
52
63
  init {
53
- packagerService.runningPackagers.onEach { newPackagers ->
54
- _state.value = _state.value.copy(
55
- runningPackagers = newPackagers
56
- )
57
- }.launchIn(viewModelScope)
64
+ packagerService
65
+ .runningPackagers
66
+ .onEach { newPackagers ->
67
+ _state.value = _state.value.copy(
68
+ runningPackagers = newPackagers
69
+ )
70
+ }
71
+ .launchIn(viewModelScope)
58
72
 
59
73
  sessionService.user.onEach { newUser ->
60
74
  when (newUser) {
@@ -67,18 +81,33 @@ class HomeViewModel() : ViewModel() {
67
81
  )
68
82
  }
69
83
  }.launchIn(viewModelScope)
84
+
85
+ packagerService.isLoading.onEach { isLoading ->
86
+ _state.value = _state.value.copy(
87
+ isFetchingPackagers = isLoading
88
+ )
89
+ }.launchIn(viewModelScope)
70
90
  }
71
91
 
72
92
  fun onAction(action: HomeAction) {
73
93
  when (action) {
74
94
  is HomeAction.OpenApp ->
75
- viewModelScope.launch {
95
+ devLauncherController.coroutineScope.launch {
76
96
  try {
77
97
  devLauncherController.loadApp(action.url.toUri(), mainActivity = null)
78
98
  } catch (e: Exception) {
79
99
  Log.e("DevLauncher", "Failed to open app: ${action.url}", e)
80
100
  }
81
101
  }
102
+
103
+ HomeAction.RefetchRunningApps -> packagerService.refetchedPackager()
104
+
105
+ HomeAction.ResetRecentlyOpenedApps -> viewModelScope.launch {
106
+ devLauncherController.clearRecentlyOpenedApps()
107
+ _state.value = _state.value.copy(recentlyOpenedApps = emptyList())
108
+ }
109
+
110
+ is HomeAction.NavigateToCrashReport -> IllegalStateException("Navigation action should be handled by the UI layer, not the ViewModel.")
82
111
  }
83
112
  }
84
113
  }
@@ -1,7 +1,8 @@
1
- package expo.modules.devlauncher.compose
1
+ package expo.modules.devlauncher.compose.models
2
2
 
3
3
  import androidx.lifecycle.ViewModel
4
4
  import androidx.lifecycle.viewModelScope
5
+ import expo.modules.devlauncher.compose.Session
5
6
  import expo.modules.devlauncher.services.SessionService
6
7
  import expo.modules.devlauncher.services.UserState
7
8
  import expo.modules.devlauncher.services.inject
@@ -0,0 +1,80 @@
1
+ package expo.modules.devlauncher.compose.models
2
+
3
+ import android.app.Application
4
+ import androidx.compose.runtime.mutableStateOf
5
+ import androidx.lifecycle.AndroidViewModel
6
+ import expo.modules.devlauncher.services.AppService
7
+ import expo.modules.devlauncher.services.ApplicationInfo
8
+ import expo.modules.devlauncher.services.inject
9
+ import expo.modules.devmenu.DevMenuPreferencesHandle
10
+
11
+ data class SettingsState(
12
+ val showMenuAtLaunch: Boolean = false,
13
+ val isShakeEnable: Boolean = true,
14
+ val isThreeFingerLongPressEnable: Boolean = true,
15
+ val isKeyCommandEnabled: Boolean = true,
16
+ val applicationInfo: ApplicationInfo? = null
17
+ )
18
+
19
+ sealed interface SettingsAction {
20
+ data class ToggleShowMenuAtLaunch(val newValue: Boolean) : SettingsAction
21
+ data class ToggleShakeEnable(val newValue: Boolean) : SettingsAction
22
+ data class ToggleThreeFingerLongPressEnable(val newValue: Boolean) : SettingsAction
23
+ data class ToggleKeyCommandEnable(val newValue: Boolean) : SettingsAction
24
+ }
25
+
26
+ class SettingsViewModel(application: Application) : AndroidViewModel(application) {
27
+ private val menuPreferences = DevMenuPreferencesHandle
28
+ private val appService = inject<AppService>()
29
+
30
+ private val _state = mutableStateOf(
31
+ SettingsState(
32
+ showMenuAtLaunch = menuPreferences.showsAtLaunch,
33
+ isShakeEnable = menuPreferences.motionGestureEnabled,
34
+ isThreeFingerLongPressEnable = menuPreferences.touchGestureEnabled,
35
+ isKeyCommandEnabled = menuPreferences.keyCommandsEnabled,
36
+ applicationInfo = appService.applicationInfo
37
+ )
38
+ )
39
+
40
+ val state
41
+ get() = _state.value
42
+
43
+ private val listener = {
44
+ _state.value = SettingsState(
45
+ showMenuAtLaunch = menuPreferences.showsAtLaunch,
46
+ isShakeEnable = menuPreferences.motionGestureEnabled,
47
+ isThreeFingerLongPressEnable = menuPreferences.touchGestureEnabled,
48
+ isKeyCommandEnabled = menuPreferences.keyCommandsEnabled
49
+ )
50
+ }
51
+
52
+ init {
53
+ menuPreferences.addOnChangeListener(listener)
54
+ }
55
+
56
+ override fun onCleared() {
57
+ super.onCleared()
58
+ menuPreferences.removeOnChangeListener(listener)
59
+ }
60
+
61
+ fun onAction(action: SettingsAction) {
62
+ when (action) {
63
+ is SettingsAction.ToggleShowMenuAtLaunch -> {
64
+ menuPreferences.showsAtLaunch = action.newValue
65
+ }
66
+
67
+ is SettingsAction.ToggleShakeEnable -> {
68
+ menuPreferences.motionGestureEnabled = action.newValue
69
+ }
70
+
71
+ is SettingsAction.ToggleThreeFingerLongPressEnable -> {
72
+ menuPreferences.touchGestureEnabled = action.newValue
73
+ }
74
+
75
+ is SettingsAction.ToggleKeyCommandEnable -> {
76
+ menuPreferences.keyCommandsEnabled = action.newValue
77
+ }
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,38 @@
1
+ package expo.modules.devlauncher.compose.routes
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.lifecycle.viewmodel.compose.viewModel
5
+ import expo.modules.devlauncher.compose.DefaultScreenContainer
6
+ import expo.modules.devlauncher.compose.models.CrashReportModel
7
+ import expo.modules.devlauncher.compose.screens.CrashReportScreen
8
+ import expo.modules.devlauncher.launcher.errors.DevLauncherErrorInstance
9
+ import kotlinx.serialization.Serializable
10
+
11
+ @Serializable
12
+ class CrashReport(
13
+ val timestamp: Long,
14
+ val message: String,
15
+ val stack: String
16
+ ) {
17
+ companion object {
18
+ fun fromErrorInstance(errorInstance: DevLauncherErrorInstance): CrashReport {
19
+ return CrashReport(
20
+ timestamp = errorInstance.timestamp,
21
+ message = errorInstance.throwable.message ?: "Unknown",
22
+ stack = errorInstance.throwable.stackTraceToString()
23
+ )
24
+ }
25
+ }
26
+ }
27
+
28
+ @Composable
29
+ fun CrashReportRoute() {
30
+ DefaultScreenContainer {
31
+ val viewModel = viewModel<CrashReportModel>()
32
+ CrashReportScreen(
33
+ timestamp = viewModel.state.timestamp,
34
+ message = viewModel.state.message,
35
+ stack = viewModel.state.stack
36
+ )
37
+ }
38
+ }
@@ -2,8 +2,10 @@ package expo.modules.devlauncher.compose.routes
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
4
  import androidx.lifecycle.viewmodel.compose.viewModel
5
+ import androidx.navigation.NavController
5
6
  import expo.modules.devlauncher.compose.DefaultScreenContainer
6
- import expo.modules.devlauncher.compose.HomeViewModel
7
+ import expo.modules.devlauncher.compose.models.HomeAction
8
+ import expo.modules.devlauncher.compose.models.HomeViewModel
7
9
  import expo.modules.devlauncher.compose.screens.HomeScreen
8
10
  import kotlinx.serialization.Serializable
9
11
 
@@ -11,12 +13,23 @@ import kotlinx.serialization.Serializable
11
13
  object Home
12
14
 
13
15
  @Composable
14
- fun HomeRoute(onProfileClick: () -> Unit) {
16
+ fun HomeRoute(
17
+ navController: NavController,
18
+ onProfileClick: () -> Unit
19
+ ) {
15
20
  DefaultScreenContainer {
16
21
  val viewModel = viewModel<HomeViewModel>()
17
22
  HomeScreen(
18
23
  state = viewModel.state,
19
- onAction = viewModel::onAction,
24
+ onAction = { action ->
25
+ when (action) {
26
+ is HomeAction.NavigateToCrashReport -> navController.navigate(
27
+ CrashReport.fromErrorInstance(action.crashReport)
28
+ )
29
+
30
+ else -> viewModel.onAction(action)
31
+ }
32
+ },
20
33
  onProfileClick = onProfileClick
21
34
  )
22
35
  }
@@ -10,8 +10,8 @@ import com.composables.core.SheetDetent.Companion.Hidden
10
10
  import expo.modules.devlauncher.compose.AuthActivity
11
11
  import expo.modules.devlauncher.compose.AuthRequestType
12
12
  import expo.modules.devlauncher.compose.AuthResult
13
- import expo.modules.devlauncher.compose.ProfileState
14
- import expo.modules.devlauncher.compose.ProfileViewModel
13
+ import expo.modules.devlauncher.compose.models.ProfileState
14
+ import expo.modules.devlauncher.compose.models.ProfileViewModel
15
15
  import expo.modules.devlauncher.compose.ui.AccountSelector
16
16
  import expo.modules.devlauncher.compose.ui.BottomSheet
17
17
  import expo.modules.devlauncher.compose.ui.ProfileLayout
@@ -1,7 +1,9 @@
1
1
  package expo.modules.devlauncher.compose.routes
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
+ import androidx.lifecycle.viewmodel.compose.viewModel
4
5
  import expo.modules.devlauncher.compose.DefaultScreenContainer
6
+ import expo.modules.devlauncher.compose.models.SettingsViewModel
5
7
  import expo.modules.devlauncher.compose.screens.SettingsScreen
6
8
  import kotlinx.serialization.Serializable
7
9
 
@@ -11,6 +13,10 @@ object Settings
11
13
  @Composable
12
14
  fun SettingsRoute() {
13
15
  DefaultScreenContainer {
14
- SettingsScreen()
16
+ val viewModel = viewModel<SettingsViewModel>()
17
+ SettingsScreen(
18
+ state = viewModel.state,
19
+ onAction = viewModel::onAction
20
+ )
15
21
  }
16
22
  }