@callstack/react-native-brownfield 1.1.0 → 2.0.0
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.
- package/README.md +153 -34
- package/android/build.gradle +13 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactDelegateWrapper.kt +36 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +129 -102
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt +10 -11
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeFragment.kt +113 -143
- package/android/src/main/java/com/callstack/reactnativebrownfield/constants/ReactNativeFragmentArgNames.kt +20 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/utils/VersionUtils.kt +17 -0
- package/android/src/newarch/ReactNativeBrownfieldModule.kt +24 -24
- package/ios/ReactNativeBrownfield.swift +1 -1
- package/ios/ReactNativeView.swift +4 -1
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</a>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
|
|
6
|
+
A set of helpers to make your brownfield integration smooth and easy.
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -21,86 +21,204 @@
|
|
|
21
21
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
|
-
- **Easily integrate** React Native with existing native app
|
|
24
|
+
- **Easily integrate** React Native with an existing native app
|
|
25
25
|
- Start React Native with **one method** and invoke code as soon as it's loaded
|
|
26
|
-
- Compatible with **both
|
|
26
|
+
- Compatible with **both legacy and new React Native architecture**!
|
|
27
27
|
- Reuse the same instance of React Native between different components
|
|
28
28
|
- Use predefined **native building blocks** - crafted for React Native
|
|
29
29
|
- Disable and enable **native gestures and hardware buttons** from JavaScript
|
|
30
|
-
- Works well with **any native navigation** pattern, as well as
|
|
30
|
+
- Works well with **any native navigation** pattern, as well as any React Native JavaScript-based navigation
|
|
31
31
|
- Compatible with all native languages **Objective-C**, **Swift**, **Java** and **Kotlin**
|
|
32
32
|
- Supports UIKit and SwiftUI on iOS and Fragments and Jetpack Compose on Android
|
|
33
33
|
|
|
34
|
+
## React Native version compatibility matrix
|
|
35
|
+
|
|
36
|
+
| Tested React Native Version | React Native Brownfield Version |
|
|
37
|
+
| --------------------------- | ------------------------------- |
|
|
38
|
+
| 0.81.x, 0.82.x | ^2.0.0-rc.0 |
|
|
39
|
+
| 0.78.x | ^1.2.0 |
|
|
34
40
|
|
|
35
41
|
## Installation
|
|
36
42
|
|
|
43
|
+
The React Native Brownfield library is intended to be installed in a React Native app that is later consumed as a framework artifact by your native iOS or Android app.
|
|
44
|
+
|
|
45
|
+
In your React Native project run:
|
|
46
|
+
|
|
37
47
|
```sh
|
|
38
48
|
npm install @callstack/react-native-brownfield
|
|
39
49
|
```
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
## Usage
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
<a href="https://www.callstack.com/ebooks/incremental-react-native-adoption-in-native-apps?utm_campaign=brownfield&utm_source=github&utm_medium=referral&utm_content=react-native-brownfield" align="center">
|
|
54
|
+
<img alt="Download a free copy of Incremental React Native adoption in native apps ebook" src="https://github.com/user-attachments/assets/ba42bb29-1e7a-4683-80c5-2602afb1a7e6">
|
|
55
|
+
</a>
|
|
56
|
+
|
|
57
|
+
### Packaging React Native app as a framework
|
|
58
|
+
|
|
59
|
+
First, we need to package our React Native app as an XCFramework or Fat-AAR.
|
|
60
|
+
|
|
61
|
+
#### With Rock
|
|
62
|
+
|
|
63
|
+
Follow [Integrating with Native Apps](https://www.rockjs.dev/docs/brownfield/intro) steps in Rock docs and run:
|
|
64
|
+
|
|
65
|
+
- `rock package:ios` for iOS
|
|
66
|
+
- `rock package:aar` for Android
|
|
67
|
+
|
|
68
|
+
#### With custom scripts
|
|
69
|
+
|
|
70
|
+
Instead of using Rock, you can create your own custom packaging scripts. Here are base versions for iOS and Android that you'll need to adjust for your project-specific setup:
|
|
71
|
+
|
|
72
|
+
- [Example iOS script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-xcframework.sh)
|
|
73
|
+
- [Example Android script](https://github.com/callstackincubator/modern-brownfield-ref/blob/main/scripts/build-aar.sh)
|
|
74
|
+
|
|
75
|
+
### Native iOS app
|
|
76
|
+
|
|
77
|
+
In your native iOS app, initialize React Native and display it where you like. For example, to display React Native views in SwiftUI, use the provided `ReactNativeView` component:
|
|
78
|
+
|
|
79
|
+
```swift
|
|
80
|
+
import SwiftUI
|
|
81
|
+
import ReactBrownfield # exposed by RN app framework
|
|
82
|
+
|
|
83
|
+
@main
|
|
84
|
+
struct MyApp: App {
|
|
85
|
+
init() {
|
|
86
|
+
ReactNativeBrownfield.shared.startReactNative {
|
|
87
|
+
print("React Native bundle loaded")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
var body: some Scene {
|
|
92
|
+
WindowGroup {
|
|
93
|
+
ContentView()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
struct ContentView: View {
|
|
99
|
+
var body: some View {
|
|
100
|
+
NavigationView {
|
|
101
|
+
VStack {
|
|
102
|
+
Text("Welcome to the Native App")
|
|
103
|
+
.padding()
|
|
104
|
+
|
|
105
|
+
NavigationLink("Push React Native Screen") {
|
|
106
|
+
ReactNativeView(moduleName: "ReactNative")
|
|
107
|
+
.navigationBarHidden(true)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
45
113
|
```
|
|
46
114
|
|
|
47
|
-
|
|
115
|
+
For more detailed instructions and API for iOS, see docs for:
|
|
116
|
+
|
|
117
|
+
- [Objective C](docs/OBJECTIVE_C.md)
|
|
118
|
+
- [Swift](docs/SWIFT.md)
|
|
119
|
+
|
|
120
|
+
### Native Android app
|
|
48
121
|
|
|
49
|
-
|
|
50
|
-
Add the following to your `android/gradle.properties`:
|
|
122
|
+
In your native Android app, create a new `RNAppFragment.kt`:
|
|
51
123
|
|
|
124
|
+
```kt
|
|
125
|
+
|
|
126
|
+
import android.os.Bundle
|
|
127
|
+
import android.view.LayoutInflater
|
|
128
|
+
import android.view.View
|
|
129
|
+
import android.view.ViewGroup
|
|
130
|
+
import androidx.fragment.app.Fragment
|
|
131
|
+
|
|
132
|
+
class RNAppFragment : Fragment() {
|
|
133
|
+
override fun onCreateView(
|
|
134
|
+
inflater: LayoutInflater,
|
|
135
|
+
container: ViewGroup?,
|
|
136
|
+
savedInstanceState: Bundle?,
|
|
137
|
+
): View? = ReactNativeBrownfield.shared.createView(activity, "BrownFieldTest")
|
|
138
|
+
}
|
|
52
139
|
```
|
|
53
|
-
|
|
54
|
-
|
|
140
|
+
|
|
141
|
+
Add a button to your `activity_main.xml`:
|
|
142
|
+
|
|
143
|
+
```xml
|
|
144
|
+
<Button
|
|
145
|
+
android:id="@+id/show_rn_app_btn"
|
|
146
|
+
android:layout_width="wrap_content"
|
|
147
|
+
android:layout_height="wrap_content"
|
|
148
|
+
android:text="Show RN App"
|
|
149
|
+
app:layout_constraintBottom_toBottomOf="parent"
|
|
150
|
+
app:layout_constraintEnd_toEndOf="parent"
|
|
151
|
+
app:layout_constraintStart_toStartOf="parent"
|
|
152
|
+
app:layout_constraintTop_toTopOf="parent" />
|
|
55
153
|
```
|
|
56
154
|
|
|
57
|
-
|
|
58
|
-
Install cocoapods with the flag:
|
|
155
|
+
Add a fragment container:
|
|
59
156
|
|
|
60
|
-
```
|
|
61
|
-
|
|
157
|
+
```xml
|
|
158
|
+
<FrameLayout
|
|
159
|
+
android:id="@+id/fragmentContainer"
|
|
160
|
+
android:layout_width="match_parent"
|
|
161
|
+
android:layout_height="match_parent" />
|
|
62
162
|
```
|
|
63
163
|
|
|
64
|
-
|
|
65
|
-
> New Architecture is enabled by default from React Native 0.76
|
|
164
|
+
Update your `MainActivity` to initialize React Native and show the fragment:
|
|
66
165
|
|
|
67
|
-
|
|
166
|
+
```kt
|
|
167
|
+
class MainActivity : AppCompatActivity() {
|
|
168
|
+
private lateinit var showRNAppBtn: Button
|
|
169
|
+
|
|
170
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
171
|
+
super.onCreate(savedInstanceState)
|
|
172
|
+
ReactNativeHostManager.shared.initialize(this.application) {
|
|
173
|
+
println("JS bundle loaded")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
showRNAppBtn = findViewById(R.id.show_rn_app_btn)
|
|
177
|
+
showRNAppBtn.setOnClickListener {
|
|
178
|
+
supportFragmentManager
|
|
179
|
+
.beginTransaction()
|
|
180
|
+
.replace(R.id.fragmentContainer, RNAppFragment())
|
|
181
|
+
.commit()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
```
|
|
68
187
|
|
|
69
|
-
|
|
188
|
+
For more detailed instructions and API for Android, see docs for:
|
|
70
189
|
|
|
71
|
-
|
|
72
|
-
|
|
190
|
+
- [Java](docs/JAVA.md)
|
|
191
|
+
- [Kotlin](docs/KOTLIN.md)
|
|
73
192
|
|
|
74
193
|
### JavaScript Module
|
|
75
194
|
|
|
76
|
-
Besides native components, we are exposing JavaScript functions to control the behavior of those components.
|
|
195
|
+
Besides native components, we are exposing JavaScript functions to control the behavior of those components from React Native app.
|
|
77
196
|
|
|
78
|
-
To use the module,
|
|
197
|
+
To use the module, import it:
|
|
79
198
|
|
|
80
199
|
```js
|
|
81
200
|
import ReactNativeBrownfield from '@callstack/react-native-brownfield';
|
|
82
201
|
```
|
|
83
202
|
|
|
84
|
-
|
|
203
|
+
and use the available methods:
|
|
85
204
|
|
|
86
|
-
|
|
205
|
+
#### setNativeBackGestureAndButtonEnabled(enabled: boolean)
|
|
87
206
|
|
|
88
|
-
A method used to toggle iOS native back gesture and Android hardware back button.
|
|
207
|
+
A method used to toggle iOS native back gesture and Android hardware back button.
|
|
89
208
|
|
|
90
|
-
```
|
|
209
|
+
```ts
|
|
91
210
|
ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled(true);
|
|
92
211
|
```
|
|
93
212
|
|
|
94
|
-
|
|
213
|
+
#### popToNative(animated[iOS only]: boolean)
|
|
95
214
|
|
|
96
|
-
A method to pop to native screen used to push React Native experience.
|
|
215
|
+
A method to pop to native screen used to push React Native experience.
|
|
97
216
|
|
|
98
|
-
```
|
|
217
|
+
```ts
|
|
99
218
|
ReactNativeBrownfield.popToNative(true);
|
|
100
219
|
```
|
|
101
220
|
|
|
102
|
-
>
|
|
103
|
-
|
|
221
|
+
> **Note:** These methods work only with native components provided by this library.
|
|
104
222
|
|
|
105
223
|
## Made with ❤️ at Callstack
|
|
106
224
|
|
|
@@ -115,13 +233,14 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|
|
115
233
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
116
234
|
<!-- prettier-ignore -->
|
|
117
235
|
| [<img src="https://avatars0.githubusercontent.com/u/7837457?s=460&v=4" width="100px;" alt="Michał Chudziak"/><br /><sub><b>Michał Chudziak</b></sub>](https://twitter.com/michalchudziak)<br />[💻](https://github.com/callstack/react-native-brownfield/commits?author=michalchudziak "Code") [📖](https://github.com/callstack/react-native-brownfield/commits?author=michalchudziak "Documentation") [🤔](#ideas-michalchudziak "Ideas, Planning, & Feedback") | [<img src="https://avatars1.githubusercontent.com/u/16336501?s=400&v=4" width="100px;" alt="Piotr Drapich"/><br /><sub><b>Piotr Drapich</b></sub>](https://twitter.com/dratwas)<br />[💻](https://github.com/callstack/react-native-brownfield/commits?author=dratwas "Code") [🤔](#ideas-dratwas "Ideas, Planning, & Feedback") |
|
|
118
|
-
|
|
|
236
|
+
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
|
119
237
|
|
|
120
238
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
121
239
|
|
|
122
240
|
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
|
123
241
|
|
|
124
242
|
<!-- badges -->
|
|
243
|
+
|
|
125
244
|
[build-badge]: https://img.shields.io/circleci/build/github/callstack/react-native-brownfield/master.svg?style=flat-square
|
|
126
245
|
[build]: https://circleci.com/gh/callstack/react-native-brownfield
|
|
127
246
|
[version-badge]: https://img.shields.io/npm/v/@callstack/react-native-brownfield.svg?style=flat-square
|
package/android/build.gradle
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import groovy.json.JsonSlurper
|
|
2
|
+
|
|
3
|
+
def reactNativeVersion = null
|
|
4
|
+
def packageJsonFile = rootProject.file("../node_modules/react-native/package.json")
|
|
5
|
+
|
|
6
|
+
if (packageJsonFile.exists()) {
|
|
7
|
+
def json = new JsonSlurper().parse(packageJsonFile)
|
|
8
|
+
reactNativeVersion = json.version
|
|
9
|
+
} else {
|
|
10
|
+
reactNativeVersion = "unknown"
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
buildscript {
|
|
2
14
|
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
|
|
3
15
|
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["RNBrownfield_kotlinVersion"]
|
|
@@ -64,6 +76,7 @@ android {
|
|
|
64
76
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
65
77
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
66
78
|
buildConfigField "boolean", "IS_HERMES_ENABLED", isHermesEnabled().toString()
|
|
79
|
+
buildConfigField "String", "RN_VERSION", "\"$reactNativeVersion\""
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
buildFeatures {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import androidx.activity.ComponentActivity
|
|
5
|
+
import com.facebook.react.ReactDelegate
|
|
6
|
+
import com.facebook.react.ReactHost
|
|
7
|
+
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
|
8
|
+
|
|
9
|
+
class ReactDelegateWrapper(
|
|
10
|
+
private val activity: ComponentActivity?,
|
|
11
|
+
resolvedReactHost: ReactHost?,
|
|
12
|
+
moduleName: String,
|
|
13
|
+
launchOptions: Bundle?
|
|
14
|
+
) : ReactDelegate(
|
|
15
|
+
activity = activity!!,
|
|
16
|
+
resolvedReactHost,
|
|
17
|
+
appKey = moduleName,
|
|
18
|
+
launchOptions = launchOptions,
|
|
19
|
+
) {
|
|
20
|
+
private lateinit var hardwareBackHandler: () -> Unit
|
|
21
|
+
private val backBtnHandler = DefaultHardwareBackBtnHandler {
|
|
22
|
+
hardwareBackHandler()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This is invoked when there is no more RN Stack to pop.
|
|
27
|
+
* What it means that this is now the initial RN screen.
|
|
28
|
+
*/
|
|
29
|
+
fun setHardwareBackHandler(backHandler: () -> Unit) {
|
|
30
|
+
hardwareBackHandler = backHandler
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun onReactHostResume() {
|
|
34
|
+
super.reactHost?.onHostResume(activity, backBtnHandler)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,141 +1,168 @@
|
|
|
1
1
|
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
|
-
import android.content.Context
|
|
5
4
|
import android.os.Bundle
|
|
6
5
|
import android.widget.FrameLayout
|
|
6
|
+
import androidx.activity.OnBackPressedCallback
|
|
7
7
|
import androidx.fragment.app.FragmentActivity
|
|
8
8
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
9
9
|
import androidx.lifecycle.LifecycleOwner
|
|
10
|
-
import com.
|
|
10
|
+
import com.callstack.reactnativebrownfield.utils.VersionUtils
|
|
11
|
+
import com.facebook.react.ReactHost
|
|
11
12
|
import com.facebook.react.ReactInstanceEventListener
|
|
12
|
-
import com.facebook.react.ReactInstanceManager
|
|
13
|
-
import com.facebook.react.ReactNativeHost
|
|
14
13
|
import com.facebook.react.ReactPackage
|
|
15
|
-
import com.facebook.react.ReactRootView
|
|
16
14
|
import com.facebook.react.bridge.ReactContext
|
|
17
|
-
import com.facebook.react.
|
|
15
|
+
import com.facebook.react.common.build.ReactBuildConfig
|
|
16
|
+
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
17
|
+
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
18
18
|
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
19
19
|
import com.facebook.soloader.SoLoader
|
|
20
20
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
21
|
-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
|
22
|
-
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
23
21
|
|
|
24
|
-
interface
|
|
25
|
-
|
|
22
|
+
fun interface OnJSBundleLoaded {
|
|
23
|
+
operator fun invoke(initialized: Boolean)
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
val shared: ReactNativeBrownfield get() = instance
|
|
26
|
+
/**
|
|
27
|
+
* The threshold RN version based on which we decide whether to
|
|
28
|
+
* load JNI libs or not. We only load JNI libs on version less
|
|
29
|
+
* than this.
|
|
30
|
+
*/
|
|
31
|
+
private const val RN_THRESHOLD_VERSION = "0.80.0"
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
33
|
+
class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
34
|
+
companion object {
|
|
35
|
+
private lateinit var instance: ReactNativeBrownfield
|
|
36
|
+
private val initialized = AtomicBoolean()
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
val reactNativeHost: ReactNativeHost =
|
|
47
|
-
object : DefaultReactNativeHost(application) {
|
|
38
|
+
@JvmStatic
|
|
39
|
+
val shared: ReactNativeBrownfield get() = instance
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
41
|
+
private fun loadNativeLibs(application: Application) {
|
|
42
|
+
val rnVersion = BuildConfig.RN_VERSION
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
if (VersionUtils.isVersionLessThan(rnVersion, RN_THRESHOLD_VERSION)) {
|
|
45
|
+
SoLoader.init(application.applicationContext, OpenSourceMergedSoMapping)
|
|
46
|
+
load()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
@JvmStatic
|
|
51
|
+
@JvmOverloads
|
|
52
|
+
fun initialize(
|
|
53
|
+
application: Application,
|
|
54
|
+
reactHost: ReactHost,
|
|
55
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
56
|
+
) {
|
|
57
|
+
if (!initialized.getAndSet(true)) {
|
|
58
|
+
loadNativeLibs(application)
|
|
59
|
+
instance = ReactNativeBrownfield(reactHost)
|
|
60
|
+
|
|
61
|
+
preloadReactNative {
|
|
62
|
+
onJSBundleLoaded?.invoke(true)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
@JvmStatic
|
|
68
|
+
@JvmOverloads
|
|
69
|
+
fun initialize(
|
|
70
|
+
application: Application,
|
|
71
|
+
options: HashMap<String, Any>,
|
|
72
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
73
|
+
) {
|
|
74
|
+
val reactHost: ReactHost by lazy {
|
|
75
|
+
getDefaultReactHost(
|
|
76
|
+
context = application,
|
|
77
|
+
packageList = (options["packages"] as? List<*> ?: emptyList<ReactPackage>())
|
|
78
|
+
.filterIsInstance<ReactPackage>(),
|
|
79
|
+
jsMainModulePath = options["mainModuleName"] as? String ?: "index",
|
|
80
|
+
useDevSupport = options["useDeveloperSupport"] as? Boolean
|
|
81
|
+
?: ReactBuildConfig.DEBUG,
|
|
82
|
+
jsRuntimeFactory = null
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
initialize(application, reactHost, onJSBundleLoaded)
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
@JvmStatic
|
|
90
|
+
@JvmOverloads
|
|
91
|
+
fun initialize(
|
|
92
|
+
application: Application,
|
|
93
|
+
packages: List<ReactPackage>,
|
|
94
|
+
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
95
|
+
) {
|
|
96
|
+
val options = hashMapOf("packages" to packages, "mainModuleName" to "index")
|
|
66
97
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
val options = hashMapOf("packages" to packages, "mainModuleName" to "index")
|
|
98
|
+
initialize(application, options, onJSBundleLoaded)
|
|
99
|
+
}
|
|
70
100
|
|
|
71
|
-
|
|
101
|
+
private fun preloadReactNative(callback: ((Boolean) -> Unit)) {
|
|
102
|
+
shared.reactHost.addReactInstanceEventListener(object :
|
|
103
|
+
ReactInstanceEventListener {
|
|
104
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
105
|
+
callback(true)
|
|
106
|
+
shared.reactHost.removeReactInstanceEventListener(this)
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
shared.reactHost.start()
|
|
110
|
+
}
|
|
72
111
|
}
|
|
73
112
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
})
|
|
90
|
-
reactNativeHost.reactInstanceManager?.createReactContextInBackground()
|
|
91
|
-
|
|
92
|
-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
93
|
-
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
|
94
|
-
load()
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
fun createView(
|
|
99
|
-
context: Context,
|
|
100
|
-
activity: FragmentActivity?,
|
|
101
|
-
moduleName: String,
|
|
102
|
-
launchOptions: Bundle? = null,
|
|
103
|
-
): FrameLayout {
|
|
104
|
-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
|
105
|
-
val reactHost = getDefaultReactHost(
|
|
106
|
-
context,
|
|
107
|
-
shared.reactNativeHost
|
|
108
|
-
)
|
|
109
|
-
val reactDelegate = ReactDelegate(activity, reactHost, moduleName, launchOptions)
|
|
110
|
-
|
|
111
|
-
activity?.lifecycle?.addObserver(object : DefaultLifecycleObserver {
|
|
112
|
-
override fun onResume(owner: LifecycleOwner) {
|
|
113
|
-
reactDelegate.onHostResume()
|
|
113
|
+
fun createView(
|
|
114
|
+
activity: FragmentActivity?,
|
|
115
|
+
moduleName: String,
|
|
116
|
+
reactDelegate: ReactDelegateWrapper? = null,
|
|
117
|
+
launchOptions: Bundle? = null,
|
|
118
|
+
): FrameLayout {
|
|
119
|
+
val reactHost = shared.reactHost
|
|
120
|
+
val resolvedDelegate =
|
|
121
|
+
reactDelegate ?: ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)
|
|
122
|
+
|
|
123
|
+
val mBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
|
124
|
+
override fun handleOnBackPressed() {
|
|
125
|
+
// invoked for JS stack back navigation
|
|
126
|
+
resolvedDelegate.onBackPressed()
|
|
127
|
+
}
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
// Register back press callback
|
|
131
|
+
activity?.onBackPressedDispatcher?.addCallback(mBackPressedCallback)
|
|
132
|
+
// invoked on the last RN screen exit
|
|
133
|
+
resolvedDelegate.setHardwareBackHandler {
|
|
134
|
+
mBackPressedCallback.isEnabled = false
|
|
135
|
+
activity?.onBackPressedDispatcher?.onBackPressed()
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
/**
|
|
139
|
+
* When createView method is called in ReactNativeFragment, a reactDelegate
|
|
140
|
+
* instance is required. In such a case, we use the lifeCycle events of the fragment.
|
|
141
|
+
* When createView method is called elsewhere, then reactDelegate is not required.
|
|
142
|
+
* In such a case, we set the lifeCycle observer.
|
|
143
|
+
*/
|
|
144
|
+
if (reactDelegate == null) {
|
|
145
|
+
activity?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate))
|
|
123
146
|
}
|
|
124
|
-
})
|
|
125
147
|
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
resolvedDelegate.loadApp()
|
|
149
|
+
return resolvedDelegate.reactRootView!!
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
launchOptions,
|
|
136
|
-
)
|
|
152
|
+
private fun getLifeCycleObserver(reactDelegate: ReactDelegateWrapper): DefaultLifecycleObserver {
|
|
153
|
+
return object : DefaultLifecycleObserver {
|
|
154
|
+
override fun onResume(owner: LifecycleOwner) {
|
|
155
|
+
reactDelegate.onReactHostResume()
|
|
156
|
+
}
|
|
137
157
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
158
|
+
override fun onPause(owner: LifecycleOwner) {
|
|
159
|
+
reactDelegate.onHostPause()
|
|
160
|
+
}
|
|
141
161
|
|
|
162
|
+
override fun onDestroy(owner: LifecycleOwner) {
|
|
163
|
+
reactDelegate.onHostDestroy()
|
|
164
|
+
owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
|
-
import java.util.Collections
|
|
5
|
-
|
|
6
4
|
import com.facebook.react.ReactPackage
|
|
7
5
|
import com.facebook.react.bridge.NativeModule
|
|
8
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
-
import com.facebook.react.uimanager.ViewManager
|
|
10
7
|
import com.facebook.react.uimanager.ReactShadowNode
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
import java.util.Collections
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class ReactNativeBrownfieldPackage : ReactPackage {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> {
|
|
14
|
+
return Collections.emptyList()
|
|
15
|
+
}
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> {
|
|
18
|
+
val modules = ArrayList<NativeModule>()
|
|
19
|
+
modules.add(ReactNativeBrownfieldModule(reactContext))
|
|
20
|
+
return modules
|
|
21
|
+
}
|
|
23
22
|
}
|
|
@@ -1,170 +1,140 @@
|
|
|
1
|
-
package com.callstack.reactnativebrownfield
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
2
|
|
|
3
|
-
import android.annotation.TargetApi
|
|
4
|
-
import android.os.Build
|
|
5
3
|
import android.os.Bundle
|
|
6
|
-
import android.
|
|
7
|
-
import
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.LayoutInflater
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import com.callstack.reactnativebrownfield.constants.ReactNativeFragmentArgNames
|
|
8
9
|
import com.facebook.react.ReactFragment
|
|
9
10
|
import com.facebook.react.ReactHost
|
|
10
|
-
import com.facebook.react.ReactNativeHost
|
|
11
11
|
import com.facebook.react.bridge.Callback
|
|
12
12
|
import com.facebook.react.bridge.WritableMap
|
|
13
|
-
import com.facebook.react.common.LifecycleState
|
|
14
|
-
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
|
15
|
-
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
|
|
16
|
-
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
|
17
13
|
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
18
14
|
import com.facebook.react.modules.core.PermissionListener
|
|
19
15
|
|
|
20
16
|
class ReactNativeFragment : ReactFragment(), PermissionAwareActivity {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
override fun onPause() {
|
|
54
|
-
super.onPause()
|
|
55
|
-
if (ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
|
|
56
|
-
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager?.onHostPause(
|
|
57
|
-
activity
|
|
58
|
-
)
|
|
17
|
+
private lateinit var permissionsCallback: Callback
|
|
18
|
+
private var permissionListener: PermissionListener? = null
|
|
19
|
+
private lateinit var moduleName: String
|
|
20
|
+
|
|
21
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
22
|
+
/**
|
|
23
|
+
* ReactFragment.onCreate will throw an exception if we do not provide arg_component_name as arguments.
|
|
24
|
+
* We silently catch this exception. The reason is we want to invoke the super<Fragment>.onCreate in
|
|
25
|
+
* ReactFragment. Then initialise the mReactDelegate with ReactDelegateWrapper instead of ReactDelegate.
|
|
26
|
+
*
|
|
27
|
+
* So we purposely force ReactFragment.onCreate to throw an exception, so that we can provide our own
|
|
28
|
+
* implementation for mReactDelegate: ReactDelegateWrapper
|
|
29
|
+
*/
|
|
30
|
+
try {
|
|
31
|
+
super.onCreate(savedInstanceState)
|
|
32
|
+
} catch (e: IllegalStateException) {
|
|
33
|
+
Log.w(
|
|
34
|
+
"ReactNativeFragment",
|
|
35
|
+
"ReactFragment threw due to missing arg_component_name: ${e.message} - This is an expected behaviour."
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
moduleName = arguments?.getString(ReactNativeFragmentArgNames.ARG_MODULE_NAME)!!
|
|
40
|
+
this.reactDelegate =
|
|
41
|
+
ReactDelegateWrapper(
|
|
42
|
+
activity,
|
|
43
|
+
this.reactHost,
|
|
44
|
+
moduleName,
|
|
45
|
+
arguments?.getBundle(ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS)
|
|
46
|
+
)
|
|
59
47
|
}
|
|
60
|
-
}
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
override fun onRequestPermissionsResult(
|
|
74
|
-
requestCode: Int,
|
|
75
|
-
permissions: Array<String>,
|
|
76
|
-
grantResults: IntArray
|
|
77
|
-
) {
|
|
78
|
-
permissionsCallback = Callback {
|
|
79
|
-
if (permissionListener != null) {
|
|
80
|
-
permissionListener?.onRequestPermissionsResult(
|
|
81
|
-
requestCode,
|
|
82
|
-
permissions,
|
|
83
|
-
grantResults
|
|
49
|
+
override fun onCreateView(
|
|
50
|
+
inflater: LayoutInflater,
|
|
51
|
+
container: ViewGroup?,
|
|
52
|
+
savedInstanceState: Bundle?
|
|
53
|
+
): View {
|
|
54
|
+
return ReactNativeBrownfield.shared.createView(
|
|
55
|
+
activity,
|
|
56
|
+
moduleName,
|
|
57
|
+
this.reactDelegate as ReactDelegateWrapper
|
|
84
58
|
)
|
|
85
|
-
|
|
86
|
-
permissionListener = null
|
|
87
|
-
}
|
|
88
59
|
}
|
|
89
|
-
}
|
|
90
60
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
61
|
+
override val reactHost: ReactHost?
|
|
62
|
+
get() = ReactNativeBrownfield.shared.reactHost
|
|
94
63
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
permissions: Array<String>,
|
|
102
|
-
requestCode: Int,
|
|
103
|
-
listener: PermissionListener?
|
|
104
|
-
) {
|
|
105
|
-
permissionListener = listener
|
|
106
|
-
this.requestPermissions(permissions, requestCode)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
|
110
|
-
var handled = false
|
|
111
|
-
if (ReactNativeBrownfield.shared.reactNativeHost.useDeveloperSupport && ReactNativeBrownfield.shared.reactNativeHost.hasInstance()) {
|
|
112
|
-
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
|
113
|
-
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.showDevOptionsDialog()
|
|
114
|
-
handled = true
|
|
115
|
-
}
|
|
116
|
-
val didDoubleTapR = activity?.currentFocus?.let {
|
|
117
|
-
Assertions.assertNotNull(doubleTapReloadRecognizer)
|
|
118
|
-
.didDoubleTapR(keyCode, it)
|
|
119
|
-
}
|
|
120
|
-
if (didDoubleTapR == true) {
|
|
121
|
-
ReactNativeBrownfield.shared.reactNativeHost.reactInstanceManager.devSupportManager.handleReloadJS()
|
|
122
|
-
handled = true
|
|
123
|
-
}
|
|
64
|
+
override fun onResume() {
|
|
65
|
+
try {
|
|
66
|
+
super.onResume()
|
|
67
|
+
} catch (_: ClassCastException) {
|
|
68
|
+
(this.reactDelegate as ReactDelegateWrapper).onReactHostResume()
|
|
69
|
+
}
|
|
124
70
|
}
|
|
125
|
-
return handled
|
|
126
|
-
}
|
|
127
71
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
72
|
+
override fun onRequestPermissionsResult(
|
|
73
|
+
requestCode: Int,
|
|
74
|
+
permissions: Array<String>,
|
|
75
|
+
grantResults: IntArray
|
|
76
|
+
) {
|
|
77
|
+
permissionsCallback = Callback {
|
|
78
|
+
if (permissionListener != null) {
|
|
79
|
+
permissionListener?.onRequestPermissionsResult(
|
|
80
|
+
requestCode,
|
|
81
|
+
permissions,
|
|
82
|
+
grantResults
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
permissionListener = null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
133
88
|
}
|
|
134
|
-
}
|
|
135
89
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@JvmOverloads
|
|
139
|
-
fun createReactNativeFragment(
|
|
140
|
-
moduleName: String,
|
|
141
|
-
initialProps: Bundle? = null
|
|
142
|
-
): ReactNativeFragment {
|
|
143
|
-
val fragment = ReactNativeFragment()
|
|
144
|
-
val args = Bundle()
|
|
145
|
-
args.putString(ARG_COMPONENT_NAME, moduleName)
|
|
146
|
-
if (initialProps != null) {
|
|
147
|
-
args.putBundle(ARG_LAUNCH_OPTIONS, initialProps)
|
|
148
|
-
}
|
|
149
|
-
fragment.arguments = args
|
|
150
|
-
return fragment
|
|
90
|
+
override fun checkPermission(permission: String, pid: Int, uid: Int): Int {
|
|
91
|
+
return requireActivity().checkPermission(permission, pid, uid)
|
|
151
92
|
}
|
|
152
93
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
moduleName: String,
|
|
156
|
-
initialProps: HashMap<String, *>
|
|
157
|
-
): ReactNativeFragment {
|
|
158
|
-
return createReactNativeFragment(moduleName, PropsBundle.fromHashMap(initialProps))
|
|
94
|
+
override fun checkSelfPermission(permission: String): Int {
|
|
95
|
+
return requireActivity().checkSelfPermission(permission)
|
|
159
96
|
}
|
|
160
97
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
|
|
98
|
+
override fun requestPermissions(
|
|
99
|
+
permissions: Array<String>,
|
|
100
|
+
requestCode: Int,
|
|
101
|
+
listener: PermissionListener?
|
|
102
|
+
) {
|
|
103
|
+
permissionListener = listener
|
|
104
|
+
this.requestPermissions(permissions, requestCode)
|
|
167
105
|
}
|
|
168
|
-
}
|
|
169
106
|
|
|
107
|
+
companion object {
|
|
108
|
+
@JvmStatic
|
|
109
|
+
@JvmOverloads
|
|
110
|
+
fun createReactNativeFragment(
|
|
111
|
+
moduleName: String,
|
|
112
|
+
initialProps: Bundle? = null
|
|
113
|
+
): ReactNativeFragment {
|
|
114
|
+
val fragment = ReactNativeFragment()
|
|
115
|
+
val args = Bundle()
|
|
116
|
+
args.putString(ReactNativeFragmentArgNames.ARG_MODULE_NAME, moduleName)
|
|
117
|
+
if (initialProps != null) {
|
|
118
|
+
args.putBundle(ReactNativeFragmentArgNames.ARG_LAUNCH_OPTIONS, initialProps)
|
|
119
|
+
}
|
|
120
|
+
fragment.arguments = args
|
|
121
|
+
return fragment
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@JvmStatic
|
|
125
|
+
fun createReactNativeFragment(
|
|
126
|
+
moduleName: String,
|
|
127
|
+
initialProps: HashMap<String, *>
|
|
128
|
+
): ReactNativeFragment {
|
|
129
|
+
return createReactNativeFragment(moduleName, PropsBundle.fromHashMap(initialProps))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@JvmStatic
|
|
133
|
+
fun createReactNativeFragment(
|
|
134
|
+
moduleName: String,
|
|
135
|
+
initialProps: WritableMap
|
|
136
|
+
): ReactNativeFragment {
|
|
137
|
+
return createReactNativeFragment(moduleName, initialProps.toHashMap())
|
|
138
|
+
}
|
|
139
|
+
}
|
|
170
140
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield.constants
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactFragment
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convenience export of arguments that can be used
|
|
7
|
+
*/
|
|
8
|
+
class ReactNativeFragmentArgNames private constructor() :
|
|
9
|
+
ReactFragment() // subclass to gain access to protected constants
|
|
10
|
+
{
|
|
11
|
+
companion object {
|
|
12
|
+
/**
|
|
13
|
+
* The module name to be loaded
|
|
14
|
+
*/
|
|
15
|
+
const val ARG_MODULE_NAME = "arg_module_name"
|
|
16
|
+
|
|
17
|
+
// re-export constants from ReactFragment to enable access
|
|
18
|
+
const val ARG_LAUNCH_OPTIONS: String = "arg_launch_options"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield.utils
|
|
2
|
+
|
|
3
|
+
object VersionUtils {
|
|
4
|
+
fun isVersionLessThan(version: String, threshold: String): Boolean {
|
|
5
|
+
val versionParts = version.split(".").map { it.toIntOrNull() ?: 0 }
|
|
6
|
+
val thresholdParts = threshold.split(".").map { it.toIntOrNull() ?: 0 }
|
|
7
|
+
|
|
8
|
+
val maxLength = maxOf(versionParts.size, thresholdParts.size)
|
|
9
|
+
for (i in 0 until maxLength) {
|
|
10
|
+
val vPart = versionParts.getOrNull(i) ?: 0
|
|
11
|
+
val tPart = thresholdParts.getOrNull(i) ?: 0
|
|
12
|
+
if (vPart != tPart) return vPart < tPart
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return false // equal versions are not less than
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -4,34 +4,34 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.facebook.react.bridge.ReactMethod
|
|
5
5
|
|
|
6
6
|
class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) :
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
NativeReactNativeBrownfieldModuleSpec(reactContext) {
|
|
8
|
+
companion object {
|
|
9
|
+
var shouldPopToNative: Boolean = false
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
@ReactMethod
|
|
13
|
+
override fun popToNative(animated: Boolean) {
|
|
14
|
+
shouldPopToNative = true
|
|
15
|
+
onBackPressed()
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
@ReactMethod
|
|
19
|
+
override fun setPopGestureRecognizerEnabled(enabled: Boolean) {
|
|
20
|
+
shouldPopToNative = enabled
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
@ReactMethod
|
|
24
|
+
override fun setHardwareBackButtonEnabled(enabled: Boolean) {
|
|
25
|
+
shouldPopToNative = enabled
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
private fun onBackPressed() {
|
|
29
|
+
reactApplicationContext.currentActivity?.runOnUiThread {
|
|
30
|
+
reactApplicationContext.currentActivity?.onBackPressed()
|
|
31
|
+
}
|
|
31
32
|
}
|
|
32
|
-
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
override fun getName(): String {
|
|
35
|
+
return "ReactNativeBrownfield"
|
|
36
|
+
}
|
|
37
37
|
}
|
|
@@ -88,7 +88,7 @@ class ReactNativeBrownfieldDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
|
88
88
|
initialProps: [AnyHashable: Any]?,
|
|
89
89
|
launchOptions: [AnyHashable: Any]? = nil
|
|
90
90
|
) -> UIView? {
|
|
91
|
-
|
|
91
|
+
rootViewFactory?.view(
|
|
92
92
|
withModuleName: moduleName,
|
|
93
93
|
initialProperties: initialProps,
|
|
94
94
|
launchOptions: launchOptions
|
|
@@ -8,7 +8,10 @@ struct ReactNativeViewRepresentable: UIViewControllerRepresentable {
|
|
|
8
8
|
var initialProperties: [String: Any] = [:]
|
|
9
9
|
|
|
10
10
|
func makeUIViewController(context: Context) -> UIViewController {
|
|
11
|
-
return ReactNativeViewController(
|
|
11
|
+
return ReactNativeViewController(
|
|
12
|
+
moduleName: moduleName,
|
|
13
|
+
initialProperties: initialProperties
|
|
14
|
+
)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@callstack/react-native-brownfield",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Michal Chudziak <mike.chudziak@callstack.com>",
|
|
6
6
|
"contributors": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"resolutions": {
|
|
63
|
-
"@types/react": "19.
|
|
63
|
+
"@types/react": "19.1.1"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"react": "*",
|
|
@@ -70,25 +70,25 @@
|
|
|
70
70
|
"@babel/core": "^7.25.2",
|
|
71
71
|
"@babel/preset-env": "^7.25.3",
|
|
72
72
|
"@babel/runtime": "^7.25.0",
|
|
73
|
-
"@react-native/babel-preset": "0.
|
|
74
|
-
"@react-native/eslint-config": "0.
|
|
75
|
-
"@react-native/typescript-config": "0.
|
|
73
|
+
"@react-native/babel-preset": "0.82.1",
|
|
74
|
+
"@react-native/eslint-config": "0.82.1",
|
|
75
|
+
"@react-native/typescript-config": "0.82.1",
|
|
76
76
|
"@release-it/conventional-changelog": "^5.0.0",
|
|
77
77
|
"@types/jest": "^29.5.13",
|
|
78
|
-
"@types/react": "^19.
|
|
79
|
-
"@types/react-test-renderer": "^19.
|
|
78
|
+
"@types/react": "^19.1.1",
|
|
79
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
80
80
|
"babel-plugin-module-resolver": "5.0.0",
|
|
81
81
|
"eslint": "^8.19.0",
|
|
82
82
|
"eslint-config-prettier": "^9.1.0",
|
|
83
83
|
"eslint-plugin-prettier": "^5.1.3",
|
|
84
84
|
"jest": "^29.6.3",
|
|
85
85
|
"prettier": "^3.5.3",
|
|
86
|
-
"react": "19.
|
|
87
|
-
"react-native": "0.
|
|
86
|
+
"react": "19.1.1",
|
|
87
|
+
"react-native": "0.82.1",
|
|
88
88
|
"react-native-builder-bob": "^0.37.0",
|
|
89
|
-
"react-test-renderer": "19.
|
|
89
|
+
"react-test-renderer": "19.1.1",
|
|
90
90
|
"release-it": "^18.1.2",
|
|
91
|
-
"typescript": "5.
|
|
91
|
+
"typescript": "5.8.3"
|
|
92
92
|
},
|
|
93
93
|
"release-it": {
|
|
94
94
|
"git": {
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
]
|
|
173
173
|
},
|
|
174
174
|
"engines": {
|
|
175
|
-
"node": ">=
|
|
175
|
+
"node": ">=20"
|
|
176
176
|
},
|
|
177
177
|
"packageManager": "yarn@3.6.4"
|
|
178
178
|
}
|