@thelacanians/vue-native-cli 0.4.3 → 0.4.4
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/dist/cli.js +34 -17
- package/native/android/README.md +205 -0
- package/native/android/VueNativeCore/build.gradle.kts +100 -0
- package/native/android/VueNativeCore/consumer-rules.pro +12 -0
- package/native/android/VueNativeCore/proguard-rules.pro +33 -0
- package/native/android/VueNativeCore/src/main/AndroidManifest.xml +17 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/ErrorOverlayView.kt +94 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/HotReloadManager.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +652 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +207 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +417 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +76 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +78 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +46 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +84 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VCheckboxFactory.kt +93 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VDropdownFactory.kt +125 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +210 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +31 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +183 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +57 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +109 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VProgressBarFactory.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRadioFactory.kt +103 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRefreshControlFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSafeAreaFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +144 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +77 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +74 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +52 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +62 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VTextFactory.kt +53 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VVideoFactory.kt +191 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +90 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +40 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/VTextNodeView.kt +23 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +16 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +292 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AppStateModule.kt +41 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +59 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt +331 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +166 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +56 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +198 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CameraModule.kt +64 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +288 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DatabaseModule.kt +229 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +193 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +68 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +61 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +111 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +26 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModule.kt +27 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +92 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +181 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +255 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +147 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +126 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +51 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +134 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +160 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +155 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +802 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +169 -0
- package/native/android/VueNativeCore/src/main/res/values/ids.xml +8 -0
- package/native/android/app/build.gradle.kts +45 -0
- package/native/android/app/proguard-rules.pro +5 -0
- package/native/android/app/src/main/AndroidManifest.xml +25 -0
- package/native/android/app/src/main/assets/.gitkeep +0 -0
- package/native/android/app/src/main/kotlin/com/vuenative/example/counter/MainActivity.kt +14 -0
- package/native/android/app/src/main/res/layout/activity_main.xml +6 -0
- package/native/android/app/src/main/res/values/strings.xml +3 -0
- package/native/android/app/src/main/res/values/themes.xml +9 -0
- package/native/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/native/android/build.gradle.kts +6 -0
- package/native/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/native/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/native/android/gradle.properties +4 -0
- package/native/android/gradlew +87 -0
- package/native/android/gradlew.bat +48 -0
- package/native/android/settings.gradle.kts +20 -0
- package/native/ios/VueNativeCore/Package.resolved +23 -0
- package/native/ios/VueNativeCore/Package.swift +32 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/CertificatePinning.swift +132 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +92 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/HotReloadManager.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +711 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSRuntime.swift +421 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +891 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/VueNativeViewController.swift +88 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/ComponentRegistry.swift +193 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActionSheetFactory.swift +91 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActivityIndicatorFactory.swift +74 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +150 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VButtonFactory.swift +93 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VCheckboxFactory.swift +114 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VDropdownFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VImageFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VInputFactory.swift +357 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VKeyboardAvoidingFactory.swift +99 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +250 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPickerFactory.swift +96 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPressableFactory.swift +168 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VProgressBarFactory.swift +39 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRadioFactory.swift +167 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRefreshControlFactory.swift +153 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSafeAreaFactory.swift +56 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSectionListFactory.swift +248 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSegmentedControlFactory.swift +73 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +63 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VStatusBarFactory.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSwitchFactory.swift +108 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +290 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift +246 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +157 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/NativeComponentFactory.swift +53 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +107 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/TouchableView.swift +136 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/UIColor+Hex.swift +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AnimationModule.swift +291 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +65 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AsyncStorageModule.swift +68 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +366 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BackgroundTaskModule.swift +135 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BiometryModule.swift +61 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BluetoothModule.swift +387 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +161 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +318 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ClipboardModule.swift +33 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ContactsModule.swift +173 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DatabaseModule.swift +259 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DeviceInfoModule.swift +34 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/FileSystemModule.swift +233 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeolocationModule.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/HapticsModule.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/IAPModule.swift +194 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/KeyboardModule.swift +31 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/LinkingModule.swift +42 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModule.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +78 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NetworkModule.swift +62 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NotificationsModule.swift +215 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/OTAModule.swift +281 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PerformanceModule.swift +138 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PermissionsModule.swift +190 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SecureStorageModule.swift +118 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SensorsModule.swift +103 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ShareModule.swift +49 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/WebSocketModule.swift +213 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Resources/vue-native-placeholder.js +8 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Styling/StyleEngine.swift +885 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSRuntimeTests.swift +362 -0
- package/package.json +3 -2
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.content.pm.PackageManager
|
|
8
|
+
import android.os.Build
|
|
9
|
+
import android.os.Handler
|
|
10
|
+
import android.os.Looper
|
|
11
|
+
import androidx.core.app.ActivityCompat
|
|
12
|
+
import androidx.core.app.NotificationCompat
|
|
13
|
+
import androidx.core.app.NotificationManagerCompat
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Native module for local and remote (push) notifications.
|
|
17
|
+
*
|
|
18
|
+
* For push notifications, the host app must:
|
|
19
|
+
* 1. Add Firebase Messaging dependency to their app-level build.gradle
|
|
20
|
+
* 2. Create a FirebaseMessagingService subclass that calls
|
|
21
|
+
* NotificationsModule.onNewToken() and onPushReceived()
|
|
22
|
+
*
|
|
23
|
+
* Methods:
|
|
24
|
+
* - requestPermission() -> { status: "granted"|"denied" }
|
|
25
|
+
* - scheduleLocal(opts) -> { id }
|
|
26
|
+
* - cancel(id)
|
|
27
|
+
* - cancelAll()
|
|
28
|
+
* - registerForPush() -> true (no-op on Android; FCM auto-registers)
|
|
29
|
+
* - getToken() -> String? (returns cached FCM token)
|
|
30
|
+
*
|
|
31
|
+
* Global events:
|
|
32
|
+
* "notification:received" -- local notification tapped
|
|
33
|
+
* "push:token" { token }
|
|
34
|
+
* "push:received" { title, body, data, remote: true }
|
|
35
|
+
*/
|
|
36
|
+
class NotificationsModule : NativeModule {
|
|
37
|
+
override val moduleName = "Notifications"
|
|
38
|
+
|
|
39
|
+
private var context: Context? = null
|
|
40
|
+
private var bridge: NativeBridge? = null
|
|
41
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
42
|
+
private var notifIdCounter = 1
|
|
43
|
+
|
|
44
|
+
companion object {
|
|
45
|
+
private const val CHANNEL_ID = "vue_native_default"
|
|
46
|
+
|
|
47
|
+
/** Singleton reference so FirebaseMessagingService can call into this module. */
|
|
48
|
+
@Volatile
|
|
49
|
+
var instance: NotificationsModule? = null
|
|
50
|
+
private set
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun initialize(context: Context, bridge: NativeBridge) {
|
|
54
|
+
this.context = context
|
|
55
|
+
this.bridge = bridge
|
|
56
|
+
instance = this
|
|
57
|
+
createDefaultChannel(context)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun createDefaultChannel(ctx: Context) {
|
|
61
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
62
|
+
val channel = NotificationChannel(
|
|
63
|
+
CHANNEL_ID,
|
|
64
|
+
"Notifications",
|
|
65
|
+
NotificationManager.IMPORTANCE_DEFAULT
|
|
66
|
+
).apply {
|
|
67
|
+
description = "Vue Native app notifications"
|
|
68
|
+
}
|
|
69
|
+
ctx.getSystemService(NotificationManager::class.java)
|
|
70
|
+
?.createNotificationChannel(channel)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// -------------------------------------------------------------------------
|
|
75
|
+
// Push token handling (called from FirebaseMessagingService in host app)
|
|
76
|
+
// -------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/** Cached FCM token. Volatile because onNewToken() is called from FCM background thread
|
|
79
|
+
* while getToken() may be called from the module invocation thread. */
|
|
80
|
+
@Volatile
|
|
81
|
+
private var fcmToken: String? = null
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Called by the host app's FirebaseMessagingService.onNewToken().
|
|
85
|
+
*/
|
|
86
|
+
fun onNewToken(token: String) {
|
|
87
|
+
fcmToken = token
|
|
88
|
+
bridge?.dispatchGlobalEvent("push:token", mapOf("token" to token))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Called by the host app's FirebaseMessagingService.onMessageReceived().
|
|
93
|
+
*/
|
|
94
|
+
fun onPushReceived(title: String, body: String, data: Map<String, String>) {
|
|
95
|
+
bridge?.dispatchGlobalEvent("push:received", mapOf(
|
|
96
|
+
"title" to title,
|
|
97
|
+
"body" to body,
|
|
98
|
+
"data" to data,
|
|
99
|
+
"remote" to true
|
|
100
|
+
))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
104
|
+
// Module invocation
|
|
105
|
+
// -------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
override fun invoke(
|
|
108
|
+
method: String,
|
|
109
|
+
args: List<Any?>,
|
|
110
|
+
bridge: NativeBridge,
|
|
111
|
+
callback: (Any?, String?) -> Unit
|
|
112
|
+
) {
|
|
113
|
+
when (method) {
|
|
114
|
+
"requestPermission" -> {
|
|
115
|
+
// Android <13: no runtime permission needed for notifications
|
|
116
|
+
// Android 13+: POST_NOTIFICATIONS must be granted via PermissionsModule
|
|
117
|
+
val granted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
118
|
+
val ctx = context ?: run { callback(null, "Not initialized"); return }
|
|
119
|
+
ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS) ==
|
|
120
|
+
PackageManager.PERMISSION_GRANTED
|
|
121
|
+
} else {
|
|
122
|
+
true
|
|
123
|
+
}
|
|
124
|
+
callback(mapOf("status" to if (granted) "granted" else "denied"), null)
|
|
125
|
+
}
|
|
126
|
+
"scheduleLocal" -> {
|
|
127
|
+
val ctx = context ?: run { callback(null, "Not initialized"); return }
|
|
128
|
+
val opts = args.getOrNull(0) as? Map<*, *>
|
|
129
|
+
?: run { callback(null, "Invalid args — expected options object"); return }
|
|
130
|
+
|
|
131
|
+
val title = opts["title"]?.toString() ?: ""
|
|
132
|
+
val body = opts["body"]?.toString() ?: ""
|
|
133
|
+
val delaySeconds = (opts["delay"] as? Number)?.toLong() ?: 0L
|
|
134
|
+
val notifId = notifIdCounter++
|
|
135
|
+
|
|
136
|
+
val show = Runnable {
|
|
137
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
|
138
|
+
ActivityCompat.checkSelfPermission(ctx, Manifest.permission.POST_NOTIFICATIONS)
|
|
139
|
+
== PackageManager.PERMISSION_GRANTED
|
|
140
|
+
) {
|
|
141
|
+
val notification = NotificationCompat.Builder(ctx, CHANNEL_ID)
|
|
142
|
+
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
|
143
|
+
.setContentTitle(title)
|
|
144
|
+
.setContentText(body)
|
|
145
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
146
|
+
.setAutoCancel(true)
|
|
147
|
+
.build()
|
|
148
|
+
NotificationManagerCompat.from(ctx).notify(notifId, notification)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (delaySeconds > 0) {
|
|
153
|
+
handler.postDelayed(show, delaySeconds * 1000L)
|
|
154
|
+
} else {
|
|
155
|
+
show.run()
|
|
156
|
+
}
|
|
157
|
+
callback(mapOf("id" to notifId), null)
|
|
158
|
+
}
|
|
159
|
+
"cancel" -> {
|
|
160
|
+
val ctx = context ?: run { callback(null, "Not initialized"); return }
|
|
161
|
+
val id = (args.getOrNull(0) as? Number)?.toInt()
|
|
162
|
+
?: run { callback(null, "Invalid args — expected notification id"); return }
|
|
163
|
+
NotificationManagerCompat.from(ctx).cancel(id)
|
|
164
|
+
callback(null, null)
|
|
165
|
+
}
|
|
166
|
+
"cancelAll" -> {
|
|
167
|
+
val ctx = context ?: run { callback(null, "Not initialized"); return }
|
|
168
|
+
NotificationManagerCompat.from(ctx).cancelAll()
|
|
169
|
+
callback(null, null)
|
|
170
|
+
}
|
|
171
|
+
"registerForPush" -> {
|
|
172
|
+
// On Android, FCM auto-registers. This is a no-op for API parity with iOS.
|
|
173
|
+
callback(true, null)
|
|
174
|
+
}
|
|
175
|
+
"getToken" -> {
|
|
176
|
+
callback(fcmToken, null)
|
|
177
|
+
}
|
|
178
|
+
else -> callback(null, "Unknown method: $method")
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import okhttp3.*
|
|
7
|
+
import java.io.File
|
|
8
|
+
import java.io.FileOutputStream
|
|
9
|
+
import java.io.IOException
|
|
10
|
+
import java.security.MessageDigest
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Native module for Over-The-Air (OTA) JS bundle updates.
|
|
14
|
+
*
|
|
15
|
+
* Methods:
|
|
16
|
+
* - checkForUpdate(serverUrl) — check for available updates
|
|
17
|
+
* - downloadUpdate(url, hash) — download a new bundle and verify integrity
|
|
18
|
+
* - applyUpdate() — swap to downloaded bundle on next launch
|
|
19
|
+
* - rollback() — revert to the embedded bundle
|
|
20
|
+
* - getCurrentVersion() — get current bundle version info
|
|
21
|
+
*
|
|
22
|
+
* Events:
|
|
23
|
+
* - ota:downloadProgress — payload: { progress, bytesDownloaded, totalBytes }
|
|
24
|
+
*/
|
|
25
|
+
class OTAModule : NativeModule {
|
|
26
|
+
override val moduleName = "OTA"
|
|
27
|
+
private var appContext: Context? = null
|
|
28
|
+
private var bridgeRef: NativeBridge? = null
|
|
29
|
+
private var prefs: SharedPreferences? = null
|
|
30
|
+
private val client = OkHttpClient()
|
|
31
|
+
|
|
32
|
+
private val KEY_PREFIX = "vue_native_ota_"
|
|
33
|
+
private val KEY_CURRENT_VERSION = "${KEY_PREFIX}current_version"
|
|
34
|
+
private val KEY_BUNDLE_PATH = "${KEY_PREFIX}bundle_path"
|
|
35
|
+
private val KEY_PREVIOUS_BUNDLE_PATH = "${KEY_PREFIX}previous_bundle_path"
|
|
36
|
+
private val KEY_PREVIOUS_VERSION = "${KEY_PREFIX}previous_version"
|
|
37
|
+
private val KEY_PENDING_BUNDLE_PATH = "${KEY_PREFIX}pending_bundle_path"
|
|
38
|
+
|
|
39
|
+
override fun initialize(context: Context, bridge: NativeBridge) {
|
|
40
|
+
appContext = context.applicationContext
|
|
41
|
+
bridgeRef = bridge
|
|
42
|
+
prefs = context.getSharedPreferences("vue_native_ota", Context.MODE_PRIVATE)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun invoke(method: String, args: List<Any?>, bridge: NativeBridge, callback: (Any?, String?) -> Unit) {
|
|
46
|
+
val p = prefs ?: run { callback(null, "OTA not initialized"); return }
|
|
47
|
+
|
|
48
|
+
when (method) {
|
|
49
|
+
"checkForUpdate" -> {
|
|
50
|
+
val serverUrl = args.getOrNull(0)?.toString()
|
|
51
|
+
?: run { callback(null, "checkForUpdate: missing serverUrl"); return }
|
|
52
|
+
checkForUpdate(serverUrl, p, callback)
|
|
53
|
+
}
|
|
54
|
+
"downloadUpdate" -> {
|
|
55
|
+
val url = args.getOrNull(0)?.toString()
|
|
56
|
+
?: run { callback(null, "downloadUpdate: missing url"); return }
|
|
57
|
+
val expectedHash = args.getOrNull(1)?.toString()
|
|
58
|
+
downloadUpdate(url, expectedHash, p, callback)
|
|
59
|
+
}
|
|
60
|
+
"applyUpdate" -> applyUpdate(p, callback)
|
|
61
|
+
"rollback" -> rollback(p, callback)
|
|
62
|
+
"getCurrentVersion" -> getCurrentVersion(p, callback)
|
|
63
|
+
else -> callback(null, "OTAModule: Unknown method '$method'")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private fun checkForUpdate(serverUrl: String, prefs: SharedPreferences, callback: (Any?, String?) -> Unit) {
|
|
68
|
+
val currentVersion = prefs.getString(KEY_CURRENT_VERSION, "0") ?: "0"
|
|
69
|
+
|
|
70
|
+
val request = Request.Builder()
|
|
71
|
+
.url(serverUrl)
|
|
72
|
+
.header("X-Current-Version", currentVersion)
|
|
73
|
+
.header("X-Platform", "android")
|
|
74
|
+
.header("X-App-Id", appContext?.packageName ?: "unknown")
|
|
75
|
+
.get()
|
|
76
|
+
.build()
|
|
77
|
+
|
|
78
|
+
client.newCall(request).enqueue(object : Callback {
|
|
79
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
80
|
+
callback(null, "Network error: ${e.message}")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onResponse(call: Call, response: Response) {
|
|
84
|
+
try {
|
|
85
|
+
val body = response.body?.string() ?: run {
|
|
86
|
+
callback(null, "Empty response from update server")
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
val json = org.json.JSONObject(body)
|
|
91
|
+
val result = mapOf(
|
|
92
|
+
"updateAvailable" to (json.optBoolean("updateAvailable", false)),
|
|
93
|
+
"version" to json.optString("version", ""),
|
|
94
|
+
"downloadUrl" to json.optString("downloadUrl", ""),
|
|
95
|
+
"hash" to json.optString("hash", ""),
|
|
96
|
+
"size" to json.optInt("size", 0),
|
|
97
|
+
"releaseNotes" to json.optString("releaseNotes", ""),
|
|
98
|
+
)
|
|
99
|
+
callback(result, null)
|
|
100
|
+
} catch (e: Exception) {
|
|
101
|
+
callback(null, "Failed to parse update response: ${e.message}")
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private fun downloadUpdate(
|
|
108
|
+
url: String,
|
|
109
|
+
expectedHash: String?,
|
|
110
|
+
prefs: SharedPreferences,
|
|
111
|
+
callback: (Any?, String?) -> Unit
|
|
112
|
+
) {
|
|
113
|
+
val request = Request.Builder().url(url).build()
|
|
114
|
+
|
|
115
|
+
client.newCall(request).enqueue(object : Callback {
|
|
116
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
117
|
+
callback(null, "Download failed: ${e.message}")
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override fun onResponse(call: Call, response: Response) {
|
|
121
|
+
try {
|
|
122
|
+
val body = response.body ?: run {
|
|
123
|
+
callback(null, "Download failed: empty response")
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
val totalBytes = body.contentLength()
|
|
128
|
+
val ctx = appContext ?: run {
|
|
129
|
+
callback(null, "Context not available")
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
val otaDir = File(ctx.filesDir, "VueNativeOTA")
|
|
134
|
+
otaDir.mkdirs()
|
|
135
|
+
val bundleFile = File(otaDir, "bundle.js")
|
|
136
|
+
|
|
137
|
+
val source = body.source()
|
|
138
|
+
val outputStream = FileOutputStream(bundleFile)
|
|
139
|
+
val buffer = ByteArray(8192)
|
|
140
|
+
var bytesDownloaded = 0L
|
|
141
|
+
|
|
142
|
+
while (true) {
|
|
143
|
+
val read = source.read(buffer)
|
|
144
|
+
if (read == -1) break
|
|
145
|
+
outputStream.write(buffer, 0, read)
|
|
146
|
+
bytesDownloaded += read
|
|
147
|
+
|
|
148
|
+
val progress = if (totalBytes > 0) bytesDownloaded.toDouble() / totalBytes else 0.0
|
|
149
|
+
bridgeRef?.dispatchGlobalEvent("ota:downloadProgress", mapOf(
|
|
150
|
+
"progress" to progress,
|
|
151
|
+
"bytesDownloaded" to bytesDownloaded,
|
|
152
|
+
"totalBytes" to totalBytes,
|
|
153
|
+
))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
outputStream.close()
|
|
157
|
+
source.close()
|
|
158
|
+
|
|
159
|
+
// Verify hash if provided
|
|
160
|
+
if (!expectedHash.isNullOrEmpty()) {
|
|
161
|
+
val actualHash = sha256(bundleFile)
|
|
162
|
+
if (!actualHash.equals(expectedHash, ignoreCase = true)) {
|
|
163
|
+
bundleFile.delete()
|
|
164
|
+
callback(null, "Bundle integrity check failed. Expected: $expectedHash, got: $actualHash")
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
prefs.edit().putString(KEY_PENDING_BUNDLE_PATH, bundleFile.absolutePath).apply()
|
|
170
|
+
|
|
171
|
+
callback(mapOf(
|
|
172
|
+
"path" to bundleFile.absolutePath,
|
|
173
|
+
"size" to bytesDownloaded,
|
|
174
|
+
), null)
|
|
175
|
+
} catch (e: Exception) {
|
|
176
|
+
callback(null, "Failed to save bundle: ${e.message}")
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun applyUpdate(prefs: SharedPreferences, callback: (Any?, String?) -> Unit) {
|
|
183
|
+
val pendingPath = prefs.getString(KEY_PENDING_BUNDLE_PATH, null)
|
|
184
|
+
if (pendingPath == null || !File(pendingPath).exists()) {
|
|
185
|
+
callback(null, "No pending update to apply")
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
val editor = prefs.edit()
|
|
190
|
+
|
|
191
|
+
// Save current for rollback
|
|
192
|
+
val currentPath = prefs.getString(KEY_BUNDLE_PATH, null)
|
|
193
|
+
val currentVersion = prefs.getString(KEY_CURRENT_VERSION, null)
|
|
194
|
+
if (currentPath != null) editor.putString(KEY_PREVIOUS_BUNDLE_PATH, currentPath)
|
|
195
|
+
if (currentVersion != null) editor.putString(KEY_PREVIOUS_VERSION, currentVersion)
|
|
196
|
+
|
|
197
|
+
// Set new bundle as current
|
|
198
|
+
editor.putString(KEY_BUNDLE_PATH, pendingPath)
|
|
199
|
+
editor.remove(KEY_PENDING_BUNDLE_PATH)
|
|
200
|
+
|
|
201
|
+
// Increment version
|
|
202
|
+
val version = (prefs.getString(KEY_CURRENT_VERSION, "0")?.toIntOrNull() ?: 0) + 1
|
|
203
|
+
editor.putString(KEY_CURRENT_VERSION, version.toString())
|
|
204
|
+
|
|
205
|
+
editor.apply()
|
|
206
|
+
|
|
207
|
+
callback(mapOf("applied" to true), null)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private fun rollback(prefs: SharedPreferences, callback: (Any?, String?) -> Unit) {
|
|
211
|
+
val editor = prefs.edit()
|
|
212
|
+
val previousPath = prefs.getString(KEY_PREVIOUS_BUNDLE_PATH, null)
|
|
213
|
+
|
|
214
|
+
if (previousPath != null) {
|
|
215
|
+
editor.putString(KEY_BUNDLE_PATH, previousPath)
|
|
216
|
+
val prevVersion = prefs.getString(KEY_PREVIOUS_VERSION, null)
|
|
217
|
+
if (prevVersion != null) editor.putString(KEY_CURRENT_VERSION, prevVersion)
|
|
218
|
+
editor.remove(KEY_PREVIOUS_BUNDLE_PATH)
|
|
219
|
+
editor.remove(KEY_PREVIOUS_VERSION)
|
|
220
|
+
editor.apply()
|
|
221
|
+
callback(mapOf("rolledBack" to true, "toEmbedded" to false), null)
|
|
222
|
+
} else {
|
|
223
|
+
// Rollback to embedded
|
|
224
|
+
editor.remove(KEY_BUNDLE_PATH)
|
|
225
|
+
editor.remove(KEY_CURRENT_VERSION)
|
|
226
|
+
editor.apply()
|
|
227
|
+
callback(mapOf("rolledBack" to true, "toEmbedded" to true), null)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fun getCurrentVersion(prefs: SharedPreferences, callback: (Any?, String?) -> Unit) {
|
|
232
|
+
val version = prefs.getString(KEY_CURRENT_VERSION, "embedded") ?: "embedded"
|
|
233
|
+
val bundlePath = prefs.getString(KEY_BUNDLE_PATH, null)
|
|
234
|
+
val isUsingOTA = bundlePath != null && File(bundlePath).exists()
|
|
235
|
+
|
|
236
|
+
callback(mapOf(
|
|
237
|
+
"version" to version,
|
|
238
|
+
"isUsingOTA" to isUsingOTA,
|
|
239
|
+
"bundlePath" to (bundlePath ?: ""),
|
|
240
|
+
), null)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun sha256(file: File): String {
|
|
244
|
+
val digest = MessageDigest.getInstance("SHA-256")
|
|
245
|
+
file.inputStream().use { input ->
|
|
246
|
+
val buffer = ByteArray(8192)
|
|
247
|
+
while (true) {
|
|
248
|
+
val read = input.read(buffer)
|
|
249
|
+
if (read == -1) break
|
|
250
|
+
digest.update(buffer, 0, read)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return digest.digest().joinToString("") { "%02x".format(it) }
|
|
254
|
+
}
|
|
255
|
+
}
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package com.vuenative.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import android.view.Choreographer
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Native module for performance profiling.
|
|
10
|
+
* Tracks FPS via Choreographer, memory usage via Runtime, and bridge operation counts.
|
|
11
|
+
* Dispatches `perf:metrics` global events every 1 second while profiling is active.
|
|
12
|
+
*/
|
|
13
|
+
class PerformanceModule : NativeModule {
|
|
14
|
+
override val moduleName = "Performance"
|
|
15
|
+
|
|
16
|
+
private var bridge: NativeBridge? = null
|
|
17
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
18
|
+
private var isProfiling = false
|
|
19
|
+
|
|
20
|
+
// FPS tracking
|
|
21
|
+
private var frameCount = 0
|
|
22
|
+
private var lastFrameTimeNanos = 0L
|
|
23
|
+
private var currentFPS = 0.0
|
|
24
|
+
|
|
25
|
+
// Bridge operation count
|
|
26
|
+
private var bridgeOpsCount = 0
|
|
27
|
+
|
|
28
|
+
// Metrics timer
|
|
29
|
+
private var metricsRunnable: Runnable? = null
|
|
30
|
+
|
|
31
|
+
// Choreographer callback
|
|
32
|
+
private var frameCallback: Choreographer.FrameCallback? = null
|
|
33
|
+
|
|
34
|
+
override fun initialize(context: Context, bridge: NativeBridge) {
|
|
35
|
+
this.bridge = bridge
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun invoke(
|
|
39
|
+
method: String,
|
|
40
|
+
args: List<Any?>,
|
|
41
|
+
bridge: NativeBridge,
|
|
42
|
+
callback: (result: Any?, error: String?) -> Unit
|
|
43
|
+
) {
|
|
44
|
+
when (method) {
|
|
45
|
+
"startProfiling" -> startProfiling(callback)
|
|
46
|
+
"stopProfiling" -> stopProfiling(callback)
|
|
47
|
+
"getMetrics" -> callback(collectMetrics(), null)
|
|
48
|
+
else -> callback(null, "PerformanceModule: unknown method '$method'")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Start / Stop ──────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
private fun startProfiling(callback: (Any?, String?) -> Unit) {
|
|
55
|
+
if (isProfiling) { callback(true, null); return }
|
|
56
|
+
isProfiling = true
|
|
57
|
+
frameCount = 0
|
|
58
|
+
lastFrameTimeNanos = 0
|
|
59
|
+
currentFPS = 0.0
|
|
60
|
+
bridgeOpsCount = 0
|
|
61
|
+
|
|
62
|
+
mainHandler.post {
|
|
63
|
+
// Choreographer for FPS measurement
|
|
64
|
+
frameCallback = object : Choreographer.FrameCallback {
|
|
65
|
+
override fun doFrame(frameTimeNanos: Long) {
|
|
66
|
+
if (!isProfiling) return
|
|
67
|
+
|
|
68
|
+
if (lastFrameTimeNanos == 0L) {
|
|
69
|
+
lastFrameTimeNanos = frameTimeNanos
|
|
70
|
+
frameCount = 0
|
|
71
|
+
} else {
|
|
72
|
+
frameCount++
|
|
73
|
+
val elapsedNanos = frameTimeNanos - lastFrameTimeNanos
|
|
74
|
+
val elapsedSeconds = elapsedNanos / 1_000_000_000.0
|
|
75
|
+
|
|
76
|
+
// Calculate FPS every 0.5 seconds
|
|
77
|
+
if (elapsedSeconds >= 0.5) {
|
|
78
|
+
currentFPS = frameCount / elapsedSeconds
|
|
79
|
+
frameCount = 0
|
|
80
|
+
lastFrameTimeNanos = frameTimeNanos
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Choreographer.getInstance().postFrameCallback(this)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
Choreographer.getInstance().postFrameCallback(frameCallback!!)
|
|
88
|
+
|
|
89
|
+
// Periodic metrics dispatch (every 1 second)
|
|
90
|
+
metricsRunnable = object : Runnable {
|
|
91
|
+
override fun run() {
|
|
92
|
+
if (!isProfiling) return
|
|
93
|
+
dispatchMetrics()
|
|
94
|
+
mainHandler.postDelayed(this, 1000)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
mainHandler.postDelayed(metricsRunnable!!, 1000)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
callback(true, null)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun stopProfiling(callback: (Any?, String?) -> Unit) {
|
|
104
|
+
if (!isProfiling) { callback(true, null); return }
|
|
105
|
+
isProfiling = false
|
|
106
|
+
|
|
107
|
+
mainHandler.post {
|
|
108
|
+
frameCallback?.let { Choreographer.getInstance().removeFrameCallback(it) }
|
|
109
|
+
frameCallback = null
|
|
110
|
+
metricsRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
111
|
+
metricsRunnable = null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
callback(true, null)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Metrics collection ────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
private fun collectMetrics(): Map<String, Any> {
|
|
120
|
+
val runtime = Runtime.getRuntime()
|
|
121
|
+
val usedMemory = (runtime.totalMemory() - runtime.freeMemory()).toDouble() / (1024 * 1024)
|
|
122
|
+
|
|
123
|
+
return mapOf(
|
|
124
|
+
"fps" to Math.round(currentFPS * 10.0) / 10.0,
|
|
125
|
+
"memoryMB" to Math.round(usedMemory * 100.0) / 100.0,
|
|
126
|
+
"bridgeOps" to bridgeOpsCount,
|
|
127
|
+
"timestamp" to System.currentTimeMillis()
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun dispatchMetrics() {
|
|
132
|
+
if (!isProfiling) return
|
|
133
|
+
bridgeOpsCount++
|
|
134
|
+
val metrics = collectMetrics()
|
|
135
|
+
bridge?.dispatchGlobalEvent("perf:metrics", metrics)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override fun destroy() {
|
|
139
|
+
isProfiling = false
|
|
140
|
+
mainHandler.post {
|
|
141
|
+
frameCallback?.let { Choreographer.getInstance().removeFrameCallback(it) }
|
|
142
|
+
frameCallback = null
|
|
143
|
+
metricsRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
144
|
+
metricsRunnable = null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|