catalyst-core 0.0.3-canary.7 โ 0.0.3-canary.9
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/changelog.md +45 -25
- package/dist/native/androidProject/.idea/gradle.xml +0 -1
- package/dist/native/androidProject/.idea/misc.xml +7 -1
- package/dist/native/androidProject/app/build.gradle.kts +44 -26
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +59 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +27 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +11 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +4 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DeviceInfoUtils.kt +60 -0
- package/dist/native/androidProject/app/src/main/res/drawable/splashscreen_fallback.png +0 -0
- package/dist/native/androidProject/app/src/main/res/values/strings.xml +1 -1
- package/dist/native/androidProject/settings.gradle.kts +1 -1
- package/dist/native/bridge/WebBridge.js +10 -6
- package/dist/native/bridge/constants/NativeInterfaces.js +16 -14
- package/dist/native/bridge/errors.js +20 -20
- package/dist/native/bridge/hooks.js +17 -17
- package/dist/native/bridge/standardizedHooks.js +25 -25
- package/dist/native/bridge/useBaseHook.js +12 -12
- package/dist/native/bridge/utils/NativeBridge.js +18 -13
- package/dist/native/buildAppAndroid.js +64 -40
- package/package.json +1 -1
- package/dist/native/androidProject/.idea/deviceManager.xml +0 -13
package/changelog.md
CHANGED
|
@@ -1,44 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3-canary.9] - 2025-09-11
|
|
4
|
+
|
|
5
|
+
- **App name configuration**
|
|
6
|
+
- **Device Info API**
|
|
7
|
+
- **Access control configuration**
|
|
8
|
+
|
|
9
|
+
## [0.0.3-canary.8] - 2025-01-06
|
|
10
|
+
|
|
11
|
+
- **App icon configuration**
|
|
12
|
+
- **Splash screen integration**
|
|
13
|
+
- **Url Whitelisting**
|
|
3
14
|
|
|
4
15
|
## [0.0.3-canary.7] - 2025-01-06
|
|
5
16
|
|
|
6
17
|
### ๐ Features
|
|
7
|
-
- **Native API Integration**: Enhanced native module integration for better performance
|
|
8
|
-
- **Automated IP Resolution**: Intelligent network detection eliminates manual IP configuration
|
|
9
|
-
- **Universal Build System**: Streamlined one-command builds for both Android and iOS platforms
|
|
10
|
-
- **Enhanced CLI Interface**: Improved command-line tools for better developer experience
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Improved
|
|
19
|
+
- **Native API Integration**: Enhanced native module integration for better performance
|
|
20
|
+
- **Automated IP Resolution**: Intelligent network detection eliminates manual IP configuration
|
|
21
|
+
- **Universal Build System**: Streamlined one-command builds for both Android and iOS platforms
|
|
22
|
+
- **Enhanced CLI Interface**: Improved command-line tools for better developer experience
|
|
23
|
+
|
|
24
|
+
### ๐ Bug Fixes
|
|
25
|
+
|
|
26
|
+
- Fixed IP address detection issues on development environments
|
|
27
|
+
- Resolved build configuration conflicts between platforms
|
|
28
|
+
- Improved error handling and recovery mechanisms
|
|
16
29
|
|
|
17
30
|
### ๐ง Improvements
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
31
|
+
|
|
32
|
+
- **Performance**: Optimized native module loading and execution
|
|
33
|
+
- **Developer Experience**: Enhanced CLI with better error messages and debugging info
|
|
34
|
+
- **Configuration**: Dynamic config updates without requiring restarts
|
|
35
|
+
- **Build Process**: Faster and more reliable build pipeline
|
|
22
36
|
|
|
23
37
|
### ๐งช Testing & Validation
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
38
|
+
|
|
39
|
+
- โ
**Android Build**: Verified APK generation, installation, and runtime behavior
|
|
40
|
+
- โ
**iOS Build**: Tested build process and device compatibility
|
|
41
|
+
- โ
**IP Detection**: Validated automatic network resolution across platforms
|
|
42
|
+
- โ
**Native Modules**: Confirmed API integration and performance benchmarks
|
|
43
|
+
- โ
**CLI Operations**: Tested enhanced command-line workflows and error handling
|
|
29
44
|
|
|
30
45
|
### ๐ Breaking Changes
|
|
46
|
+
|
|
31
47
|
**None** - All changes maintain backward compatibility while enhancing existing functionality
|
|
32
48
|
|
|
33
49
|
### ๐ Technical Details
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
50
|
+
|
|
51
|
+
- Improved native API bridge for better cross-platform communication
|
|
52
|
+
- Enhanced error reporting with detailed stack traces and suggestions
|
|
53
|
+
- Optimized build configuration for faster development cycles
|
|
54
|
+
- Added comprehensive logging for debugging and monitoring
|
|
38
55
|
|
|
39
56
|
### ๐ Migration Notes
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
57
|
+
|
|
58
|
+
- No migration steps required
|
|
59
|
+
- Existing projects will automatically benefit from improvements
|
|
60
|
+
- Optional: Update CLI usage to leverage new enhanced features
|
|
61
|
+
|
|
43
62
|
## Version
|
|
44
|
-
|
|
63
|
+
|
|
64
|
+
- Target version: 0.0.3-canary.7
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1
2
|
<project version="4">
|
|
2
3
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
3
|
-
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"
|
|
4
|
+
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
|
5
|
+
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
6
|
+
</component>
|
|
7
|
+
<component name="ProjectType">
|
|
8
|
+
<option name="id" value="Android" />
|
|
9
|
+
</component>
|
|
4
10
|
</project>
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import java.net.NetworkInterface
|
|
2
2
|
import java.io.File
|
|
3
3
|
import java.util.Properties
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
|
|
7
|
+
buildscript {
|
|
8
|
+
dependencies {
|
|
9
|
+
classpath("org.json:json:20231013")
|
|
10
|
+
}
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
val configPath: String? by project.properties
|
|
6
14
|
val keystorePassword: String? by project.properties // Changed from keyStorePassword to keystorePassword
|
|
@@ -133,6 +141,7 @@ dependencies {
|
|
|
133
141
|
androidTestImplementation(libs.androidx.espresso.core)
|
|
134
142
|
implementation("androidx.webkit:webkit:1.12.1")
|
|
135
143
|
implementation("org.json:json:20231013")
|
|
144
|
+
implementation("androidx.core:core-splashscreen:1.0.1")
|
|
136
145
|
|
|
137
146
|
// Ktor Server dependencies for FrameworkServer (~200KB total)
|
|
138
147
|
implementation("io.ktor:ktor-server-core:2.3.7")
|
|
@@ -196,12 +205,15 @@ tasks.register("generateWebViewConfig") {
|
|
|
196
205
|
}
|
|
197
206
|
|
|
198
207
|
val configContent = configJsonFile.readText()
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
|
|
209
|
+
val jsonObject = JSONObject(configContent)
|
|
210
|
+
|
|
211
|
+
if (!jsonObject.has("WEBVIEW_CONFIG")) {
|
|
212
|
+
throw GradleException("WEBVIEW_CONFIG not found in config file")
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
val webviewConfig = jsonObject.getJSONObject("WEBVIEW_CONFIG")
|
|
216
|
+
|
|
205
217
|
val properties = Properties()
|
|
206
218
|
|
|
207
219
|
// Set different IP based on build type
|
|
@@ -215,30 +227,36 @@ tasks.register("generateWebViewConfig") {
|
|
|
215
227
|
properties.setProperty("buildOptimisation", "false")
|
|
216
228
|
}
|
|
217
229
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
fun extractProperties(jsonObj: org.json.JSONObject, prefix: String = "") {
|
|
231
|
+
try {
|
|
232
|
+
val keys = jsonObj.keys()
|
|
233
|
+
|
|
234
|
+
while (keys.hasNext()) {
|
|
235
|
+
val key = keys.next()
|
|
236
|
+
val value = jsonObj.opt(key)
|
|
237
|
+
val fullKey = if (prefix.isEmpty()) key else "$prefix.$key"
|
|
238
|
+
|
|
239
|
+
when (value) {
|
|
240
|
+
is org.json.JSONObject -> {
|
|
241
|
+
extractProperties(value, fullKey)
|
|
242
|
+
}
|
|
243
|
+
is org.json.JSONArray -> {
|
|
244
|
+
val arrayValues = (0 until value.length()).map { i ->
|
|
245
|
+
value.opt(i).toString()
|
|
246
|
+
}.joinToString(",")
|
|
247
|
+
properties.setProperty(fullKey, arrayValues)
|
|
248
|
+
}
|
|
249
|
+
else -> {
|
|
250
|
+
properties.setProperty(fullKey, value.toString())
|
|
251
|
+
}
|
|
252
|
+
}
|
|
225
253
|
}
|
|
254
|
+
} catch (e: Exception) {
|
|
255
|
+
throw RuntimeException("Failed to extract properties from JSON at prefix: $prefix", e)
|
|
226
256
|
}
|
|
227
257
|
}
|
|
228
258
|
|
|
229
|
-
|
|
230
|
-
val androidRegex = """"android"\s*:\s*\{([^}]*)\}""".toRegex()
|
|
231
|
-
val androidMatch = androidRegex.find(webviewConfigContent ?: "")
|
|
232
|
-
val androidContent = androidMatch?.groupValues?.get(1)
|
|
233
|
-
|
|
234
|
-
// Parse android object properties
|
|
235
|
-
val androidPropsRegex = """"([^"]+)"\s*:\s*"([^"]+)"""".toRegex()
|
|
236
|
-
androidContent?.let {
|
|
237
|
-
androidPropsRegex.findAll(it).forEach { matchResult ->
|
|
238
|
-
val (key, value) = matchResult.destructured
|
|
239
|
-
properties.setProperty("android.$key", value)
|
|
240
|
-
}
|
|
241
|
-
}
|
|
259
|
+
extractProperties(webviewConfig)
|
|
242
260
|
|
|
243
261
|
// Set production-specific properties for release builds
|
|
244
262
|
if (gradle.startParameter.taskNames.any { it.contains("Release") || it.contains("release") }) {
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt
CHANGED
|
@@ -2,6 +2,7 @@ package io.yourname.androidproject
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
5
6
|
import android.graphics.Bitmap
|
|
6
7
|
import android.net.Uri
|
|
7
8
|
import android.os.Bundle
|
|
@@ -11,6 +12,8 @@ import android.webkit.*
|
|
|
11
12
|
import android.widget.ProgressBar
|
|
12
13
|
import androidx.webkit.WebViewAssetLoader
|
|
13
14
|
import io.yourname.androidproject.WebCacheManager
|
|
15
|
+
import io.yourname.androidproject.isUrlAllowed
|
|
16
|
+
import io.yourname.androidproject.isExternalDomain
|
|
14
17
|
import kotlinx.coroutines.*
|
|
15
18
|
import java.util.Properties
|
|
16
19
|
|
|
@@ -34,6 +37,8 @@ class CustomWebView(
|
|
|
34
37
|
private var isInitialPageLoaded: Boolean = false
|
|
35
38
|
private var buildOptimisation: Boolean = false // Added property for build optimization
|
|
36
39
|
private lateinit var assetLoader: WebViewAssetLoader
|
|
40
|
+
private var allowedUrls: List<String> = emptyList()
|
|
41
|
+
private var accessControlEnabled: Boolean = false
|
|
37
42
|
|
|
38
43
|
// Counters for asset loading statistics
|
|
39
44
|
private var assetLoadAttempts = 0
|
|
@@ -56,6 +61,13 @@ class CustomWebView(
|
|
|
56
61
|
// Parse buildOptimisation property
|
|
57
62
|
buildOptimisation = properties.getProperty("buildOptimisation", "false").toBoolean()
|
|
58
63
|
|
|
64
|
+
// Load access control settings from properties
|
|
65
|
+
accessControlEnabled = properties.getProperty("accessControl.enabled", "false").toBoolean()
|
|
66
|
+
allowedUrls = properties.getProperty("accessControl.allowedUrls", "")
|
|
67
|
+
.split(",")
|
|
68
|
+
.map { it.trim() }
|
|
69
|
+
.filter { it.isNotEmpty() }
|
|
70
|
+
|
|
59
71
|
// Set initial flags based on buildOptimisation
|
|
60
72
|
if (buildOptimisation) {
|
|
61
73
|
isInitialApiCalled = false
|
|
@@ -71,6 +83,8 @@ class CustomWebView(
|
|
|
71
83
|
Log.d(TAG, "Cache Pattern: $cachePatterns")
|
|
72
84
|
Log.d(TAG, "API Base URL: $apiBaseUrl")
|
|
73
85
|
Log.d(TAG, "Build Optimisation: $buildOptimisation")
|
|
86
|
+
Log.d(TAG, "Access Control Enabled: $accessControlEnabled")
|
|
87
|
+
Log.d(TAG, "Allowed URLs: $allowedUrls")
|
|
74
88
|
Log.d(TAG, "Initial API Called: $isInitialApiCalled")
|
|
75
89
|
Log.d(TAG, "Initial Page Loaded: $isInitialPageLoaded")
|
|
76
90
|
}
|
|
@@ -198,6 +212,22 @@ class CustomWebView(
|
|
|
198
212
|
}
|
|
199
213
|
}
|
|
200
214
|
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
private fun openInInAppBrowser(url: String) {
|
|
218
|
+
try {
|
|
219
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
220
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
221
|
+
context.startActivity(intent)
|
|
222
|
+
if (BuildConfig.DEBUG) {
|
|
223
|
+
Log.d(TAG, "๐ Opening external URL in in-app browser: $url")
|
|
224
|
+
}
|
|
225
|
+
} catch (e: Exception) {
|
|
226
|
+
Log.e(TAG, "โ Failed to open URL in in-app browser: $url", e)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
201
231
|
private fun isApiCall(url: String): Boolean {
|
|
202
232
|
// Check if URL is an API call based on your API base URL
|
|
203
233
|
return apiBaseUrl.isNotEmpty() && url.startsWith(apiBaseUrl)
|
|
@@ -290,6 +320,27 @@ class CustomWebView(
|
|
|
290
320
|
webView.webViewClient = object : WebViewClient() {
|
|
291
321
|
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
|
292
322
|
request?.url?.let { url ->
|
|
323
|
+
val urlString = url.toString()
|
|
324
|
+
|
|
325
|
+
if (accessControlEnabled) {
|
|
326
|
+
// Check if URL is an external domain
|
|
327
|
+
if (url.scheme in listOf("http", "https") && isExternalDomain(urlString, allowedUrls)) {
|
|
328
|
+
if (BuildConfig.DEBUG) {
|
|
329
|
+
Log.d(TAG, "๐ External domain detected, opening in in-app browser: $urlString")
|
|
330
|
+
}
|
|
331
|
+
openInInAppBrowser(urlString)
|
|
332
|
+
return true
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Check if URL is allowed for internal navigation
|
|
336
|
+
if (!isUrlAllowed(urlString, allowedUrls)) {
|
|
337
|
+
if (BuildConfig.DEBUG) {
|
|
338
|
+
Log.w(TAG, "๐ซ URL blocked by access control: $urlString")
|
|
339
|
+
}
|
|
340
|
+
return true
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
293
344
|
// Let WebView handle loading non-API HTTP/HTTPS URLs
|
|
294
345
|
if (url.scheme in listOf("http", "https")) {
|
|
295
346
|
return false
|
|
@@ -307,6 +358,14 @@ class CustomWebView(
|
|
|
307
358
|
Log.d(TAG, "๐ Intercepting request for: $url on thread: ${Thread.currentThread().name}")
|
|
308
359
|
}
|
|
309
360
|
|
|
361
|
+
if (accessControlEnabled && !isUrlAllowed(url, allowedUrls)) {
|
|
362
|
+
if (BuildConfig.DEBUG) {
|
|
363
|
+
Log.w(TAG, "๐ซ Network request blocked by access control: $url")
|
|
364
|
+
}
|
|
365
|
+
// Return an empty response to block the request
|
|
366
|
+
return WebResourceResponse("text/plain", "utf-8", null)
|
|
367
|
+
}
|
|
368
|
+
|
|
310
369
|
// Handle the initial route request - intercept first request regardless of host
|
|
311
370
|
if (!isInitialApiCalled && request.method == "GET") {
|
|
312
371
|
isInitialApiCalled = true
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt
CHANGED
|
@@ -10,6 +10,7 @@ import io.yourname.androidproject.NativeBridge
|
|
|
10
10
|
import kotlinx.coroutines.CoroutineScope
|
|
11
11
|
import kotlinx.coroutines.MainScope
|
|
12
12
|
import kotlinx.coroutines.cancelChildren
|
|
13
|
+
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
13
14
|
|
|
14
15
|
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
20
21
|
private lateinit var properties: Properties
|
|
21
22
|
private var isHardwareAccelerationEnabled = false
|
|
22
23
|
private var currentUrl: String = ""
|
|
24
|
+
private var splashStartTime: Long = 0
|
|
23
25
|
|
|
24
26
|
private fun enableHardwareAcceleration() {
|
|
25
27
|
if (!isHardwareAccelerationEnabled) {
|
|
@@ -45,6 +47,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
50
|
+
val splashScreen = installSplashScreen()
|
|
48
51
|
super.onCreate(savedInstanceState)
|
|
49
52
|
if (BuildConfig.DEBUG) {
|
|
50
53
|
Log.d(TAG, "๐ฑ onCreate started on thread: ${Thread.currentThread().name}")
|
|
@@ -63,6 +66,10 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
63
66
|
properties.setProperty("buildOptimisation", (!BuildConfig.DEBUG).toString())
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
// Configure splash screen
|
|
70
|
+
splashStartTime = System.currentTimeMillis()
|
|
71
|
+
configureSplashScreen(splashScreen)
|
|
72
|
+
|
|
66
73
|
// Setup UI
|
|
67
74
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
68
75
|
supportActionBar?.hide()
|
|
@@ -108,7 +115,9 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
108
115
|
// Use the local development server in debug mode
|
|
109
116
|
val local_ip = properties.getProperty("LOCAL_IP", "localhost")
|
|
110
117
|
val port = properties.getProperty("port", "3005")
|
|
111
|
-
|
|
118
|
+
val useHttps = properties.getProperty("useHttps", "false").toBoolean()
|
|
119
|
+
val protocol = if (useHttps) "https" else "http"
|
|
120
|
+
currentUrl = "$protocol://$local_ip:$port"
|
|
112
121
|
} else {
|
|
113
122
|
// In production, use the configured production URL or fallback to a file:// URL
|
|
114
123
|
currentUrl = properties.getProperty("PRODUCTION_URL", "")
|
|
@@ -159,4 +168,21 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
159
168
|
customWebView.destroy()
|
|
160
169
|
super.onDestroy()
|
|
161
170
|
}
|
|
171
|
+
|
|
172
|
+
private fun configureSplashScreen(splashScreen: androidx.core.splashscreen.SplashScreen) {
|
|
173
|
+
val durationProperty = properties.getProperty("splashScreen.duration")
|
|
174
|
+
|
|
175
|
+
splashScreen.setKeepOnScreenCondition {
|
|
176
|
+
val webViewLoaded = ::customWebView.isInitialized &&
|
|
177
|
+
customWebView.getWebView().progress >= 100
|
|
178
|
+
|
|
179
|
+
if (durationProperty != null) {
|
|
180
|
+
val duration = durationProperty.toLong()
|
|
181
|
+
val timeElapsed = System.currentTimeMillis() - splashStartTime >= duration
|
|
182
|
+
!timeElapsed
|
|
183
|
+
} else {
|
|
184
|
+
!webViewLoaded
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
162
188
|
}
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt
CHANGED
|
@@ -132,6 +132,17 @@ class NativeBridge(private val mainActivity: MainActivity, private val webView:
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
@JavascriptInterface
|
|
136
|
+
fun getDeviceInfo(options: String?) {
|
|
137
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_DEVICE_INFO_ERROR, "get device info") {
|
|
138
|
+
mainActivity.runOnUiThread {
|
|
139
|
+
val deviceInfo = DeviceInfoUtils.getDeviceInfo(mainActivity)
|
|
140
|
+
BridgeUtils.logDebug(TAG, "Device info retrieved: $deviceInfo")
|
|
141
|
+
BridgeUtils.notifyWeb(webView, BridgeUtils.WebEvents.ON_DEVICE_INFO_SUCCESS, deviceInfo.toString())
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
135
146
|
private fun initializeCameraLauncher() {
|
|
136
147
|
cameraLauncher = mainActivity.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
|
137
148
|
CameraUtils.processCameraResult(mainActivity, webView, currentPhotoUri, success)
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt
CHANGED
|
@@ -48,6 +48,10 @@ object BridgeUtils {
|
|
|
48
48
|
ON_FRAMEWORK_SERVER_READY("ON_FRAMEWORK_SERVER_READY"),
|
|
49
49
|
ON_FRAMEWORK_SERVER_ERROR("ON_FRAMEWORK_SERVER_ERROR"),
|
|
50
50
|
ON_FRAMEWORK_SERVER_STOPPED("ON_FRAMEWORK_SERVER_STOPPED"),
|
|
51
|
+
|
|
52
|
+
// Device info events
|
|
53
|
+
ON_DEVICE_INFO_SUCCESS("ON_DEVICE_INFO_SUCCESS"),
|
|
54
|
+
ON_DEVICE_INFO_ERROR("ON_DEVICE_INFO_ERROR"),
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package io.yourname.androidproject.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.util.DisplayMetrics
|
|
6
|
+
import android.view.WindowManager
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
import io.yourname.androidproject.BuildConfig
|
|
9
|
+
import android.hardware.display.DisplayManager
|
|
10
|
+
|
|
11
|
+
object DeviceInfoUtils {
|
|
12
|
+
private const val TAG = "DeviceInfoUtils"
|
|
13
|
+
|
|
14
|
+
fun getDeviceInfo(context: Context): JSONObject {
|
|
15
|
+
val deviceInfo = JSONObject()
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Basic device information
|
|
19
|
+
deviceInfo.apply {
|
|
20
|
+
put("model", Build.MODEL)
|
|
21
|
+
put("manufacturer", Build.MANUFACTURER)
|
|
22
|
+
put("platform", "android")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Screen dimensions only
|
|
26
|
+
val displayMetrics = getDisplayMetrics(context)
|
|
27
|
+
deviceInfo.apply {
|
|
28
|
+
put("screenWidth", displayMetrics.widthPixels)
|
|
29
|
+
put("screenHeight", displayMetrics.heightPixels)
|
|
30
|
+
put("screenDensity", displayMetrics.density.toDouble())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
} catch (e: Exception) {
|
|
34
|
+
BridgeUtils.logError(TAG, "Error getting device info", e)
|
|
35
|
+
return JSONObject().apply {
|
|
36
|
+
put("error", "Failed to get device info: ${e.message}")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return deviceInfo
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun getDisplayMetrics(context: Context): DisplayMetrics {
|
|
44
|
+
val displayMetrics = DisplayMetrics()
|
|
45
|
+
|
|
46
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
47
|
+
// Use modern Display API for Android 11+
|
|
48
|
+
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
|
49
|
+
displayManager.getDisplay(0)?.getMetrics(displayMetrics)
|
|
50
|
+
} else {
|
|
51
|
+
// Fallback to deprecated API for older versions
|
|
52
|
+
@Suppress("DEPRECATION")
|
|
53
|
+
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
54
|
+
@Suppress("DEPRECATION")
|
|
55
|
+
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return displayMetrics
|
|
59
|
+
}
|
|
60
|
+
}
|
|
Binary file
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _NativeInterfaces=require("./constants/NativeInterfaces.js");class WebBridge{constructor(){this.handlers=new Map();this.initialized=false;this._logInitialization();}/**
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _NativeInterfaces=require("./constants/NativeInterfaces.js");var _NativeBridge=_interopRequireDefault(require("./utils/NativeBridge.js"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}class WebBridge{constructor(){this.handlers=new Map();this.initialized=false;this._logInitialization();}/**
|
|
2
2
|
* Log initialization info
|
|
3
|
-
*/_logInitialization(){console.log("๐ WebBridge initialized");console.log("๐ Available callback interfaces:",_NativeInterfaces.ALL_CALLBACKS.length);if(process.env.NODE_ENV===
|
|
3
|
+
*/_logInitialization(){console.log("๐ WebBridge initialized");console.log("๐ Available callback interfaces:",_NativeInterfaces.ALL_CALLBACKS.length);if(process.env.NODE_ENV==="development"){(0,_NativeInterfaces.debugInterfaces)();}}/**
|
|
4
4
|
* Static method to initialize WebBridge on window object
|
|
5
|
-
*/static init=()=>{if(typeof window===
|
|
5
|
+
*/static init=()=>{if(typeof window==="undefined"){console.error("๐ WebBridge cannot be initialized outside the browser!");return null;}if(window.WebBridge){console.warn("๐ WebBridge already initialized!");return window.WebBridge;}const bridge=new WebBridge();window.WebBridge=bridge;bridge.initialized=true;const{platform}=_NativeBridge.default.getEnvironmentInfo();console.log("๐ WebBridge created and attached to window");return{bridge,platform,getDeviceInfo:bridge.getDeviceInfo};};/**
|
|
6
6
|
* Main callback handler called by native platforms
|
|
7
7
|
* @param {string} interfaceName - The callback interface name
|
|
8
8
|
* @param {any} data - Data from native platform
|
|
9
|
-
*/callback=(interfaceName,data)=>{console.log(`๐ WebBridge callback: ${interfaceName}`,data?{data}:
|
|
9
|
+
*/callback=(interfaceName,data)=>{console.log(`๐ WebBridge callback: ${interfaceName}`,data?{data}:"");// Validate interface
|
|
10
10
|
if(!(0,_NativeInterfaces.isValidCallback)(interfaceName)){console.error(`๐ Invalid callback interface: ${interfaceName}`);console.log("๐ Available callbacks:",_NativeInterfaces.ALL_CALLBACKS);return;}if(!this.handlers.has(interfaceName)){console.warn(`๐ No handler registered for interface: ${interfaceName}`);return;}try{const handler=this.handlers.get(interfaceName);handler(data);}catch(error){console.error(`๐ Error executing callback for ${interfaceName}:`,error);// In development, provide more helpful error info
|
|
11
|
-
if(process.env.NODE_ENV===
|
|
11
|
+
if(process.env.NODE_ENV==="development"){console.error("๐ Handler details:",{interfaceName,data,handlerType:typeof this.handlers.get(interfaceName)});}}};/**
|
|
12
12
|
* Register a callback handler for a native interface
|
|
13
13
|
* @param {string} interfaceName - The callback interface name (from NATIVE_CALLBACKS)
|
|
14
14
|
* @param {function} callback - The callback function to execute
|
|
@@ -39,4 +39,8 @@ if(this.handlers.has(interfaceName)){console.warn(`๐ Interface ${interfaceNam
|
|
|
39
39
|
* Test callback functionality (for debugging)
|
|
40
40
|
* @param {string} interfaceName - The callback interface to test
|
|
41
41
|
* @param {any} testData - Test data to pass
|
|
42
|
-
*/testCallback=(interfaceName,testData=
|
|
42
|
+
*/testCallback=(interfaceName,testData="test")=>{if(process.env.NODE_ENV!=="development"){console.warn("๐ testCallback is only available in development mode");return;}console.log(`๐ Testing callback: ${interfaceName}`);this.callback(interfaceName,testData);};/**
|
|
43
|
+
* Get device information
|
|
44
|
+
* @returns {Promise<Object>} - Promise that resolves with device info or rejects with error
|
|
45
|
+
*/getDeviceInfo=()=>{// Key mapping from native to web keys
|
|
46
|
+
const DEVICE_INFO_KEY_MAP={android:{model:"model",manufacturer:"manufacturer",platform:"platform",screenWidth:"screenWidth",screenHeight:"screenHeight",screenDensity:"screenDensity"},ios:{model:"model",manufacturer:"manufacturer",platform:"platform",screenWidth:"screenWidth",screenHeight:"screenHeight",screenDensity:"screenDensity"}};const parseDeviceInfo=data=>{const rawDeviceInfo=JSON.parse(data);const{platform}=_NativeBridge.default.getEnvironmentInfo();const platformMapping=DEVICE_INFO_KEY_MAP[platform]||{};return Object.fromEntries(Object.entries(rawDeviceInfo).map(([key,value])=>[platformMapping[key]||key,value]));};return new Promise((resolve,reject)=>{const cleanup=()=>{this.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_DEVICE_INFO_SUCCESS);this.unregister(_NativeInterfaces.NATIVE_CALLBACKS.ON_DEVICE_INFO_ERROR);};this.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_DEVICE_INFO_SUCCESS,data=>{cleanup();resolve(parseDeviceInfo(data));});this.register(_NativeInterfaces.NATIVE_CALLBACKS.ON_DEVICE_INFO_ERROR,error=>{cleanup();reject(new Error(error));});try{_NativeBridge.default.device.getDeviceInfo();}catch(error){cleanup();reject(error);}});};}var _default=exports.default=WebBridge;
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
// Centralized definitions for all native interface communications
|
|
3
3
|
// Command Interfaces: Web โ Native (outgoing commands)
|
|
4
4
|
const NATIVE_COMMANDS=exports.NATIVE_COMMANDS={// Camera commands
|
|
5
|
-
OPEN_CAMERA:
|
|
6
|
-
PICK_FILE:
|
|
7
|
-
REQUEST_HAPTIC_FEEDBACK:
|
|
5
|
+
OPEN_CAMERA:"openCamera",REQUEST_CAMERA_PERMISSION:"requestCameraPermission",// File commands
|
|
6
|
+
PICK_FILE:"pickFile",OPEN_FILE_WITH_INTENT:"openFileWithIntent",// Haptic feedback commands
|
|
7
|
+
REQUEST_HAPTIC_FEEDBACK:"requestHapticFeedback",// Device info commands
|
|
8
|
+
GET_DEVICE_INFO:"getDeviceInfo"};// Callback Interfaces: Native โ Web (incoming events/callbacks)
|
|
8
9
|
const NATIVE_CALLBACKS=exports.NATIVE_CALLBACKS={// Camera callbacks
|
|
9
|
-
CAMERA_PERMISSION_STATUS:
|
|
10
|
-
ON_FILE_PICKED:
|
|
11
|
-
ON_INTENT_SUCCESS:
|
|
12
|
-
HAPTIC_FEEDBACK:
|
|
13
|
-
|
|
10
|
+
CAMERA_PERMISSION_STATUS:"CAMERA_PERMISSION_STATUS",ON_CAMERA_CAPTURE:"ON_CAMERA_CAPTURE",ON_CAMERA_ERROR:"ON_CAMERA_ERROR",// File picker callbacks
|
|
11
|
+
ON_FILE_PICKED:"ON_FILE_PICKED",ON_FILE_PICK_ERROR:"ON_FILE_PICK_ERROR",ON_FILE_PICK_CANCELLED:"ON_FILE_PICK_CANCELLED",ON_FILE_PICK_STATE_UPDATE:"ON_FILE_PICK_STATE_UPDATE",// Intent callbacks
|
|
12
|
+
ON_INTENT_SUCCESS:"ON_INTENT_SUCCESS",ON_INTENT_ERROR:"ON_INTENT_ERROR",ON_INTENT_CANCELLED:"ON_INTENT_CANCELLED",// Haptic feedback callbacks
|
|
13
|
+
HAPTIC_FEEDBACK:"HAPTIC_FEEDBACK",// Device info callbacks
|
|
14
|
+
ON_DEVICE_INFO_SUCCESS:"ON_DEVICE_INFO_SUCCESS",ON_DEVICE_INFO_ERROR:"ON_DEVICE_INFO_ERROR"};// Interface Categories for easier management
|
|
15
|
+
const INTERFACE_CATEGORIES=exports.INTERFACE_CATEGORIES={CAMERA:{commands:[NATIVE_COMMANDS.OPEN_CAMERA,NATIVE_COMMANDS.REQUEST_CAMERA_PERMISSION],callbacks:[NATIVE_CALLBACKS.CAMERA_PERMISSION_STATUS,NATIVE_CALLBACKS.ON_CAMERA_CAPTURE,NATIVE_CALLBACKS.ON_CAMERA_ERROR]},FILE:{commands:[NATIVE_COMMANDS.PICK_FILE,NATIVE_COMMANDS.OPEN_FILE_WITH_INTENT],callbacks:[NATIVE_CALLBACKS.ON_FILE_PICKED,NATIVE_CALLBACKS.ON_FILE_PICK_ERROR,NATIVE_CALLBACKS.ON_FILE_PICK_CANCELLED,NATIVE_CALLBACKS.ON_FILE_PICK_STATE_UPDATE]},INTENT:{commands:[NATIVE_COMMANDS.OPEN_FILE_WITH_INTENT],callbacks:[NATIVE_CALLBACKS.ON_INTENT_SUCCESS,NATIVE_CALLBACKS.ON_INTENT_ERROR,NATIVE_CALLBACKS.ON_INTENT_CANCELLED]},HAPTIC:{commands:[NATIVE_COMMANDS.REQUEST_HAPTIC_FEEDBACK],callbacks:[NATIVE_CALLBACKS.HAPTIC_FEEDBACK]},DEVICE_INFO:{commands:[NATIVE_COMMANDS.GET_DEVICE_INFO],callbacks:[NATIVE_CALLBACKS.ON_DEVICE_INFO_SUCCESS,NATIVE_CALLBACKS.ON_DEVICE_INFO_ERROR]}};// All available interfaces (for validation)
|
|
14
16
|
const ALL_COMMANDS=exports.ALL_COMMANDS=Object.values(NATIVE_COMMANDS);const ALL_CALLBACKS=exports.ALL_CALLBACKS=Object.values(NATIVE_CALLBACKS);const ALL_INTERFACES=exports.ALL_INTERFACES=[...ALL_COMMANDS,...ALL_CALLBACKS];// Permission status constants
|
|
15
|
-
const PERMISSION_STATUS=exports.PERMISSION_STATUS={GRANTED:
|
|
16
|
-
const HAPTIC_FEEDBACK_TYPES=exports.HAPTIC_FEEDBACK_TYPES={LIGHT:
|
|
17
|
-
const FILE_PICKER_STATES=exports.FILE_PICKER_STATES={OPENING:
|
|
18
|
-
const INTENT_STATES=exports.INTENT_STATES={PROCESSING:
|
|
19
|
-
const RESPONSE_STATUS=exports.RESPONSE_STATUS={SUCCESS:
|
|
17
|
+
const PERMISSION_STATUS=exports.PERMISSION_STATUS={GRANTED:"GRANTED",DENIED:"DENIED",NOT_DETERMINED:"NOT_DETERMINED",RESTRICTED:"RESTRICTED"};// Haptic feedback types
|
|
18
|
+
const HAPTIC_FEEDBACK_TYPES=exports.HAPTIC_FEEDBACK_TYPES={LIGHT:"light",MEDIUM:"medium",HEAVY:"heavy",SUCCESS:"success",WARNING:"warning",ERROR:"error"};// File picker state constants
|
|
19
|
+
const FILE_PICKER_STATES=exports.FILE_PICKER_STATES={OPENING:"opening",PROCESSING:"processing",IDLE:null};// Intent processing states
|
|
20
|
+
const INTENT_STATES=exports.INTENT_STATES={PROCESSING:"processing",DOWNLOADING:"downloading",OPENING:"opening",IDLE:null};// Response status constants
|
|
21
|
+
const RESPONSE_STATUS=exports.RESPONSE_STATUS={SUCCESS:"SUCCESS",ERROR:"ERROR",CANCELLED:"CANCELLED"};// Utility functions for interface validation
|
|
20
22
|
const isValidCommand=command=>ALL_COMMANDS.includes(command);exports.isValidCommand=isValidCommand;const isValidCallback=callback=>ALL_CALLBACKS.includes(callback);exports.isValidCallback=isValidCallback;const isValidInterface=interfaceName=>ALL_INTERFACES.includes(interfaceName);// Get interfaces by category
|
|
21
23
|
exports.isValidInterface=isValidInterface;const getInterfacesByCategory=category=>{const categoryData=INTERFACE_CATEGORIES[category.toUpperCase()];if(!categoryData){throw new Error(`Invalid category: ${category}`);}return categoryData;};// Debug helper to list all interfaces
|
|
22
|
-
exports.getInterfacesByCategory=getInterfacesByCategory;const debugInterfaces=()=>{console.group(
|
|
24
|
+
exports.getInterfacesByCategory=getInterfacesByCategory;const debugInterfaces=()=>{console.group("๐ Native Bridge Interfaces");console.log("Commands:",ALL_COMMANDS);console.log("Callbacks:",ALL_CALLBACKS);console.log("Categories:",Object.keys(INTERFACE_CATEGORIES));console.groupEnd();};exports.debugInterfaces=debugInterfaces;var _default=exports.default={NATIVE_COMMANDS,NATIVE_CALLBACKS,INTERFACE_CATEGORIES,ALL_COMMANDS,ALL_CALLBACKS,ALL_INTERFACES,PERMISSION_STATUS,HAPTIC_FEEDBACK_TYPES,FILE_PICKER_STATES,INTENT_STATES,RESPONSE_STATUS,isValidCommand,isValidCallback,isValidInterface,getInterfacesByCategory,debugInterfaces};
|