@strata-game-library/react-native-plugin 1.0.1 → 1.1.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/android/build.gradle +9 -17
- package/android/src/main/AndroidManifest.xml +3 -3
- package/android/src/main/java/com/strata/reactnative/StrataModule.kt +169 -0
- package/android/src/main/java/com/strata/reactnative/StrataPackage.kt +20 -0
- package/dist/index.d.ts +2 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -56
- package/dist/index.js.map +1 -1
- package/dist/tests/index.test.js +20 -23
- package/dist/tests/index.test.js.map +1 -1
- package/ios/Strata.podspec +22 -0
- package/ios/StrataModule.m +17 -0
- package/ios/StrataModule.swift +143 -0
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
buildscript {
|
|
2
|
-
ext.
|
|
3
|
-
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
-
}
|
|
2
|
+
ext.kotlin_version = '1.9.0'
|
|
5
3
|
repositories {
|
|
6
4
|
google()
|
|
7
5
|
mavenCentral()
|
|
8
6
|
}
|
|
9
7
|
dependencies {
|
|
10
8
|
classpath "com.android.tools.build:gradle:8.1.1"
|
|
11
|
-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
12
10
|
}
|
|
13
11
|
}
|
|
14
12
|
|
|
@@ -20,11 +18,11 @@ def getExtOrDefault(name) {
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
android {
|
|
23
|
-
namespace "com.
|
|
21
|
+
namespace "com.strata.reactnative"
|
|
24
22
|
compileSdkVersion getExtOrDefault("compileSdkVersion") ?: 34
|
|
25
23
|
|
|
26
24
|
defaultConfig {
|
|
27
|
-
minSdkVersion getExtOrDefault("minSdkVersion") ?:
|
|
25
|
+
minSdkVersion getExtOrDefault("minSdkVersion") ?: 24
|
|
28
26
|
targetSdkVersion getExtOrDefault("targetSdkVersion") ?: 34
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -39,28 +37,22 @@ android {
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
compileOptions {
|
|
42
|
-
sourceCompatibility JavaVersion.
|
|
43
|
-
targetCompatibility JavaVersion.
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
kotlinOptions {
|
|
47
|
-
jvmTarget =
|
|
45
|
+
jvmTarget = '17'
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
repositories {
|
|
52
|
-
mavenLocal()
|
|
53
|
-
maven {
|
|
54
|
-
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
|
55
|
-
url("$projectDir/../node_modules/react-native/android")
|
|
56
|
-
}
|
|
57
50
|
google()
|
|
58
51
|
mavenCentral()
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
dependencies {
|
|
62
|
-
|
|
63
|
-
implementation "
|
|
64
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.10"
|
|
55
|
+
implementation 'com.facebook.react:react-native:0.73.0'
|
|
56
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
65
57
|
implementation "androidx.core:core-ktx:1.9.0"
|
|
66
58
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package com.strata.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.*
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.pm.ActivityInfo
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.VibrationEffect
|
|
8
|
+
import android.os.Vibrator
|
|
9
|
+
import android.os.VibratorManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Strata React Native module for Android.
|
|
14
|
+
*
|
|
15
|
+
* Provides device detection, input handling, and haptic feedback.
|
|
16
|
+
*/
|
|
17
|
+
class StrataModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
18
|
+
|
|
19
|
+
override fun getName(): String = "StrataModule"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get device profile information.
|
|
23
|
+
*/
|
|
24
|
+
@ReactMethod
|
|
25
|
+
fun getDeviceProfile(promise: Promise) {
|
|
26
|
+
val result = Arguments.createMap().apply {
|
|
27
|
+
putString("platform", "android")
|
|
28
|
+
putString("deviceType", getDeviceType())
|
|
29
|
+
putBoolean("hasTouch", true)
|
|
30
|
+
putBoolean("hasGamepad", false) // TODO: detect gamepad
|
|
31
|
+
putDouble("screenWidth", getScreenWidth())
|
|
32
|
+
putDouble("screenHeight", getScreenHeight())
|
|
33
|
+
putDouble("pixelRatio", getPixelRatio())
|
|
34
|
+
putMap("safeAreaInsets", getSafeAreaInsets())
|
|
35
|
+
putString("performanceMode", getPerformanceModeInternal())
|
|
36
|
+
}
|
|
37
|
+
promise.resolve(result)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun getPerformanceModeInternal(): String {
|
|
41
|
+
var isLowPowerMode = false
|
|
42
|
+
val powerManager = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as? android.os.PowerManager
|
|
43
|
+
if (powerManager != null) {
|
|
44
|
+
isLowPowerMode = powerManager.isPowerSaveMode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
val mi = android.app.ActivityManager.MemoryInfo()
|
|
48
|
+
val activityManager = reactApplicationContext.getSystemService(Context.ACTIVITY_SERVICE) as? android.app.ActivityManager
|
|
49
|
+
activityManager?.getMemoryInfo(mi)
|
|
50
|
+
|
|
51
|
+
return if (isLowPowerMode || mi.totalMem < 2L * 1024 * 1024 * 1024) {
|
|
52
|
+
"low"
|
|
53
|
+
} else if (mi.totalMem < 4L * 1024 * 1024 * 1024) {
|
|
54
|
+
"medium"
|
|
55
|
+
} else {
|
|
56
|
+
"high"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@ReactMethod
|
|
61
|
+
fun getPerformanceMode(promise: Promise) {
|
|
62
|
+
val map = Arguments.createMap()
|
|
63
|
+
|
|
64
|
+
var isLowPowerMode = false
|
|
65
|
+
val powerManager = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as? android.os.PowerManager
|
|
66
|
+
if (powerManager != null) {
|
|
67
|
+
isLowPowerMode = powerManager.isPowerSaveMode
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
val mi = android.app.ActivityManager.MemoryInfo()
|
|
71
|
+
val activityManager = reactApplicationContext.getSystemService(Context.ACTIVITY_SERVICE) as? android.app.ActivityManager
|
|
72
|
+
activityManager?.getMemoryInfo(mi)
|
|
73
|
+
|
|
74
|
+
map.putString("mode", getPerformanceModeInternal())
|
|
75
|
+
map.putBoolean("isLowPowerMode", isLowPowerMode)
|
|
76
|
+
map.putDouble("totalMemory", mi.totalMem.toDouble())
|
|
77
|
+
promise.resolve(map)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@ReactMethod
|
|
81
|
+
fun setOrientation(orientation: String) {
|
|
82
|
+
val currentActivity = currentActivity ?: return
|
|
83
|
+
val orientationConstant = when (orientation) {
|
|
84
|
+
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
85
|
+
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
86
|
+
else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
|
87
|
+
}
|
|
88
|
+
currentActivity.requestedOrientation = orientationConstant
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Trigger haptic feedback.
|
|
93
|
+
*/
|
|
94
|
+
@ReactMethod
|
|
95
|
+
fun triggerHaptics(intensity: String, promise: Promise) {
|
|
96
|
+
try {
|
|
97
|
+
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
98
|
+
val vibratorManager = reactApplicationContext.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
|
99
|
+
vibratorManager.defaultVibrator
|
|
100
|
+
} else {
|
|
101
|
+
@Suppress("DEPRECATION")
|
|
102
|
+
reactApplicationContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!vibrator.hasVibrator()) {
|
|
106
|
+
promise.resolve(null)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
111
|
+
val effect = when (intensity) {
|
|
112
|
+
"light" -> VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE)
|
|
113
|
+
"heavy" -> VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE)
|
|
114
|
+
else -> VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE) // medium
|
|
115
|
+
}
|
|
116
|
+
vibrator.vibrate(effect)
|
|
117
|
+
} else {
|
|
118
|
+
@Suppress("DEPRECATION")
|
|
119
|
+
val duration = when (intensity) {
|
|
120
|
+
"light" -> 50L
|
|
121
|
+
"heavy" -> 200L
|
|
122
|
+
else -> 100L
|
|
123
|
+
}
|
|
124
|
+
vibrator.vibrate(duration)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
promise.resolve(null)
|
|
128
|
+
} catch (e: Exception) {
|
|
129
|
+
promise.reject("HAPTICS_ERROR", "Failed to trigger haptics: ${e.message}")
|
|
130
|
+
} catch (e: Error) {
|
|
131
|
+
promise.reject("HAPTICS_ERROR", "Critical error triggering haptics: ${e.message}")
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private fun getDeviceType(): String {
|
|
136
|
+
val context = reactApplicationContext
|
|
137
|
+
val metrics = context.resources.displayMetrics
|
|
138
|
+
val widthInches = metrics.widthPixels / metrics.xdpi
|
|
139
|
+
val heightInches = metrics.heightPixels / metrics.ydpi
|
|
140
|
+
val diagonalInches = Math.hypot(widthInches.toDouble(), heightInches.toDouble())
|
|
141
|
+
|
|
142
|
+
return when {
|
|
143
|
+
diagonalInches < 7 -> "mobile"
|
|
144
|
+
diagonalInches < 10 -> "tablet"
|
|
145
|
+
else -> "tablet"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private fun getScreenWidth(): Double {
|
|
150
|
+
return reactApplicationContext.resources.displayMetrics.widthPixels.toDouble()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private fun getScreenHeight(): Double {
|
|
154
|
+
return reactApplicationContext.resources.displayMetrics.heightPixels.toDouble()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private fun getPixelRatio(): Double {
|
|
158
|
+
return reactApplicationContext.resources.displayMetrics.density.toDouble()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun getSafeAreaInsets(): WritableMap {
|
|
162
|
+
return Arguments.createMap().apply {
|
|
163
|
+
putDouble("top", 0.0)
|
|
164
|
+
putDouble("right", 0.0)
|
|
165
|
+
putDouble("bottom", 0.0)
|
|
166
|
+
putDouble("left", 0.0)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.strata.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
|
+
* React Native package for Strata.
|
|
10
|
+
*/
|
|
11
|
+
class StrataPackage : ReactPackage {
|
|
12
|
+
|
|
13
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
14
|
+
return listOf(StrataModule(reactContext))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
18
|
+
return emptyList()
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,8 +43,7 @@ export interface InputSnapshot {
|
|
|
43
43
|
touches: TouchState[];
|
|
44
44
|
}
|
|
45
45
|
export interface HapticsOptions {
|
|
46
|
-
|
|
47
|
-
intensity?: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error';
|
|
46
|
+
intensity?: 'light' | 'medium' | 'heavy';
|
|
48
47
|
customIntensity?: number;
|
|
49
48
|
duration?: number;
|
|
50
49
|
pattern?: number[];
|
|
@@ -73,7 +72,7 @@ export declare function useHaptics(): {
|
|
|
73
72
|
/**
|
|
74
73
|
* Set screen orientation
|
|
75
74
|
*/
|
|
76
|
-
export declare function setOrientation(
|
|
75
|
+
export declare function setOrientation(orientation: 'portrait' | 'landscape' | 'default'): Promise<void>;
|
|
77
76
|
/**
|
|
78
77
|
* Hook for control hints based on device and input
|
|
79
78
|
*/
|
|
@@ -82,10 +81,4 @@ export declare function useControlHints(): {
|
|
|
82
81
|
action: string;
|
|
83
82
|
camera: string;
|
|
84
83
|
};
|
|
85
|
-
/**
|
|
86
|
-
* Subscribe to gamepad connection events
|
|
87
|
-
*/
|
|
88
|
-
export declare function onGamepadUpdate(callback: (event: {
|
|
89
|
-
connected: boolean;
|
|
90
|
-
}) => void): import("react-native").EmitterSubscription | null;
|
|
91
84
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAkB/B,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IACzD,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;IACpC,SAAS,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvD,WAAW,EAAE,UAAU,GAAG,WAAW,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,eAAe,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC5C;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,UAAU,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAyEzC;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,aAAa,CAgCxC;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;CAC7C,CA6DA,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,IAAI;IAAE,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAmBpF;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAIrG;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwBtF"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
|
-
import { NativeModules,
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { NativeModules, Platform, Dimensions, PixelRatio, View } from 'react-native';
|
|
3
|
+
const { StrataModule, StrataReactNativePlugin } = NativeModules;
|
|
4
|
+
const Plugin = StrataModule || StrataReactNativePlugin;
|
|
5
|
+
if (!Plugin && Platform.OS !== 'web') {
|
|
6
|
+
console.warn('StrataModule: Native module not found. Check your native installation.');
|
|
6
7
|
}
|
|
7
|
-
const eventEmitter = RNStrata ? new NativeEventEmitter(RNStrata) : null;
|
|
8
8
|
/**
|
|
9
9
|
* Hook to get and track device information
|
|
10
10
|
*/
|
|
@@ -27,27 +27,45 @@ export function useDevice() {
|
|
|
27
27
|
});
|
|
28
28
|
useEffect(() => {
|
|
29
29
|
const updateDeviceInfo = async () => {
|
|
30
|
-
|
|
30
|
+
const { width, height } = Dimensions.get('window');
|
|
31
|
+
let nativeInfo = {
|
|
32
|
+
deviceType: 'mobile',
|
|
33
|
+
platform: Platform.OS
|
|
34
|
+
};
|
|
35
|
+
if (Plugin) {
|
|
31
36
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
// Try modern getDeviceProfile first
|
|
38
|
+
if (Plugin.getDeviceProfile) {
|
|
39
|
+
nativeInfo = await Plugin.getDeviceProfile();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Fallback to individual calls
|
|
43
|
+
const [info, insets, perf] = await Promise.all([
|
|
44
|
+
Plugin.getDeviceInfo ? Plugin.getDeviceInfo() : Promise.resolve({}),
|
|
45
|
+
Plugin.getSafeAreaInsets ? Plugin.getSafeAreaInsets() : Promise.resolve({}),
|
|
46
|
+
Plugin.getPerformanceMode ? Plugin.getPerformanceMode() : Promise.resolve({})
|
|
47
|
+
]);
|
|
48
|
+
nativeInfo = {
|
|
49
|
+
...info,
|
|
50
|
+
safeAreaInsets: insets,
|
|
51
|
+
performanceMode: perf.mode || perf.performanceMode
|
|
52
|
+
};
|
|
53
|
+
}
|
|
37
54
|
}
|
|
38
55
|
catch (e) {
|
|
39
|
-
console.error('Failed to get native device
|
|
56
|
+
console.error('Failed to get native device info', e);
|
|
40
57
|
}
|
|
41
58
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
setDeviceProfile(prev => ({
|
|
60
|
+
...prev,
|
|
61
|
+
...nativeInfo,
|
|
62
|
+
deviceType: nativeInfo.deviceType || 'mobile',
|
|
63
|
+
orientation: height >= width ? 'portrait' : 'landscape',
|
|
64
|
+
screenWidth: width,
|
|
65
|
+
screenHeight: height,
|
|
66
|
+
platform: Platform.OS,
|
|
67
|
+
performanceMode: nativeInfo.performanceMode || 'high',
|
|
68
|
+
}));
|
|
51
69
|
};
|
|
52
70
|
updateDeviceInfo();
|
|
53
71
|
const subscription = Dimensions.addEventListener('change', updateDeviceInfo);
|
|
@@ -72,27 +90,24 @@ export function useInput() {
|
|
|
72
90
|
touches: [],
|
|
73
91
|
});
|
|
74
92
|
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (_e) {
|
|
88
|
-
// Ignore gamepad errors in poll
|
|
93
|
+
if (!Plugin || Platform.OS === 'web' || !Plugin.getInputSnapshot)
|
|
94
|
+
return;
|
|
95
|
+
const interval = setInterval(async () => {
|
|
96
|
+
try {
|
|
97
|
+
const snapshot = await Plugin.getInputSnapshot();
|
|
98
|
+
if (snapshot) {
|
|
99
|
+
setInput(prev => ({
|
|
100
|
+
...prev,
|
|
101
|
+
...snapshot,
|
|
102
|
+
touches: prev.touches // Keep JS-side touches
|
|
103
|
+
}));
|
|
89
104
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
}
|
|
106
|
+
catch (_e) {
|
|
107
|
+
// Silently fail polling
|
|
108
|
+
}
|
|
109
|
+
}, 16); // ~60fps poll for native input
|
|
110
|
+
return () => clearInterval(interval);
|
|
96
111
|
}, []);
|
|
97
112
|
return input;
|
|
98
113
|
}
|
|
@@ -149,13 +164,18 @@ export const StrataInputProvider = ({ children, onInput }) => {
|
|
|
149
164
|
export function useHaptics() {
|
|
150
165
|
const trigger = useCallback(async (options) => {
|
|
151
166
|
if (Platform.OS === 'web') {
|
|
152
|
-
if ('vibrate' in navigator) {
|
|
167
|
+
if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {
|
|
153
168
|
navigator.vibrate(options.duration || 50);
|
|
154
169
|
}
|
|
155
170
|
return;
|
|
156
171
|
}
|
|
157
|
-
if (
|
|
158
|
-
|
|
172
|
+
if (Plugin) {
|
|
173
|
+
if (Plugin.triggerHaptics) {
|
|
174
|
+
await Plugin.triggerHaptics(options.intensity || 'medium');
|
|
175
|
+
}
|
|
176
|
+
else if (Plugin.triggerHaptic) {
|
|
177
|
+
await Plugin.triggerHaptic(options.intensity || 'medium');
|
|
178
|
+
}
|
|
159
179
|
}
|
|
160
180
|
}, []);
|
|
161
181
|
return { trigger };
|
|
@@ -163,9 +183,10 @@ export function useHaptics() {
|
|
|
163
183
|
/**
|
|
164
184
|
* Set screen orientation
|
|
165
185
|
*/
|
|
166
|
-
export async function setOrientation(
|
|
167
|
-
|
|
168
|
-
|
|
186
|
+
export async function setOrientation(orientation) {
|
|
187
|
+
if (Plugin?.setOrientation) {
|
|
188
|
+
await Plugin.setOrientation(orientation);
|
|
189
|
+
}
|
|
169
190
|
}
|
|
170
191
|
/**
|
|
171
192
|
* Hook for control hints based on device and input
|
|
@@ -192,13 +213,4 @@ export function useControlHints() {
|
|
|
192
213
|
camera: 'Mouse / Right Stick'
|
|
193
214
|
};
|
|
194
215
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Subscribe to gamepad connection events
|
|
197
|
-
*/
|
|
198
|
-
export function onGamepadUpdate(callback) {
|
|
199
|
-
if (eventEmitter) {
|
|
200
|
-
return eventEmitter.addListener('onGamepadUpdate', callback);
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
216
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EACL,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
|
package/dist/tests/index.test.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { renderHook } from '@testing-library/react-hooks/native';
|
|
2
2
|
import { useDevice } from '../index';
|
|
3
|
-
import { NativeModules } from 'react-native';
|
|
4
3
|
jest.mock('react-native', () => ({
|
|
5
4
|
NativeModules: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
StrataModule: {
|
|
6
|
+
getDeviceProfile: jest.fn().mockResolvedValue({
|
|
7
|
+
deviceType: 'mobile',
|
|
8
|
+
platform: 'ios',
|
|
9
|
+
safeAreaInsets: { top: 47, right: 0, bottom: 34, left: 0 },
|
|
10
|
+
performanceMode: 'high'
|
|
11
|
+
}),
|
|
10
12
|
},
|
|
13
|
+
StrataReactNativePlugin: {
|
|
14
|
+
getDeviceInfo: jest.fn(),
|
|
15
|
+
getSafeAreaInsets: jest.fn(),
|
|
16
|
+
getPerformanceMode: jest.fn(),
|
|
17
|
+
}
|
|
11
18
|
},
|
|
12
|
-
NativeEventEmitter: jest.fn(() => ({
|
|
13
|
-
addListener: jest.fn(),
|
|
14
|
-
removeListeners: jest.fn(),
|
|
15
|
-
})),
|
|
16
19
|
Platform: {
|
|
17
20
|
OS: 'ios',
|
|
18
|
-
select: jest.fn(obj => obj.ios),
|
|
21
|
+
select: jest.fn((obj) => obj.ios),
|
|
19
22
|
},
|
|
20
23
|
Dimensions: {
|
|
21
24
|
get: jest.fn(() => ({ width: 390, height: 844 })),
|
|
@@ -24,26 +27,20 @@ jest.mock('react-native', () => ({
|
|
|
24
27
|
PixelRatio: {
|
|
25
28
|
get: jest.fn(() => 3),
|
|
26
29
|
},
|
|
30
|
+
NativeEventEmitter: jest.fn().mockImplementation(() => ({
|
|
31
|
+
addListener: jest.fn(),
|
|
32
|
+
removeAllListeners: jest.fn(),
|
|
33
|
+
})),
|
|
27
34
|
}));
|
|
28
35
|
describe('useDevice', () => {
|
|
29
36
|
it('should return initial device profile', () => {
|
|
30
|
-
const mockDetails = {
|
|
31
|
-
deviceType: 'mobile',
|
|
32
|
-
platform: 'ios',
|
|
33
|
-
inputMode: 'touch',
|
|
34
|
-
orientation: 'portrait',
|
|
35
|
-
hasTouch: true,
|
|
36
|
-
hasGamepad: false,
|
|
37
|
-
screenWidth: 390,
|
|
38
|
-
screenHeight: 844,
|
|
39
|
-
pixelRatio: 3,
|
|
40
|
-
safeAreaInsets: { top: 47, right: 0, bottom: 34, left: 0 }
|
|
41
|
-
};
|
|
42
|
-
NativeModules.RNStrata.getDeviceDetails.mockResolvedValue(mockDetails);
|
|
43
37
|
const { result } = renderHook(() => useDevice());
|
|
44
38
|
// Initial state
|
|
45
39
|
expect(result.current.platform).toBe('ios');
|
|
46
40
|
expect(result.current.hasTouch).toBe(true);
|
|
41
|
+
expect(result.current.screenWidth).toBe(390);
|
|
42
|
+
expect(result.current.screenHeight).toBe(844);
|
|
43
|
+
expect(result.current.pixelRatio).toBe(3);
|
|
47
44
|
});
|
|
48
45
|
});
|
|
49
46
|
//# sourceMappingURL=index.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/tests/index.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/tests/index.test.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/B,aAAa,EAAE;QACb,YAAY,EAAE;YACZ,gBAAgB,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;gBAC5C,UAAU,EAAE,QAAQ;gBACpB,QAAQ,EAAE,KAAK;gBACf,cAAc,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;gBAC1D,eAAe,EAAE,MAAM;aACxB,CAAC;SACH;QACD,uBAAuB,EAAE;YACvB,aAAa,EAAE,IAAI,CAAC,EAAE,EAAE;YACxB,iBAAiB,EAAE,IAAI,CAAC,EAAE,EAAE;YAC5B,kBAAkB,EAAE,IAAI,CAAC,EAAE,EAAE;SAC9B;KACF;IACD,QAAQ,EAAE;QACR,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,GAA4B,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC;KAC3D;IACD,UAAU,EAAE;QACV,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACjD,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;KACzD;IACD,UAAU,EAAE;QACV,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;KACtB;IACD,kBAAkB,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,CAAC;QACtD,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE;QACtB,kBAAkB,EAAE,IAAI,CAAC,EAAE,EAAE;KAC9B,CAAC,CAAC;CACJ,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;QAEjD,gBAAgB;QAChB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
5
|
+
rescue => e
|
|
6
|
+
raise "Failed to read package.json: #{e.message}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Pod::Spec.new do |s|
|
|
10
|
+
s.name = 'Strata'
|
|
11
|
+
s.version = package['version'] || '1.0.0'
|
|
12
|
+
s.summary = package['description'] || 'React Native plugin for Strata 3D'
|
|
13
|
+
s.homepage = package['homepage'] || ''
|
|
14
|
+
s.license = package['license'] || 'MIT'
|
|
15
|
+
s.author = package['author'] || 'JB.com'
|
|
16
|
+
s.source = { :git => (package.dig('repository', 'url') || ''), :tag => s.version }
|
|
17
|
+
s.source_files = '*.{h,m,swift}'
|
|
18
|
+
s.platform = :ios, '14.0'
|
|
19
|
+
s.swift_version = '5.0'
|
|
20
|
+
|
|
21
|
+
s.dependency 'React-Core'
|
|
22
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(StrataModule, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(getDeviceProfile:(RCTPromiseResolveBlock)resolve
|
|
6
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
7
|
+
|
|
8
|
+
RCT_EXTERN_METHOD(triggerHaptics:(NSString *)intensity
|
|
9
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
10
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
|
|
12
|
+
RCT_EXTERN_METHOD(getPerformanceMode:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(setOrientation:(NSString *)orientation)
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Strata React Native module for iOS.
|
|
6
|
+
*
|
|
7
|
+
* Provides device detection, input handling, and haptic feedback.
|
|
8
|
+
*/
|
|
9
|
+
@objc(StrataModule)
|
|
10
|
+
class StrataModule: NSObject {
|
|
11
|
+
|
|
12
|
+
@objc
|
|
13
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get device profile information.
|
|
19
|
+
*/
|
|
20
|
+
@objc
|
|
21
|
+
func getDeviceProfile(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
22
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
23
|
+
DispatchQueue.main.async {
|
|
24
|
+
let screen = UIScreen.main
|
|
25
|
+
|
|
26
|
+
let result: [String: Any] = [
|
|
27
|
+
"platform": "ios",
|
|
28
|
+
"deviceType": self.getDeviceType(),
|
|
29
|
+
"hasTouch": true,
|
|
30
|
+
"hasGamepad": false, // TODO: detect MFi controller
|
|
31
|
+
"screenWidth": screen.bounds.width,
|
|
32
|
+
"screenHeight": screen.bounds.height,
|
|
33
|
+
"pixelRatio": screen.scale,
|
|
34
|
+
"safeAreaInsets": self.getSafeAreaInsets(),
|
|
35
|
+
"performanceMode": self.getPerformanceModeInternal()
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
resolve(result)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private func getPerformanceModeInternal() -> String {
|
|
43
|
+
let isLowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled
|
|
44
|
+
if isLowPowerMode {
|
|
45
|
+
return "low"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let physicalMemory = ProcessInfo.processInfo.physicalMemory
|
|
49
|
+
if physicalMemory < 2 * 1024 * 1024 * 1024 {
|
|
50
|
+
return "low"
|
|
51
|
+
} else if physicalMemory < 4 * 1024 * 1024 * 1024 {
|
|
52
|
+
return "medium"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return "high"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@objc
|
|
59
|
+
func getPerformanceMode(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
60
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
61
|
+
let isLowPowerMode = ProcessInfo.processInfo.isLowPowerModeEnabled
|
|
62
|
+
|
|
63
|
+
resolve([
|
|
64
|
+
"mode": self.getPerformanceModeInternal(),
|
|
65
|
+
"isLowPowerMode": isLowPowerMode,
|
|
66
|
+
"totalMemory": ProcessInfo.processInfo.physicalMemory
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@objc
|
|
71
|
+
func setOrientation(_ orientation: String) {
|
|
72
|
+
DispatchQueue.main.async {
|
|
73
|
+
var orientationValue: UIInterfaceOrientation = .unknown
|
|
74
|
+
if orientation == "portrait" {
|
|
75
|
+
orientationValue = .portrait
|
|
76
|
+
} else if orientation == "landscape" {
|
|
77
|
+
orientationValue = .landscapeLeft
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if #available(iOS 16.0, *) {
|
|
81
|
+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
82
|
+
let window = windowScene.windows.first
|
|
83
|
+
window?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
UIDevice.current.setValue(orientationValue.rawValue, forKey: "orientation")
|
|
87
|
+
UIViewController.attemptRotationToDeviceOrientation()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Trigger haptic feedback.
|
|
94
|
+
*/
|
|
95
|
+
@objc
|
|
96
|
+
func triggerHaptics(_ intensity: String,
|
|
97
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
98
|
+
reject: @escaping RCTPromiseRejectBlock) {
|
|
99
|
+
DispatchQueue.main.async {
|
|
100
|
+
let generator: UIImpactFeedbackGenerator
|
|
101
|
+
|
|
102
|
+
switch intensity {
|
|
103
|
+
case "heavy":
|
|
104
|
+
generator = UIImpactFeedbackGenerator(style: .heavy)
|
|
105
|
+
case "light":
|
|
106
|
+
generator = UIImpactFeedbackGenerator(style: .light)
|
|
107
|
+
default:
|
|
108
|
+
generator = UIImpactFeedbackGenerator(style: .medium)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
generator.prepare()
|
|
112
|
+
generator.impactOccurred()
|
|
113
|
+
|
|
114
|
+
resolve(nil)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private func getDeviceType() -> String {
|
|
119
|
+
switch UIDevice.current.userInterfaceIdiom {
|
|
120
|
+
case .phone:
|
|
121
|
+
return "mobile"
|
|
122
|
+
case .pad:
|
|
123
|
+
return "tablet"
|
|
124
|
+
default:
|
|
125
|
+
return "mobile"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private func getSafeAreaInsets() -> [String: CGFloat] {
|
|
130
|
+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
131
|
+
let window = windowScene.windows.first else {
|
|
132
|
+
return ["top": 0, "right": 0, "bottom": 0, "left": 0]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let insets = window.safeAreaInsets
|
|
136
|
+
return [
|
|
137
|
+
"top": insets.top,
|
|
138
|
+
"right": insets.right,
|
|
139
|
+
"bottom": insets.bottom,
|
|
140
|
+
"left": insets.left
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strata-game-library/react-native-plugin",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React Native plugin for Strata 3D - cross-platform input, device detection, and haptics for mobile games",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|