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