1 package expo.modules.devlauncher.launcher
2 
3 import android.os.Bundle
4 import android.os.Handler
5 import android.view.KeyEvent
6 import android.view.MotionEvent
7 import android.view.View
8 import android.view.ViewGroup
9 import com.facebook.react.ReactActivity
10 import com.facebook.react.ReactActivityDelegate
11 import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
12 import com.facebook.react.defaults.DefaultReactActivityDelegate
13 import com.facebook.react.ReactInstanceManager
14 import com.facebook.react.ReactRootView
15 import com.facebook.react.bridge.ReactContext
16 import expo.modules.core.utilities.EmulatorUtilities
17 import expo.modules.devlauncher.koin.DevLauncherKoinComponent
18 import expo.modules.devlauncher.splashscreen.DevLauncherSplashScreen
19 import expo.modules.devlauncher.splashscreen.DevLauncherSplashScreenProvider
20 import expo.modules.devmenu.DevMenuManager
21 import org.koin.core.component.inject
22 
23 const val SEARCH_FOR_ROOT_VIEW_INTERVAL = 20L
24 
25 class DevLauncherActivity : ReactActivity(), ReactInstanceManager.ReactInstanceEventListener, DevLauncherKoinComponent {
26   private val controller: DevLauncherControllerInterface by inject()
27   private var devMenuManager: DevMenuManager = DevMenuManager
28   private var splashScreen: DevLauncherSplashScreen? = null
29   private var rootView: ViewGroup? = null
30   private lateinit var contentView: ViewGroup
31   private val handler = Handler()
32 
getMainComponentNamenull33   override fun getMainComponentName() = "main"
34 
35   override fun createReactActivityDelegate(): ReactActivityDelegate {
36     return object : DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) {
37 
38       override fun getReactNativeHost() = controller.devClientHost
39 
40       override fun getLaunchOptions() = Bundle().apply {
41         putBoolean("isSimulator", isSimulator)
42       }
43     }
44   }
45 
onStartnull46   override fun onStart() {
47     overridePendingTransition(0, 0)
48     super.onStart()
49   }
50 
onCreatenull51   override fun onCreate(savedInstanceState: Bundle?) {
52     super.onCreate(savedInstanceState)
53 
54     contentView = findViewById(android.R.id.content) ?: return
55     splashScreen = DevLauncherSplashScreenProvider()
56       .attachSplashScreenViewAsync(this)
57     searchForRootView()
58   }
59 
onPostCreatenull60   override fun onPostCreate(savedInstanceState: Bundle?) {
61     super.onPostCreate(savedInstanceState)
62     reactInstanceManager.currentReactContext?.let {
63       onReactContextInitialized(it)
64       return
65     }
66 
67     reactInstanceManager.addReactInstanceEventListener(this)
68   }
69 
onPausenull70   override fun onPause() {
71     overridePendingTransition(0, 0)
72     super.onPause()
73   }
74 
dispatchTouchEventnull75   override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
76     devMenuManager.onTouchEvent(ev)
77     return super.dispatchTouchEvent(ev)
78   }
79 
onKeyUpnull80   override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
81     return devMenuManager.onKeyEvent(keyCode, event) == true || super.onKeyUp(keyCode, event)
82   }
83 
onReactContextInitializednull84   override fun onReactContextInitialized(context: ReactContext) {
85     reactInstanceManager.removeReactInstanceEventListener(this)
86   }
87 
88   private val isSimulator
89     get() = EmulatorUtilities.isRunningOnEmulator()
90 
searchForRootViewnull91   private fun searchForRootView() {
92     if (rootView != null) {
93       return
94     }
95     // RootView is successfully found in first check (nearly impossible for first call)
96     findRootView(contentView)?.let { return@searchForRootView handleRootView(it) }
97     handler.postDelayed({ searchForRootView() }, SEARCH_FOR_ROOT_VIEW_INTERVAL)
98   }
99 
findRootViewnull100   private fun findRootView(view: View): ViewGroup? {
101     if (view is ReactRootView) {
102       return view
103     }
104     if (view != splashScreen && view is ViewGroup) {
105       for (idx in 0 until view.childCount) {
106         findRootView(view.getChildAt(idx))?.let { return@findRootView it }
107       }
108     }
109     return null
110   }
111 
handleRootViewnull112   private fun handleRootView(view: ViewGroup) {
113     rootView = view
114     if ((rootView?.childCount ?: 0) > 0) {
115       hideSplashScreen()
116     }
117 
118     view.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
119       override fun onChildViewRemoved(parent: View, child: View) = Unit
120       override fun onChildViewAdded(parent: View, child: View) {
121         // react only to first child
122         if (rootView?.childCount == 1) {
123           hideSplashScreen()
124         }
125       }
126     })
127   }
128 
hideSplashScreennull129   private fun hideSplashScreen() {
130     splashScreen?.let {
131       runOnUiThread {
132         contentView.removeView(it)
133         splashScreen = null
134       }
135     }
136   }
137 }
138