1c596d6cdSTomasz Sapeta package host.exp.exponent.kernel 2c596d6cdSTomasz Sapeta 3c596d6cdSTomasz Sapeta import android.annotation.SuppressLint 4c596d6cdSTomasz Sapeta import android.content.Context 5c596d6cdSTomasz Sapeta import android.content.pm.ActivityInfo 6c596d6cdSTomasz Sapeta import android.hardware.SensorManager 7c596d6cdSTomasz Sapeta import android.os.Bundle 85ee48aa9STomasz Sapeta import android.os.Handler 9c596d6cdSTomasz Sapeta import android.util.Log 10c596d6cdSTomasz Sapeta import android.view.View 11c596d6cdSTomasz Sapeta import android.view.ViewGroup 12c596d6cdSTomasz Sapeta import com.facebook.react.ReactRootView 13c596d6cdSTomasz Sapeta import com.facebook.react.bridge.Arguments 14c596d6cdSTomasz Sapeta import com.facebook.react.bridge.ReadableMap 15c596d6cdSTomasz Sapeta import com.facebook.react.bridge.UiThreadUtil 16c596d6cdSTomasz Sapeta import com.facebook.react.bridge.WritableMap 175ee48aa9STomasz Sapeta import de.greenrobot.event.EventBus 18c596d6cdSTomasz Sapeta import host.exp.exponent.utils.ShakeDetector 19c596d6cdSTomasz Sapeta import host.exp.exponent.Constants 20c596d6cdSTomasz Sapeta import versioned.host.exp.exponent.modules.internal.DevMenuModule 21c596d6cdSTomasz Sapeta import host.exp.exponent.di.NativeModuleDepsProvider 22c596d6cdSTomasz Sapeta import host.exp.exponent.experience.ExperienceActivity 235ee48aa9STomasz Sapeta import host.exp.exponent.experience.ReactNativeActivity 24c596d6cdSTomasz Sapeta import versioned.host.exp.exponent.ReactUnthemedRootView 25c596d6cdSTomasz Sapeta import java.util.* 26c596d6cdSTomasz Sapeta import javax.inject.Inject 27c596d6cdSTomasz Sapeta import host.exp.exponent.modules.ExponentKernelModule 28c596d6cdSTomasz Sapeta import host.exp.exponent.storage.ExponentSharedPreferences 29c596d6cdSTomasz Sapeta 30c596d6cdSTomasz Sapeta private const val DEV_MENU_JS_MODULE_NAME = "HomeMenu" 31c596d6cdSTomasz Sapeta 32c596d6cdSTomasz Sapeta /** 33c596d6cdSTomasz Sapeta * DevMenuManager is like a singleton that manages the dev menu in the whole application 34c596d6cdSTomasz Sapeta * and delegates calls from [ExponentKernelModule] to the specific [DevMenuModule] 35c596d6cdSTomasz Sapeta * that is linked with a react context for which the dev menu is going to be rendered. 36c596d6cdSTomasz Sapeta * Its instance can be injected as a dependency of other classes by [NativeModuleDepsProvider] 37c596d6cdSTomasz Sapeta */ 38c596d6cdSTomasz Sapeta class DevMenuManager { 39c596d6cdSTomasz Sapeta private var shakeDetector: ShakeDetector? = null 40c596d6cdSTomasz Sapeta private var reactRootView: ReactRootView? = null 41c596d6cdSTomasz Sapeta private var orientationBeforeShowingDevMenu: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED 42c596d6cdSTomasz Sapeta private val devMenuModulesRegistry = WeakHashMap<ExperienceActivity, DevMenuModuleInterface>() 43c596d6cdSTomasz Sapeta 44c596d6cdSTomasz Sapeta @Inject 457113164bSWill Schurman internal lateinit var kernel: Kernel 46c596d6cdSTomasz Sapeta 47c596d6cdSTomasz Sapeta @Inject 487113164bSWill Schurman internal lateinit var exponentSharedPreferences: ExponentSharedPreferences 49c596d6cdSTomasz Sapeta 50c596d6cdSTomasz Sapeta init { 51*094c4dddSWill Schurman NativeModuleDepsProvider.instance.inject(DevMenuManager::class.java, this) 525ee48aa9STomasz Sapeta EventBus.getDefault().register(this) 53c596d6cdSTomasz Sapeta } 54c596d6cdSTomasz Sapeta 55c596d6cdSTomasz Sapeta //region publics 56c596d6cdSTomasz Sapeta 57c596d6cdSTomasz Sapeta /** 58c596d6cdSTomasz Sapeta * Links given [DevMenuModule] with given [ExperienceActivity]. [DevMenuManager] needs to know this to 59c596d6cdSTomasz Sapeta * get appropriate data or pass requests down to the correct [DevMenuModule] that handles 60c596d6cdSTomasz Sapeta * all these stuff for a specific experience (DevMenuManager only delegates those calls). 61c596d6cdSTomasz Sapeta */ registerDevMenuModuleForActivitynull62c596d6cdSTomasz Sapeta fun registerDevMenuModuleForActivity(devMenuModule: DevMenuModuleInterface, activity: ExperienceActivity) { 63c596d6cdSTomasz Sapeta // Start shake detector once the first DevMenuModule registers in the manager. 64c596d6cdSTomasz Sapeta maybeStartDetectingShakes(activity.applicationContext) 65c596d6cdSTomasz Sapeta devMenuModulesRegistry[activity] = devMenuModule 66c596d6cdSTomasz Sapeta } 67c596d6cdSTomasz Sapeta 68c596d6cdSTomasz Sapeta /** 69c596d6cdSTomasz Sapeta * Shows dev menu in given experience activity. Ensures it never happens in standalone apps and is run on the UI thread. 70c596d6cdSTomasz Sapeta */ 71c596d6cdSTomasz Sapeta @SuppressLint("SourceLockedOrientationActivity") showInActivitynull72c596d6cdSTomasz Sapeta fun showInActivity(activity: ExperienceActivity) { 73c596d6cdSTomasz Sapeta if (Constants.isStandaloneApp()) { 74c596d6cdSTomasz Sapeta return 75c596d6cdSTomasz Sapeta } 76c596d6cdSTomasz Sapeta 77c596d6cdSTomasz Sapeta UiThreadUtil.runOnUiThread { 78c596d6cdSTomasz Sapeta try { 79c596d6cdSTomasz Sapeta val devMenuModule = devMenuModulesRegistry[activity] ?: return@runOnUiThread 80c596d6cdSTomasz Sapeta val devMenuView = prepareRootView(devMenuModule.getInitialProps()) 81c596d6cdSTomasz Sapeta 82399e3fd3STomasz Sapeta loseFocusInActivity(activity) 83399e3fd3STomasz Sapeta 84c596d6cdSTomasz Sapeta // We need to force the device to use portrait orientation as the dev menu doesn't support landscape. 85c596d6cdSTomasz Sapeta // However, when removing it, we should set it back to the orientation from before showing the dev menu. 86c596d6cdSTomasz Sapeta orientationBeforeShowingDevMenu = activity.requestedOrientation 87c596d6cdSTomasz Sapeta activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 88c596d6cdSTomasz Sapeta 8927700081SBartłomiej Bukowski activity.addReactViewToContentContainer(devMenuView) 90c596d6cdSTomasz Sapeta 91c596d6cdSTomasz Sapeta // @tsapeta: We need to call onHostResume on kernel's react instance with the new ExperienceActivity. 92c596d6cdSTomasz Sapeta // Otherwise, touches and other gestures may not work correctly. 937113164bSWill Schurman kernel.reactInstanceManager?.onHostResume(activity) 94c596d6cdSTomasz Sapeta } catch (exception: Exception) { 95b488c655SBartłomiej Bukowski Log.e("ExpoDevMenu", exception.message ?: "No error message.") 96c596d6cdSTomasz Sapeta } 97c596d6cdSTomasz Sapeta } 98c596d6cdSTomasz Sapeta } 99c596d6cdSTomasz Sapeta 100c596d6cdSTomasz Sapeta /** 101c596d6cdSTomasz Sapeta * Hides dev menu in given experience activity. Ensures it never happens in standalone apps and is run on the UI thread. 102c596d6cdSTomasz Sapeta */ hideInActivitynull103c596d6cdSTomasz Sapeta fun hideInActivity(activity: ExperienceActivity) { 104c596d6cdSTomasz Sapeta if (Constants.isStandaloneApp()) { 105c596d6cdSTomasz Sapeta return 106c596d6cdSTomasz Sapeta } 107c596d6cdSTomasz Sapeta 108c596d6cdSTomasz Sapeta UiThreadUtil.runOnUiThread { 109c596d6cdSTomasz Sapeta reactRootView?.let { 110c596d6cdSTomasz Sapeta val parentView = it.parent as ViewGroup? 111c596d6cdSTomasz Sapeta 112c596d6cdSTomasz Sapeta // Restore the original orientation that had been set before the dev menu was displayed. 113c596d6cdSTomasz Sapeta activity.requestedOrientation = orientationBeforeShowingDevMenu 114c596d6cdSTomasz Sapeta 115c596d6cdSTomasz Sapeta it.visibility = View.GONE 116c596d6cdSTomasz Sapeta parentView?.removeView(it) 117c596d6cdSTomasz Sapeta tryToPauseHostActivity(activity) 118c596d6cdSTomasz Sapeta } 119c596d6cdSTomasz Sapeta } 120c596d6cdSTomasz Sapeta } 121c596d6cdSTomasz Sapeta 122c596d6cdSTomasz Sapeta /** 123c596d6cdSTomasz Sapeta * Hides dev menu in the currently shown experience activity. 124c596d6cdSTomasz Sapeta * Does nothing if the current activity is not of type [ExperienceActivity]. 125c596d6cdSTomasz Sapeta */ hideInCurrentActivitynull126c596d6cdSTomasz Sapeta fun hideInCurrentActivity() { 1277113164bSWill Schurman val currentActivity = ExperienceActivity.currentActivity 128c596d6cdSTomasz Sapeta if (currentActivity != null) { 129c596d6cdSTomasz Sapeta hideInActivity(currentActivity) 130c596d6cdSTomasz Sapeta } 131c596d6cdSTomasz Sapeta } 132c596d6cdSTomasz Sapeta 133c596d6cdSTomasz Sapeta /** 134c596d6cdSTomasz Sapeta * Toggles dev menu visibility in given experience activity. 135c596d6cdSTomasz Sapeta */ toggleInActivitynull136c596d6cdSTomasz Sapeta fun toggleInActivity(activity: ExperienceActivity) { 1377113164bSWill Schurman if (isDevMenuVisible() && reactRootView != null && activity.hasReactView(reactRootView!!)) { 138c596d6cdSTomasz Sapeta requestToClose(activity) 139c596d6cdSTomasz Sapeta } else { 140c596d6cdSTomasz Sapeta showInActivity(activity) 141c596d6cdSTomasz Sapeta } 142c596d6cdSTomasz Sapeta } 143c596d6cdSTomasz Sapeta 144c596d6cdSTomasz Sapeta /** 145c596d6cdSTomasz Sapeta * Requests JavaScript side to start closing the dev menu (start the animation or so). 146c596d6cdSTomasz Sapeta * Fully closes the dev menu once it receives a response from that event. 147c596d6cdSTomasz Sapeta */ requestToClosenull148c596d6cdSTomasz Sapeta fun requestToClose(activity: ExperienceActivity) { 149c596d6cdSTomasz Sapeta if (Constants.isStandaloneApp()) { 150c596d6cdSTomasz Sapeta return 151c596d6cdSTomasz Sapeta } 152c596d6cdSTomasz Sapeta 153dca21aa2SWill Schurman ExponentKernelModule.queueEvent( 154dca21aa2SWill Schurman "ExponentKernel.requestToCloseDevMenu", Arguments.createMap(), 155dca21aa2SWill Schurman object : ExponentKernelModuleProvider.KernelEventCallback { 156c596d6cdSTomasz Sapeta override fun onEventSuccess(result: ReadableMap) { 157c596d6cdSTomasz Sapeta hideInActivity(activity) 158c596d6cdSTomasz Sapeta } 159c596d6cdSTomasz Sapeta 160aa3c92eaSWill Schurman override fun onEventFailure(errorMessage: String?) { 161c596d6cdSTomasz Sapeta hideInActivity(activity) 162c596d6cdSTomasz Sapeta } 163dca21aa2SWill Schurman } 164dca21aa2SWill Schurman ) 165c596d6cdSTomasz Sapeta } 166c596d6cdSTomasz Sapeta 167c596d6cdSTomasz Sapeta /** 168c596d6cdSTomasz Sapeta * Simplified version of the above function, but operates on the current experience activity. 169c596d6cdSTomasz Sapeta */ requestToClosenull170c596d6cdSTomasz Sapeta fun requestToClose() { 171c596d6cdSTomasz Sapeta getCurrentExperienceActivity()?.let { 172c596d6cdSTomasz Sapeta requestToClose(it) 173c596d6cdSTomasz Sapeta } 174c596d6cdSTomasz Sapeta } 175c596d6cdSTomasz Sapeta 176c596d6cdSTomasz Sapeta /** 177c596d6cdSTomasz Sapeta * Gets a map of dev menu options available in the currently shown [ExperienceActivity]. 178c596d6cdSTomasz Sapeta * If the experience doesn't support developer tools just returns an empty response. 179c596d6cdSTomasz Sapeta */ getMenuItemsnull180c596d6cdSTomasz Sapeta fun getMenuItems(): WritableMap { 181c596d6cdSTomasz Sapeta val devMenuModule = getCurrentDevMenuModule() 182c596d6cdSTomasz Sapeta val menuItemsBundle = devMenuModule?.getMenuItems() 183c596d6cdSTomasz Sapeta 184c596d6cdSTomasz Sapeta return if (menuItemsBundle != null && devMenuModule.isDevSupportEnabled()) { 185c596d6cdSTomasz Sapeta Arguments.fromBundle(menuItemsBundle) 186c596d6cdSTomasz Sapeta } else { 187c596d6cdSTomasz Sapeta Arguments.createMap() 188c596d6cdSTomasz Sapeta } 189c596d6cdSTomasz Sapeta } 190c596d6cdSTomasz Sapeta 191c596d6cdSTomasz Sapeta /** 192c596d6cdSTomasz Sapeta * Function called every time the dev menu option is selected. It passes this request down 193c596d6cdSTomasz Sapeta * to the specific [DevMenuModule] that is linked with the currently shown [ExperienceActivity]. 194c596d6cdSTomasz Sapeta */ selectItemWithKeynull195c596d6cdSTomasz Sapeta fun selectItemWithKey(itemKey: String) { 196c596d6cdSTomasz Sapeta getCurrentDevMenuModule()?.selectItemWithKey(itemKey) 197c596d6cdSTomasz Sapeta } 198c596d6cdSTomasz Sapeta 199c596d6cdSTomasz Sapeta /** 200c596d6cdSTomasz Sapeta * Reloads app with the manifest, falls back to reloading just JS bundle if reloading manifest fails. 201c596d6cdSTomasz Sapeta */ reloadAppnull202c596d6cdSTomasz Sapeta fun reloadApp() { 203c596d6cdSTomasz Sapeta getCurrentDevMenuModule()?.let { 204c596d6cdSTomasz Sapeta try { 205c596d6cdSTomasz Sapeta val manifestUrl = it.getManifestUrl() 2067113164bSWill Schurman kernel.reloadVisibleExperience(manifestUrl, false) 207c596d6cdSTomasz Sapeta } catch (reloadingException: Exception) { 208c596d6cdSTomasz Sapeta reloadingException.printStackTrace() 209c596d6cdSTomasz Sapeta // If anything goes wrong here, we can fall back to plain JS reload. 210c596d6cdSTomasz Sapeta it.reloadApp() 211c596d6cdSTomasz Sapeta } 212c596d6cdSTomasz Sapeta } 213c596d6cdSTomasz Sapeta } 214c596d6cdSTomasz Sapeta 215c596d6cdSTomasz Sapeta /** 216c596d6cdSTomasz Sapeta * Returns boolean value determining whether the current app supports developer tools. 217c596d6cdSTomasz Sapeta */ isDevSupportEnabledByCurrentActivitynull218c596d6cdSTomasz Sapeta fun isDevSupportEnabledByCurrentActivity(): Boolean { 219c596d6cdSTomasz Sapeta val devMenuModule = getCurrentDevMenuModule() 220c596d6cdSTomasz Sapeta return devMenuModule?.isDevSupportEnabled() ?: false 221c596d6cdSTomasz Sapeta } 222c596d6cdSTomasz Sapeta 223c596d6cdSTomasz Sapeta /** 224c596d6cdSTomasz Sapeta * Checks whether the dev menu is shown over given experience activity. 225c596d6cdSTomasz Sapeta */ isShownInActivitynull226c596d6cdSTomasz Sapeta fun isShownInActivity(activity: ExperienceActivity): Boolean { 2277113164bSWill Schurman return reactRootView != null && activity.hasReactView(reactRootView!!) 228c596d6cdSTomasz Sapeta } 229c596d6cdSTomasz Sapeta 230c596d6cdSTomasz Sapeta /** 231c596d6cdSTomasz Sapeta * Checks whether the dev menu onboarding is already finished. 232c596d6cdSTomasz Sapeta * Onboarding is a screen that shows the dev menu to the user that opens any experience for the first time. 233c596d6cdSTomasz Sapeta */ isOnboardingFinishednull234c596d6cdSTomasz Sapeta fun isOnboardingFinished(): Boolean { 23588711250SWill Schurman return exponentSharedPreferences.getBoolean(ExponentSharedPreferences.ExponentSharedPreferencesKey.IS_ONBOARDING_FINISHED_KEY) ?: false 236c596d6cdSTomasz Sapeta } 237c596d6cdSTomasz Sapeta 238c596d6cdSTomasz Sapeta /** 239c596d6cdSTomasz Sapeta * Sets appropriate setting in shared preferences that user's onboarding has finished. 240c596d6cdSTomasz Sapeta */ setIsOnboardingFinishednull241c596d6cdSTomasz Sapeta fun setIsOnboardingFinished(isOnboardingFinished: Boolean = true) { 24288711250SWill Schurman exponentSharedPreferences.setBoolean(ExponentSharedPreferences.ExponentSharedPreferencesKey.IS_ONBOARDING_FINISHED_KEY, isOnboardingFinished) 243c596d6cdSTomasz Sapeta } 244c596d6cdSTomasz Sapeta 245c596d6cdSTomasz Sapeta /** 246c596d6cdSTomasz Sapeta * In case the user switches from [host.exp.exponent.experience.HomeActivity] to [ExperienceActivity] which has a visible dev menu, 247c596d6cdSTomasz Sapeta * we need to call onHostResume on the kernel's react instance manager to change its current activity. 248c596d6cdSTomasz Sapeta */ maybeResumeHostWithActivitynull249c596d6cdSTomasz Sapeta fun maybeResumeHostWithActivity(activity: ExperienceActivity) { 250c596d6cdSTomasz Sapeta if (isShownInActivity(activity)) { 2517113164bSWill Schurman kernel.reactInstanceManager?.onHostResume(activity) 252c596d6cdSTomasz Sapeta } 253c596d6cdSTomasz Sapeta } 254c596d6cdSTomasz Sapeta 2555ee48aa9STomasz Sapeta /** 2565ee48aa9STomasz Sapeta * Receives events of type [ReactNativeActivity.ExperienceDoneLoadingEvent] once the experience finishes loading. 2575ee48aa9STomasz Sapeta */ onEventnull2585ee48aa9STomasz Sapeta fun onEvent(event: ReactNativeActivity.ExperienceDoneLoadingEvent) { 2595ee48aa9STomasz Sapeta (event.activity as? ExperienceActivity)?.let { 2605ee48aa9STomasz Sapeta maybeShowWithOnboarding(it) 2615ee48aa9STomasz Sapeta } 2625ee48aa9STomasz Sapeta } 2635ee48aa9STomasz Sapeta 264c596d6cdSTomasz Sapeta //endregion publics 265c596d6cdSTomasz Sapeta //region internals 266c596d6cdSTomasz Sapeta 267c596d6cdSTomasz Sapeta /** 2685ee48aa9STomasz Sapeta * Says whether the dev menu should show onboarding view if this is the first time 2695ee48aa9STomasz Sapeta * the user opens an experience, or he hasn't finished onboarding yet. 2705ee48aa9STomasz Sapeta */ shouldShowOnboardingnull2715ee48aa9STomasz Sapeta private fun shouldShowOnboarding(): Boolean { 2727ad79459SBrent Vatne return !Constants.isStandaloneApp() && !KernelConfig.HIDE_ONBOARDING && !isOnboardingFinished() && !Constants.DISABLE_NUX 2735ee48aa9STomasz Sapeta } 2745ee48aa9STomasz Sapeta 2755ee48aa9STomasz Sapeta /** 2765ee48aa9STomasz Sapeta * Shows dev menu in given activity but only when the onboarding view should show up. 2775ee48aa9STomasz Sapeta */ maybeShowWithOnboardingnull2785ee48aa9STomasz Sapeta private fun maybeShowWithOnboarding(activity: ExperienceActivity) { 2795ee48aa9STomasz Sapeta if (shouldShowOnboarding() && !isShownInActivity(activity)) { 2805ee48aa9STomasz Sapeta // @tsapeta: We need a small delay to allow the experience to be fully rendered. 2815ee48aa9STomasz Sapeta // Without the delay we were having some weird issues with style props being set on nonexistent shadow views. 2825ee48aa9STomasz Sapeta // From the other side, it's good that we don't show it immediately so the user can see his app first. 2835ee48aa9STomasz Sapeta Handler().postDelayed({ showInActivity(activity) }, 2000) 2845ee48aa9STomasz Sapeta } 2855ee48aa9STomasz Sapeta } 2865ee48aa9STomasz Sapeta 2875ee48aa9STomasz Sapeta /** 288c596d6cdSTomasz Sapeta * Starts [ShakeDetector] if it's not running yet. 289c596d6cdSTomasz Sapeta */ maybeStartDetectingShakesnull290c596d6cdSTomasz Sapeta private fun maybeStartDetectingShakes(context: Context) { 291c596d6cdSTomasz Sapeta if (shakeDetector != null) { 292c596d6cdSTomasz Sapeta return 293c596d6cdSTomasz Sapeta } 294c596d6cdSTomasz Sapeta shakeDetector = ShakeDetector { this.onShakeGesture() } 295c596d6cdSTomasz Sapeta shakeDetector?.start(context.getSystemService(Context.SENSOR_SERVICE) as SensorManager) 296c596d6cdSTomasz Sapeta } 297c596d6cdSTomasz Sapeta 298c596d6cdSTomasz Sapeta /** 299c596d6cdSTomasz Sapeta * If this is the first time when we're going to show the dev menu, it creates a new react root view 300c596d6cdSTomasz Sapeta * that will render the other endpoint of home app whose name is described by [DEV_MENU_JS_MODULE_NAME] constant. 301c596d6cdSTomasz Sapeta * Also sets initialProps, layout settings and initial animation values. 302c596d6cdSTomasz Sapeta */ 303c596d6cdSTomasz Sapeta @Throws(Exception::class) prepareRootViewnull304c596d6cdSTomasz Sapeta private fun prepareRootView(initialProps: Bundle): ReactRootView { 305c596d6cdSTomasz Sapeta // Throw an exception in case the kernel is not initialized yet. 3067113164bSWill Schurman if (kernel.reactInstanceManager == null) { 307c596d6cdSTomasz Sapeta throw Exception("Kernel's React instance manager is not initialized yet.") 308c596d6cdSTomasz Sapeta } 309c596d6cdSTomasz Sapeta 310c596d6cdSTomasz Sapeta if (reactRootView == null) { 311c596d6cdSTomasz Sapeta reactRootView = ReactUnthemedRootView(kernel.activityContext) 312c596d6cdSTomasz Sapeta reactRootView?.startReactApplication(kernel.reactInstanceManager, DEV_MENU_JS_MODULE_NAME, initialProps) 313c596d6cdSTomasz Sapeta } else { 314c596d6cdSTomasz Sapeta reactRootView?.appProperties = initialProps 315c596d6cdSTomasz Sapeta } 316c596d6cdSTomasz Sapeta 317c596d6cdSTomasz Sapeta val rootView = reactRootView!! 318c596d6cdSTomasz Sapeta 319c596d6cdSTomasz Sapeta rootView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) 320c596d6cdSTomasz Sapeta rootView.visibility = View.VISIBLE 321c596d6cdSTomasz Sapeta 322c596d6cdSTomasz Sapeta return rootView 323c596d6cdSTomasz Sapeta } 324c596d6cdSTomasz Sapeta 325c596d6cdSTomasz Sapeta /** 326399e3fd3STomasz Sapeta * Loses view focus in given activity. It makes sure that system's keyboard is hidden when presenting dev menu view. 327399e3fd3STomasz Sapeta */ loseFocusInActivitynull328399e3fd3STomasz Sapeta private fun loseFocusInActivity(activity: ExperienceActivity) { 329399e3fd3STomasz Sapeta activity.getCurrentFocus()?.clearFocus() 330399e3fd3STomasz Sapeta } 331399e3fd3STomasz Sapeta 332399e3fd3STomasz Sapeta /** 333c596d6cdSTomasz Sapeta * Returns an instance implementing [DevMenuModuleInterface] linked to the current [ExperienceActivity], or null if the current 334c596d6cdSTomasz Sapeta * activity is not of [ExperienceActivity] type or there is no module registered for that activity. 335c596d6cdSTomasz Sapeta */ getCurrentDevMenuModulenull336c596d6cdSTomasz Sapeta private fun getCurrentDevMenuModule(): DevMenuModuleInterface? { 337c596d6cdSTomasz Sapeta val currentActivity = getCurrentExperienceActivity() 338c596d6cdSTomasz Sapeta return if (currentActivity != null) devMenuModulesRegistry[currentActivity] else null 339c596d6cdSTomasz Sapeta } 340c596d6cdSTomasz Sapeta 341c596d6cdSTomasz Sapeta /** 342c596d6cdSTomasz Sapeta * Returns current activity if it's of type [ExperienceActivity], or null otherwise. 343c596d6cdSTomasz Sapeta */ getCurrentExperienceActivitynull344c596d6cdSTomasz Sapeta private fun getCurrentExperienceActivity(): ExperienceActivity? { 3457113164bSWill Schurman return ExperienceActivity.currentActivity 346c596d6cdSTomasz Sapeta } 347c596d6cdSTomasz Sapeta 348c596d6cdSTomasz Sapeta /** 349c596d6cdSTomasz Sapeta * Checks whether the dev menu is visible anywhere. 350c596d6cdSTomasz Sapeta */ isDevMenuVisiblenull351c596d6cdSTomasz Sapeta private fun isDevMenuVisible(): Boolean { 352c596d6cdSTomasz Sapeta return reactRootView?.parent != null 353c596d6cdSTomasz Sapeta } 354c596d6cdSTomasz Sapeta 355c596d6cdSTomasz Sapeta /** 356c596d6cdSTomasz Sapeta * Handles shake gesture which simply toggles the dev menu. 357c596d6cdSTomasz Sapeta */ onShakeGesturenull358c596d6cdSTomasz Sapeta private fun onShakeGesture() { 3597113164bSWill Schurman val currentActivity = ExperienceActivity.currentActivity 360c596d6cdSTomasz Sapeta 361c596d6cdSTomasz Sapeta if (currentActivity != null) { 362c596d6cdSTomasz Sapeta toggleInActivity(currentActivity) 363c596d6cdSTomasz Sapeta } 364c596d6cdSTomasz Sapeta } 365c596d6cdSTomasz Sapeta tryToPauseHostActivitynull366c596d6cdSTomasz Sapeta private fun tryToPauseHostActivity(activity: ExperienceActivity) { 367c596d6cdSTomasz Sapeta try { 3687113164bSWill Schurman kernel.reactInstanceManager?.onHostPause(activity) 369c596d6cdSTomasz Sapeta } catch (e: AssertionError) { 370c596d6cdSTomasz Sapeta // nothing 371c596d6cdSTomasz Sapeta } 372c596d6cdSTomasz Sapeta } 373c596d6cdSTomasz Sapeta 374c596d6cdSTomasz Sapeta //endregion internals 375c596d6cdSTomasz Sapeta } 376