package expo.modules.devlauncher.launcher import android.os.Bundle import android.os.Handler import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.ViewGroup import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.ReactInstanceEventListener import com.facebook.react.ReactRootView import com.facebook.react.bridge.ReactContext import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate import expo.modules.core.utilities.EmulatorUtilities import expo.modules.devlauncher.koin.DevLauncherKoinComponent import expo.modules.devlauncher.splashscreen.DevLauncherSplashScreen import expo.modules.devlauncher.splashscreen.DevLauncherSplashScreenProvider import expo.modules.devmenu.DevMenuManager import org.koin.core.component.inject const val SEARCH_FOR_ROOT_VIEW_INTERVAL = 20L class DevLauncherActivity : ReactActivity(), ReactInstanceEventListener, DevLauncherKoinComponent { private val controller: DevLauncherControllerInterface by inject() private var devMenuManager: DevMenuManager = DevMenuManager private var splashScreen: DevLauncherSplashScreen? = null private var rootView: ViewGroup? = null private lateinit var contentView: ViewGroup private val handler = Handler() override fun getMainComponentName() = "main" override fun createReactActivityDelegate(): ReactActivityDelegate { return object : DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) { override fun getReactNativeHost() = controller.devClientHost.reactNativeHost override fun getReactHost() = controller.devClientHost.reactHost override fun getLaunchOptions() = Bundle().apply { putBoolean("isSimulator", isSimulator) } } } override fun onStart() { overridePendingTransition(0, 0) super.onStart() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) contentView = findViewById(android.R.id.content) ?: return splashScreen = DevLauncherSplashScreenProvider() .attachSplashScreenViewAsync(this) searchForRootView() } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) controller.devClientHost.currentReactContext?.let { onReactContextInitialized(it) return } controller.devClientHost.addReactInstanceEventListener(this) } override fun onPause() { overridePendingTransition(0, 0) super.onPause() } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { devMenuManager.onTouchEvent(ev) return super.dispatchTouchEvent(ev) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { return devMenuManager.onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event) } override fun onReactContextInitialized(context: ReactContext) { controller.devClientHost.removeReactInstanceEventListener(this) } private val isSimulator get() = EmulatorUtilities.isRunningOnEmulator() private fun searchForRootView() { if (rootView != null) { return } // RootView is successfully found in first check (nearly impossible for first call) findRootView(contentView)?.let { return@searchForRootView handleRootView(it) } handler.postDelayed({ searchForRootView() }, SEARCH_FOR_ROOT_VIEW_INTERVAL) } private fun findRootView(view: View): ViewGroup? { if (view is ReactRootView) { return view } if (view != splashScreen && view is ViewGroup) { for (idx in 0 until view.childCount) { findRootView(view.getChildAt(idx))?.let { return@findRootView it } } } return null } private fun handleRootView(view: ViewGroup) { rootView = view if ((rootView?.childCount ?: 0) > 0) { hideSplashScreen() } view.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener { override fun onChildViewRemoved(parent: View, child: View) = Unit override fun onChildViewAdded(parent: View, child: View) { // react only to first child if (rootView?.childCount == 1) { hideSplashScreen() } } }) } private fun hideSplashScreen() { splashScreen?.let { runOnUiThread { contentView.removeView(it) splashScreen = null } } } }