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
@@ -0,0 +1,341 @@
1
+ package io.yourname.androidproject
2
+
3
+ import android.content.Context
4
+ import android.os.Debug
5
+ import android.os.SystemClock
6
+ import android.util.Log
7
+ import android.view.Choreographer
8
+ import java.util.concurrent.TimeUnit
9
+ import java.util.concurrent.atomic.AtomicInteger
10
+
11
+ /**
12
+ * Simple metrics monitor for WebView-based Android applications.
13
+ * Tracks key performance metrics and logs them on app destroy.
14
+ */
15
+ class MetricsMonitor(private val context: Context) {
16
+ private val TAG = "MetricsMonitor"
17
+
18
+ // App Startup metrics
19
+ private var appStartTime: Long = SystemClock.elapsedRealtime()
20
+ private var coldStartDuration: Long = 0
21
+
22
+ // Page Load metrics
23
+ private val pageLoadTimes = mutableMapOf<String, Long>()
24
+ private var currentPageLoadStartTime: Long = 0
25
+ private var avgPageLoadTime: Long = 0
26
+ private var totalPagesLoaded: Int = 0
27
+
28
+ // Frame rate metrics
29
+ private var frameCounter = AtomicInteger(0)
30
+ private var lastFpsCheckTime = SystemClock.elapsedRealtime()
31
+ private var currentFps = 0.0
32
+ private var minFps = Double.MAX_VALUE
33
+ private var maxFps = 0.0
34
+ private var avgFps = 0.0
35
+ private var fpsReadings = 0
36
+
37
+ // Memory metrics
38
+ private var initialMemory: Long = getMemoryUsage()
39
+ private var peakMemory: Long = initialMemory
40
+ private var currentMemory: Long = initialMemory
41
+
42
+ // Cache metrics
43
+ private var cacheHits = 0
44
+ private var cacheMisses = 0
45
+ private val cachedFiles = mutableSetOf<String>()
46
+ private val filesServedFromCache = mutableListOf<String>()
47
+ private val cacheMissedUrls = mutableListOf<String>()
48
+ private val allNetworkRequests = mutableListOf<String>()
49
+ private val cacheEvaluatedRequests = mutableListOf<String>()
50
+
51
+ // Thread metrics
52
+ private var uiBlockingEvents = 0
53
+ private var longTaskThreshold = 16L // ms (1 frame at 60fps)
54
+ private var longTasksDetected = 0
55
+
56
+ // Battery impact (simplified)
57
+ private var appRunDuration: Long = 0
58
+
59
+ private val frameCallback = object : Choreographer.FrameCallback {
60
+ override fun doFrame(frameTimeNanos: Long) {
61
+ frameCounter.incrementAndGet()
62
+ Choreographer.getInstance().postFrameCallback(this)
63
+ }
64
+ }
65
+
66
+ init {
67
+ recordAppStart()
68
+ startFrameMonitoring()
69
+ monitorMemoryPeriodically()
70
+ }
71
+
72
+ fun recordAppStart() {
73
+ appStartTime = SystemClock.elapsedRealtime()
74
+ }
75
+
76
+ fun markAppStartComplete() {
77
+ coldStartDuration = SystemClock.elapsedRealtime() - appStartTime
78
+ Log.d(TAG, "App cold start completed in: $coldStartDuration ms")
79
+ }
80
+
81
+ fun trackPageLoadStart(url: String) {
82
+ currentPageLoadStartTime = SystemClock.elapsedRealtime()
83
+ pageLoadTimes[url] = currentPageLoadStartTime
84
+ if (BuildConfig.DEBUG) {
85
+ Log.d(TAG, "📄 PAGE_LOAD_START: $url")
86
+ }
87
+ }
88
+
89
+ fun trackPageLoadEnd(url: String) {
90
+ val startTime = pageLoadTimes[url] ?: return
91
+ val loadTime = SystemClock.elapsedRealtime() - startTime
92
+
93
+ // Update average
94
+ val totalTime = avgPageLoadTime * totalPagesLoaded + loadTime
95
+ totalPagesLoaded++
96
+ avgPageLoadTime = totalTime / totalPagesLoaded
97
+
98
+ if (BuildConfig.DEBUG) {
99
+ Log.d(TAG, "📄 PAGE_LOAD_END: $url in $loadTime ms")
100
+ Log.d(TAG, "📄 PAGE_STATS: Total pages: $totalPagesLoaded, Avg time: $avgPageLoadTime ms")
101
+ }
102
+ }
103
+
104
+ fun recordCacheHit(url: String = "") {
105
+ cacheHits++
106
+ if (url.isNotEmpty()) {
107
+ filesServedFromCache.add(url)
108
+ if (BuildConfig.DEBUG) {
109
+ Log.d(TAG, "💾 CACHE_HIT: $url (Total hits: $cacheHits)")
110
+ }
111
+ }
112
+ }
113
+
114
+ fun recordCacheMiss(url: String = "") {
115
+ cacheMisses++
116
+ if (url.isNotEmpty()) {
117
+ cacheMissedUrls.add(url)
118
+ if (BuildConfig.DEBUG) {
119
+ Log.d(TAG, "❌ CACHE_MISS: $url (Total misses: $cacheMisses)")
120
+ }
121
+ }
122
+ }
123
+
124
+ fun recordCachedFile(url: String) {
125
+ cachedFiles.add(url)
126
+ if (BuildConfig.DEBUG) {
127
+ Log.d(TAG, "💾 FILE_CACHED: $url (Total cached files: ${cachedFiles.size})")
128
+ }
129
+ }
130
+
131
+ fun recordNetworkRequest(url: String) {
132
+ if (url.isNotEmpty()) {
133
+ allNetworkRequests.add(url)
134
+ }
135
+ }
136
+
137
+ fun recordCacheEvaluation(url: String) {
138
+ if (url.isNotEmpty()) {
139
+ cacheEvaluatedRequests.add(url)
140
+ }
141
+ }
142
+
143
+ fun recordUiBlocking() {
144
+ uiBlockingEvents++
145
+ }
146
+
147
+ fun recordLongTask() {
148
+ longTasksDetected++
149
+ }
150
+
151
+ private fun startFrameMonitoring() {
152
+ Choreographer.getInstance().postFrameCallback(frameCallback)
153
+
154
+ // Start a thread to calculate FPS every second
155
+ Thread {
156
+ while (!Thread.interrupted()) {
157
+ try {
158
+ Thread.sleep(1000)
159
+ calculateFps()
160
+ } catch (e: InterruptedException) {
161
+ break
162
+ }
163
+ }
164
+ }.apply {
165
+ isDaemon = true
166
+ start()
167
+ }
168
+ }
169
+
170
+ private fun calculateFps() {
171
+ val now = SystemClock.elapsedRealtime()
172
+ val frames = frameCounter.getAndSet(0)
173
+ val timeSpan = now - lastFpsCheckTime
174
+ lastFpsCheckTime = now
175
+
176
+ if (timeSpan > 0) {
177
+ currentFps = frames * 1000.0 / timeSpan
178
+
179
+ // Update min/max/avg
180
+ if (minFps == Double.MAX_VALUE) minFps = currentFps
181
+ minFps = minFps.coerceAtMost(currentFps)
182
+ maxFps = maxFps.coerceAtLeast(currentFps)
183
+
184
+ val totalFps = avgFps * fpsReadings + currentFps
185
+ fpsReadings++
186
+ avgFps = totalFps / fpsReadings
187
+ }
188
+ }
189
+
190
+ private fun monitorMemoryPeriodically() {
191
+ Thread {
192
+ while (!Thread.interrupted()) {
193
+ try {
194
+ currentMemory = getMemoryUsage()
195
+ peakMemory = peakMemory.coerceAtLeast(currentMemory)
196
+ Thread.sleep(5000) // Check every 5 seconds
197
+ } catch (e: InterruptedException) {
198
+ break
199
+ }
200
+ }
201
+ }.apply {
202
+ isDaemon = true
203
+ start()
204
+ }
205
+ }
206
+
207
+ private fun getMemoryUsage(): Long {
208
+ val memoryInfo = Debug.MemoryInfo()
209
+ Debug.getMemoryInfo(memoryInfo)
210
+ return memoryInfo.totalPss.toLong() // in kilobytes
211
+ }
212
+
213
+ fun getCacheStats(): String {
214
+ val cacheRate = if ((cacheHits + cacheMisses) > 0)
215
+ (cacheHits * 100 / (cacheHits + cacheMisses)) else 0
216
+
217
+ return "Cache: $cacheHits hits, $cacheMisses misses, ${cacheRate}% hit rate, ${cachedFiles.size} files cached"
218
+ }
219
+
220
+ fun getFilesServedFromCache(): List<String> {
221
+ return filesServedFromCache.toList()
222
+ }
223
+
224
+ fun logAllMetrics() {
225
+ appRunDuration = SystemClock.elapsedRealtime() - appStartTime
226
+
227
+ val memoryDelta = currentMemory - initialMemory
228
+ val memoryGrowth = if (initialMemory > 0) (memoryDelta * 100 / initialMemory) else 0
229
+
230
+ val cacheRate = if ((cacheHits + cacheMisses) > 0)
231
+ (cacheHits * 100 / (cacheHits + cacheMisses)) else 0
232
+
233
+ val sb = StringBuilder()
234
+ sb.appendLine("==================== PERFORMANCE METRICS ====================")
235
+ sb.appendLine("App Run Duration: ${formatDuration(appRunDuration)}")
236
+ sb.appendLine("Cold Start Time: $coldStartDuration ms")
237
+ sb.appendLine("")
238
+ sb.appendLine("PAGE LOADING:")
239
+ sb.appendLine("- Pages Loaded: $totalPagesLoaded")
240
+ sb.appendLine("- Avg Load Time: $avgPageLoadTime ms")
241
+ sb.appendLine("")
242
+ sb.appendLine("RENDERING PERFORMANCE:")
243
+ sb.appendLine("- Current FPS: ${"%.1f".format(currentFps)}")
244
+ sb.appendLine("- Min FPS: ${"%.1f".format(if (minFps == Double.MAX_VALUE) 0.0 else minFps)}")
245
+ sb.appendLine("- Max FPS: ${"%.1f".format(maxFps)}")
246
+ sb.appendLine("- Avg FPS: ${"%.1f".format(avgFps)}")
247
+ sb.appendLine("- UI Blocking Events: $uiBlockingEvents")
248
+ sb.appendLine("- Long Tasks (>16ms): $longTasksDetected")
249
+ sb.appendLine("")
250
+ sb.appendLine("MEMORY USAGE:")
251
+ sb.appendLine("- Initial: ${initialMemory / 1024} MB")
252
+ sb.appendLine("- Current: ${currentMemory / 1024} MB")
253
+ sb.appendLine("- Peak: ${peakMemory / 1024} MB")
254
+ sb.appendLine("- Growth: $memoryGrowth%")
255
+ sb.appendLine("")
256
+ sb.appendLine("CACHE EFFICIENCY:")
257
+ sb.appendLine("- Cache Hits: $cacheHits")
258
+ sb.appendLine("- Cache Misses: $cacheMisses")
259
+ sb.appendLine("- Cache Hit Rate: $cacheRate%")
260
+ sb.appendLine("- Total Network Requests: ${allNetworkRequests.size}")
261
+ sb.appendLine("- Cache Evaluated Requests: ${cacheEvaluatedRequests.size}")
262
+ sb.appendLine("- Not Evaluated for Cache: ${allNetworkRequests.size - cacheEvaluatedRequests.size}")
263
+ sb.appendLine("- Total Files Cached: ${cachedFiles.size}")
264
+ sb.appendLine("- Files Served from Cache: ${filesServedFromCache.size}")
265
+ sb.appendLine("")
266
+ if (filesServedFromCache.isNotEmpty()) {
267
+ sb.appendLine("FILES SERVED FROM CACHE:")
268
+ filesServedFromCache.take(10).forEach { url ->
269
+ sb.appendLine(" ✅ ${url.substringAfterLast("/")}")
270
+ }
271
+ if (filesServedFromCache.size > 10) {
272
+ sb.appendLine(" ... and ${filesServedFromCache.size - 10} more files")
273
+ }
274
+ sb.appendLine("")
275
+ }
276
+
277
+ if (cacheMissedUrls.isNotEmpty()) {
278
+ sb.appendLine("CACHE MISSES (URLs that should have been cached):")
279
+ // Group by file extension to make it easier to analyze
280
+ val missedByExtension = cacheMissedUrls.groupBy { url ->
281
+ url.substringAfterLast('.').substringBefore('?').substringBefore('#').lowercase()
282
+ }
283
+ missedByExtension.forEach { (ext, urls) ->
284
+ sb.appendLine(" Extension .$ext: ${urls.size} files")
285
+ urls.take(3).forEach { url ->
286
+ sb.appendLine(" ❌ ${url.substringAfterLast("/")}")
287
+ }
288
+ if (urls.size > 3) {
289
+ sb.appendLine(" ... and ${urls.size - 3} more")
290
+ }
291
+ }
292
+ sb.appendLine("")
293
+ }
294
+
295
+ // Show requests that were not evaluated for caching
296
+ val notEvaluated = allNetworkRequests.filter { it !in cacheEvaluatedRequests }
297
+ if (notEvaluated.isNotEmpty()) {
298
+ sb.appendLine("REQUESTS NOT EVALUATED FOR CACHE (${notEvaluated.size} total):")
299
+ val groupedNotEvaluated = notEvaluated.groupBy { url ->
300
+ val ext = url.substringAfterLast('.').substringBefore('?').substringBefore('#').lowercase()
301
+ if (ext.length > 10 || !ext.matches(Regex("[a-z0-9]+"))) "other" else ext
302
+ }
303
+ groupedNotEvaluated.forEach { (ext, urls) ->
304
+ sb.appendLine(" .$ext: ${urls.size} files")
305
+ urls.take(2).forEach { url ->
306
+ sb.appendLine(" • ${url.substringAfterLast("/").take(50)}")
307
+ }
308
+ if (urls.size > 2) {
309
+ sb.appendLine(" ... and ${urls.size - 2} more")
310
+ }
311
+ }
312
+ sb.appendLine("")
313
+ }
314
+ sb.appendLine("==============================================================")
315
+
316
+ Log.i(TAG, sb.toString())
317
+ }
318
+
319
+ private fun formatDuration(millis: Long): String {
320
+ return String.format("%02d:%02d:%02d",
321
+ TimeUnit.MILLISECONDS.toHours(millis),
322
+ TimeUnit.MILLISECONDS.toMinutes(millis) % TimeUnit.HOURS.toMinutes(1),
323
+ TimeUnit.MILLISECONDS.toSeconds(millis) % TimeUnit.MINUTES.toSeconds(1))
324
+ }
325
+
326
+ fun cleanup() {
327
+ // Remove frame callback
328
+ Choreographer.getInstance().removeFrameCallback(frameCallback)
329
+ }
330
+
331
+ companion object {
332
+ private var instance: MetricsMonitor? = null
333
+
334
+ fun getInstance(context: Context): MetricsMonitor {
335
+ return instance ?: synchronized(this) {
336
+ instance ?: MetricsMonitor(context.applicationContext)
337
+ .also { instance = it }
338
+ }
339
+ }
340
+ }
341
+ }