catalyst-core 0.0.3-canary.9 → 0.1.0-beta.2

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 (182) hide show
  1. package/README.md +4 -5
  2. package/bin/catalyst.js +28 -15
  3. package/changelog.md +139 -30
  4. package/dist/native/androidProject/app/build.gradle.kts +125 -15
  5. package/dist/native/androidProject/app/proguard-rules.pro +85 -14
  6. package/dist/native/androidProject/app/src/debug/res/xml/network_security_config.xml +8 -0
  7. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +10 -3
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +552 -0
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +109 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +605 -124
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +405 -47
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +341 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +954 -71
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +111 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/URLWhitelistManager.kt +231 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/Utils.kt +49 -55
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +81 -8
  18. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/design/DesignTokens.kt +307 -0
  19. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/EmulatorDetector.kt +203 -0
  20. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +187 -0
  21. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertHandler.kt +170 -0
  22. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityAlertUI.kt +366 -0
  23. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityBottomSheet.kt +138 -0
  24. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckManager.kt +511 -0
  25. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/SecurityCheckScheduler.kt +68 -0
  26. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +47 -11
  27. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DeviceInfoUtils.kt +38 -13
  28. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/DownloadUtils.kt +36 -14
  29. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FileUtils.kt +3 -2
  30. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +57 -8
  31. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/IntentUtils.kt +31 -6
  32. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +82 -0
  33. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NetworkUtils.kt +105 -0
  34. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationConstants.kt +28 -0
  35. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/NotificationUtils.kt +639 -0
  36. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/SafeAreaUtils.kt +62 -0
  37. package/dist/native/androidProject/app/src/main/res/drawable/splashscreen_fallback.png +0 -0
  38. package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +1 -0
  39. package/dist/native/androidProject/app/src/main/res/layout/activity_splash.xml +20 -0
  40. package/dist/native/androidProject/app/src/main/res/values/themes.xml +23 -0
  41. package/dist/native/androidProject/app/src/main/res/values-night/themes.xml +11 -0
  42. package/dist/native/androidProject/app/src/main/res/xml/network_security_config.xml +12 -0
  43. package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +166 -0
  44. package/dist/native/androidProject/app/src/noFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +94 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeMessageValidatorTest.kt +267 -0
  46. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +286 -0
  47. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +296 -0
  48. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +594 -0
  49. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +466 -0
  50. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +187 -0
  51. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +304 -0
  52. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +424 -0
  53. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +469 -0
  54. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +199 -0
  55. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/URLWhitelistManagerTest.kt +462 -0
  56. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +391 -0
  57. package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/NotificationManager.kt +224 -0
  58. package/dist/native/androidProject/app/src/withFcm/java/io/yourname/androidproject/utils/PushNotificationUtils.kt +399 -0
  59. package/dist/native/androidProject/build.gradle.kts +6 -0
  60. package/dist/native/androidProject/gradle/libs.versions.toml +8 -2
  61. package/dist/native/androidSetup.js +4 -3
  62. package/dist/native/assets/catalyst.png +0 -0
  63. package/dist/native/bridge/WebBridge.js +16 -3
  64. package/dist/native/bridge/constants/NativeInterfaces.js +28 -10
  65. package/dist/native/bridge/errors.js +1 -1
  66. package/dist/native/bridge/hooks.js +76 -16
  67. package/dist/native/bridge/safeArea.js +5 -0
  68. package/dist/native/bridge/standardizedHooks.js +23 -5
  69. package/dist/native/bridge/useBaseHook.js +7 -9
  70. package/dist/native/bridge/utils/CameraUtils.js +58 -0
  71. package/dist/native/bridge/utils/FileObjectConverter.js +40 -0
  72. package/dist/native/bridge/utils/NativeBridge.js +57 -8
  73. package/dist/native/buildAppAndroid.js +115 -43
  74. package/dist/native/buildAppIos.js +291 -26
  75. package/dist/native/iosnativeWebView/Configurations/Debug.xcconfig +21 -0
  76. package/dist/native/iosnativeWebView/Configurations/Release.xcconfig +24 -0
  77. package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +50 -0
  78. package/dist/native/iosnativeWebView/Package.resolved +32 -0
  79. package/dist/native/iosnativeWebView/Package.swift +37 -0
  80. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +293 -0
  81. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +299 -0
  82. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +199 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +125 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +70 -0
  85. package/dist/native/iosnativeWebView/Sources/Core/Notifications/NotificationModels.swift +156 -0
  86. package/dist/native/iosnativeWebView/Sources/Core/Protocols/NotificationHandlerProtocol.swift +35 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +26 -0
  88. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/CacheManager.swift +69 -36
  89. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +363 -0
  90. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +67 -0
  91. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +1053 -0
  92. package/dist/native/iosnativeWebView/Sources/Core/Utils/FrameworkServerUtils.swift +901 -0
  93. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ImageHandler.swift +206 -20
  94. package/dist/native/iosnativeWebView/Sources/Core/Utils/NetworkMonitor.swift +112 -0
  95. package/dist/native/iosnativeWebView/Sources/Core/Utils/NotificationHandlerProvider.swift +20 -0
  96. package/dist/native/iosnativeWebView/Sources/Core/Utils/NullNotificationHandler.swift +85 -0
  97. package/dist/native/iosnativeWebView/{iosnativeWebView → Sources/Core/Utils}/ResourceURLProtocol.swift +60 -25
  98. package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +132 -0
  99. package/dist/native/iosnativeWebView/Sources/Core/Utils/ScreenSecureManager.swift +131 -0
  100. package/dist/native/iosnativeWebView/Sources/Core/Utils/URLWhitelistManager.swift +224 -0
  101. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +43 -0
  102. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +991 -0
  103. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +166 -0
  104. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +340 -0
  105. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +149 -0
  106. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +611 -0
  107. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +334 -0
  108. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +95 -0
  109. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +86 -0
  110. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +181 -0
  111. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +182 -0
  112. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +395 -0
  113. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +84 -0
  114. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AccentColor.colorset/Contents.json +8 -8
  115. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AppIcon.appiconset/Contents.json +10 -10
  116. package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/Contents.json +4 -4
  117. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +65 -10
  118. package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +21 -0
  119. package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +13 -0
  120. package/dist/native/iosnativeWebView/iosnativeWebView/Preview Content/Preview Assets.xcassets/Contents.json +4 -4
  121. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +12 -0
  122. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +80 -0
  123. package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
  124. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +118 -100
  125. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +69 -0
  126. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/xcshareddata/xcschemes/iosnativeWebView.xcscheme +9 -4
  127. package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +47 -0
  128. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +236 -0
  129. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +212 -0
  130. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +418 -0
  131. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +390 -0
  132. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +299 -0
  133. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +383 -0
  134. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +391 -0
  135. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +581 -0
  136. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NotificationHandlerTests.swift +430 -0
  137. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +121 -0
  138. package/dist/native/iosnativeWebView/iosnativeWebViewTests/URLWhitelistManagerTests.swift +367 -0
  139. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +440 -0
  140. package/dist/native/renameAndroidProject.js +80 -70
  141. package/dist/native/setupEmulatorIos.js +22 -5
  142. package/dist/native/setupServer.js +11 -10
  143. package/dist/native/terminalProgress.js +10 -10
  144. package/dist/native/utils.js +4 -5
  145. package/dist/otel.js +4 -0
  146. package/dist/scripts/build.js +2 -2
  147. package/dist/scripts/devBuild.js +2 -2
  148. package/dist/scripts/loadEnvironmentVariables.js +3 -1
  149. package/dist/scripts/scriptUtils.js +2 -2
  150. package/dist/sentry.js +4 -1
  151. package/dist/server/renderer/document/Body.js +4 -4
  152. package/dist/server/renderer/handler.js +7 -5
  153. package/dist/webpack/development.client.babel.js +1 -1
  154. package/mcp_v2/conversion-tasks.json +326 -0
  155. package/mcp_v2/knowledge-base.json +1068 -0
  156. package/mcp_v2/lib/helpers.js +165 -0
  157. package/mcp_v2/mcp.js +563 -0
  158. package/mcp_v2/package.json +13 -0
  159. package/mcp_v2/schema.sql +88 -0
  160. package/mcp_v2/setup.js +278 -0
  161. package/mcp_v2/tools/build.js +686 -0
  162. package/mcp_v2/tools/config.js +453 -0
  163. package/mcp_v2/tools/conversion.js +799 -0
  164. package/mcp_v2/tools/debug.js +113 -0
  165. package/mcp_v2/tools/knowledge.js +219 -0
  166. package/mcp_v2/tools/sync.js +23 -0
  167. package/mcp_v2/tools/tasks.js +945 -0
  168. package/package.json +11 -15
  169. package/dist/native/androidProject/.idea/.name +0 -1
  170. package/dist/native/androidProject/.idea/AndroidProjectSystem.xml +0 -6
  171. package/dist/native/androidProject/.idea/compiler.xml +0 -6
  172. package/dist/native/androidProject/.idea/deploymentTargetSelector.xml +0 -10
  173. package/dist/native/androidProject/.idea/gradle.xml +0 -18
  174. package/dist/native/androidProject/.idea/migrations.xml +0 -10
  175. package/dist/native/androidProject/.idea/misc.xml +0 -10
  176. package/dist/native/androidProject/.idea/runConfigurations.xml +0 -17
  177. package/dist/native/androidProject/.idea/vcs.xml +0 -6
  178. package/dist/native/iosnativeWebView/iosnativeWebView/NativeBridge.swift +0 -217
  179. package/dist/native/iosnativeWebView/iosnativeWebView/WebView.swift +0 -96
  180. package/dist/native/iosnativeWebView/iosnativeWebView/WebViewModel.swift +0 -47
  181. package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +0 -140
  182. package/license +0 -10
@@ -1,27 +1,52 @@
1
1
  package io.yourname.androidproject
2
2
 
3
+ import android.content.Intent
4
+ import android.os.Build
3
5
  import android.os.Bundle
4
6
  import android.util.Log
5
7
  import android.view.WindowManager
8
+ import androidx.activity.OnBackPressedCallback
6
9
  import androidx.appcompat.app.AppCompatActivity
10
+ import androidx.core.view.ViewCompat
11
+ import androidx.core.view.WindowCompat
12
+
13
+ import org.json.JSONObject
7
14
  import java.util.Properties
8
15
  import io.yourname.androidproject.databinding.ActivityMainBinding
9
16
  import io.yourname.androidproject.NativeBridge
17
+ import io.yourname.androidproject.utils.BridgeUtils
18
+ import io.yourname.androidproject.utils.KeyboardUtil
19
+ import io.yourname.androidproject.utils.NetworkUtils
20
+ import io.yourname.androidproject.utils.NotificationConstants
21
+ import io.yourname.androidproject.utils.SafeAreaInsets
22
+ import io.yourname.androidproject.utils.SafeAreaUtils
23
+ import io.yourname.androidproject.security.SecurityCheckScheduler
10
24
  import kotlinx.coroutines.CoroutineScope
11
25
  import kotlinx.coroutines.MainScope
12
26
  import kotlinx.coroutines.cancelChildren
13
- import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
14
27
 
15
28
  class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
16
29
 
17
- private val TAG = "WebViewDebug"
30
+ companion object {
31
+ private const val TAG = "WebViewDebug"
32
+ private const val PREFS_NAME = "safe_area_prefs"
33
+ private const val PREF_SAFE_AREA_TOP = "safe_area_top"
34
+ private const val PREF_SAFE_AREA_RIGHT = "safe_area_right"
35
+ private const val PREF_SAFE_AREA_BOTTOM = "safe_area_bottom"
36
+ private const val PREF_SAFE_AREA_LEFT = "safe_area_left"
37
+ private const val PREF_SAFE_AREA_CACHED = "safe_area_cached"
38
+ }
39
+
18
40
  private lateinit var binding: ActivityMainBinding
19
41
  private lateinit var nativeBridge: NativeBridge
20
42
  private lateinit var customWebView: CustomWebView
21
- private lateinit var properties: Properties
43
+ lateinit var properties: Properties
44
+ private lateinit var metricsMonitor: MetricsMonitor
45
+ private lateinit var keyboardUtil: KeyboardUtil
22
46
  private var isHardwareAccelerationEnabled = false
23
47
  private var currentUrl: String = ""
24
- private var splashStartTime: Long = 0
48
+ private var edgeToEdgeEnabled = false
49
+ private var latestSafeAreaInsets: SafeAreaInsets = SafeAreaInsets.ZERO
25
50
 
26
51
  private fun enableHardwareAcceleration() {
27
52
  if (!isHardwareAccelerationEnabled) {
@@ -46,13 +71,193 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
46
71
  }
47
72
  }
48
73
 
49
- override fun onCreate(savedInstanceState: Bundle?) {
50
- val splashScreen = installSplashScreen()
51
- super.onCreate(savedInstanceState)
74
+ private fun configureEdgeToEdge() {
75
+ edgeToEdgeEnabled = properties
76
+ .getProperty("edgeToEdge.enabled", "false")
77
+ .equals("true", ignoreCase = true)
78
+
79
+ if (edgeToEdgeEnabled) {
80
+ WindowCompat.setDecorFitsSystemWindows(window, false)
81
+ } else {
82
+ WindowCompat.setDecorFitsSystemWindows(window, true)
83
+ }
84
+ }
85
+
86
+ private fun setupSafeAreaHandling() {
87
+ val rootView = binding.root
88
+ val cachedInsets = loadCachedSafeAreaInsets()
89
+
52
90
  if (BuildConfig.DEBUG) {
53
- Log.d(TAG, "📱 onCreate started on thread: ${Thread.currentThread().name}")
91
+ Log.d(TAG, "📋 Cache lookup result: $cachedInsets")
54
92
  }
55
93
 
94
+ if (cachedInsets != null) {
95
+ latestSafeAreaInsets = cachedInsets
96
+ customWebView.setDefaultRequestHeaders(buildSafeAreaHeaders())
97
+
98
+ if (BuildConfig.DEBUG) {
99
+ Log.d(TAG, "✅ Using cached safe area insets: $cachedInsets")
100
+ }
101
+ } else {
102
+ val initialInsets = SafeAreaUtils.getSafeAreaInsets(window, rootView, edgeToEdgeEnabled)
103
+ latestSafeAreaInsets = initialInsets
104
+ customWebView.setDefaultRequestHeaders(buildSafeAreaHeaders())
105
+
106
+ if (BuildConfig.DEBUG) {
107
+ Log.d(TAG, "🆕 No cache found - initial insets: $initialInsets")
108
+ }
109
+ }
110
+
111
+ rootView.post {
112
+ val postLayoutInsets = SafeAreaUtils.getSafeAreaInsets(window, rootView, edgeToEdgeEnabled)
113
+
114
+ if (postLayoutInsets != latestSafeAreaInsets) {
115
+ latestSafeAreaInsets = postLayoutInsets
116
+ customWebView.setDefaultRequestHeaders(buildSafeAreaHeaders())
117
+ notifySafeAreaUpdate(postLayoutInsets)
118
+
119
+ if (BuildConfig.DEBUG) {
120
+ Log.d(TAG, "🔄 Updated safe area insets: $postLayoutInsets")
121
+ }
122
+ }
123
+
124
+ // Save to cache if not already cached or if values changed
125
+ if (cachedInsets == null || cachedInsets != postLayoutInsets) {
126
+ saveSafeAreaInsetsToCache(postLayoutInsets)
127
+ }
128
+ }
129
+ }
130
+
131
+ private fun buildSafeAreaHeaders(): Map<String, String> = mapOf(
132
+ "X-Safe-Area-Top" to latestSafeAreaInsets.top.toString(),
133
+ "X-Safe-Area-Right" to latestSafeAreaInsets.right.toString(),
134
+ "X-Safe-Area-Bottom" to latestSafeAreaInsets.bottom.toString(),
135
+ "X-Safe-Area-Left" to latestSafeAreaInsets.left.toString(),
136
+ // Prevent caching of SSR response so updated headers are always used
137
+ "Cache-Control" to "no-cache, no-store, must-revalidate",
138
+ "Pragma" to "no-cache"
139
+ )
140
+
141
+ // Public method for NativeBridge to get current safe area insets
142
+ fun getCurrentSafeAreaInsets(): SafeAreaInsets = latestSafeAreaInsets
143
+
144
+ fun clearWebCache() {
145
+ customWebView.clearAllCache()
146
+ }
147
+
148
+ fun setScreenSecure(enable: Boolean) {
149
+ if (enable) {
150
+ window.setFlags(
151
+ WindowManager.LayoutParams.FLAG_SECURE,
152
+ WindowManager.LayoutParams.FLAG_SECURE
153
+ )
154
+ } else {
155
+ window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
156
+ }
157
+ }
158
+
159
+ fun isScreenSecure(): Boolean {
160
+ return (window.attributes.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0
161
+ }
162
+
163
+ /**
164
+ * Notify WebView when safe area insets are updated
165
+ * Called when deferred inset calculation completes
166
+ */
167
+ private fun notifySafeAreaUpdate(insets: SafeAreaInsets) {
168
+ if (::nativeBridge.isInitialized && ::customWebView.isInitialized) {
169
+ val insetsJson = org.json.JSONObject().apply {
170
+ put("top", insets.top)
171
+ put("right", insets.right)
172
+ put("bottom", insets.bottom)
173
+ put("left", insets.left)
174
+ }
175
+
176
+ BridgeUtils.notifyWebJson(
177
+ customWebView.getWebView(),
178
+ BridgeUtils.WebEvents.ON_SAFE_AREA_INSETS_UPDATED,
179
+ insetsJson
180
+ )
181
+
182
+ if (BuildConfig.DEBUG) {
183
+ Log.d(TAG, "📐 Safe area insets updated and sent to WebView: $insets")
184
+ }
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Load cached safe area insets from SharedPreferences
190
+ * Returns null if no cache exists
191
+ */
192
+ private fun loadCachedSafeAreaInsets(): SafeAreaInsets? {
193
+ val prefs = getSharedPreferences(PREFS_NAME, android.content.Context.MODE_PRIVATE)
194
+
195
+ if (!prefs.getBoolean(PREF_SAFE_AREA_CACHED, false)) {
196
+ return null
197
+ }
198
+
199
+ return SafeAreaInsets(
200
+ top = prefs.getInt(PREF_SAFE_AREA_TOP, 0),
201
+ right = prefs.getInt(PREF_SAFE_AREA_RIGHT, 0),
202
+ bottom = prefs.getInt(PREF_SAFE_AREA_BOTTOM, 0),
203
+ left = prefs.getInt(PREF_SAFE_AREA_LEFT, 0)
204
+ )
205
+ }
206
+
207
+ /**
208
+ * Save safe area insets to SharedPreferences for future launches
209
+ * Only saves if insets contain at least one non-zero value
210
+ */
211
+ private fun saveSafeAreaInsetsToCache(insets: SafeAreaInsets) {
212
+ // Only cache if we have meaningful (non-zero) values
213
+ // This prevents caching zeros from incomplete initialization
214
+ if (insets.top == 0 && insets.right == 0 && insets.bottom == 0 && insets.left == 0) {
215
+ if (BuildConfig.DEBUG) {
216
+ Log.d(TAG, "⚠️ Skipping cache save - all insets are zero")
217
+ }
218
+ return
219
+ }
220
+
221
+ val saved = getSharedPreferences(PREFS_NAME, android.content.Context.MODE_PRIVATE).edit().apply {
222
+ putInt(PREF_SAFE_AREA_TOP, insets.top)
223
+ putInt(PREF_SAFE_AREA_RIGHT, insets.right)
224
+ putInt(PREF_SAFE_AREA_BOTTOM, insets.bottom)
225
+ putInt(PREF_SAFE_AREA_LEFT, insets.left)
226
+ putBoolean(PREF_SAFE_AREA_CACHED, true)
227
+ }.commit() // Use commit() instead of apply() to ensure immediate persistence
228
+
229
+ if (BuildConfig.DEBUG) {
230
+ if (saved) {
231
+ // Verify by reading back immediately
232
+ val prefs = getSharedPreferences(PREFS_NAME, android.content.Context.MODE_PRIVATE)
233
+ val verified = SafeAreaInsets(
234
+ top = prefs.getInt(PREF_SAFE_AREA_TOP, -1),
235
+ right = prefs.getInt(PREF_SAFE_AREA_RIGHT, -1),
236
+ bottom = prefs.getInt(PREF_SAFE_AREA_BOTTOM, -1),
237
+ left = prefs.getInt(PREF_SAFE_AREA_LEFT, -1)
238
+ )
239
+ Log.d(TAG, "💾 Cached safe area insets: $insets")
240
+ Log.d(TAG, "✓ Verified cached values: $verified")
241
+ } else {
242
+ Log.e(TAG, "❌ Failed to cache safe area insets: $insets")
243
+ }
244
+ }
245
+ }
246
+
247
+ private fun loadUrlWithSafeArea(url: String) {
248
+ // Set headers with current safe area insets
249
+ customWebView.setDefaultRequestHeaders(buildSafeAreaHeaders())
250
+ // Load URL with safe area headers
251
+ customWebView.loadUrl(url)
252
+
253
+ if (BuildConfig.DEBUG) {
254
+ Log.d(TAG, "🔄 Loading URL with safe area headers: $latestSafeAreaInsets")
255
+ }
256
+ }
257
+
258
+ override fun onCreate(savedInstanceState: Bundle?) {
259
+ super.onCreate(savedInstanceState)
260
+
56
261
  // Load properties
57
262
  properties = Properties()
58
263
  try {
@@ -66,15 +271,29 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
66
271
  properties.setProperty("buildOptimisation", (!BuildConfig.DEBUG).toString())
67
272
  }
68
273
 
69
- // Configure splash screen
70
- splashStartTime = System.currentTimeMillis()
71
- configureSplashScreen(splashScreen)
274
+ configureEdgeToEdge()
275
+
276
+ // Initialize security checks (release builds only)
277
+ if (!BuildConfig.DEBUG) {
278
+ SecurityCheckScheduler.initialize(this, this, object : SecurityCheckScheduler.SecurityCheckCallback {
279
+ override fun onSecurityCheckComplete(results: JSONObject) {
280
+ io.yourname.androidproject.security.SecurityAlertHandler.handleSecurityCheckResults(this@MainActivity, results)
281
+ }
282
+ })
283
+ }
284
+
285
+ // Initialize MetricsMonitor
286
+ metricsMonitor = MetricsMonitor.getInstance(this)
72
287
 
73
288
  // Setup UI
74
289
  binding = ActivityMainBinding.inflate(layoutInflater)
75
290
  supportActionBar?.hide()
76
291
  setContentView(binding.root)
77
-
292
+
293
+ // Initialize keyboard utility
294
+ keyboardUtil = KeyboardUtil(this, binding.webviewContainer)
295
+ keyboardUtil.initialize()
296
+
78
297
  // Enable hardware acceleration for the window
79
298
  enableHardwareAcceleration()
80
299
 
@@ -100,39 +319,80 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
100
319
 
101
320
  // Clean the cache
102
321
  customWebView.cleanupCache()
103
-
322
+
104
323
  // Setup NativeBridge
105
324
  try {
106
- nativeBridge = NativeBridge(this, customWebView.getWebView())
325
+ nativeBridge = NativeBridge(this, customWebView.getWebView(), properties)
107
326
  customWebView.addJavascriptInterface(nativeBridge, "NativeBridge")
108
327
  } catch (e: Exception) {
109
328
  Log.e(TAG, "Failed to initialize NativeBridge: ${e.message}")
110
329
  }
330
+
331
+ setupSafeAreaHandling()
332
+
333
+ // Setup back press handler (modern API)
334
+ onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
335
+ override fun handleOnBackPressed() {
336
+ if (customWebView.canGoBack()) {
337
+ customWebView.goBack()
338
+ } else {
339
+ // Disable this callback and let the system handle back press
340
+ isEnabled = false
341
+ onBackPressedDispatcher.onBackPressed()
342
+ }
343
+ }
344
+ })
111
345
 
112
- // Load URL based on environment
346
+ // Load URL based on environment and deep link data
113
347
  try {
114
- if (BuildConfig.DEBUG) {
115
348
  // Use the local development server in debug mode
116
349
  val local_ip = properties.getProperty("LOCAL_IP", "localhost")
350
+ val initial_url = properties.getProperty("initial_url", "")
117
351
  val port = properties.getProperty("port", "3005")
118
352
  val useHttps = properties.getProperty("useHttps", "false").toBoolean()
119
353
  val protocol = if (useHttps) "https" else "http"
120
- currentUrl = "$protocol://$local_ip:$port"
354
+ currentUrl = "$protocol://$local_ip:$port/$initial_url"
355
+ customWebView.updateLastTargetUrl(currentUrl)
356
+
357
+
358
+ // Check for notification and handle via /notification endpoint
359
+ if (intent.getBooleanExtra(NotificationConstants.EXTRA_IS_NOTIFICATION, false)) {
360
+ handleNotificationClick(currentUrl, intent)
121
361
  } else {
122
- // In production, use the configured production URL or fallback to a file:// URL
123
- currentUrl = properties.getProperty("PRODUCTION_URL", "")
124
- if (currentUrl.isEmpty()) {
125
- // If no production URL is configured, load the local index.html
126
- currentUrl = "file:///android_asset/build/public/index.html"
362
+ val isOnline = NetworkUtils.getCurrentStatus(this).isOnline
363
+ if (isOnline) {
364
+ if (BuildConfig.DEBUG) {
365
+ Log.d(TAG, "🔗 Loading base URL: $currentUrl")
366
+ }
367
+ loadUrlWithSafeArea(currentUrl)
368
+ } else {
369
+ if (BuildConfig.DEBUG) {
370
+ Log.w(TAG, "📴 Device offline on launch, showing offline page")
371
+ }
372
+ customWebView.showOfflinePage()
127
373
  }
128
374
  }
129
-
130
- // Load URL
131
- customWebView.loadUrl(currentUrl)
375
+
376
+ metricsMonitor.markAppStartComplete()
377
+
132
378
  } catch (e: Exception) {
133
379
  Log.e(TAG, "Failed to load initial URL: ${e.message}")
380
+ // TODO: Add HTML file workflow - error.html not available yet
134
381
  // Fallback to local asset as a last resort
135
- customWebView.loadUrl("file:///android_asset/build/public/error.html")
382
+ // customWebView.loadUrl("file:///android_asset/build/public/error.html")
383
+ }
384
+ }
385
+
386
+ override fun onNewIntent(intent: Intent?) {
387
+ super.onNewIntent(intent)
388
+ // Handle notification click when app is already running
389
+ intent?.let { newIntent ->
390
+ if (newIntent.getBooleanExtra(NotificationConstants.EXTRA_IS_NOTIFICATION, false)) {
391
+ handleNotificationClick(currentUrl, newIntent)
392
+ } else {
393
+ Log.d(TAG, "🔗 onNewIntent - Loading base URL: $currentUrl")
394
+ loadUrlWithSafeArea(currentUrl)
395
+ }
136
396
  }
137
397
  }
138
398
 
@@ -145,17 +405,15 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
145
405
  }
146
406
  }
147
407
 
148
- override fun onBackPressed() {
149
- if (customWebView.canGoBack()) {
150
- customWebView.goBack()
151
- } else {
152
- super.onBackPressed()
153
- }
154
- }
155
-
156
408
  override fun onPause() {
157
409
  super.onPause()
158
410
  customWebView.onPause()
411
+
412
+ // Log metrics on pause in case onDestroy isn't called
413
+ if (BuildConfig.DEBUG) {
414
+ Log.d(TAG, "⏸️ App paused - logging current metrics...")
415
+ metricsMonitor.logAllMetrics()
416
+ }
159
417
  }
160
418
 
161
419
  override fun onResume() {
@@ -163,26 +421,126 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
163
421
  customWebView.onResume()
164
422
  }
165
423
 
424
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
425
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
426
+ if (::nativeBridge.isInitialized) {
427
+ nativeBridge.handlePermissionResult(requestCode, permissions, grantResults)
428
+ }
429
+ }
430
+
166
431
  override fun onDestroy() {
432
+ // Log all performance metrics before destroying
433
+ if (BuildConfig.DEBUG) {
434
+ Log.d(TAG, "🏁 App shutting down - logging final metrics...")
435
+ }
436
+ metricsMonitor.logAllMetrics()
437
+ metricsMonitor.cleanup()
438
+
439
+ // Cleanup NativeBridge resources (stops FrameworkServer, cancels coroutines)
440
+ if (::nativeBridge.isInitialized) {
441
+ nativeBridge.cleanup()
442
+ }
443
+
444
+ // Cleanup keyboard utility
445
+ if (::keyboardUtil.isInitialized) {
446
+ keyboardUtil.cleanup()
447
+ }
448
+ if (::nativeBridge.isInitialized) {
449
+ nativeBridge.cleanup()
450
+ }
167
451
  coroutineContext.cancelChildren()
168
452
  customWebView.destroy()
169
453
  super.onDestroy()
170
454
  }
171
455
 
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
456
+ /**
457
+ * Handle notification click by navigating to /notification route
458
+ * Ultra-simple approach: always go to /notification with minimal params
459
+ */
460
+ private fun handleNotificationClick(baseUrl: String, intent: Intent) {
461
+ try {
462
+ val action = intent.getStringExtra(NotificationConstants.EXTRA_ACTION)
463
+ val notificationData = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_DATA)
464
+ val notificationId = intent.getStringExtra(NotificationConstants.EXTRA_NOTIFICATION_ID)
465
+
466
+ Log.d(TAG, "🔔 Handling notification click - Action: ${action ?: "none"}")
467
+
468
+ // Dismiss the notification
469
+ val notificationManager = getSystemService(android.content.Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
470
+ if (!notificationId.isNullOrBlank()) {
471
+ notificationManager.cancel(notificationId.hashCode())
472
+ } else {
473
+ notificationManager.cancelAll() // Fallback if we didn't get an ID
474
+ }
475
+
476
+ // Build simple /notification URL
477
+ val url = buildNotificationUrl(baseUrl, action, notificationData)
478
+ Log.d(TAG, "🔔 Navigating to: $url")
479
+
480
+ // Notify web layer about the action/tap
481
+ try {
482
+ val payload = org.json.JSONObject().apply {
483
+ put("action", action ?: "tap")
484
+ put("deepLinkURL", url)
485
+ if (!notificationData.isNullOrBlank()) {
486
+ put("data", org.json.JSONObject(notificationData))
487
+ }
488
+ if (!notificationId.isNullOrBlank()) {
489
+ put("notificationId", notificationId)
490
+ }
491
+ }
492
+ val webView = customWebView.getWebView()
493
+ if (action.isNullOrBlank()) {
494
+ BridgeUtils.notifyWeb(webView, BridgeUtils.WebEvents.NOTIFICATION_TAPPED, payload.toString())
495
+ } else {
496
+ BridgeUtils.notifyWeb(webView, BridgeUtils.WebEvents.NOTIFICATION_ACTION_PERFORMED, payload.toString())
497
+ }
498
+ } catch (e: Exception) {
499
+ Log.e(TAG, "Failed to notify web of notification action", e)
500
+ }
501
+
502
+ customWebView.updateLastTargetUrl(url)
503
+ val isOnline = NetworkUtils.getCurrentStatus(this).isOnline
504
+ if (isOnline) {
505
+ loadUrlWithSafeArea(url)
183
506
  } else {
184
- !webViewLoaded
507
+ Log.w(TAG, "📴 Offline during notification click, showing offline page")
508
+ customWebView.showOfflinePage()
509
+ }
510
+
511
+ } catch (e: Exception) {
512
+ Log.e(TAG, "🔔 Error handling notification click: ${e.message}", e)
513
+ loadUrlWithSafeArea(baseUrl)
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Build ultra-simple /notification URL
519
+ */
520
+ private fun buildNotificationUrl(baseUrl: String, action: String?, notificationData: String?): String {
521
+ return try {
522
+ val url = StringBuilder("$baseUrl/notification")
523
+ val params = mutableListOf<String>()
524
+
525
+ // Add action if present
526
+ if (!action.isNullOrEmpty()) {
527
+ params.add("action=${java.net.URLEncoder.encode(action, "UTF-8")}")
185
528
  }
529
+
530
+ // Add data if present
531
+ if (!notificationData.isNullOrEmpty()) {
532
+ params.add("data=${java.net.URLEncoder.encode(notificationData, "UTF-8")}")
533
+ }
534
+
535
+ if (params.isNotEmpty()) {
536
+ url.append("?").append(params.joinToString("&"))
537
+ }
538
+
539
+ url.toString()
540
+
541
+ } catch (e: Exception) {
542
+ Log.e(TAG, "Error building notification URL: ${e.message}", e)
543
+ "$baseUrl/notification"
186
544
  }
187
545
  }
188
- }
546
+ }