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