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