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 com.reactnativecommunity.slider.ReactSliderPackage
11 import com.horcrux.svg.SvgPackage
12 import com.reactnativepagerview.PagerViewPackage
13 import com.shopify.reactnative.flash_list.ReactNativeFlashListPackage
14 import com.shopify.reactnative.skia.RNSkiaPackage
15 import com.swmansion.rnscreens.RNScreensPackage
16 import com.swmansion.gesturehandler.RNGestureHandlerPackage
17 import com.swmansion.gesturehandler.react.RNGestureHandlerModule
18 import expo.modules.adapters.react.ReactModuleRegistryProvider
19 import expo.modules.core.interfaces.Package
20 import expo.modules.core.interfaces.SingletonModule
21 import expo.modules.kotlin.ModulesProvider
22 import expo.modules.manifests.core.Manifest
23 import host.exp.exponent.Constants
24 import host.exp.exponent.analytics.EXL
25 import host.exp.exponent.kernel.ExperienceKey
26 // WHEN_VERSIONING_REMOVE_FROM_HERE
27 import host.exp.exponent.kernel.ExponentKernelModuleProvider
28 // WHEN_VERSIONING_REMOVE_TO_HERE
29 import host.exp.exponent.kernel.KernelConstants
30 import host.exp.exponent.utils.ScopedContext
31 import org.json.JSONException
32 import versioned.host.exp.exponent.modules.api.*
33 import versioned.host.exp.exponent.modules.api.cognito.RNAWSCognitoModule
34 import versioned.host.exp.exponent.modules.api.components.datetimepicker.RNDateTimePickerPackage
35 import versioned.host.exp.exponent.modules.api.components.lottie.LottiePackage
36 import versioned.host.exp.exponent.modules.api.components.maps.MapsPackage
37 import versioned.host.exp.exponent.modules.api.components.maskedview.RNCMaskedViewPackage
38 import versioned.host.exp.exponent.modules.api.components.picker.RNCPickerPackage
39 import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.StripeSdkPackage
40 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementModule
41 import versioned.host.exp.exponent.modules.api.components.sharedelement.RNSharedElementPackage
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.safeareacontext.SafeAreaContextPackage
47 import versioned.host.exp.exponent.modules.api.viewshot.RNViewShotModule
48 import versioned.host.exp.exponent.modules.internal.DevMenuModule
49 import versioned.host.exp.exponent.modules.internal.ExponentAsyncStorageModule
50 import versioned.host.exp.exponent.modules.internal.ExponentUnsignedAsyncStorageModule
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, ExperiencePackagePicker)
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     nativeModules.add(if (isVerified) ExponentAsyncStorageModule(reactContext, manifest) else ExponentUnsignedAsyncStorageModule(reactContext))
114 
115     if (isKernel) {
116       // WHEN_VERSIONING_REMOVE_FROM_HERE
117       nativeModules.add((ExponentKernelModuleProvider.newInstance(reactContext) as NativeModule?)!!)
118       // WHEN_VERSIONING_REMOVE_TO_HERE
119     }
120     if (!isKernel && !Constants.isStandaloneApp()) {
121       // We need DevMenuModule only in non-home and non-standalone apps.
122       nativeModules.add(DevMenuModule(reactContext, experienceProperties, manifest))
123     }
124 
125     if (isVerified) {
126       try {
127         val experienceKey = ExperienceKey.fromManifest(manifest)
128         val scopedContext = ScopedContext(reactContext, experienceKey)
129         nativeModules.add(NotificationsModule(reactContext, experienceKey, manifest.getStableLegacyID(), manifest.getEASProjectID()))
130         nativeModules.add(RNViewShotModule(reactContext, scopedContext))
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(RNCWebViewModule(reactContext))
137         nativeModules.add(NetInfoModule(reactContext))
138         nativeModules.add(RNSharedElementModule(reactContext))
139         nativeModules.addAll(SvgPackage().createNativeModules(reactContext))
140         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
141         nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
142         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
143         nativeModules.addAll(skiaPackage.createNativeModules(reactContext))
144 
145         // Call to create native modules has to be at the bottom --
146         // -- ExpoModuleRegistryAdapter uses the list of native modules
147         // to create Bindings for internal modules.
148         nativeModules.addAll(
149           moduleRegistryAdapter.createNativeModules(
150             scopedContext,
151             experienceKey,
152             experienceProperties,
153             manifest,
154             nativeModules
155           )
156         )
157       } catch (e: JSONException) {
158         EXL.e(TAG, e.toString())
159       } catch (e: UnsupportedEncodingException) {
160         EXL.e(TAG, e.toString())
161       }
162     }
163     return nativeModules
164   }
165 
166   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
167     val viewManagers = mutableListOf<ViewManager<*, *>>()
168 
169     // Add view manager from 3rd party library packages.
170     addViewManagersFromPackages(
171       reactContext,
172       viewManagers,
173       listOf(
174         SvgPackage(),
175         MapsPackage(),
176         LottiePackage(),
177         RNGestureHandlerPackage(),
178         RNScreensPackage(),
179         RNCWebViewPackage(),
180         SafeAreaContextPackage(),
181         RNSharedElementPackage(),
182         RNDateTimePickerPackage(),
183         RNCMaskedViewPackage(),
184         RNCPickerPackage(),
185         ReactSliderPackage(),
186         PagerViewPackage(),
187         stripePackage,
188         skiaPackage,
189         ReactNativeFlashListPackage()
190       )
191     )
192     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
193     return viewManagers
194   }
195 
196   private fun addViewManagersFromPackages(
197     reactContext: ReactApplicationContext,
198     viewManagers: MutableList<ViewManager<*, *>>,
199     packages: List<ReactPackage>
200   ) {
201     for (pack in packages) {
202       viewManagers.addAll(pack.createViewManagers(reactContext))
203     }
204   }
205 
206   private fun createDefaultModuleRegistryAdapterForPackages(
207     packages: List<Package>,
208     singletonModules: List<SingletonModule>?,
209     modulesProvider: ModulesProvider? = null
210   ): ExpoModuleRegistryAdapter {
211     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules), modulesProvider)
212   }
213 
214   companion object {
215     private val TAG = ExponentPackage::class.java.simpleName
216 
217     private val singletonModules = mutableListOf<SingletonModule>()
218     private val singletonModulesClasses = mutableSetOf<Class<*>>()
219 
220     // Need to avoid initializing duplicated packages
221     private val stripePackage = StripeSdkPackage()
222     private val skiaPackage = RNSkiaPackage()
223 
224     fun kernelExponentPackage(
225       context: Context,
226       manifest: Manifest,
227       expoPackages: List<Package>,
228       initialURL: String?
229     ): ExponentPackage {
230       val kernelExperienceProperties = mutableMapOf(
231         KernelConstants.LINKING_URI_KEY to "exp://",
232         KernelConstants.IS_HEADLESS_KEY to false
233       ).apply {
234         if (initialURL != null) {
235           this[KernelConstants.INTENT_URI_KEY] = initialURL
236         }
237       }
238       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
239       return ExponentPackage(
240         true,
241         kernelExperienceProperties,
242         manifest,
243         expoPackages,
244         singletonModules
245       )
246     }
247 
248     fun getOrCreateSingletonModules(
249       context: Context?,
250       manifest: Manifest?,
251       providedExpoPackages: List<Package>?
252     ): List<SingletonModule> {
253       if (Looper.getMainLooper() != Looper.myLooper()) {
254         throw RuntimeException("Singleton modules must be created on the main thread.")
255       }
256 
257       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
258 
259       for (expoPackage in expoPackages) {
260         // For now we just accumulate more and more singleton modules,
261         // but in fact we should only return singleton modules from the requested
262         // unimodules. This solution also unnecessarily creates singleton modules
263         // which are going to be deallocated in a tick, but there's no better solution
264         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
265         // is TaskService which is safe to initialize more than once.
266         val packageSingletonModules = expoPackage.createSingletonModules(context)
267         for (singletonModule in packageSingletonModules) {
268           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
269             singletonModules.add(singletonModule)
270             singletonModulesClasses.add(singletonModule.javaClass)
271           }
272         }
273       }
274 
275       return singletonModules
276     }
277   }
278 }
279