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.updates.manifest.raw.RawManifest
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 import java.util.*
56 
57 // This is an Expo module but not a unimodule
58 class ExponentPackage : ReactPackage {
59   private val isKernel: Boolean
60   private val experienceProperties: Map<String, Any?>
61   private val manifest: RawManifest
62   private val moduleRegistryAdapter: ScopedModuleRegistryAdapter
63 
64   private constructor(
65     isKernel: Boolean,
66     experienceProperties: Map<String, Any?>,
67     manifest: RawManifest,
68     expoPackages: List<Package>,
69     singletonModules: List<SingletonModule>?
70   ) {
71     this.isKernel = isKernel
72     this.experienceProperties = experienceProperties
73     this.manifest = manifest
74     moduleRegistryAdapter = createDefaultModuleRegistryAdapterForPackages(expoPackages, singletonModules)
75   }
76 
77   constructor(
78     experienceProperties: Map<String, Any?>,
79     manifest: RawManifest,
80     expoPackages: List<Package>?,
81     delegate: ExponentPackageDelegate?,
82     singletonModules: List<SingletonModule>
83   ) {
84     isKernel = false
85     this.experienceProperties = experienceProperties
86     this.manifest = manifest
87     val packages = expoPackages ?: ExperiencePackagePicker.packages(manifest)
88     // Delegate may not be null only when the app is detached
89     moduleRegistryAdapter = createModuleRegistryAdapter(delegate, singletonModules, packages)
90   }
91 
92   private fun createModuleRegistryAdapter(
93     delegate: ExponentPackageDelegate?,
94     singletonModules: List<SingletonModule>,
95     packages: List<Package>
96   ): ScopedModuleRegistryAdapter {
97     var registryAdapter: ScopedModuleRegistryAdapter? = null
98     if (delegate != null) {
99       registryAdapter = delegate.getScopedModuleRegistryAdapterForPackages(packages, singletonModules)
100     }
101     if (registryAdapter == null) {
102       registryAdapter = createDefaultModuleRegistryAdapterForPackages(packages, singletonModules)
103     }
104     return registryAdapter
105   }
106 
107   override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
108     val isVerified = manifest.isVerified() ?: false
109     val nativeModules: MutableList<NativeModule> = mutableListOf(
110       URLHandlerModule(reactContext),
111       ShakeModule(reactContext),
112       KeyboardModule(reactContext)
113     )
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.fromRawManifest(manifest)
128         val scopedContext = ScopedContext(reactContext, experienceKey)
129         nativeModules.add(NotificationsModule(reactContext, experienceKey, manifest.getStableLegacyID(), experienceProperties))
130         nativeModules.add(RNViewShotModule(reactContext, scopedContext))
131         nativeModules.add(RandomModule(reactContext))
132         nativeModules.add(ExponentTestNativeModule(reactContext))
133         nativeModules.add(PedometerModule(reactContext))
134         nativeModules.add(ScreenOrientationModule(reactContext))
135         nativeModules.add(RNGestureHandlerModule(reactContext))
136         nativeModules.add(RNAWSCognitoModule(reactContext))
137         nativeModules.add(ReanimatedModule(reactContext))
138         nativeModules.add(RNCWebViewModule(reactContext))
139         nativeModules.add(NetInfoModule(reactContext))
140         nativeModules.add(RNSharedElementModule(reactContext))
141 
142         // @tsapeta: Using ExpoAppearanceModule in home app causes some issues with the dev menu,
143         // when home's setting is set to automatic and the system theme is different
144         // than this supported by the experience in which we opened the dev menu.
145         if (isKernel) {
146           nativeModules.add(RNCAppearanceModule(reactContext))
147         } else {
148           nativeModules.add(ExpoAppearanceModule(reactContext))
149         }
150 
151         nativeModules.addAll(SvgPackage().createNativeModules(reactContext))
152         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
153         nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
154         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
155 
156         // Call to create native modules has to be at the bottom --
157         // -- ExpoModuleRegistryAdapter uses the list of native modules
158         // to create Bindings for internal modules.
159         nativeModules.addAll(
160           moduleRegistryAdapter.createNativeModules(
161             scopedContext,
162             experienceKey,
163             experienceProperties,
164             manifest,
165             manifest.getStableLegacyID(),
166             nativeModules
167           )
168         )
169       } catch (e: JSONException) {
170         EXL.e(TAG, e.toString())
171       } catch (e: UnsupportedEncodingException) {
172         EXL.e(TAG, e.toString())
173       }
174     }
175     return nativeModules
176   }
177 
178   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
179     val viewManagers = mutableListOf<ViewManager<*, *>>()
180 
181     // Add view manager from 3rd party library packages.
182     addViewManagersFromPackages(
183       reactContext,
184       viewManagers,
185       listOf(
186         SvgPackage(),
187         MapsPackage(),
188         LottiePackage(),
189         RNGestureHandlerPackage(),
190         RNScreensPackage(),
191         RNCWebViewPackage(),
192         SafeAreaContextPackage(),
193         RNSharedElementPackage(),
194         RNDateTimePickerPackage(),
195         RNCMaskedViewPackage(),
196         RNCPickerPackage(),
197         ReactSliderPackage(),
198         RNCViewPagerPackage(),
199         ExpoAppearancePackage(),
200         stripePackage
201       )
202     )
203     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
204     return viewManagers
205   }
206 
207   private fun addViewManagersFromPackages(
208     reactContext: ReactApplicationContext,
209     viewManagers: MutableList<ViewManager<*, *>>,
210     packages: List<ReactPackage>
211   ) {
212     for (pack in packages) {
213       viewManagers.addAll(pack.createViewManagers(reactContext))
214     }
215   }
216 
217   private fun createDefaultModuleRegistryAdapterForPackages(
218     packages: List<Package>,
219     singletonModules: List<SingletonModule>?
220   ): ExpoModuleRegistryAdapter {
221     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules))
222   }
223 
224   companion object {
225     private val TAG = ExponentPackage::class.java.simpleName
226 
227     private val singletonModules = mutableListOf<SingletonModule>()
228     private val singletonModulesClasses = mutableSetOf<Class<*>>()
229 
230     // Need to avoid initializing 2 StripeSdkPackages
231     private val stripePackage = StripeSdkPackage()
232 
233     fun kernelExponentPackage(
234       context: Context,
235       manifest: RawManifest,
236       expoPackages: List<Package>,
237       initialURL: String?
238     ): ExponentPackage {
239       val kernelExperienceProperties = mutableMapOf(
240         KernelConstants.LINKING_URI_KEY to "exp://",
241         KernelConstants.IS_HEADLESS_KEY to false
242       ).apply {
243         if (initialURL != null) {
244           this[KernelConstants.INTENT_URI_KEY] = initialURL
245         }
246       }
247       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
248       return ExponentPackage(
249         true,
250         kernelExperienceProperties,
251         manifest,
252         expoPackages,
253         singletonModules
254       )
255     }
256 
257     fun getOrCreateSingletonModules(
258       context: Context?,
259       manifest: RawManifest?,
260       providedExpoPackages: List<Package>?
261     ): List<SingletonModule> {
262       if (Looper.getMainLooper() != Looper.myLooper()) {
263         throw RuntimeException("Singleton modules must be created on the main thread.")
264       }
265 
266       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
267 
268       for (expoPackage in expoPackages) {
269         // For now we just accumulate more and more singleton modules,
270         // but in fact we should only return singleton modules from the requested
271         // unimodules. This solution also unnecessarily creates singleton modules
272         // which are going to be deallocated in a tick, but there's no better solution
273         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
274         // is TaskService which is safe to initialize more than once.
275         val packageSingletonModules = expoPackage.createSingletonModules(context)
276         for (singletonModule in packageSingletonModules) {
277           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
278             singletonModules.add(singletonModule)
279             singletonModulesClasses.add(singletonModule.javaClass)
280           }
281         }
282       }
283 
284       return singletonModules
285     }
286   }
287 }
288