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.webview.RNCWebViewModule
41 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewPackage
42 import versioned.host.exp.exponent.modules.api.netinfo.NetInfoModule
43 import versioned.host.exp.exponent.modules.api.notifications.NotificationsModule
44 import versioned.host.exp.exponent.modules.api.safeareacontext.SafeAreaContextPackage
45 import versioned.host.exp.exponent.modules.api.viewshot.RNViewShotModule
46 import versioned.host.exp.exponent.modules.internal.DevMenuModule
47 import versioned.host.exp.exponent.modules.internal.ExponentAsyncStorageModule
48 import versioned.host.exp.exponent.modules.internal.ExponentUnsignedAsyncStorageModule
49 import versioned.host.exp.exponent.modules.test.ExponentTestNativeModule
50 import versioned.host.exp.exponent.modules.universal.ExpoModuleRegistryAdapter
51 import versioned.host.exp.exponent.modules.universal.ScopedModuleRegistryAdapter
52 import java.io.UnsupportedEncodingException
53 
54 // This is an Expo module but not a unimodule
55 class ExponentPackage : ReactPackage {
56   private val isKernel: Boolean
57   private val experienceProperties: Map<String, Any?>
58   private val manifest: Manifest
59   private val moduleRegistryAdapter: ScopedModuleRegistryAdapter
60 
61   private constructor(
62     isKernel: Boolean,
63     experienceProperties: Map<String, Any?>,
64     manifest: Manifest,
65     expoPackages: List<Package>,
66     singletonModules: List<SingletonModule>?
67   ) {
68     this.isKernel = isKernel
69     this.experienceProperties = experienceProperties
70     this.manifest = manifest
71     moduleRegistryAdapter = createDefaultModuleRegistryAdapterForPackages(expoPackages, singletonModules)
72   }
73 
74   constructor(
75     experienceProperties: Map<String, Any?>,
76     manifest: Manifest,
77     expoPackages: List<Package>?,
78     delegate: ExponentPackageDelegate?,
79     singletonModules: List<SingletonModule>
80   ) {
81     isKernel = false
82     this.experienceProperties = experienceProperties
83     this.manifest = manifest
84     val packages = expoPackages ?: ExperiencePackagePicker.packages(manifest)
85     // Delegate may not be null only when the app is detached
86     moduleRegistryAdapter = createModuleRegistryAdapter(delegate, singletonModules, packages)
87   }
88 
89   private fun createModuleRegistryAdapter(
90     delegate: ExponentPackageDelegate?,
91     singletonModules: List<SingletonModule>,
92     packages: List<Package>
93   ): ScopedModuleRegistryAdapter {
94     var registryAdapter: ScopedModuleRegistryAdapter? = null
95     if (delegate != null) {
96       registryAdapter = delegate.getScopedModuleRegistryAdapterForPackages(packages, singletonModules)
97     }
98     if (registryAdapter == null) {
99       registryAdapter = createDefaultModuleRegistryAdapterForPackages(packages, singletonModules, ExperiencePackagePicker)
100     }
101     return registryAdapter
102   }
103 
104   override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
105     val isVerified = manifest.isVerified() ?: false
106     val nativeModules: MutableList<NativeModule> = mutableListOf(
107       URLHandlerModule(reactContext),
108       ShakeModule(reactContext),
109       KeyboardModule(reactContext)
110     )
111     nativeModules.add(if (isVerified) ExponentAsyncStorageModule(reactContext, manifest) else ExponentUnsignedAsyncStorageModule(reactContext))
112 
113     if (isKernel) {
114       // WHEN_VERSIONING_REMOVE_FROM_HERE
115       nativeModules.add((ExponentKernelModuleProvider.newInstance(reactContext) as NativeModule?)!!)
116       // WHEN_VERSIONING_REMOVE_TO_HERE
117     }
118     if (!isKernel && !Constants.isStandaloneApp()) {
119       // We need DevMenuModule only in non-home and non-standalone apps.
120       nativeModules.add(DevMenuModule(reactContext, experienceProperties, manifest))
121     }
122 
123     if (isVerified) {
124       try {
125         val experienceKey = ExperienceKey.fromManifest(manifest)
126         val scopedContext = ScopedContext(reactContext, experienceKey)
127         nativeModules.add(NotificationsModule(reactContext, experienceKey, manifest.getStableLegacyID(), manifest.getEASProjectID()))
128         nativeModules.add(RNViewShotModule(reactContext, scopedContext))
129         nativeModules.add(ExponentTestNativeModule(reactContext))
130         nativeModules.add(PedometerModule(reactContext))
131         nativeModules.add(ScreenOrientationModule(reactContext))
132         nativeModules.add(RNGestureHandlerModule(reactContext))
133         nativeModules.add(RNAWSCognitoModule(reactContext))
134         nativeModules.add(RNCWebViewModule(reactContext))
135         nativeModules.add(NetInfoModule(reactContext))
136         nativeModules.addAll(SvgPackage().getNativeModuleIterator(reactContext).map { it.module })
137         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
138         nativeModules.addAll(RNDateTimePickerPackage().getNativeModuleIterator(reactContext).map { it.module })
139         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
140         nativeModules.addAll(skiaPackage.createNativeModules(reactContext))
141 
142         // Call to create native modules has to be at the bottom --
143         // -- ExpoModuleRegistryAdapter uses the list of native modules
144         // to create Bindings for internal modules.
145         nativeModules.addAll(
146           moduleRegistryAdapter.createNativeModules(
147             scopedContext,
148             experienceKey,
149             experienceProperties,
150             manifest,
151             nativeModules
152           )
153         )
154       } catch (e: JSONException) {
155         EXL.e(TAG, e.toString())
156       } catch (e: UnsupportedEncodingException) {
157         EXL.e(TAG, e.toString())
158       }
159     }
160     return nativeModules
161   }
162 
163   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
164     val viewManagers = mutableListOf<ViewManager<*, *>>()
165 
166     // Add view manager from 3rd party library packages.
167     addViewManagersFromPackages(
168       reactContext,
169       viewManagers,
170       listOf(
171         SvgPackage(),
172         MapsPackage(),
173         LottiePackage(),
174         RNGestureHandlerPackage(),
175         RNScreensPackage(),
176         RNCWebViewPackage(),
177         SafeAreaContextPackage(),
178         RNDateTimePickerPackage(),
179         RNCMaskedViewPackage(),
180         RNCPickerPackage(),
181         ReactSliderPackage(),
182         PagerViewPackage(),
183         stripePackage,
184         skiaPackage,
185         ReactNativeFlashListPackage()
186       )
187     )
188     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
189     return viewManagers
190   }
191 
192   private fun addViewManagersFromPackages(
193     reactContext: ReactApplicationContext,
194     viewManagers: MutableList<ViewManager<*, *>>,
195     packages: List<ReactPackage>
196   ) {
197     for (pack in packages) {
198       viewManagers.addAll(pack.createViewManagers(reactContext))
199     }
200   }
201 
202   private fun createDefaultModuleRegistryAdapterForPackages(
203     packages: List<Package>,
204     singletonModules: List<SingletonModule>?,
205     modulesProvider: ModulesProvider? = null
206   ): ExpoModuleRegistryAdapter {
207     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules), modulesProvider)
208   }
209 
210   companion object {
211     private val TAG = ExponentPackage::class.java.simpleName
212 
213     private val singletonModules = mutableListOf<SingletonModule>()
214     private val singletonModulesClasses = mutableSetOf<Class<*>>()
215 
216     // Need to avoid initializing duplicated packages
217     private val stripePackage = StripeSdkPackage()
218     private val skiaPackage = RNSkiaPackage()
219 
220     fun kernelExponentPackage(
221       context: Context,
222       manifest: Manifest,
223       expoPackages: List<Package>,
224       initialURL: String?
225     ): ExponentPackage {
226       val kernelExperienceProperties = mutableMapOf(
227         KernelConstants.LINKING_URI_KEY to "exp://",
228         KernelConstants.IS_HEADLESS_KEY to false
229       ).apply {
230         if (initialURL != null) {
231           this[KernelConstants.INTENT_URI_KEY] = initialURL
232         }
233       }
234       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
235       return ExponentPackage(
236         true,
237         kernelExperienceProperties,
238         manifest,
239         expoPackages,
240         singletonModules
241       )
242     }
243 
244     fun getOrCreateSingletonModules(
245       context: Context?,
246       manifest: Manifest?,
247       providedExpoPackages: List<Package>?
248     ): List<SingletonModule> {
249       if (Looper.getMainLooper() != Looper.myLooper()) {
250         throw RuntimeException("Singleton modules must be created on the main thread.")
251       }
252 
253       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
254 
255       for (expoPackage in expoPackages) {
256         // For now we just accumulate more and more singleton modules,
257         // but in fact we should only return singleton modules from the requested
258         // unimodules. This solution also unnecessarily creates singleton modules
259         // which are going to be deallocated in a tick, but there's no better solution
260         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
261         // is TaskService which is safe to initialize more than once.
262         val packageSingletonModules = expoPackage.createSingletonModules(context)
263         for (singletonModule in packageSingletonModules) {
264           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
265             singletonModules.add(singletonModule)
266             singletonModulesClasses.add(singletonModule.javaClass)
267           }
268         }
269       }
270 
271       return singletonModules
272     }
273   }
274 }
275