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.kotlin.ModulesProvider
14 import expo.modules.random.RandomModule
15 import expo.modules.manifests.core.Manifest
16 import host.exp.exponent.Constants
17 import host.exp.exponent.analytics.EXL
18 import host.exp.exponent.kernel.ExperienceKey
19 // WHEN_VERSIONING_REMOVE_FROM_HERE
20 import host.exp.exponent.kernel.ExponentKernelModuleProvider
21 // WHEN_VERSIONING_REMOVE_TO_HERE
22 import host.exp.exponent.kernel.KernelConstants
23 import host.exp.exponent.utils.ScopedContext
24 import org.json.JSONException
25 import versioned.host.exp.exponent.modules.api.*
26 import versioned.host.exp.exponent.modules.api.appearance.ExpoAppearanceModule
27 import versioned.host.exp.exponent.modules.api.appearance.ExpoAppearancePackage
28 import versioned.host.exp.exponent.modules.api.appearance.rncappearance.RNCAppearanceModule
29 import versioned.host.exp.exponent.modules.api.cognito.RNAWSCognitoModule
30 import versioned.host.exp.exponent.modules.api.components.datetimepicker.RNDateTimePickerPackage
31 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerModule
32 import versioned.host.exp.exponent.modules.api.components.gesturehandler.react.RNGestureHandlerPackage
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.slider.ReactSliderPackage
41 import versioned.host.exp.exponent.modules.api.components.svg.SvgPackage
42 import versioned.host.exp.exponent.modules.api.components.pagerview.PagerViewPackage
43 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewModule
44 import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewPackage
45 import versioned.host.exp.exponent.modules.api.netinfo.NetInfoModule
46 import versioned.host.exp.exponent.modules.api.notifications.NotificationsModule
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, 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 
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(), manifest.getEASProjectID()))
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(RNCWebViewModule(reactContext))
137         nativeModules.add(NetInfoModule(reactContext))
138         nativeModules.add(RNSharedElementModule(reactContext))
139 
140         // @tsapeta: Using ExpoAppearanceModule in home app causes some issues with the dev menu,
141         // when home's setting is set to automatic and the system theme is different
142         // than this supported by the experience in which we opened the dev menu.
143         if (isKernel) {
144           nativeModules.add(RNCAppearanceModule(reactContext))
145         } else {
146           nativeModules.add(ExpoAppearanceModule(reactContext))
147         }
148 
149         nativeModules.addAll(SvgPackage().createNativeModules(reactContext))
150         nativeModules.addAll(MapsPackage().createNativeModules(reactContext))
151         nativeModules.addAll(RNDateTimePickerPackage().createNativeModules(reactContext))
152         nativeModules.addAll(stripePackage.createNativeModules(reactContext))
153 
154         // Call to create native modules has to be at the bottom --
155         // -- ExpoModuleRegistryAdapter uses the list of native modules
156         // to create Bindings for internal modules.
157         nativeModules.addAll(
158           moduleRegistryAdapter.createNativeModules(
159             scopedContext,
160             experienceKey,
161             experienceProperties,
162             manifest,
163             nativeModules
164           )
165         )
166       } catch (e: JSONException) {
167         EXL.e(TAG, e.toString())
168       } catch (e: UnsupportedEncodingException) {
169         EXL.e(TAG, e.toString())
170       }
171     }
172     return nativeModules
173   }
174 
175   override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
176     val viewManagers = mutableListOf<ViewManager<*, *>>()
177 
178     // Add view manager from 3rd party library packages.
179     addViewManagersFromPackages(
180       reactContext,
181       viewManagers,
182       listOf(
183         SvgPackage(),
184         MapsPackage(),
185         LottiePackage(),
186         RNGestureHandlerPackage(),
187         RNScreensPackage(),
188         RNCWebViewPackage(),
189         SafeAreaContextPackage(),
190         RNSharedElementPackage(),
191         RNDateTimePickerPackage(),
192         RNCMaskedViewPackage(),
193         RNCPickerPackage(),
194         ReactSliderPackage(),
195         PagerViewPackage(),
196         ExpoAppearancePackage(),
197         stripePackage
198       )
199     )
200     viewManagers.addAll(moduleRegistryAdapter.createViewManagers(reactContext))
201     return viewManagers
202   }
203 
204   private fun addViewManagersFromPackages(
205     reactContext: ReactApplicationContext,
206     viewManagers: MutableList<ViewManager<*, *>>,
207     packages: List<ReactPackage>
208   ) {
209     for (pack in packages) {
210       viewManagers.addAll(pack.createViewManagers(reactContext))
211     }
212   }
213 
214   private fun createDefaultModuleRegistryAdapterForPackages(
215     packages: List<Package>,
216     singletonModules: List<SingletonModule>?,
217     modulesProvider: ModulesProvider? = null
218   ): ExpoModuleRegistryAdapter {
219     return ExpoModuleRegistryAdapter(ReactModuleRegistryProvider(packages, singletonModules), modulesProvider)
220   }
221 
222   companion object {
223     private val TAG = ExponentPackage::class.java.simpleName
224 
225     private val singletonModules = mutableListOf<SingletonModule>()
226     private val singletonModulesClasses = mutableSetOf<Class<*>>()
227 
228     // Need to avoid initializing 2 StripeSdkPackages
229     private val stripePackage = StripeSdkPackage()
230 
231     fun kernelExponentPackage(
232       context: Context,
233       manifest: Manifest,
234       expoPackages: List<Package>,
235       initialURL: String?
236     ): ExponentPackage {
237       val kernelExperienceProperties = mutableMapOf(
238         KernelConstants.LINKING_URI_KEY to "exp://",
239         KernelConstants.IS_HEADLESS_KEY to false
240       ).apply {
241         if (initialURL != null) {
242           this[KernelConstants.INTENT_URI_KEY] = initialURL
243         }
244       }
245       val singletonModules = getOrCreateSingletonModules(context, manifest, expoPackages)
246       return ExponentPackage(
247         true,
248         kernelExperienceProperties,
249         manifest,
250         expoPackages,
251         singletonModules
252       )
253     }
254 
255     fun getOrCreateSingletonModules(
256       context: Context?,
257       manifest: Manifest?,
258       providedExpoPackages: List<Package>?
259     ): List<SingletonModule> {
260       if (Looper.getMainLooper() != Looper.myLooper()) {
261         throw RuntimeException("Singleton modules must be created on the main thread.")
262       }
263 
264       val expoPackages = providedExpoPackages ?: ExperiencePackagePicker.packages(manifest)
265 
266       for (expoPackage in expoPackages) {
267         // For now we just accumulate more and more singleton modules,
268         // but in fact we should only return singleton modules from the requested
269         // unimodules. This solution also unnecessarily creates singleton modules
270         // which are going to be deallocated in a tick, but there's no better solution
271         // without a bigger-than-minimal refactor. In SDK32 the only singleton module
272         // is TaskService which is safe to initialize more than once.
273         val packageSingletonModules = expoPackage.createSingletonModules(context)
274         for (singletonModule in packageSingletonModules) {
275           if (!singletonModulesClasses.contains(singletonModule.javaClass)) {
276             singletonModules.add(singletonModule)
277             singletonModulesClasses.add(singletonModule.javaClass)
278           }
279         }
280       }
281 
282       return singletonModules
283     }
284   }
285 }
286