1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package versioned.host.exp.exponent
3 
4 import android.content.Context
5 import android.os.Looper
6 import com.facebook.react.ReactPackage
7 import com.facebook.react.bridge.NativeModule
8 import com.facebook.react.bridge.ReactApplicationContext
9 import com.facebook.react.uimanager.ViewManager
10 import expo.modules.adapters.react.ReactModuleRegistryProvider
11 import expo.modules.core.interfaces.Package
12 import expo.modules.core.interfaces.SingletonModule
13 import expo.modules.random.RandomModule
14 import expo.modules.manifests.core.Manifest
15 import host.exp.exponent.Constants
16 import host.exp.exponent.analytics.EXL
17 import host.exp.exponent.kernel.ExperienceKey
18 // WHEN_VERSIONING_REMOVE_FROM_HERE
19 import host.exp.exponent.kernel.ExponentKernelModuleProvider
20 // WHEN_VERSIONING_REMOVE_TO_HERE
21 import host.exp.exponent.kernel.KernelConstants
22 import host.exp.exponent.utils.ScopedContext
23 import org.json.JSONException
24 import versioned.host.exp.exponent.modules.api.*
25 import versioned.host.exp.exponent.modules.api.appearance.ExpoAppearanceModule
26 import versioned.host.exp.exponent.modules.api.appearance.ExpoAppearancePackage
27 import versioned.host.exp.exponent.modules.api.appearance.rncappearance.RNCAppearanceModule
28 import versioned.host.exp.exponent.modules.api.cognito.RNAWSCognitoModule
29 import versioned.host.exp.exponent.modules.api.components.datetimepicker.RNDateTimePickerPackage
30 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerModule
31 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerPackage
32 import versioned.host.exp.exponent.modules.api.components.lottie.LottiePackage
33 import versioned.host.exp.exponent.modules.api.components.maps.MapsPackage
34 import versioned.host.exp.exponent.modules.api.components.maskedview.RNCMaskedViewPackage
35 import versioned.host.exp.exponent.modules.api.components.picker.RNCPickerPackage
36 import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.StripeSdkPackage
37 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementModule
38 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementPackage
39 import versioned.host.exp.exponent.modules.api.components.slider.ReactSliderPackage
40 import versioned.host.exp.exponent.modules.api.components.svg.SvgPackage
41 import versioned.host.exp.exponent.modules.api.components.viewpager.RNCViewPagerPackage
42 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewModule
43 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewPackage
44 import versioned.host.exp.exponent.modules.api.netinfo.NetInfoModule
45 import versioned.host.exp.exponent.modules.api.notifications.NotificationsModule
46 import versioned.host.exp.exponent.modules.api.reanimated.ReanimatedModule
47 import versioned.host.exp.exponent.modules.api.safeareacontext.SafeAreaContextPackage
48 import versioned.host.exp.exponent.modules.api.screens.RNScreensPackage
49 import versioned.host.exp.exponent.modules.api.viewshot.RNViewShotModule
50 import versioned.host.exp.exponent.modules.internal.DevMenuModule
51 import versioned.host.exp.exponent.modules.test.ExponentTestNativeModule
52 import versioned.host.exp.exponent.modules.universal.ExpoModuleRegistryAdapter
53 import versioned.host.exp.exponent.modules.universal.ScopedModuleRegistryAdapter
54 import java.io.UnsupportedEncodingException
55 
56 // This is an Expo module but not a unimodule
57 class ExponentPackage : ReactPackage {
58   private val isKernel: Boolean
59   private val experienceProperties: Map<String, Any?>
60   private val manifest: Manifest
61   private val moduleRegistryAdapter: ScopedModuleRegistryAdapter
62 
63   private constructor(
64     isKernel: Boolean,
65     experienceProperties: Map<String, Any?>,
66     manifest: Manifest,
67     expoPackages: List<Package>,
68     singletonModules: List<SingletonModule>?
69   ) {
70     this.isKernel = isKernel
71     this.experienceProperties = experienceProperties
72     this.manifest = manifest
73     moduleRegistryAdapter = createDefaultModuleRegistryAdapterForPackages(expoPackages, singletonModules)
74   }
75 
76   constructor(
77     experienceProperties: Map<String, Any?>,
78     manifest: Manifest,
79     expoPackages: List<Package>?,
80     delegate: ExponentPackageDelegate?,
81     singletonModules: List<SingletonModule>
82   ) {
83     isKernel = false
84     this.experienceProperties = experienceProperties
85     this.manifest = manifest
86     val packages = expoPackages ?: ExperiencePackagePicker.packages(manifest)
87     // Delegate may not be null only when the app is detached
88     moduleRegistryAdapter = createModuleRegistryAdapter(delegate, singletonModules, packages)
89   }
90 
91   private fun createModuleRegistryAdapter(
92     delegate: ExponentPackageDelegate?,
93     singletonModules: List<SingletonModule>,
94     packages: List<Package>
95   ): ScopedModuleRegistryAdapter {
96     var registryAdapter: ScopedModuleRegistryAdapter? = null
97     if (delegate != null) {
98       registryAdapter = delegate.getScopedModuleRegistryAdapterForPackages(packages, singletonModules)
99     }
100     if (registryAdapter == null) {
101       registryAdapter = createDefaultModuleRegistryAdapterForPackages(packages, singletonModules)
102     }
103     return registryAdapter
104   }
105 
106   override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
107     val isVerified = manifest.isVerified() ?: false
108     val nativeModules: MutableList<NativeModule> = mutableListOf(
109       URLHandlerModule(reactContext),
110       ShakeModule(reactContext),
111       KeyboardModule(reactContext)
112     )
113 
114     if (isKernel) {
115       // WHEN_VERSIONING_REMOVE_FROM_HERE
116       nativeModules.add((ExponentKernelModuleProvider.newInstance(reactContext) as NativeModule?)!!)
117       // WHEN_VERSIONING_REMOVE_TO_HERE
118     }
119     if (!isKernel && !Constants.isStandaloneApp()) {
120       // We need DevMenuModule only in non-home and non-standalone apps.
121       nativeModules.add(DevMenuModule(reactContext, experienceProperties, manifest))
122     }
123 
124     if (isVerified) {
125       try {
126         val experienceKey = ExperienceKey.fromManifest(manifest)
127         val scopedContext = ScopedContext(reactContext, experienceKey)
128         nativeModules.add(NotificationsModule(reactContext, experienceKey, manifest.getStableLegacyID(), manifest.getEASProjectID()))
129         nativeModules.add(RNViewShotModule(reactContext, scopedContext))
130         nativeModules.add(RandomModule(reactContext))
131         nativeModules.add(ExponentTestNativeModule(reactContext))
132         nativeModules.add(PedometerModule(reactContext))
133         nativeModules.add(ScreenOrientationModule(reactContext))
134         nativeModules.add(RNGestureHandlerModule(reactContext))
135         nativeModules.add(RNAWSCognitoModule(reactContext))
136         nativeModules.add(ReanimatedModule(reactContext))
137         nativeModules.add(RNCWebViewModule(reactContext))
138         nativeModules.add(NetInfoModule(reactContext))
139         nativeModules.add(RNSharedElementModule(reactContext))
140 
141         // @tsapeta: Using ExpoAppearanceModule in home app causes some issues with the dev menu,
142         // when home's setting is set to automatic and the system theme is different
143         // than this supported by the experience in which we opened the dev menu.
144         if (isKernel) {
145           nativeModules.add(RNCAppearanceModule(reactContext))
146         } else {
147           nativeModules.add(ExpoAppearanceModule(reactContext))
148         }
149 
150         nativeModules.addAll(SvgPackage().createNativeModules(reactContext))
151         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
152         nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
153         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
154 
155         // Call to create native modules has to be at the bottom --
156         // -- ExpoModuleRegistryAdapter uses the list of native modules
157         // to create Bindings for internal modules.
158         nativeModules.addAll(
159           moduleRegistryAdapter.createNativeModules(
160             scopedContext,
161             experienceKey,
162             experienceProperties,
163             manifest,
164             nativeModules
165           )
166         )
167       } catch (e: JSONException) {
168         EXL.e(TAG, e.toString())
169       } catch (e: UnsupportedEncodingException) {
170         EXL.e(TAG, e.toString())
171       }
172     }
173     return nativeModules
174   }
175 
176   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
177     val viewManagers = mutableListOf<ViewManager<*, *>>()
178 
179     // Add view manager from 3rd party library packages.
180     addViewManagersFromPackages(
181       reactContext,
182       viewManagers,
183       listOf(
184         SvgPackage(),
185         MapsPackage(),
186         LottiePackage(),
187         RNGestureHandlerPackage(),
188         RNScreensPackage(),
189         RNCWebViewPackage(),
190         SafeAreaContextPackage(),
191         RNSharedElementPackage(),
192         RNDateTimePickerPackage(),
193         RNCMaskedViewPackage(),
194         RNCPickerPackage(),
195         ReactSliderPackage(),
196         RNCViewPagerPackage(),
197         ExpoAppearancePackage(),
198         stripePackage
199       )
200     )
201     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
202     return viewManagers
203   }
204 
205   private fun addViewManagersFromPackages(
206     reactContext: ReactApplicationContext,
207     viewManagers: MutableList<ViewManager<*, *>>,
208     packages: List<ReactPackage>
209   ) {
210     for (pack in packages) {
211       viewManagers.addAll(pack.createViewManagers(reactContext))
212     }
213   }
214 
215   private fun createDefaultModuleRegistryAdapterForPackages(
216     packages: List<Package>,
217     singletonModules: List<SingletonModule>?
218   ): ExpoModuleRegistryAdapter {
219     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules))
220   }
221 
222   companion object {
223     private val TAG = ExponentPackage::class.java.simpleName
224 
225     private val singletonModules = mutableListOf<SingletonModule>()
226     private val singletonModulesClasses = mutableSetOf<Class<*>>()
227 
228     // Need to avoid initializing 2 StripeSdkPackages
229     private val stripePackage = StripeSdkPackage()
230 
231     fun kernelExponentPackage(
232       context: Context,
233       manifest: Manifest,
234       expoPackages: List<Package>,
235       initialURL: String?
236     ): ExponentPackage {
237       val kernelExperienceProperties = mutableMapOf(
238         KernelConstants.LINKING_URI_KEY to "exp://",
239         KernelConstants.IS_HEADLESS_KEY to false
240       ).apply {
241         if (initialURL != null) {
242           this[KernelConstants.INTENT_URI_KEY] = initialURL
243         }
244       }
245       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
246       return ExponentPackage(
247         true,
248         kernelExperienceProperties,
249         manifest,
250         expoPackages,
251         singletonModules
252       )
253     }
254 
255     fun getOrCreateSingletonModules(
256       context: Context?,
257       manifest: Manifest?,
258       providedExpoPackages: List<Package>?
259     ): List<SingletonModule> {
260       if (Looper.getMainLooper() != Looper.myLooper()) {
261         throw RuntimeException("Singleton modules must be created on the main thread.")
262       }
263 
264       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
265 
266       for (expoPackage in expoPackages) {
267         // For now we just accumulate more and more singleton modules,
268         // but in fact we should only return singleton modules from the requested
269         // unimodules. This solution also unnecessarily creates singleton modules
270         // which are going to be deallocated in a tick, but there's no better solution
271         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
272         // is TaskService which is safe to initialize more than once.
273         val packageSingletonModules = expoPackage.createSingletonModules(context)
274         for (singletonModule in packageSingletonModules) {
275           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
276             singletonModules.add(singletonModule)
277             singletonModulesClasses.add(singletonModule.javaClass)
278           }
279         }
280       }
281 
282       return singletonModules
283     }
284   }
285 }
286