@ionic/portals-react-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE.md +67 -0
  2. package/README.md +192 -0
  3. package/ReactNativePortals.podspec +20 -0
  4. package/android/build.gradle +122 -0
  5. package/android/gradle.properties +4 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsModule.kt +203 -0
  8. package/android/src/main/java/io/ionic/portals/reactnative/ReactNativePortalsPackage.kt +20 -0
  9. package/ios/Podfile +14 -0
  10. package/ios/Podfile.lock +389 -0
  11. package/ios/PortalManager.m +14 -0
  12. package/ios/PortalView.m +14 -0
  13. package/ios/PortalsPubSub.m +16 -0
  14. package/ios/ReactNativePortals-Bridging-Header.h +3 -0
  15. package/ios/ReactNativePortals.swift +113 -0
  16. package/ios/ReactNativePortals.xcodeproj/project.pbxproj +417 -0
  17. package/ios/ReactNativePortals.xcodeproj/xcshareddata/xcschemes/ReactNativePortals.xcscheme +67 -0
  18. package/ios/ReactNativePortals.xcworkspace/contents.xcworkspacedata +10 -0
  19. package/ios/ReactNativePortals.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  20. package/lib/commonjs/PortalView.android.js +37 -0
  21. package/lib/commonjs/PortalView.android.js.map +1 -0
  22. package/lib/commonjs/PortalView.js +22 -0
  23. package/lib/commonjs/PortalView.js.map +1 -0
  24. package/lib/commonjs/index.js +62 -0
  25. package/lib/commonjs/index.js.map +1 -0
  26. package/lib/module/PortalView.android.js +23 -0
  27. package/lib/module/PortalView.android.js.map +1 -0
  28. package/lib/module/PortalView.js +10 -0
  29. package/lib/module/PortalView.js.map +1 -0
  30. package/lib/module/index.js +30 -0
  31. package/lib/module/index.js.map +1 -0
  32. package/lib/typescript/PortalView.android.d.ts +4 -0
  33. package/lib/typescript/PortalView.d.ts +4 -0
  34. package/lib/typescript/index.d.ts +18 -0
  35. package/package.json +133 -0
  36. package/src/PortalView.android.tsx +31 -0
  37. package/src/PortalView.tsx +11 -0
  38. package/src/index.ts +53 -0
package/LICENSE.md ADDED
@@ -0,0 +1,67 @@
1
+ # Ionic Portals License 1.0.0
2
+
3
+ Based on the text of the licenses in the Polyform Project with new account registration conditions.
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it, but only so long as you comply with the Account Registration Feature requirement below. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
12
+
13
+ ## Account Registration Feature
14
+
15
+ You must preserve, and you must not remove or modify, the Account Registration Feature in the software.
16
+
17
+ ## Distribution License
18
+
19
+ The licensor grants you an additional copyright license to distribute copies of the software, but only so long as you comply with the Account Registration Feature requirement above and the Notice requirement below. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
20
+
21
+ ## Notices
22
+
23
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
24
+
25
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
26
+
27
+ ## Changes and New Works License
28
+
29
+ The licensor grants you an additional copyright license to make changes and new works based on the software, but only so long as you comply with the Account Registration Feature requirement above.
30
+
31
+ ## Patent License
32
+
33
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
34
+
35
+ ## Fair Use
36
+
37
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
38
+
39
+ ## No Other Rights
40
+
41
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
42
+
43
+ ## Patent Defense
44
+
45
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
46
+
47
+ ## Violations
48
+
49
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 30 days of receiving notice. Otherwise, all your licenses end immediately.
50
+
51
+ ## No Liability
52
+
53
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
54
+
55
+ ## Definitions
56
+
57
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
58
+
59
+ **You** refers to the individual or entity agreeing to these terms.
60
+
61
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
62
+
63
+ **Your licenses** are all the licenses granted to you for the software under these terms.
64
+
65
+ **Use** means anything you do with the software requiring one of your licenses.
66
+
67
+ **Account Registration Feature** is the portion of the software marked by the licensor as the Account Registration Feature in the software. The Account Registration Feature may be a license key, token, or any other such mechanism designated by the licensor.
package/README.md ADDED
@@ -0,0 +1,192 @@
1
+ <br />
2
+ <div align="center">
3
+ <img src="https://user-images.githubusercontent.com/5769389/134952353-7d7b4145-3a80-4946-9b08-17b3a22c03a1.png" width="560" />
4
+ </div>
5
+ <div align="center">
6
+ ⚡️ A supercharged native Web View for iOS and Android ⚡️
7
+ </div>
8
+ <br />
9
+ <p align="center">
10
+ <a href="https://github.com/ionic-team/react-native-ionic-portals/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ionic-team/ionic-portals/CI?style=flat-square" /></a>
11
+ <a href="https://www.npmjs.com/package/@ionic/react-native-portals"><img src="https://img.shields.io/npm/l/@ionic/react-native-portals?style=flat-square" /></a>
12
+ <a href="https://www.npmjs.com/package/@ionic/react-native-portals"><img src="https://img.shields.io/npm/v/@ionic/react-native-portals?style=flat-square" /></a>
13
+ </p>
14
+ <p align="center">
15
+ <a href="https://ionic.io/docs/portals"><img src="https://img.shields.io/static/v1?label=docs&message=ionic.io/portals&color=blue&style=flat-square" /></a>
16
+ <a href="https://twitter.com/ionicframework"><img src="https://img.shields.io/twitter/follow/ionicframework" /></a>
17
+ </p>
18
+
19
+ ---
20
+
21
+ Ionic Portals is a supercharged native Web View component for iOS and Android that lets you add web-based experiences to native mobile apps. It enables native and web teams to better collaborate and bring new and existing web experiences to mobile in a safe, controlled way.
22
+
23
+ ## Getting Started
24
+
25
+ ### Installation
26
+ `npm install @ionic/portals-react-native`
27
+ or
28
+ `yarn add @ionic/portals-react-native`
29
+
30
+ ### Usage
31
+ Register Portals with your [product key](#Registration):
32
+ ```javascript
33
+ import { register } from '@ionic/portals-react-native';
34
+
35
+ register('YOUR_PORTAL_KEY_HERE');
36
+ ```
37
+
38
+ Create a Portal and add it to the portal registry:
39
+ ```javascript
40
+ import { addPortal } from '@ionic/portals-react-native';
41
+ const helloPortal = {
42
+ // A unique name to reference later
43
+ name: 'hello',
44
+ // This is the location of your web bundle relative to the asset directory in Android and Bundle.main in iOS
45
+ // This will default to the name of the portal
46
+ startDir: 'portals/hello',
47
+ // Any initial state to be provided to a Portal if needed
48
+ initialContext: {
49
+ greeting: 'Hello, world!'
50
+ }
51
+ };
52
+
53
+ addPortal(helloPortal);
54
+ ```
55
+
56
+ Create a PortalView in your view hierarchy:
57
+ ```javascript
58
+ import { PortalView } from '@ionic/portals-react-native';
59
+
60
+ <PortalView
61
+ // The name of the portal to be used in the view
62
+ name='hello'
63
+
64
+ // Set any initial context you may want to override.
65
+ initialContext={{ greeting: 'Goodbye!' }}
66
+
67
+ // Setting a size is required
68
+ style={{ flex: 1, height: 300 }}
69
+ />
70
+ ```
71
+
72
+ #### iOS Specific Configuration
73
+ ##### AppDelegate
74
+ Both Capacitor and React Native have classes named `AppDelegate`. To prevent a clash that can prevent your React Native application from launching,
75
+ you will need to rename `AppDelegate` to something else:
76
+ ```objective-c
77
+ // AppDelegate.h
78
+ @interface RNAppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
79
+ ```
80
+
81
+ ```objective-c
82
+ // AppDelegate.m
83
+ @implementation RNAppDelegate
84
+ @end
85
+ ```
86
+
87
+ ```objective-c
88
+ // main.m
89
+ #import <UIKit/UIKit.h>
90
+
91
+ #import "AppDelegate.h"
92
+
93
+ int main(int argc, char *argv[])
94
+ {
95
+ @autoreleasepool {
96
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([RNAppDelegate class]));
97
+ }
98
+ }
99
+ ```
100
+
101
+ ##### Podfile
102
+ Because many of the Ionic Portals dependencies are comprised of Swift code and have custom module maps, you will need to add `use_frameworks!` to your iOS Podfile and remove `use_flipper!()`
103
+
104
+ ### Communicating between React Native and Web
105
+ One of the key features of Ionic Portals for React Native is facilitating communication between the web and React Native layers of your application.
106
+ Publishing a message to the web:
107
+ ```javascript
108
+ import { publish } from '@ionic/portals-react-native';
109
+
110
+ publish('topic', { number: 1 })
111
+ ```
112
+
113
+ Subscribe to messages from the web:
114
+ ```javascript
115
+ import { subscribe } from '@ionic/portals-react-native';
116
+
117
+ let subscriptionReference = await subscribe('topic', message => {
118
+ // Here you have access to:
119
+ // message.data - Any data sent from the web
120
+ // message.subscriptionRef - The subscription reference used to manage the lifecycle of the subscription
121
+ // message.topic - The topic the message was published on
122
+ })
123
+ ```
124
+
125
+ When you no longer need to receive events, unsubscribe:
126
+ ```javascript
127
+ import { unsubscribe } from '@ionic/portals-react-native';
128
+
129
+ unsubscribe('channel:topic', subscriptionReference)
130
+ ```
131
+
132
+ To see an example of Portals Pub/Sub in action that manages the lifecycle of a subscription with the lifecycle of a React Native component, refer to the [`PubSubLabel`](https://github.com/ionic-team/react-native-ionic-portals/blob/af19df0d66059d85ab8dde493504368c3bf39127/example/App.tsx#L53) implementation in the example project.
133
+
134
+ ### Using Capacitor Plugins
135
+ If you need to use any Capacitor plugins, you will have to register them in your Android project. This will also require creating and registering your Portals in native code as well:
136
+
137
+ **Android**
138
+ ```java
139
+ public class MainApplication extends Application implements ReactApplication {
140
+ @Override
141
+ public void onCreate() {
142
+ super.onCreate();
143
+
144
+ PortalManager.register("YOUR_PORTAL_KEY_HERE");
145
+ PortalManager.newPortal("hello")
146
+ .addPlugin(MyCapacitorPlugin.class) // Plugin registration
147
+ .setInitialContext(Map.of("greeting", "Hello, world!"))
148
+ .setStartDir("portals/hello")
149
+ .create();
150
+ }
151
+ }
152
+ ```
153
+
154
+ **iOS**
155
+ ```objective-c
156
+ @implementation RNAppDelegate
157
+ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDicationary *)launchOptions {
158
+ // React Native boilerplate
159
+ [PortalManager register:@"YOUR_PORTAL_KEY_HERE"];
160
+ PortalBuilder *builder = [[PortalBuilder alloc] init:@"hello"];
161
+ [builder setStartDir:@"portals/hello"];
162
+ [builder setInitialContext: @{ @"greeting": @"Hello, world!" }]
163
+ Portal *portal = [builder create];
164
+ [PortalManager addPortal:portal];
165
+ }
166
+ @end
167
+ ```
168
+
169
+ ### Bundling Your Web Apps
170
+ Currently there is no tooling for bundling your web apps directly as part of @ionic/portals-react-native. Please follow the [native guides](https://ionic.io/docs/portals/how-to/pull-in-web-bundle#setup-the-web-asset-directory) to manage this as part of the native build process.
171
+
172
+
173
+ ## Registration
174
+
175
+ Ionic Portals for React Native requires a key to use. Once you have integrated Portals into your project, login to your ionic account to get a key. See our doc on [how to register for free and get your Portals license key](https://ionic.io/docs/portals/how-to/get-a-product-key) and refer to the [usage](#Usage) section on how to add your key to your React Native application.
176
+
177
+ ## FAQ
178
+
179
+ ### What is the pricing for Portals use?
180
+
181
+ Portals is free to use in non-production environments. Businesses with more than USD $1 million in annual revenue are required to purchase a license from Ionic before using Portals in production.
182
+
183
+ ### Is Portals Open Source?
184
+
185
+ See our [license](https://github.com/ionic-team/ionic-portals/blob/main/LICENSE.md).
186
+
187
+ ### How is Portals Related to Capacitor and Ionic?
188
+
189
+ Ionic Portals is a solution that lets you add web-based experiences to your native mobile apps. Portals uses [Capacitor](https://capacitorjs.com) as a bridge between the native code and the web code to allow for cross-communication between the two layers. Because Portals uses Capacitor under the hood, you are able to use any existing [Capacitor Plugins](https://capacitorjs.com/docs/plugins) and even most [Cordova Plugins](https://capacitorjs.com/docs/plugins/cordova) while continuing to use your existing native workflow. Portals for React Native brings these capabilities to React Native applications.
190
+
191
+ [Ionic Framework](https://ionicframework.com/) is the open-source mobile app development framework that makes it easy to build top quality native and progressive web apps with web technologies. Your web experiences can be developed with Ionic, but it is not necessary to use Portals.
192
+
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "ReactNativePortals"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => "14.0" }
14
+ s.source = { :git => "https://github.com/ionic-team/react-native-ionic-portals.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
17
+
18
+ s.dependency "React-Core"
19
+ s.dependency "IonicPortals", "~> 0.5.1"
20
+ end
@@ -0,0 +1,122 @@
1
+ buildscript {
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
+ def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['ReactNativePortals_kotlinVersion']
4
+
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+
10
+ dependencies {
11
+ classpath 'com.android.tools.build:gradle:7.1.2'
12
+ // noinspection DifferentKotlinGradleVersion
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
+ }
15
+ }
16
+
17
+ apply plugin: 'com.android.library'
18
+ apply plugin: 'kotlin-android'
19
+
20
+ def getExtOrIntegerDefault(name) {
21
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativePortals_' + name]).toInteger()
22
+ }
23
+
24
+ android {
25
+ compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
26
+ defaultConfig {
27
+ minSdkVersion 16
28
+ targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
29
+ versionCode 1
30
+ versionName "1.0"
31
+ }
32
+
33
+ buildTypes {
34
+ release {
35
+ minifyEnabled false
36
+ }
37
+ }
38
+ lintOptions {
39
+ disable 'GradleCompatible'
40
+ }
41
+ compileOptions {
42
+ sourceCompatibility JavaVersion.VERSION_1_8
43
+ targetCompatibility JavaVersion.VERSION_1_8
44
+ }
45
+ }
46
+
47
+ repositories {
48
+ mavenCentral()
49
+ jcenter()
50
+ google()
51
+
52
+ def found = false
53
+ def defaultDir = null
54
+ def androidSourcesName = 'React Native sources'
55
+
56
+ if (rootProject.ext.has('reactNativeAndroidRoot')) {
57
+ defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
58
+ } else {
59
+ defaultDir = new File(
60
+ projectDir,
61
+ '/../../../node_modules/react-native/android'
62
+ )
63
+ }
64
+
65
+ if (defaultDir.exists()) {
66
+ maven {
67
+ url defaultDir.toString()
68
+ name androidSourcesName
69
+ }
70
+
71
+ logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
72
+ found = true
73
+ } else {
74
+ def parentDir = rootProject.projectDir
75
+
76
+ 1.upto(5, {
77
+ if (found) return true
78
+ parentDir = parentDir.parentFile
79
+
80
+ def androidSourcesDir = new File(
81
+ parentDir,
82
+ 'node_modules/react-native'
83
+ )
84
+
85
+ def androidPrebuiltBinaryDir = new File(
86
+ parentDir,
87
+ 'node_modules/react-native/android'
88
+ )
89
+
90
+ if (androidPrebuiltBinaryDir.exists()) {
91
+ maven {
92
+ url androidPrebuiltBinaryDir.toString()
93
+ name androidSourcesName
94
+ }
95
+
96
+ logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
97
+ found = true
98
+ } else if (androidSourcesDir.exists()) {
99
+ maven {
100
+ url androidSourcesDir.toString()
101
+ name androidSourcesName
102
+ }
103
+
104
+ logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
105
+ found = true
106
+ }
107
+ })
108
+ }
109
+
110
+ if (!found) {
111
+ throw new GradleException(
112
+ "${project.name}: unable to locate React Native android sources. " +
113
+ "Ensure you have you installed React Native as a dependency in your project and try again."
114
+ )
115
+ }
116
+ }
117
+
118
+ dependencies {
119
+ // noinspection GradleDynamicVersion
120
+ api 'com.facebook.react:react-native:+'
121
+ implementation "io.ionic:portals:0.5.0"
122
+ }
@@ -0,0 +1,4 @@
1
+ ReactNativePortals_kotlinVersion=1.5.31
2
+ ReactNativePortals_compileSdkVersion=29
3
+ ReactNativePortals_targetSdkVersion=29
4
+ android.useAndroidX=true
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="io.ionic.portals.reactnative">
3
+
4
+ </manifest>
@@ -0,0 +1,203 @@
1
+ package io.ionic.portals.reactnative
2
+
3
+ import android.view.Choreographer
4
+ import android.view.View
5
+ import android.view.ViewGroup
6
+ import android.widget.FrameLayout
7
+ import androidx.fragment.app.FragmentActivity
8
+ import com.facebook.react.bridge.*
9
+ import com.facebook.react.modules.core.DeviceEventManagerModule
10
+ import com.facebook.react.uimanager.ThemedReactContext
11
+ import com.facebook.react.uimanager.ViewGroupManager
12
+ import com.facebook.react.uimanager.annotations.ReactProp
13
+ import com.getcapacitor.JSObject
14
+ import io.ionic.portals.*
15
+ import org.json.JSONArray
16
+ import org.json.JSONException
17
+ import org.json.JSONObject
18
+
19
+ class PortalManagerModule(reactContext: ReactApplicationContext) :
20
+ ReactContextBaseJavaModule(reactContext) {
21
+ override fun getName(): String {
22
+ return "IONPortalManager"
23
+ }
24
+
25
+ @ReactMethod
26
+ fun register(key: String) {
27
+ PortalManager.register(key)
28
+ }
29
+
30
+ @ReactMethod
31
+ fun addPortal(map: ReadableMap) {
32
+ map.getString("name")?.let { name ->
33
+ val startDir = map.getString("startDir")
34
+ val initialContext = map.getMap("initialContext")?.toHashMap()
35
+
36
+ val portalBuilder = PortalBuilder(name)
37
+
38
+ if (startDir != null) {
39
+ portalBuilder.setStartDir(startDir)
40
+ }
41
+
42
+ if (initialContext != null) {
43
+ portalBuilder.setInitialContext(initialContext)
44
+ }
45
+
46
+ // TODO: We need to figure out if we can register plugins from javascript
47
+ val portal = portalBuilder
48
+ .addPlugin(PortalsPlugin::class.java)
49
+ .create()
50
+
51
+ PortalManager.addPortal(portal)
52
+ }
53
+ }
54
+ }
55
+
56
+ class PortalsPubSubModule(reactContext: ReactApplicationContext) :
57
+ ReactContextBaseJavaModule(reactContext) {
58
+ override fun getName(): String {
59
+ return "IONPortalsPubSub"
60
+ }
61
+
62
+ @ReactMethod
63
+ fun subscribe(topic: String, promise: Promise) {
64
+ val reference = PortalsPlugin.subscribe(topic) { result ->
65
+ reactApplicationContext
66
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
67
+ .emit("PortalsSubscription", result.toJSObject().toReactMap())
68
+ }
69
+
70
+ promise.resolve(reference)
71
+ }
72
+
73
+ @ReactMethod
74
+ fun publish(topic: String, data: ReadableMap) {
75
+ PortalsPlugin.publish(topic, data.toJSObject())
76
+ }
77
+
78
+ @ReactMethod
79
+ fun unsubscribe(topic: String, reference: Int) {
80
+ PortalsPlugin.unsubscribe(topic, reference)
81
+ }
82
+
83
+ // These are required to be an EventEmitter in javascript
84
+
85
+ @ReactMethod
86
+ fun addListener(eventName: String) {
87
+ }
88
+
89
+ @ReactMethod
90
+ fun removeListeners(count: Int) {
91
+ }
92
+ }
93
+
94
+ fun JSONObject.toReactMap(): ReadableMap =
95
+ keys().asSequence().fold(WritableNativeMap()) { map, key ->
96
+ try {
97
+ when (val value = get(key)) {
98
+ is JSONObject -> map.putMap(key, value.toReactMap())
99
+ is JSONArray -> map.putArray(key, value.toReactArray())
100
+ is Boolean -> map.putBoolean(key, value)
101
+ is Int -> map.putInt(key, value)
102
+ is Double -> map.putDouble(key, value)
103
+ is String -> map.putString(key, value)
104
+ null -> map.putNull(key)
105
+ else -> map.putString(key, value.toString())
106
+ }
107
+ } catch (_: JSONException) {
108
+ }
109
+
110
+ return@fold map
111
+ }
112
+
113
+ fun JSONArray.toReactArray(): ReadableArray =
114
+ (0 until length()).fold(WritableNativeArray()) { array, index ->
115
+ try {
116
+ when (val value = get(index)) {
117
+ is JSONObject -> array.pushMap(value.toReactMap())
118
+ is JSONArray -> array.pushArray(value.toReactArray())
119
+ is Boolean -> array.pushBoolean(value)
120
+ is Int -> array.pushInt(value)
121
+ is Double -> array.pushDouble(value)
122
+ is String -> array.pushString(value)
123
+ null -> array.pushNull()
124
+ else -> array.pushString(value.toString())
125
+ }
126
+ } catch (_: JSONException) {
127
+ }
128
+
129
+ return@fold array
130
+ }
131
+
132
+ fun ReadableMap.toJSObject(): JSObject = JSObject.fromJSONObject(JSONObject(toHashMap()))
133
+
134
+ class PortalViewManager(private val context: ReactApplicationContext) :
135
+ ViewGroupManager<FrameLayout>() {
136
+ private val createId = 1
137
+ private var portal: Portal? = null
138
+
139
+ @ReactProp(name = "name")
140
+ fun setPortal(viewGroup: ViewGroup, portalName: String) {
141
+ portal = PortalManager.getPortal(portalName)
142
+ }
143
+
144
+ @ReactProp(name = "initialContext")
145
+ fun setInitialContext(viewGroup: ViewGroup, initialContext: ReadableMap) {
146
+ portal?.setInitialContext(initialContext.toHashMap())
147
+ }
148
+
149
+ override fun getName(): String {
150
+ return "AndroidPortalView"
151
+ }
152
+
153
+ override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
154
+ return FrameLayout(reactContext)
155
+ }
156
+
157
+ override fun getCommandsMap(): MutableMap<String, Int>? {
158
+ return mutableMapOf("create" to createId)
159
+ }
160
+
161
+ override fun receiveCommand(root: FrameLayout, commandId: String?, args: ReadableArray?) {
162
+ super.receiveCommand(root, commandId, args)
163
+ val viewId = args?.getInt(0) ?: return
164
+ val commandId = commandId?.toIntOrNull() ?: return
165
+
166
+ when (commandId) {
167
+ createId -> createFragment(root, viewId)
168
+ }
169
+ }
170
+
171
+ private fun createFragment(root: FrameLayout, viewId: Int) {
172
+ val portal = portal ?: return
173
+ val parentView = root.findViewById<ViewGroup>(viewId)
174
+ setupLayout(parentView)
175
+
176
+ val portalFragment = PortalFragment(portal)
177
+ val fragmentActivity = context.currentActivity as? FragmentActivity ?: return
178
+ fragmentActivity.supportFragmentManager
179
+ .beginTransaction()
180
+ .replace(viewId, portalFragment, "$viewId")
181
+ .commit()
182
+ }
183
+
184
+ private fun setupLayout(view: ViewGroup) {
185
+ Choreographer.getInstance().postFrameCallback {
186
+ layoutChildren(view)
187
+ view.viewTreeObserver.dispatchOnGlobalLayout()
188
+ }
189
+ }
190
+
191
+ private fun layoutChildren(view: ViewGroup) {
192
+ for (i in 0 until view.childCount) {
193
+ val child = view.getChildAt(i)
194
+
195
+ child.measure(
196
+ View.MeasureSpec.makeMeasureSpec(view.measuredWidth, View.MeasureSpec.EXACTLY),
197
+ View.MeasureSpec.makeMeasureSpec(view.measuredHeight, View.MeasureSpec.EXACTLY)
198
+ )
199
+
200
+ child.layout(0, 0, child.measuredWidth, child.measuredHeight)
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,20 @@
1
+ package io.ionic.portals.reactnative
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+
9
+ class ReactNativePortalsPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(
12
+ PortalManagerModule(reactContext),
13
+ PortalsPubSubModule(reactContext)
14
+ )
15
+ }
16
+
17
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
18
+ return listOf(PortalViewManager(reactContext))
19
+ }
20
+ }
package/ios/Podfile ADDED
@@ -0,0 +1,14 @@
1
+ require_relative '../node_modules/react-native/scripts/react_native_pods'
2
+ require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3
+
4
+ platform :ios, '14.0'
5
+
6
+ target 'ReactNativePortals' do
7
+ # Comment the next line if you don't want to use dynamic frameworks
8
+ config = use_native_modules!
9
+ use_frameworks!
10
+ use_react_native!(:path => config["reactNativePath"])
11
+ # Pods for ReactNativePortals
12
+ pod 'IonicPortals', '~> 0.5.1'
13
+ end
14
+