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(), experienceProperties))
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             manifest.getStableLegacyID(),
165             nativeModules
166           )
167         )
168       } catch (e: JSONException) {
169         EXL.e(TAG, e.toString())
170       } catch (e: UnsupportedEncodingException) {
171         EXL.e(TAG, e.toString())
172       }
173     }
174     return nativeModules
175   }
176 
177   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
178     val viewManagers = mutableListOf<ViewManager<*, *>>()
179 
180     // Add view manager from 3rd party library packages.
181     addViewManagersFromPackages(
182       reactContext,
183       viewManagers,
184       listOf(
185         SvgPackage(),
186         MapsPackage(),
187         LottiePackage(),
188         RNGestureHandlerPackage(),
189         RNScreensPackage(),
190         RNCWebViewPackage(),
191         SafeAreaContextPackage(),
192         RNSharedElementPackage(),
193         RNDateTimePickerPackage(),
194         RNCMaskedViewPackage(),
195         RNCPickerPackage(),
196         ReactSliderPackage(),
197         RNCViewPagerPackage(),
198         ExpoAppearancePackage(),
199         stripePackage
200       )
201     )
202     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
203     return viewManagers
204   }
205 
206   private fun addViewManagersFromPackages(
207     reactContext: ReactApplicationContext,
208     viewManagers: MutableList<ViewManager<*, *>>,
209     packages: List<ReactPackage>
210   ) {
211     for (pack in packages) {
212       viewManagers.addAll(pack.createViewManagers(reactContext))
213     }
214   }
215 
216   private fun createDefaultModuleRegistryAdapterForPackages(
217     packages: List<Package>,
218     singletonModules: List<SingletonModule>?
219   ): ExpoModuleRegistryAdapter {
220     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules))
221   }
222 
223   companion object {
224     private val TAG = ExponentPackage::class.java.simpleName
225 
226     private val singletonModules = mutableListOf<SingletonModule>()
227     private val singletonModulesClasses = mutableSetOf<Class<*>>()
228 
229     // Need to avoid initializing 2 StripeSdkPackages
230     private val stripePackage = StripeSdkPackage()
231 
232     fun kernelExponentPackage(
233       context: Context,
234       manifest: Manifest,
235       expoPackages: List<Package>,
236       initialURL: String?
237     ): ExponentPackage {
238       val kernelExperienceProperties = mutableMapOf(
239         KernelConstants.LINKING_URI_KEY to "exp://",
240         KernelConstants.IS_HEADLESS_KEY to false
241       ).apply {
242         if (initialURL != null) {
243           this[KernelConstants.INTENT_URI_KEY] = initialURL
244         }
245       }
246       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
247       return ExponentPackage(
248         true,
249         kernelExperienceProperties,
250         manifest,
251         expoPackages,
252         singletonModules
253       )
254     }
255 
256     fun getOrCreateSingletonModules(
257       context: Context?,
258       manifest: Manifest?,
259       providedExpoPackages: List<Package>?
260     ): List<SingletonModule> {
261       if (Looper.getMainLooper() != Looper.myLooper()) {
262         throw RuntimeException("Singleton modules must be created on the main thread.")
263       }
264 
265       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
266 
267       for (expoPackage in expoPackages) {
268         // For now we just accumulate more and more singleton modules,
269         // but in fact we should only return singleton modules from the requested
270         // unimodules. This solution also unnecessarily creates singleton modules
271         // which are going to be deallocated in a tick, but there's no better solution
272         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
273         // is TaskService which is safe to initialize more than once.
274         val packageSingletonModules = expoPackage.createSingletonModules(context)
275         for (singletonModule in packageSingletonModules) {
276           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
277             singletonModules.add(singletonModule)
278             singletonModulesClasses.add(singletonModule.javaClass)
279           }
280         }
281       }
282 
283       return singletonModules
284     }
285   }
286 }
287