@onekeyfe/react-native-device-utils 1.1.11

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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/ReactNativeDeviceUtils.podspec +29 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +132 -0
  6. package/android/gradle.properties +4 -0
  7. package/android/src/main/AndroidManifest.xml +1 -0
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/reactnativedeviceutils/ReactNativeDeviceUtils.kt +343 -0
  10. package/android/src/main/java/com/margelo/nitro/reactnativedeviceutils/ReactNativeDeviceUtilsPackage.kt +26 -0
  11. package/ios/ReactNativeDeviceUtils.swift +41 -0
  12. package/lib/module/ReactNativeDeviceUtils.nitro.js +4 -0
  13. package/lib/module/ReactNativeDeviceUtils.nitro.js.map +1 -0
  14. package/lib/module/index.js +6 -0
  15. package/lib/module/index.js.map +1 -0
  16. package/lib/module/package.json +1 -0
  17. package/lib/typescript/package.json +1 -0
  18. package/lib/typescript/src/ReactNativeDeviceUtils.nitro.d.ts +21 -0
  19. package/lib/typescript/src/ReactNativeDeviceUtils.nitro.d.ts.map +1 -0
  20. package/lib/typescript/src/index.d.ts +4 -0
  21. package/lib/typescript/src/index.d.ts.map +1 -0
  22. package/nitro.json +17 -0
  23. package/nitrogen/generated/android/c++/JDualScreenInfoRect.hpp +69 -0
  24. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridReactNativeDeviceUtilsSpec.cpp +123 -0
  26. package/nitrogen/generated/android/c++/JHybridReactNativeDeviceUtilsSpec.hpp +72 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativedeviceutils/DualScreenInfoRect.kt +47 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativedeviceutils/Func_void_bool.kt +80 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativedeviceutils/HybridReactNativeDeviceUtilsSpec.kt +91 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativedeviceutils/reactnativedeviceutilsOnLoad.kt +35 -0
  31. package/nitrogen/generated/android/reactnativedeviceutils+autolinking.cmake +81 -0
  32. package/nitrogen/generated/android/reactnativedeviceutils+autolinking.gradle +27 -0
  33. package/nitrogen/generated/android/reactnativedeviceutilsOnLoad.cpp +46 -0
  34. package/nitrogen/generated/android/reactnativedeviceutilsOnLoad.hpp +25 -0
  35. package/nitrogen/generated/ios/ReactNativeDeviceUtils+autolinking.rb +60 -0
  36. package/nitrogen/generated/ios/ReactNativeDeviceUtils-Swift-Cxx-Bridge.cpp +65 -0
  37. package/nitrogen/generated/ios/ReactNativeDeviceUtils-Swift-Cxx-Bridge.hpp +217 -0
  38. package/nitrogen/generated/ios/ReactNativeDeviceUtils-Swift-Cxx-Umbrella.hpp +49 -0
  39. package/nitrogen/generated/ios/ReactNativeDeviceUtilsAutolinking.mm +33 -0
  40. package/nitrogen/generated/ios/ReactNativeDeviceUtilsAutolinking.swift +25 -0
  41. package/nitrogen/generated/ios/c++/HybridReactNativeDeviceUtilsSpecSwift.cpp +11 -0
  42. package/nitrogen/generated/ios/c++/HybridReactNativeDeviceUtilsSpecSwift.hpp +130 -0
  43. package/nitrogen/generated/ios/swift/DualScreenInfoRect.swift +69 -0
  44. package/nitrogen/generated/ios/swift/Func_void_DualScreenInfoRect.swift +47 -0
  45. package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
  46. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
  47. package/nitrogen/generated/ios/swift/Func_void_std__vector_DualScreenInfoRect_.swift +47 -0
  48. package/nitrogen/generated/ios/swift/HybridReactNativeDeviceUtilsSpec.swift +63 -0
  49. package/nitrogen/generated/ios/swift/HybridReactNativeDeviceUtilsSpec_cxx.swift +237 -0
  50. package/nitrogen/generated/shared/c++/DualScreenInfoRect.hpp +87 -0
  51. package/nitrogen/generated/shared/c++/HybridReactNativeDeviceUtilsSpec.cpp +28 -0
  52. package/nitrogen/generated/shared/c++/HybridReactNativeDeviceUtilsSpec.hpp +73 -0
  53. package/package.json +170 -0
  54. package/src/ReactNativeDeviceUtils.nitro.ts +21 -0
  55. package/src/index.tsx +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OneKey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # react-native-device-utils
2
+
3
+ react-native-device-utils
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install react-native-device-utils react-native-nitro-modules
9
+
10
+ > `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import { ReactNativeDeviceUtils } from 'react-native-device-utils';
17
+
18
+ // ...
19
+
20
+ const result = await ReactNativeDeviceUtils.hello({ message: 'World' });
21
+ console.log(result); // { success: true, data: 'Hello, World!' }
22
+ ```
23
+
24
+ ## Contributing
25
+
26
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
27
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
28
+ - [Code of conduct](CODE_OF_CONDUCT.md)
29
+
30
+ ## License
31
+
32
+ MIT
33
+
34
+ ---
35
+
36
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,29 @@
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 = "ReactNativeDeviceUtils"
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 => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/OneKeyHQ/app-modules/react-native-device-utils.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = [
17
+ "ios/**/*.{swift}",
18
+ "ios/**/*.{m,mm}",
19
+ "cpp/**/*.{hpp,cpp}",
20
+ ]
21
+
22
+ s.dependency 'React-jsi'
23
+ s.dependency 'React-callinvoker'
24
+
25
+ load 'nitrogen/generated/ios/ReactNativeDeviceUtils+autolinking.rb'
26
+ add_nitrogen_files(s)
27
+
28
+ install_modules_dependencies(s)
29
+ end
@@ -0,0 +1,24 @@
1
+ project(reactnativedeviceutils)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set(PACKAGE_NAME reactnativedeviceutils)
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 20)
7
+
8
+ # Define C++ library and add all sources
9
+ add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
10
+
11
+ # Add Nitrogen specs :)
12
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/reactnativedeviceutils+autolinking.cmake)
13
+
14
+ # Set up local includes
15
+ include_directories("src/main/cpp" "../cpp")
16
+
17
+ find_library(LOG_LIB log)
18
+
19
+ # Link all libraries together
20
+ target_link_libraries(
21
+ ${PACKAGE_NAME}
22
+ ${LOG_LIB}
23
+ android # <-- Android core
24
+ )
@@ -0,0 +1,132 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeDeviceUtils_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+ def reactNativeArchitectures() {
19
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
20
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
21
+ }
22
+
23
+ apply plugin: "com.android.library"
24
+ apply plugin: "kotlin-android"
25
+ apply from: '../nitrogen/generated/android/reactnativedeviceutils+autolinking.gradle'
26
+
27
+ apply plugin: "com.facebook.react"
28
+
29
+ def getExtOrIntegerDefault(name) {
30
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeDeviceUtils_" + name]).toInteger()
31
+ }
32
+
33
+ android {
34
+ namespace "com.margelo.nitro.reactnativedeviceutils"
35
+
36
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
37
+
38
+ defaultConfig {
39
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
40
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
41
+
42
+ externalNativeBuild {
43
+ cmake {
44
+ cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
45
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
46
+ abiFilters (*reactNativeArchitectures())
47
+
48
+ buildTypes {
49
+ debug {
50
+ cppFlags "-O1 -g"
51
+ }
52
+ release {
53
+ cppFlags "-O2"
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ externalNativeBuild {
61
+ cmake {
62
+ path "CMakeLists.txt"
63
+ }
64
+ }
65
+
66
+ packagingOptions {
67
+ excludes = [
68
+ "META-INF",
69
+ "META-INF/**",
70
+ "**/libc++_shared.so",
71
+ "**/libfbjni.so",
72
+ "**/libjsi.so",
73
+ "**/libfolly_json.so",
74
+ "**/libfolly_runtime.so",
75
+ "**/libglog.so",
76
+ "**/libhermes.so",
77
+ "**/libhermes-executor-debug.so",
78
+ "**/libhermes_executor.so",
79
+ "**/libreactnative.so",
80
+ "**/libreactnativejni.so",
81
+ "**/libturbomodulejsijni.so",
82
+ "**/libreact_nativemodule_core.so",
83
+ "**/libjscexecutor.so"
84
+ ]
85
+ }
86
+
87
+ buildFeatures {
88
+ buildConfig true
89
+ prefab true
90
+ }
91
+
92
+ buildTypes {
93
+ release {
94
+ minifyEnabled false
95
+ }
96
+ }
97
+
98
+ lintOptions {
99
+ disable "GradleCompatible"
100
+ }
101
+
102
+ compileOptions {
103
+ sourceCompatibility JavaVersion.VERSION_1_8
104
+ targetCompatibility JavaVersion.VERSION_1_8
105
+ }
106
+
107
+ sourceSets {
108
+ main {
109
+ java.srcDirs += [
110
+ "generated/java",
111
+ "generated/jni"
112
+ ]
113
+ }
114
+ }
115
+ }
116
+
117
+ repositories {
118
+ mavenCentral()
119
+ google()
120
+ }
121
+
122
+ def kotlin_version = getExtOrDefault("kotlinVersion")
123
+
124
+ dependencies {
125
+ implementation "com.facebook.react:react-android"
126
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
127
+ implementation project(":react-native-nitro-modules")
128
+
129
+ // WindowManager library for foldable device detection
130
+ implementation "androidx.window:window:1.5.1"
131
+ implementation "androidx.window:window-java:1.5.1"
132
+ }
@@ -0,0 +1,4 @@
1
+ ReactNativeDeviceUtils_kotlinVersion=1.9.25
2
+ ReactNativeDeviceUtils_compileSdkVersion=35
3
+ ReactNativeDeviceUtils_targetSdkVersion=35
4
+ ReactNativeDeviceUtils_minSdkVersion=24
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,6 @@
1
+ #include <jni.h>
2
+ #include "reactnativedeviceutilsOnLoad.hpp"
3
+
4
+ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
5
+ return margelo::nitro::reactnativedeviceutils::initialize(vm);
6
+ }
@@ -0,0 +1,343 @@
1
+ package com.margelo.nitro.reactnativedeviceutils
2
+
3
+ import android.app.Activity
4
+ import android.graphics.Color
5
+ import android.graphics.Rect
6
+ import android.os.Build
7
+ import androidx.core.content.ContextCompat
8
+ import androidx.core.util.Consumer
9
+ import androidx.window.layout.FoldingFeature
10
+ import androidx.window.layout.WindowInfoTracker
11
+ import androidx.window.layout.WindowLayoutInfo
12
+ import com.facebook.proguard.annotations.DoNotStrip
13
+ import com.facebook.react.bridge.LifecycleEventListener
14
+ import com.facebook.react.bridge.ReactApplicationContext
15
+ import com.margelo.nitro.NitroModules
16
+ import com.margelo.nitro.core.Promise
17
+ import java.util.concurrent.Executor
18
+
19
+ data class Listener(
20
+ val id: Double,
21
+ val callback: (Boolean) -> Unit
22
+ )
23
+
24
+ @DoNotStrip
25
+ class ReactNativeDeviceUtils : HybridReactNativeDeviceUtilsSpec(), LifecycleEventListener {
26
+
27
+ private var windowLayoutInfo: WindowLayoutInfo? = null
28
+ private var isSpanning = false
29
+ private var layoutInfoConsumer: Consumer<WindowLayoutInfo>? = null
30
+ private var windowInfoTracker: WindowInfoTracker? = null
31
+ private var spanningChangedListeners: MutableList<Listener> = mutableListOf()
32
+ private var isObservingLayoutChanges = false
33
+ private var nextListenerId = 0.0
34
+ private var isDualScreenDeviceDetected: Boolean? = null
35
+
36
+ companion object {
37
+ private var reactContext: ReactApplicationContext? = null
38
+
39
+ fun setReactContext(context: ReactApplicationContext) {
40
+ reactContext = context
41
+ }
42
+ }
43
+
44
+ init {
45
+ NitroModules.applicationContext?.let { ctx ->
46
+ ctx.addLifecycleEventListener(this)
47
+ } ?: run {
48
+
49
+ }
50
+ }
51
+
52
+ private fun getCurrentActivity(): Activity? {
53
+ return NitroModules.applicationContext?.currentActivity
54
+ }
55
+
56
+ override fun initEventListeners() {
57
+ startObservingLayoutChanges()
58
+ }
59
+
60
+ // MARK: - Dual Screen Detection
61
+
62
+ override fun isDualScreenDevice(): Boolean {
63
+ if (isDualScreenDeviceDetected != null) {
64
+ return isDualScreenDeviceDetected!!
65
+ }
66
+ val activity = getCurrentActivity() ?: return false
67
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
68
+ isDualScreenDeviceDetected = hasFoldingFeature(activity)
69
+ return isDualScreenDeviceDetected!!
70
+ }
71
+ isDualScreenDeviceDetected = false
72
+ return isDualScreenDeviceDetected!!
73
+ }
74
+ private fun isFoldableDeviceByName(): Boolean {
75
+ val deviceModel = Build.MODEL.lowercase()
76
+ val deviceManufacturer = Build.MANUFACTURER.lowercase()
77
+
78
+ // Common foldable device patterns
79
+ val foldablePatterns = listOf(
80
+ "fold", "flip", "duo", "surface duo", "galaxy z",
81
+ "mate x", "mix fold", "find n", "magic v",
82
+ "pixel fold", "honor magic v", "vivo x fold",
83
+ "xiaomi mix fold", "oppo find n"
84
+ )
85
+
86
+ for (pattern in foldablePatterns) {
87
+ if (deviceModel.contains(pattern) ||
88
+ (deviceManufacturer + " " + deviceModel).contains(pattern)) {
89
+ return true
90
+ }
91
+ }
92
+ return false
93
+ }
94
+
95
+ private fun hasFoldingFeature(activity: Activity): Boolean {
96
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
97
+ return isFoldableDeviceByName()
98
+ }
99
+
100
+ // Check if device has folding features using WindowManager library
101
+ // This is the recommended approach for detecting foldable devices
102
+ try {
103
+ val windowInfoTracker = WindowInfoTracker.getOrCreate(activity)
104
+ // If WindowInfoTracker is available, the device supports foldable features
105
+ // We can also check for specific display features
106
+ val displayFeatures = windowLayoutInfo?.displayFeatures
107
+ if (displayFeatures != null) {
108
+ for (feature in displayFeatures) {
109
+ if (feature is FoldingFeature) {
110
+ return true
111
+ }
112
+ }
113
+ }
114
+ // Check device model name to determine if it's a foldable device
115
+ return isFoldableDeviceByName()
116
+ } catch (e: Exception) {
117
+ // WindowManager library not available or device doesn't support foldables
118
+ return false
119
+ }
120
+ }
121
+
122
+ override fun isSpanning(): Boolean {
123
+ return this.isSpanning
124
+ }
125
+
126
+ private fun checkIsSpanning(layoutInfo: WindowLayoutInfo?): Boolean {
127
+ if (layoutInfo == null) {
128
+ return false
129
+ }
130
+
131
+ val foldingFeature = getFoldingFeature(layoutInfo)
132
+ if (foldingFeature == null) {
133
+ return false
134
+ }
135
+
136
+ // Consider spanning if the folding feature divides the screen
137
+ return foldingFeature.state == FoldingFeature.State.FLAT ||
138
+ foldingFeature.state == FoldingFeature.State.HALF_OPENED
139
+ }
140
+
141
+ private fun getFoldingFeature(layoutInfo: WindowLayoutInfo?): FoldingFeature? {
142
+ if (layoutInfo == null) {
143
+ return null
144
+ }
145
+
146
+ val features = layoutInfo.displayFeatures
147
+ for (feature in features) {
148
+ if (feature is FoldingFeature) {
149
+ return feature
150
+ }
151
+ }
152
+ return null
153
+ }
154
+
155
+ // MARK: - Window Information
156
+
157
+ override fun getWindowRects(): Promise<Array<DualScreenInfoRect>> {
158
+ return Promise.async {
159
+ val activity = getCurrentActivity()
160
+ if (activity == null || windowLayoutInfo == null) {
161
+ return@async arrayOf()
162
+ }
163
+
164
+ val layoutInfo = windowLayoutInfo
165
+ if (layoutInfo != null) {
166
+ getWindowRectsFromLayoutInfo(activity, layoutInfo)
167
+ } else {
168
+ arrayOf()
169
+ }
170
+ }
171
+ }
172
+
173
+ private fun getWindowRectsFromLayoutInfo(activity: Activity, layoutInfo: WindowLayoutInfo): Array<DualScreenInfoRect> {
174
+ val rects = mutableListOf<DualScreenInfoRect>()
175
+
176
+ val foldingFeature = getFoldingFeature(layoutInfo)
177
+ if (foldingFeature == null) {
178
+ // No folding feature, return full screen rect
179
+ val screenRect = Rect()
180
+ activity.window.decorView.getWindowVisibleDisplayFrame(screenRect)
181
+ rects.add(rectToDualScreenInfoRect(screenRect))
182
+ return rects.toTypedArray()
183
+ }
184
+
185
+ // Split screen based on folding feature
186
+ val hingeBounds = foldingFeature.bounds
187
+ val screenRect = Rect()
188
+ activity.window.decorView.getWindowVisibleDisplayFrame(screenRect)
189
+
190
+ if (foldingFeature.orientation == FoldingFeature.Orientation.VERTICAL) {
191
+ // Vertical fold - left and right screens
192
+ val leftRect = Rect(screenRect.left, screenRect.top, hingeBounds.left, screenRect.bottom)
193
+ val rightRect = Rect(hingeBounds.right, screenRect.top, screenRect.right, screenRect.bottom)
194
+ rects.add(rectToDualScreenInfoRect(leftRect))
195
+ rects.add(rectToDualScreenInfoRect(rightRect))
196
+ } else {
197
+ // Horizontal fold - top and bottom screens
198
+ val topRect = Rect(screenRect.left, screenRect.top, screenRect.right, hingeBounds.top)
199
+ val bottomRect = Rect(screenRect.left, hingeBounds.bottom, screenRect.right, screenRect.bottom)
200
+ rects.add(rectToDualScreenInfoRect(topRect))
201
+ rects.add(rectToDualScreenInfoRect(bottomRect))
202
+ }
203
+
204
+ return rects.toTypedArray()
205
+ }
206
+
207
+ private fun rectToDualScreenInfoRect(rect: Rect): DualScreenInfoRect {
208
+ return DualScreenInfoRect(
209
+ x = rect.left.toDouble(),
210
+ y = rect.top.toDouble(),
211
+ width = rect.width().toDouble(),
212
+ height = rect.height().toDouble()
213
+ )
214
+ }
215
+
216
+ override fun getHingeBounds(): Promise<DualScreenInfoRect> {
217
+ return Promise.async {
218
+ val layoutInfo = windowLayoutInfo
219
+ if (layoutInfo == null) {
220
+ return@async DualScreenInfoRect(x = 0.0, y = 0.0, width = 0.0, height = 0.0)
221
+ }
222
+
223
+ val foldingFeature = getFoldingFeature(layoutInfo)
224
+ if (foldingFeature != null) {
225
+ val bounds = foldingFeature.bounds
226
+ DualScreenInfoRect(
227
+ x = bounds.left.toDouble(),
228
+ y = bounds.top.toDouble(),
229
+ width = bounds.width().toDouble(),
230
+ height = bounds.height().toDouble()
231
+ )
232
+ } else {
233
+ DualScreenInfoRect(x = 0.0, y = 0.0, width = 0.0, height = 0.0)
234
+ }
235
+ }
236
+ }
237
+
238
+ fun callSpanningChangedListeners(isSpanning: Boolean) {
239
+ for (listener in spanningChangedListeners) {
240
+ listener.callback(isSpanning)
241
+ }
242
+ }
243
+
244
+ override fun addSpanningChangedListener(callback: (isSpanning: Boolean) -> Unit): Double {
245
+ var id = nextListenerId
246
+ nextListenerId++
247
+ val listener = Listener(id, callback)
248
+ spanningChangedListeners.add(listener)
249
+ return id
250
+ }
251
+
252
+ override fun removeSpanningChangedListener(id: Double) {
253
+ spanningChangedListeners.removeIf { it.id == id }
254
+ }
255
+
256
+ private fun startObservingLayoutChanges() {
257
+ if (isObservingLayoutChanges) {
258
+ return
259
+ }
260
+ isObservingLayoutChanges = true
261
+ val activity = getCurrentActivity() ?: return
262
+
263
+ // Window Manager library requires API 24+, but full foldable support is API 30+
264
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
265
+ try {
266
+ windowInfoTracker = WindowInfoTracker.getOrCreate(activity)
267
+
268
+ // Create consumer for window layout info
269
+ layoutInfoConsumer = Consumer<WindowLayoutInfo> { layoutInfo ->
270
+ onWindowLayoutInfoChanged(layoutInfo)
271
+ }
272
+
273
+ // Use main executor for callbacks
274
+ val mainExecutor: Executor = ContextCompat.getMainExecutor(activity)
275
+
276
+ // Subscribe to window layout changes using the Java adapter
277
+ val callbackAdapter = androidx.window.java.layout.WindowInfoTrackerCallbackAdapter(windowInfoTracker!!)
278
+
279
+ callbackAdapter.addWindowLayoutInfoListener(
280
+ activity,
281
+ mainExecutor,
282
+ layoutInfoConsumer!!
283
+ )
284
+ } catch (e: Exception) {
285
+ // Window tracking not supported on this device/API level, ignore
286
+ }
287
+ }
288
+ }
289
+
290
+ private fun stopObservingLayoutChanges() {
291
+ if (!isObservingLayoutChanges) {
292
+ return
293
+ }
294
+ isObservingLayoutChanges = false
295
+ if (windowInfoTracker != null && layoutInfoConsumer != null) {
296
+ try {
297
+ // The listener will be cleaned up when the activity is destroyed
298
+ layoutInfoConsumer = null
299
+ windowInfoTracker = null
300
+ } catch (e: Exception) {
301
+ // Ignore cleanup errors
302
+ }
303
+ }
304
+ }
305
+
306
+ private fun onWindowLayoutInfoChanged(layoutInfo: WindowLayoutInfo) {
307
+ this.windowLayoutInfo = layoutInfo
308
+
309
+ val wasSpanning = this.isSpanning
310
+ this.isSpanning = checkIsSpanning(layoutInfo)
311
+
312
+ // Emit event if spanning state changed
313
+ if (wasSpanning != this.isSpanning) {
314
+ this.callSpanningChangedListeners(this.isSpanning)
315
+ }
316
+ }
317
+
318
+ // MARK: - Background Color
319
+
320
+ override fun changeBackgroundColor(r: Double, g: Double, b: Double, a: Double) {
321
+ val activity = getCurrentActivity() ?: return
322
+ activity.runOnUiThread {
323
+ try {
324
+ val rootView = activity.window.decorView
325
+ rootView.rootView.setBackgroundColor(Color.rgb(r.toInt(), g.toInt(), b.toInt()))
326
+ } catch (e: Exception) {
327
+ e.printStackTrace()
328
+ }
329
+ }
330
+ }
331
+
332
+ override fun onHostResume() {
333
+ startObservingLayoutChanges()
334
+ }
335
+
336
+ override fun onHostPause() {
337
+ stopObservingLayoutChanges()
338
+ }
339
+
340
+ override fun onHostDestroy() {
341
+ stopObservingLayoutChanges()
342
+ }
343
+ }
@@ -0,0 +1,26 @@
1
+ package com.margelo.nitro.reactnativedeviceutils
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+
8
+ class ReactNativeDeviceUtilsPackage : BaseReactPackage() {
9
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
10
+ // Set the React context for the DeviceUtils module
11
+ ReactNativeDeviceUtils.setReactContext(reactContext)
12
+ return null
13
+ }
14
+
15
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
16
+ return ReactModuleInfoProvider { HashMap() }
17
+ }
18
+
19
+ companion object {
20
+ init {
21
+ System.loadLibrary("reactnativedeviceutils")
22
+ }
23
+ }
24
+ }
25
+
26
+
@@ -0,0 +1,41 @@
1
+ import NitroModules
2
+ import UIKit
3
+
4
+ class ReactNativeDeviceUtils: HybridReactNativeDeviceUtilsSpec {
5
+
6
+ public func isDualScreenDevice() throws -> Bool {
7
+ return false
8
+ }
9
+
10
+ public func initEventListeners() throws -> Void {
11
+ }
12
+
13
+ public func isSpanning() throws -> Bool {
14
+ return false
15
+ }
16
+
17
+ public func getWindowRects() throws -> Promise<[DualScreenInfoRect]> {
18
+ return Promise.resolved(withResult: [])
19
+ }
20
+
21
+ public func getHingeBounds() throws -> Promise<DualScreenInfoRect> {
22
+ return Promise.resolved(withResult: DualScreenInfoRect(x: 0, y: 0, width: 0, height: 0))
23
+ }
24
+
25
+
26
+ func addSpanningChangedListener(callback: @escaping (Bool) -> Void) throws -> Double {
27
+ return 0
28
+ }
29
+
30
+ func removeSpanningChangedListener(id: Double) throws -> Void {
31
+ }
32
+
33
+
34
+ public func changeBackgroundColor(r: Double, g: Double, b: Double, a: Double) throws -> Void {
35
+ DispatchQueue.main.async {
36
+ let color = UIColor(red: r/255, green: g/255, blue: b/255, alpha: a/255)
37
+ let rootViewController = UIApplication.shared.delegate?.window??.rootViewController
38
+ rootViewController?.view.backgroundColor = color
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=ReactNativeDeviceUtils.nitro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["ReactNativeDeviceUtils.nitro.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+
3
+ import { NitroModules } from 'react-native-nitro-modules';
4
+ const ReactNativeDeviceUtilsHybridObject = NitroModules.createHybridObject('ReactNativeDeviceUtils');
5
+ export const ReactNativeDeviceUtils = ReactNativeDeviceUtilsHybridObject;
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NitroModules","ReactNativeDeviceUtilsHybridObject","createHybridObject","ReactNativeDeviceUtils"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,4BAA4B;AAGzD,MAAMC,kCAAkC,GACtCD,YAAY,CAACE,kBAAkB,CAA6B,wBAAwB,CAAC;AAEvF,OAAO,MAAMC,sBAAsB,GAAGF,kCAAkC","ignoreList":[]}