@thelacanians/vue-native-cli 0.4.4 → 0.4.5
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 +71 -18
- package/native/android/VueNativeCore/build.gradle.kts +16 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt +3 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +8 -8
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +6 -3
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +10 -10
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/CertificatePinning.swift +6 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +1 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +15 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActionSheetFactory.swift +1 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +1 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift +1 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSectionListFactory.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift +15 -11
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +5 -3
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/Extensions.swift +31 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/TouchableView.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AnimationModule.swift +0 -8
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +20 -26
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +13 -8
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BackgroundTaskModule.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BluetoothModule.swift +0 -8
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +0 -7
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +14 -17
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/IAPModule.swift +2 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PerformanceModule.swift +5 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ShareModule.swift +1 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +24 -8
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Styling/StyleEngine.swift +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { join, dirname } from "path";
|
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { existsSync } from "fs";
|
|
12
12
|
import pc from "picocolors";
|
|
13
|
-
var VERSION = "0.4.
|
|
13
|
+
var VERSION = "0.4.5";
|
|
14
14
|
var createCommand = new Command("create").description("Create a new Vue Native project").argument("<name>", "project name").option("-t, --template <template>", "project template (blank, tabs, drawer)", "blank").action(async (name, options) => {
|
|
15
15
|
const template = options.template;
|
|
16
16
|
if (!["blank", "tabs", "drawer"].includes(template)) {
|
|
@@ -226,12 +226,20 @@ class AppViewController: VueNativeViewController {
|
|
|
226
226
|
const androidPkgPath = androidPkg.replace(/\./g, "/");
|
|
227
227
|
const androidSrcDir = join(androidAppDir, "src", "main");
|
|
228
228
|
const androidKotlinDir = join(androidSrcDir, "kotlin", androidPkgPath);
|
|
229
|
+
const androidResValuesDir = join(androidSrcDir, "res", "values");
|
|
230
|
+
const androidResXmlDir = join(androidSrcDir, "res", "xml");
|
|
231
|
+
const androidDebugResXmlDir = join(androidAppDir, "src", "debug", "res", "xml");
|
|
232
|
+
const androidGradleWrapperDir = join(androidDir, "gradle", "wrapper");
|
|
229
233
|
await mkdir(androidKotlinDir, { recursive: true });
|
|
234
|
+
await mkdir(androidResValuesDir, { recursive: true });
|
|
235
|
+
await mkdir(androidResXmlDir, { recursive: true });
|
|
236
|
+
await mkdir(androidDebugResXmlDir, { recursive: true });
|
|
237
|
+
await mkdir(androidGradleWrapperDir, { recursive: true });
|
|
230
238
|
await writeFile(join(androidDir, "build.gradle.kts"), `// Top-level build file
|
|
231
239
|
plugins {
|
|
232
|
-
id("com.android.application") version "8.
|
|
233
|
-
id("com.android.library") version "8.
|
|
234
|
-
id("org.jetbrains.kotlin.android") version "
|
|
240
|
+
id("com.android.application") version "8.7.3" apply false
|
|
241
|
+
id("com.android.library") version "8.7.3" apply false
|
|
242
|
+
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
|
|
235
243
|
}
|
|
236
244
|
`);
|
|
237
245
|
await writeFile(join(androidDir, "settings.gradle.kts"), `pluginManagement {
|
|
@@ -250,8 +258,8 @@ dependencyResolutionManagement {
|
|
|
250
258
|
maven {
|
|
251
259
|
url = uri("https://maven.pkg.github.com/abdul-hamid-achik/vue-native")
|
|
252
260
|
credentials {
|
|
253
|
-
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR")
|
|
254
|
-
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN")
|
|
261
|
+
username = providers.gradleProperty("gpr.user").orNull ?: System.getenv("GITHUB_ACTOR") ?: ""
|
|
262
|
+
password = providers.gradleProperty("gpr.key").orNull ?: System.getenv("GITHUB_TOKEN") ?: ""
|
|
255
263
|
}
|
|
256
264
|
}
|
|
257
265
|
}
|
|
@@ -267,19 +275,19 @@ include(":app")
|
|
|
267
275
|
|
|
268
276
|
android {
|
|
269
277
|
namespace = "${androidPkg}"
|
|
270
|
-
compileSdk =
|
|
278
|
+
compileSdk = 35
|
|
271
279
|
|
|
272
280
|
defaultConfig {
|
|
273
281
|
applicationId = "${androidPkg}"
|
|
274
|
-
minSdk =
|
|
275
|
-
targetSdk =
|
|
282
|
+
minSdk = 24
|
|
283
|
+
targetSdk = 35
|
|
276
284
|
versionCode = 1
|
|
277
285
|
versionName = "1.0"
|
|
278
286
|
}
|
|
279
287
|
|
|
280
288
|
buildTypes {
|
|
281
289
|
release {
|
|
282
|
-
isMinifyEnabled =
|
|
290
|
+
isMinifyEnabled = true
|
|
283
291
|
proguardFiles(
|
|
284
292
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
285
293
|
"proguard-rules.pro"
|
|
@@ -287,6 +295,10 @@ android {
|
|
|
287
295
|
}
|
|
288
296
|
}
|
|
289
297
|
|
|
298
|
+
buildFeatures {
|
|
299
|
+
buildConfig = true
|
|
300
|
+
}
|
|
301
|
+
|
|
290
302
|
compileOptions {
|
|
291
303
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
292
304
|
targetCompatibility = JavaVersion.VERSION_17
|
|
@@ -299,7 +311,16 @@ android {
|
|
|
299
311
|
|
|
300
312
|
dependencies {
|
|
301
313
|
implementation("com.vuenative:core:${VERSION}")
|
|
314
|
+
implementation("androidx.appcompat:appcompat:1.7.0")
|
|
315
|
+
implementation("com.google.android.material:material:1.12.0")
|
|
316
|
+
implementation("androidx.core:core-ktx:1.15.0")
|
|
302
317
|
}
|
|
318
|
+
`);
|
|
319
|
+
await writeFile(join(androidAppDir, "proguard-rules.pro"), `# Vue Native
|
|
320
|
+
-keep class com.vuenative.** { *; }
|
|
321
|
+
|
|
322
|
+
# J2V8
|
|
323
|
+
-keep class com.eclipsesource.v8.** { *; }
|
|
303
324
|
`);
|
|
304
325
|
await writeFile(join(androidSrcDir, "AndroidManifest.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
305
326
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -307,13 +328,15 @@ dependencies {
|
|
|
307
328
|
|
|
308
329
|
<application
|
|
309
330
|
android:allowBackup="true"
|
|
310
|
-
android:label="
|
|
331
|
+
android:label="@string/app_name"
|
|
311
332
|
android:supportsRtl="true"
|
|
312
|
-
android:theme="@style/Theme.
|
|
333
|
+
android:theme="@style/Theme.VueNative"
|
|
313
334
|
android:networkSecurityConfig="@xml/network_security_config">
|
|
314
335
|
<activity
|
|
315
336
|
android:name=".MainActivity"
|
|
316
|
-
android:exported="true"
|
|
337
|
+
android:exported="true"
|
|
338
|
+
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|locale|layoutDirection|fontScale|uiMode|density"
|
|
339
|
+
android:windowSoftInputMode="adjustResize">
|
|
317
340
|
<intent-filter>
|
|
318
341
|
<action android:name="android.intent.action.MAIN" />
|
|
319
342
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
@@ -322,9 +345,30 @@ dependencies {
|
|
|
322
345
|
</application>
|
|
323
346
|
</manifest>
|
|
324
347
|
`);
|
|
325
|
-
|
|
326
|
-
|
|
348
|
+
await writeFile(join(androidResValuesDir, "strings.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
349
|
+
<resources>
|
|
350
|
+
<string name="app_name">${name}</string>
|
|
351
|
+
</resources>
|
|
352
|
+
`);
|
|
353
|
+
await writeFile(join(androidResValuesDir, "themes.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
354
|
+
<resources>
|
|
355
|
+
<style name="Theme.VueNative" parent="Theme.MaterialComponents.Light.NoActionBar">
|
|
356
|
+
<item name="colorPrimary">#4F46E5</item>
|
|
357
|
+
<item name="colorPrimaryVariant">#3730A3</item>
|
|
358
|
+
<item name="colorOnPrimary">#FFFFFF</item>
|
|
359
|
+
<item name="colorSecondary">#10B981</item>
|
|
360
|
+
<item name="colorSecondaryVariant">#059669</item>
|
|
361
|
+
<item name="colorOnSecondary">#FFFFFF</item>
|
|
362
|
+
<item name="android:statusBarColor">@android:color/transparent</item>
|
|
363
|
+
</style>
|
|
364
|
+
</resources>
|
|
365
|
+
`);
|
|
327
366
|
await writeFile(join(androidResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
367
|
+
<network-security-config>
|
|
368
|
+
<base-config cleartextTrafficPermitted="false" />
|
|
369
|
+
</network-security-config>
|
|
370
|
+
`);
|
|
371
|
+
await writeFile(join(androidDebugResXmlDir, "network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?>
|
|
328
372
|
<network-security-config>
|
|
329
373
|
<domain-config cleartextTrafficPermitted="true">
|
|
330
374
|
<domain includeSubdomains="true">localhost</domain>
|
|
@@ -343,7 +387,7 @@ class MainActivity : VueNativeActivity() {
|
|
|
343
387
|
}
|
|
344
388
|
|
|
345
389
|
override fun getDevServerUrl(): String? {
|
|
346
|
-
return "ws://10.0.2.2:8174"
|
|
390
|
+
return if (BuildConfig.DEBUG) "ws://10.0.2.2:8174" else null
|
|
347
391
|
}
|
|
348
392
|
}
|
|
349
393
|
`);
|
|
@@ -352,6 +396,13 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
|
352
396
|
android.useAndroidX=true
|
|
353
397
|
kotlin.code.style=official
|
|
354
398
|
android.nonTransitiveRClass=true
|
|
399
|
+
`);
|
|
400
|
+
await writeFile(join(androidGradleWrapperDir, "gradle-wrapper.properties"), `distributionBase=GRADLE_USER_HOME
|
|
401
|
+
distributionPath=wrapper/dists
|
|
402
|
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
403
|
+
networkTimeout=10000
|
|
404
|
+
zipStoreBase=GRADLE_USER_HOME
|
|
405
|
+
zipStorePath=wrapper/dists
|
|
355
406
|
`);
|
|
356
407
|
await writeFile(join(dir, "vue-native.config.ts"), `import { defineConfig } from '@thelacanians/vue-native-cli'
|
|
357
408
|
|
|
@@ -363,8 +414,8 @@ export default defineConfig({
|
|
|
363
414
|
deploymentTarget: '16.0',
|
|
364
415
|
},
|
|
365
416
|
android: {
|
|
366
|
-
minSdk:
|
|
367
|
-
targetSdk:
|
|
417
|
+
minSdk: 24,
|
|
418
|
+
targetSdk: 35,
|
|
368
419
|
},
|
|
369
420
|
})
|
|
370
421
|
`);
|
|
@@ -413,6 +464,8 @@ local.properties
|
|
|
413
464
|
console.log(pc.white(" To run on iOS:"));
|
|
414
465
|
console.log(pc.white(" vue-native run ios\n"));
|
|
415
466
|
console.log(pc.white(" To run on Android:"));
|
|
467
|
+
console.log(pc.dim(" Open android/ in Android Studio, or run:"));
|
|
468
|
+
console.log(pc.dim(" cd android && gradle wrapper && cd .."));
|
|
416
469
|
console.log(pc.white(" vue-native run android\n"));
|
|
417
470
|
} catch (err) {
|
|
418
471
|
console.error(pc.red(`Error creating project: ${err.message}`));
|
|
@@ -6,11 +6,11 @@ plugins {
|
|
|
6
6
|
|
|
7
7
|
android {
|
|
8
8
|
namespace = "com.vuenative.core"
|
|
9
|
-
compileSdk =
|
|
9
|
+
compileSdk = 35
|
|
10
10
|
|
|
11
11
|
defaultConfig {
|
|
12
12
|
minSdk = 21
|
|
13
|
-
targetSdk =
|
|
13
|
+
targetSdk = 35
|
|
14
14
|
|
|
15
15
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
16
16
|
consumerProguardFiles("consumer-rules.pro")
|
|
@@ -66,6 +66,12 @@ dependencies {
|
|
|
66
66
|
// Kotlin Coroutines
|
|
67
67
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
|
68
68
|
|
|
69
|
+
// Lifecycle Process (for ProcessLifecycleOwner)
|
|
70
|
+
implementation("androidx.lifecycle:lifecycle-process:2.7.0")
|
|
71
|
+
|
|
72
|
+
// WorkManager (for BackgroundTaskModule)
|
|
73
|
+
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
|
74
|
+
|
|
69
75
|
// Location (for GeolocationModule)
|
|
70
76
|
implementation("com.google.android.gms:play-services-location:21.1.0")
|
|
71
77
|
|
|
@@ -74,6 +80,13 @@ dependencies {
|
|
|
74
80
|
|
|
75
81
|
// Secure Storage (for SecureStorageModule)
|
|
76
82
|
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
|
83
|
+
|
|
84
|
+
// Google Play Billing (for IAPModule)
|
|
85
|
+
implementation("com.android.billingclient:billing:7.0.0")
|
|
86
|
+
|
|
87
|
+
// Credential Manager + Google Identity (for SocialAuthModule)
|
|
88
|
+
implementation("androidx.credentials:credentials:1.2.2")
|
|
89
|
+
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
afterEvaluate {
|
|
@@ -82,7 +95,7 @@ afterEvaluate {
|
|
|
82
95
|
create<MavenPublication>("release") {
|
|
83
96
|
groupId = "com.vuenative"
|
|
84
97
|
artifactId = "core"
|
|
85
|
-
version = "0.4.
|
|
98
|
+
version = "0.4.5"
|
|
86
99
|
from(components["release"])
|
|
87
100
|
}
|
|
88
101
|
}
|
|
@@ -157,7 +157,7 @@ class VListFactory : NativeComponentFactory {
|
|
|
157
157
|
/**
|
|
158
158
|
* Called when the parent view is being destroyed. Cleans up the RecyclerView.
|
|
159
159
|
*/
|
|
160
|
-
fun destroyView(view: View) {
|
|
160
|
+
override fun destroyView(view: View) {
|
|
161
161
|
val rv = view as? RecyclerView ?: return
|
|
162
162
|
cleanupRecyclerView(rv)
|
|
163
163
|
}
|
|
@@ -94,7 +94,7 @@ class VModalFactory : NativeComponentFactory {
|
|
|
94
94
|
* Dismisses the dialog and clears all map entries that reference the view,
|
|
95
95
|
* preventing memory leaks.
|
|
96
96
|
*/
|
|
97
|
-
fun destroyView(view: View) {
|
|
97
|
+
override fun destroyView(view: View) {
|
|
98
98
|
dialogs[view]?.let { dialog ->
|
|
99
99
|
if (dialog.isShowing) dialog.dismiss()
|
|
100
100
|
}
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt
CHANGED
|
@@ -14,7 +14,7 @@ import com.google.android.flexbox.JustifyContent
|
|
|
14
14
|
* with configurable active opacity and support for press and long press events.
|
|
15
15
|
* Mirrors the Swift TouchableView (UIView subclass) for Android.
|
|
16
16
|
*/
|
|
17
|
-
class TouchableView(context: Context) : FlexboxLayout(context) {
|
|
17
|
+
open class TouchableView(context: Context) : FlexboxLayout(context) {
|
|
18
18
|
|
|
19
19
|
/** The opacity to apply when the user is pressing the view. */
|
|
20
20
|
var activeOpacity: Float = 1.0f
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt
CHANGED
|
@@ -108,14 +108,14 @@ class AudioModule : NativeModule {
|
|
|
108
108
|
mp.setOnCompletionListener {
|
|
109
109
|
isPlaying = false
|
|
110
110
|
stopProgressReporting()
|
|
111
|
-
bridge?.
|
|
111
|
+
bridge?.dispatchGlobalEvent("audio:complete", emptyMap())
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
mp.setOnErrorListener { _, what, extra ->
|
|
115
115
|
isPlaying = false
|
|
116
116
|
stopProgressReporting()
|
|
117
117
|
val msg = "MediaPlayer error: what=$what extra=$extra"
|
|
118
|
-
bridge?.
|
|
118
|
+
bridge?.dispatchGlobalEvent("audio:error", mapOf("message" to msg))
|
|
119
119
|
true
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -179,7 +179,7 @@ class AudioModule : NativeModule {
|
|
|
179
179
|
override fun run() {
|
|
180
180
|
val mp = player ?: return
|
|
181
181
|
if (isPlaying) {
|
|
182
|
-
bridge?.
|
|
182
|
+
bridge?.dispatchGlobalEvent("audio:progress", mapOf(
|
|
183
183
|
"currentTime" to mp.currentPosition.toDouble() / 1000.0,
|
|
184
184
|
"duration" to mp.duration.toDouble() / 1000.0
|
|
185
185
|
))
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt
CHANGED
|
@@ -90,7 +90,7 @@ class BluetoothModule : NativeModule {
|
|
|
90
90
|
"getState" -> {
|
|
91
91
|
val state = when {
|
|
92
92
|
bluetoothAdapter == null -> "unsupported"
|
|
93
|
-
|
|
93
|
+
bluetoothAdapter?.isEnabled != true -> "poweredOff"
|
|
94
94
|
else -> "poweredOn"
|
|
95
95
|
}
|
|
96
96
|
callback(state, null)
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt
CHANGED
|
@@ -148,9 +148,9 @@ class IAPModule : NativeModule, PurchasesUpdatedListener {
|
|
|
148
148
|
if (offerToken != null) {
|
|
149
149
|
productBuilder.setOfferToken(offerToken)
|
|
150
150
|
}
|
|
151
|
-
flowParamsBuilder.
|
|
151
|
+
flowParamsBuilder.setProductDetailsParamsList(listOf(productBuilder.build()))
|
|
152
152
|
|
|
153
|
-
val activity =
|
|
153
|
+
val activity = (context as? android.app.Activity) ?: run {
|
|
154
154
|
callback(null, "purchase: no activity available")
|
|
155
155
|
pendingPurchaseCallback = null
|
|
156
156
|
return
|
|
@@ -178,14 +178,14 @@ class IAPModule : NativeModule, PurchasesUpdatedListener {
|
|
|
178
178
|
acknowledgePurchase(purchase)
|
|
179
179
|
val info = mapOf(
|
|
180
180
|
"productId" to (purchase.products.firstOrNull() ?: ""),
|
|
181
|
-
"transactionId" to purchase.orderId,
|
|
181
|
+
"transactionId" to (purchase.orderId ?: ""),
|
|
182
182
|
"purchaseDate" to purchase.purchaseTime.toString(),
|
|
183
183
|
)
|
|
184
184
|
callback?.invoke(info, null)
|
|
185
|
-
bridge?.
|
|
185
|
+
bridge?.dispatchGlobalEvent("iap:transactionUpdate", mapOf(
|
|
186
186
|
"productId" to (purchase.products.firstOrNull() ?: ""),
|
|
187
187
|
"state" to "purchased",
|
|
188
|
-
"transactionId" to purchase.orderId,
|
|
188
|
+
"transactionId" to (purchase.orderId ?: ""),
|
|
189
189
|
))
|
|
190
190
|
} else {
|
|
191
191
|
callback?.invoke(null, "purchase: no purchase returned")
|
|
@@ -193,7 +193,7 @@ class IAPModule : NativeModule, PurchasesUpdatedListener {
|
|
|
193
193
|
}
|
|
194
194
|
BillingClient.BillingResponseCode.USER_CANCELED -> {
|
|
195
195
|
callback?.invoke(null, "purchase: user cancelled")
|
|
196
|
-
bridge?.
|
|
196
|
+
bridge?.dispatchGlobalEvent("iap:transactionUpdate", mapOf(
|
|
197
197
|
"productId" to "",
|
|
198
198
|
"state" to "failed",
|
|
199
199
|
"error" to "user cancelled",
|
|
@@ -202,7 +202,7 @@ class IAPModule : NativeModule, PurchasesUpdatedListener {
|
|
|
202
202
|
else -> {
|
|
203
203
|
val error = "purchase failed: ${result.debugMessage}"
|
|
204
204
|
callback?.invoke(null, error)
|
|
205
|
-
bridge?.
|
|
205
|
+
bridge?.dispatchGlobalEvent("iap:transactionUpdate", mapOf(
|
|
206
206
|
"productId" to "",
|
|
207
207
|
"state" to "failed",
|
|
208
208
|
"error" to error,
|
|
@@ -291,7 +291,7 @@ class IAPModule : NativeModule, PurchasesUpdatedListener {
|
|
|
291
291
|
|
|
292
292
|
private fun purchaseToMap(purchase: Purchase): Map<String, Any?> = mapOf(
|
|
293
293
|
"productId" to (purchase.products.firstOrNull() ?: ""),
|
|
294
|
-
"transactionId" to purchase.orderId,
|
|
294
|
+
"transactionId" to (purchase.orderId ?: ""),
|
|
295
295
|
"purchaseDate" to purchase.purchaseTime.toString(),
|
|
296
296
|
)
|
|
297
297
|
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt
CHANGED
|
@@ -10,6 +10,9 @@ import androidx.credentials.*
|
|
|
10
10
|
import androidx.credentials.exceptions.GetCredentialCancellationException
|
|
11
11
|
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
|
12
12
|
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
|
13
|
+
import kotlinx.coroutines.Dispatchers
|
|
14
|
+
import kotlinx.coroutines.GlobalScope
|
|
15
|
+
import kotlinx.coroutines.launch
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Native module for social authentication (Google Sign In on Android).
|
|
@@ -62,10 +65,10 @@ class SocialAuthModule : NativeModule {
|
|
|
62
65
|
|
|
63
66
|
private fun handleGoogleSignIn(clientId: String, callback: (Any?, String?) -> Unit) {
|
|
64
67
|
val ctx = context ?: run { callback(null, "SocialAuth: no context"); return }
|
|
65
|
-
val activity =
|
|
68
|
+
val activity = ctx as? Activity
|
|
66
69
|
|
|
67
70
|
if (activity == null) {
|
|
68
|
-
callback(null, "signInWithGoogle: no activity available")
|
|
71
|
+
callback(null, "signInWithGoogle: no activity available (context is not an Activity)")
|
|
69
72
|
return
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -81,7 +84,7 @@ class SocialAuthModule : NativeModule {
|
|
|
81
84
|
.build()
|
|
82
85
|
|
|
83
86
|
mainHandler.post {
|
|
84
|
-
|
|
87
|
+
GlobalScope.launch(Dispatchers.Main) {
|
|
85
88
|
try {
|
|
86
89
|
val result = credentialManager.getCredential(activity, request)
|
|
87
90
|
val credential = result.credential
|
package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt
CHANGED
|
@@ -147,7 +147,7 @@ object StyleEngine {
|
|
|
147
147
|
}
|
|
148
148
|
"minWidth" -> updateFlexProps(view) { fp -> fp.copy(minWidth = dpToPx(ctx, toFloat(value, 0f)).toInt()) }
|
|
149
149
|
"minHeight" -> updateFlexProps(view) { fp -> fp.copy(minHeight = dpToPx(ctx, toFloat(value, 0f)).toInt()) }
|
|
150
|
-
"maxWidth" -> view
|
|
150
|
+
"maxWidth" -> updateFlexProps(view) { fp -> fp.copy(maxWidth = dpToPx(ctx, toFloat(value, 0f)).toInt()) }
|
|
151
151
|
|
|
152
152
|
// --- Flex props (stored in FlexProps, applied when inserted) ---
|
|
153
153
|
"flex" -> {
|
|
@@ -192,17 +192,14 @@ object StyleEngine {
|
|
|
192
192
|
"gap" -> {
|
|
193
193
|
val px = dpToPx(ctx, toFloat(value, 0f)).toInt()
|
|
194
194
|
view.setTag(TAG_GAP, px)
|
|
195
|
-
(view as? FlexboxLayout)?.gap = px
|
|
196
195
|
}
|
|
197
196
|
"rowGap" -> {
|
|
198
197
|
val px = dpToPx(ctx, toFloat(value, 0f)).toInt()
|
|
199
198
|
view.setTag(TAG_GAP, px)
|
|
200
|
-
(view as? FlexboxLayout)?.rowGap = px
|
|
201
199
|
}
|
|
202
200
|
"columnGap" -> {
|
|
203
201
|
val px = dpToPx(ctx, toFloat(value, 0f)).toInt()
|
|
204
202
|
view.setTag(TAG_GAP, px)
|
|
205
|
-
(view as? FlexboxLayout)?.columnGap = px
|
|
206
203
|
}
|
|
207
204
|
|
|
208
205
|
// --- Position (absolute) ---
|
|
@@ -424,8 +421,8 @@ object StyleEngine {
|
|
|
424
421
|
tv.paintFlags = when (value) {
|
|
425
422
|
"underline" -> tv.paintFlags or android.graphics.Paint.UNDERLINE_TEXT_FLAG
|
|
426
423
|
"line-through" -> tv.paintFlags or android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
|
|
427
|
-
"none" -> tv.paintFlags and android.graphics.Paint.UNDERLINE_TEXT_FLAG.inv()
|
|
428
|
-
and android.graphics.Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
|
424
|
+
"none" -> (tv.paintFlags and android.graphics.Paint.UNDERLINE_TEXT_FLAG.inv()
|
|
425
|
+
and android.graphics.Paint.STRIKE_THRU_TEXT_FLAG.inv())
|
|
429
426
|
else -> tv.paintFlags
|
|
430
427
|
}
|
|
431
428
|
}
|
|
@@ -467,6 +464,8 @@ object StyleEngine {
|
|
|
467
464
|
val order: Int = 1,
|
|
468
465
|
val minWidth: Int = 0,
|
|
469
466
|
val minHeight: Int = 0,
|
|
467
|
+
val maxWidth: Int = Int.MAX_VALUE,
|
|
468
|
+
val maxHeight: Int = Int.MAX_VALUE,
|
|
470
469
|
)
|
|
471
470
|
|
|
472
471
|
fun getFlexProps(view: View): FlexProps =
|
|
@@ -501,9 +500,10 @@ object StyleEngine {
|
|
|
501
500
|
order = fp.order
|
|
502
501
|
minWidth = fp.minWidth
|
|
503
502
|
minHeight = fp.minHeight
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
503
|
+
maxWidth = fp.maxWidth
|
|
504
|
+
maxHeight = fp.maxHeight
|
|
505
|
+
// Percentage dimensions stored as tags for custom layout logic
|
|
506
|
+
// (FlexboxLayout.LayoutParams does not have widthPercent/heightPercent)
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
|
|
@@ -602,7 +602,7 @@ object StyleEngine {
|
|
|
602
602
|
private fun parseFlexWrap(value: Any?) = when (value) {
|
|
603
603
|
"wrap" -> FlexWrap.WRAP
|
|
604
604
|
"wrap-reverse" -> FlexWrap.WRAP_REVERSE
|
|
605
|
-
else -> FlexWrap.
|
|
605
|
+
else -> FlexWrap.NOWRAP
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
private fun parseAlignItems(value: Any?) = when (value) {
|
|
@@ -94,9 +94,13 @@ public final class CertificatePinning: NSObject, URLSessionDelegate {
|
|
|
94
94
|
|
|
95
95
|
// Check each certificate in the chain against our pins
|
|
96
96
|
let certCount = SecTrustGetCertificateCount(serverTrust)
|
|
97
|
+
guard let certChain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] else {
|
|
98
|
+
completionHandler(.cancelAuthenticationChallenge, nil)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
97
101
|
for i in 0..<certCount {
|
|
98
|
-
guard
|
|
99
|
-
|
|
102
|
+
guard i < certChain.count else { continue }
|
|
103
|
+
let certificate = certChain[i]
|
|
100
104
|
let spkiHash = sha256OfSPKI(for: certificate)
|
|
101
105
|
if expectedPins.contains(spkiHash) {
|
|
102
106
|
completionHandler(.useCredential, URLCredential(trust: serverTrust))
|
|
@@ -76,10 +76,7 @@ final class ErrorOverlayView: UIView {
|
|
|
76
76
|
|
|
77
77
|
static func show(error: String) {
|
|
78
78
|
DispatchQueue.main.async {
|
|
79
|
-
guard let window = UIApplication.shared.
|
|
80
|
-
.compactMap({ $0 as? UIWindowScene })
|
|
81
|
-
.flatMap({ $0.windows })
|
|
82
|
-
.first(where: { $0.isKeyWindow }) else { return }
|
|
79
|
+
guard let window = UIApplication.shared.vn_keyWindow else { return }
|
|
83
80
|
|
|
84
81
|
// Remove any existing overlay
|
|
85
82
|
window.subviews.compactMap { $0 as? ErrorOverlayView }.forEach { $0.removeFromSuperview() }
|
|
@@ -879,8 +879,23 @@ public final class NativeBridge {
|
|
|
879
879
|
private final class TraitObserverView: UIView {
|
|
880
880
|
var onChange: ((Bool) -> Void)?
|
|
881
881
|
|
|
882
|
+
override init(frame: CGRect) {
|
|
883
|
+
super.init(frame: frame)
|
|
884
|
+
if #available(iOS 17.0, *) {
|
|
885
|
+
registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (view: TraitObserverView, _: UITraitCollection) in
|
|
886
|
+
self?.onChange?(view.traitCollection.userInterfaceStyle == .dark)
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
required init?(coder: NSCoder) {
|
|
892
|
+
super.init(coder: coder)
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Fallback for iOS 16
|
|
882
896
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
|
883
897
|
super.traitCollectionDidChange(previousTraitCollection)
|
|
898
|
+
if #available(iOS 17.0, *) { return }
|
|
884
899
|
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
|
|
885
900
|
onChange?(traitCollection.userInterfaceStyle == .dark)
|
|
886
901
|
}
|
|
@@ -58,8 +58,7 @@ final class VActionSheetFactory: NativeComponentFactory {
|
|
|
58
58
|
})
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
guard let
|
|
62
|
-
let rootVC = scene.windows.first?.rootViewController else { return }
|
|
61
|
+
guard let rootVC = UIApplication.shared.vn_keyWindow?.rootViewController else { return }
|
|
63
62
|
var top = rootVC
|
|
64
63
|
while let p = top.presentedViewController { top = p }
|
|
65
64
|
|
|
@@ -140,8 +140,7 @@ final class VAlertDialogFactory: NativeComponentFactory {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Find the topmost presented view controller to present from
|
|
143
|
-
guard let
|
|
144
|
-
let rootVC = scene.windows.first?.rootViewController else { return }
|
|
143
|
+
guard let rootVC = UIApplication.shared.vn_keyWindow?.rootViewController else { return }
|
|
145
144
|
var topVC = rootVC
|
|
146
145
|
while let presented = topVC.presentedViewController { topVC = presented }
|
|
147
146
|
topVC.present(alert, animated: true)
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift
CHANGED
|
@@ -39,10 +39,7 @@ final class VModalFactory: NativeComponentFactory {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
private func showOverlay(for placeholder: UIView) {
|
|
42
|
-
guard let window = UIApplication.shared.
|
|
43
|
-
.compactMap({ $0 as? UIWindowScene })
|
|
44
|
-
.flatMap({ $0.windows })
|
|
45
|
-
.first(where: { $0.isKeyWindow }) else { return }
|
|
42
|
+
guard let window = UIApplication.shared.vn_keyWindow else { return }
|
|
46
43
|
|
|
47
44
|
// Get or create overlay
|
|
48
45
|
let overlay: UIView
|
|
@@ -221,7 +221,7 @@ private final class VSectionListInternalDelegate: NSObject,
|
|
|
221
221
|
return container?.estimatedItemHeight ?? 44
|
|
222
222
|
}
|
|
223
223
|
let h = container.sections[indexPath.section].itemViews[indexPath.row].frame.size.height
|
|
224
|
-
return h > 1 ? h :
|
|
224
|
+
return h > 1 ? h : container.estimatedItemHeight
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift
CHANGED
|
@@ -5,6 +5,12 @@ import AVKit
|
|
|
5
5
|
import FlexLayout
|
|
6
6
|
import ObjectiveC
|
|
7
7
|
|
|
8
|
+
// File-level keys used in non-isolated contexts (e.g. deinit).
|
|
9
|
+
// Must live outside the @MainActor-isolated class so they can be
|
|
10
|
+
// passed as `inout` without actor-isolation violations.
|
|
11
|
+
private nonisolated(unsafe) var _timeObserverKey: UInt8 = 7
|
|
12
|
+
private nonisolated(unsafe) var _endObserverKey: UInt8 = 9
|
|
13
|
+
|
|
8
14
|
/// Factory for VVideo — the video playback component.
|
|
9
15
|
/// Uses AVPlayer + AVPlayerLayer for inline video playback.
|
|
10
16
|
final class VVideoFactory: NativeComponentFactory {
|
|
@@ -18,9 +24,7 @@ final class VVideoFactory: NativeComponentFactory {
|
|
|
18
24
|
private static var onErrorKey: UInt8 = 4
|
|
19
25
|
private static var onProgressKey: UInt8 = 5
|
|
20
26
|
private static var playerKey: UInt8 = 6
|
|
21
|
-
private static var timeObserverKey: UInt8 = 7
|
|
22
27
|
private static var statusObserverKey: UInt8 = 8
|
|
23
|
-
private static var endObserverKey: UInt8 = 9
|
|
24
28
|
|
|
25
29
|
// MARK: - NativeComponentFactory
|
|
26
30
|
|
|
@@ -169,11 +173,11 @@ final class VVideoFactory: NativeComponentFactory {
|
|
|
169
173
|
payload: ["currentTime": currentTime, "duration": dur])
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
|
-
objc_setAssociatedObject(container, &
|
|
176
|
+
objc_setAssociatedObject(container, &_timeObserverKey, timeObserver as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
173
177
|
|
|
174
178
|
// End observer
|
|
175
179
|
let endObserver = NotificationCenter.default.addObserver(
|
|
176
|
-
forName: .
|
|
180
|
+
forName: .AVPlayerItemDidPlayToEndTime,
|
|
177
181
|
object: playerItem,
|
|
178
182
|
queue: .main
|
|
179
183
|
) { [weak container] _ in
|
|
@@ -184,20 +188,20 @@ final class VVideoFactory: NativeComponentFactory {
|
|
|
184
188
|
container.player?.play()
|
|
185
189
|
}
|
|
186
190
|
}
|
|
187
|
-
objc_setAssociatedObject(container, &
|
|
191
|
+
objc_setAssociatedObject(container, &_endObserverKey, endObserver as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
private func cleanupPlayer(for container: VideoContainerView) {
|
|
191
195
|
// Remove time observer
|
|
192
|
-
if let timeObserver = objc_getAssociatedObject(container, &
|
|
196
|
+
if let timeObserver = objc_getAssociatedObject(container, &_timeObserverKey) {
|
|
193
197
|
container.player?.removeTimeObserver(timeObserver)
|
|
194
|
-
objc_setAssociatedObject(container, &
|
|
198
|
+
objc_setAssociatedObject(container, &_timeObserverKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
// Remove end observer
|
|
198
|
-
if let endObserver = objc_getAssociatedObject(container, &
|
|
202
|
+
if let endObserver = objc_getAssociatedObject(container, &_endObserverKey) {
|
|
199
203
|
NotificationCenter.default.removeObserver(endObserver)
|
|
200
|
-
objc_setAssociatedObject(container, &
|
|
204
|
+
objc_setAssociatedObject(container, &_endObserverKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
// Remove status observer
|
|
@@ -232,10 +236,10 @@ private class VideoContainerView: UIView {
|
|
|
232
236
|
|
|
233
237
|
deinit {
|
|
234
238
|
// Clean up player resources
|
|
235
|
-
if let timeObserver = objc_getAssociatedObject(self, &
|
|
239
|
+
if let timeObserver = objc_getAssociatedObject(self, &_timeObserverKey) {
|
|
236
240
|
player?.removeTimeObserver(timeObserver)
|
|
237
241
|
}
|
|
238
|
-
if let endObserver = objc_getAssociatedObject(self, &
|
|
242
|
+
if let endObserver = objc_getAssociatedObject(self, &_endObserverKey) {
|
|
239
243
|
NotificationCenter.default.removeObserver(endObserver)
|
|
240
244
|
}
|
|
241
245
|
player?.pause()
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift
CHANGED
|
@@ -12,9 +12,11 @@ import FlexLayout
|
|
|
12
12
|
final class VWebViewFactory: NativeComponentFactory {
|
|
13
13
|
|
|
14
14
|
// fileprivate so the inner delegate/handler classes in this file can access them.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
fileprivate static var
|
|
15
|
+
// nonisolated(unsafe) allows usage from non-isolated contexts (WKNavigationDelegate,
|
|
16
|
+
// WKScriptMessageHandler callbacks) without actor-isolation errors.
|
|
17
|
+
nonisolated(unsafe) fileprivate static var onLoadKey: UInt8 = 0
|
|
18
|
+
nonisolated(unsafe) fileprivate static var onErrorKey: UInt8 = 1
|
|
19
|
+
nonisolated(unsafe) fileprivate static var onMessageKey: UInt8 = 2
|
|
18
20
|
fileprivate static var delegateKey: UInt8 = 3
|
|
19
21
|
fileprivate static var msgHandlerKey: UInt8 = 4
|
|
20
22
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
// MARK: - Safe array subscript
|
|
5
|
+
|
|
6
|
+
extension Array {
|
|
7
|
+
subscript(safe index: Int) -> Element? {
|
|
8
|
+
indices.contains(index) ? self[index] : nil
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// MARK: - Key window helper (avoids deprecated UIWindowScene.windows)
|
|
13
|
+
|
|
14
|
+
extension UIApplication {
|
|
15
|
+
/// Returns the key window from the first foreground-active window scene.
|
|
16
|
+
var vn_keyWindow: UIWindow? {
|
|
17
|
+
connectedScenes
|
|
18
|
+
.compactMap { $0 as? UIWindowScene }
|
|
19
|
+
.compactMap { $0.keyWindow }
|
|
20
|
+
.first
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Returns the topmost presented view controller.
|
|
24
|
+
var vn_topViewController: UIViewController? {
|
|
25
|
+
guard let root = vn_keyWindow?.rootViewController else { return nil }
|
|
26
|
+
var top = root
|
|
27
|
+
while let presented = top.presentedViewController { top = presented }
|
|
28
|
+
return top
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
#endif
|
|
@@ -3,7 +3,7 @@ import UIKit
|
|
|
3
3
|
|
|
4
4
|
/// Custom UIView subclass that provides button-like touch behavior
|
|
5
5
|
/// with configurable active opacity and support for press and long press events.
|
|
6
|
-
|
|
6
|
+
class TouchableView: UIView {
|
|
7
7
|
|
|
8
8
|
// MARK: - Public properties
|
|
9
9
|
|
|
@@ -280,12 +280,4 @@ final class AnimationModule: NativeModule {
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
// MARK: - Array safe subscript
|
|
284
|
-
|
|
285
|
-
private extension Array {
|
|
286
|
-
subscript(safe index: Int) -> Element? {
|
|
287
|
-
guard index >= 0 && index < count else { return nil }
|
|
288
|
-
return self[index]
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
283
|
#endif
|
|
@@ -1,43 +1,37 @@
|
|
|
1
1
|
#if canImport(UIKit)
|
|
2
2
|
import UIKit
|
|
3
3
|
|
|
4
|
-
final class AppStateModule: NativeModule {
|
|
4
|
+
final class AppStateModule: NSObject, NativeModule {
|
|
5
5
|
var moduleName: String { "AppState" }
|
|
6
6
|
private weak var bridge: NativeBridge?
|
|
7
|
+
private var observers: [NSObjectProtocol] = []
|
|
7
8
|
|
|
8
9
|
init(bridge: NativeBridge) {
|
|
9
10
|
self.bridge = bridge
|
|
11
|
+
super.init()
|
|
10
12
|
setupObservers()
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
private func setupObservers() {
|
|
14
|
-
NotificationCenter.default.addObserver(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
17
|
+
forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
|
|
18
|
+
) { [weak self] _ in
|
|
19
|
+
self?.bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
|
|
20
|
+
})
|
|
21
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
22
|
+
forName: UIApplication.willResignActiveNotification, object: nil, queue: .main
|
|
23
|
+
) { [weak self] _ in
|
|
24
|
+
self?.bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "inactive"])
|
|
25
|
+
})
|
|
26
|
+
observers.append(NotificationCenter.default.addObserver(
|
|
27
|
+
forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main
|
|
28
|
+
) { [weak self] _ in
|
|
29
|
+
self?.bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "background"])
|
|
30
|
+
})
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
DispatchQueue.main.async {
|
|
25
|
-
bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "active"])
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@objc private func willResignActive() {
|
|
30
|
-
let bridge = bridge
|
|
31
|
-
DispatchQueue.main.async {
|
|
32
|
-
bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "inactive"])
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@objc private func didEnterBackground() {
|
|
37
|
-
let bridge = bridge
|
|
38
|
-
DispatchQueue.main.async {
|
|
39
|
-
bridge?.dispatchGlobalEvent("appState:change", payload: ["state": "background"])
|
|
40
|
-
}
|
|
33
|
+
deinit {
|
|
34
|
+
observers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
@@ -17,11 +17,11 @@ import AVFoundation
|
|
|
17
17
|
/// - resumeRecording() -- resume recording
|
|
18
18
|
/// - getStatus() -- returns current playback status
|
|
19
19
|
///
|
|
20
|
-
/// Events (via bridge.
|
|
20
|
+
/// Events (via bridge.dispatchGlobalEvent):
|
|
21
21
|
/// - audio:progress { currentTime, duration }
|
|
22
22
|
/// - audio:complete {}
|
|
23
23
|
/// - audio:error { message }
|
|
24
|
-
final class AudioModule: NativeModule {
|
|
24
|
+
final class AudioModule: NSObject, NativeModule {
|
|
25
25
|
let moduleName = "Audio"
|
|
26
26
|
|
|
27
27
|
private var player: AVAudioPlayer?
|
|
@@ -116,7 +116,7 @@ final class AudioModule: NativeModule {
|
|
|
116
116
|
guard let self = self else { return }
|
|
117
117
|
if let error = error {
|
|
118
118
|
callback(nil, "Failed to download audio: \(error.localizedDescription)")
|
|
119
|
-
self.bridge?.
|
|
119
|
+
self.bridge?.dispatchGlobalEvent("audio:error", payload: ["message": error.localizedDescription])
|
|
120
120
|
return
|
|
121
121
|
}
|
|
122
122
|
guard let data = data else {
|
|
@@ -153,7 +153,9 @@ final class AudioModule: NativeModule {
|
|
|
153
153
|
guard let self = self else { return }
|
|
154
154
|
self.isPlaying = false
|
|
155
155
|
self.stopProgressReporting()
|
|
156
|
-
|
|
156
|
+
DispatchQueue.main.async { [weak self] in
|
|
157
|
+
self?.bridge?.dispatchGlobalEvent("audio:complete", payload: [:])
|
|
158
|
+
}
|
|
157
159
|
}
|
|
158
160
|
player.delegate = delegate
|
|
159
161
|
self.playerDelegate = delegate
|
|
@@ -233,10 +235,13 @@ final class AudioModule: NativeModule {
|
|
|
233
235
|
|
|
234
236
|
@objc private func reportProgress() {
|
|
235
237
|
guard let player = player, isPlaying else { return }
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"
|
|
239
|
-
|
|
238
|
+
DispatchQueue.main.async { [weak self] in
|
|
239
|
+
guard let self = self, let player = self.player else { return }
|
|
240
|
+
self.bridge?.dispatchGlobalEvent("audio:progress", payload: [
|
|
241
|
+
"currentTime": player.currentTime,
|
|
242
|
+
"duration": player.duration,
|
|
243
|
+
])
|
|
244
|
+
}
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
// MARK: - Recording
|
|
@@ -44,7 +44,7 @@ final class BackgroundTaskModule: NativeModule {
|
|
|
44
44
|
callback(nil, "cancelTask: missing taskId")
|
|
45
45
|
return
|
|
46
46
|
}
|
|
47
|
-
BGTaskScheduler.shared.cancel(
|
|
47
|
+
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: taskId)
|
|
48
48
|
callback(nil, nil)
|
|
49
49
|
|
|
50
50
|
case "cancelAllTasks":
|
|
@@ -115,14 +115,6 @@ final class BluetoothModule: NativeModule {
|
|
|
115
115
|
func invokeSync(method: String, args: [Any]) -> Any? { nil }
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// MARK: - Safe array subscript
|
|
119
|
-
|
|
120
|
-
private extension Array {
|
|
121
|
-
subscript(safe index: Int) -> Element? {
|
|
122
|
-
indices.contains(index) ? self[index] : nil
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
118
|
// MARK: - BLE Manager
|
|
127
119
|
|
|
128
120
|
@MainActor
|
|
@@ -151,11 +151,4 @@ final class CalendarModule: NativeModule {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
// MARK: - Safe array subscript (shared)
|
|
155
|
-
|
|
156
|
-
private extension Array {
|
|
157
|
-
subscript(safe index: Int) -> Element? {
|
|
158
|
-
indices.contains(index) ? self[index] : nil
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
154
|
#endif
|
|
@@ -178,13 +178,7 @@ final class CameraModule: NativeModule {
|
|
|
178
178
|
// MARK: - Helpers
|
|
179
179
|
|
|
180
180
|
private func topViewController() -> UIViewController? {
|
|
181
|
-
|
|
182
|
-
let root = scene.windows.first(where: { $0.isKeyWindow })?.rootViewController else {
|
|
183
|
-
return nil
|
|
184
|
-
}
|
|
185
|
-
var top = root
|
|
186
|
-
while let presented = top.presentedViewController { top = presented }
|
|
187
|
-
return top
|
|
181
|
+
return UIApplication.shared.vn_topViewController
|
|
188
182
|
}
|
|
189
183
|
|
|
190
184
|
private static var delegateKey: UInt8 = 0
|
|
@@ -271,16 +265,19 @@ private final class QRScanDelegate: NSObject, AVCaptureMetadataOutputObjectsDele
|
|
|
271
265
|
let data = readable.stringValue else { return }
|
|
272
266
|
|
|
273
267
|
let bounds = readable.bounds
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"
|
|
277
|
-
|
|
278
|
-
"
|
|
279
|
-
"
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
268
|
+
let type = readable.type.rawValue
|
|
269
|
+
DispatchQueue.main.async { [weak self] in
|
|
270
|
+
self?.bridge?.dispatchGlobalEvent("camera:qrDetected", payload: [
|
|
271
|
+
"data": data,
|
|
272
|
+
"type": type,
|
|
273
|
+
"bounds": [
|
|
274
|
+
"x": bounds.origin.x,
|
|
275
|
+
"y": bounds.origin.y,
|
|
276
|
+
"width": bounds.size.width,
|
|
277
|
+
"height": bounds.size.height,
|
|
278
|
+
],
|
|
279
|
+
])
|
|
280
|
+
}
|
|
284
281
|
}
|
|
285
282
|
}
|
|
286
283
|
|
|
@@ -154,7 +154,7 @@ final class IAPModule: NativeModule {
|
|
|
154
154
|
"transactionId": String(transaction.id),
|
|
155
155
|
]
|
|
156
156
|
await MainActor.run {
|
|
157
|
-
self.bridge?.
|
|
157
|
+
self.bridge?.dispatchGlobalEvent("iap:transactionUpdate", payload: payload)
|
|
158
158
|
}
|
|
159
159
|
} catch {
|
|
160
160
|
let payload: [String: Any] = [
|
|
@@ -163,7 +163,7 @@ final class IAPModule: NativeModule {
|
|
|
163
163
|
"error": error.localizedDescription,
|
|
164
164
|
]
|
|
165
165
|
await MainActor.run {
|
|
166
|
-
self.bridge?.
|
|
166
|
+
self.bridge?.dispatchGlobalEvent("iap:transactionUpdate", payload: payload)
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
}
|
|
@@ -4,7 +4,7 @@ import UIKit
|
|
|
4
4
|
/// Native module for performance profiling.
|
|
5
5
|
/// Tracks FPS via CADisplayLink, memory usage via task_info, and bridge operation counts.
|
|
6
6
|
/// Dispatches `perf:metrics` global events every 1 second while profiling is active.
|
|
7
|
-
final class PerformanceModule: NativeModule {
|
|
7
|
+
final class PerformanceModule: NSObject, NativeModule {
|
|
8
8
|
|
|
9
9
|
let moduleName = "Performance"
|
|
10
10
|
|
|
@@ -25,6 +25,7 @@ final class PerformanceModule: NativeModule {
|
|
|
25
25
|
|
|
26
26
|
init(bridge: NativeBridge) {
|
|
27
27
|
self.bridge = bridge
|
|
28
|
+
super.init()
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
|
|
@@ -116,7 +117,9 @@ final class PerformanceModule: NativeModule {
|
|
|
116
117
|
guard isProfiling else { return }
|
|
117
118
|
bridgeOpsCount += 1 // Count the metrics dispatch itself
|
|
118
119
|
let metrics = collectMetrics()
|
|
119
|
-
|
|
120
|
+
DispatchQueue.main.async { [weak self] in
|
|
121
|
+
self?.bridge?.dispatchGlobalEvent("perf:metrics", payload: metrics)
|
|
122
|
+
}
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// MARK: - Memory measurement
|
|
@@ -18,8 +18,7 @@ final class ShareModule: NativeModule {
|
|
|
18
18
|
|
|
19
19
|
DispatchQueue.main.async {
|
|
20
20
|
let vc = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
|
21
|
-
if let
|
|
22
|
-
let rootVC = windowScene.windows.first?.rootViewController {
|
|
21
|
+
if let rootVC = UIApplication.shared.vn_keyWindow?.rootViewController {
|
|
23
22
|
// iPad popover support
|
|
24
23
|
if let popover = vc.popoverPresentationController {
|
|
25
24
|
popover.sourceView = rootVC.view
|
|
@@ -114,9 +114,7 @@ final class SocialAuthModule: NSObject, NativeModule, ASAuthorizationControllerD
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
117
|
-
return UIApplication.shared.
|
|
118
|
-
.compactMap { $0 as? UIWindowScene }
|
|
119
|
-
.first?.windows.first { $0.isKeyWindow } ?? UIWindow()
|
|
117
|
+
return UIApplication.shared.vn_keyWindow ?? UIWindow()
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
// MARK: - Google Sign In (URL-based OAuth)
|
|
@@ -164,10 +162,11 @@ final class SocialAuthModule: NSObject, NativeModule, ASAuthorizationControllerD
|
|
|
164
162
|
|
|
165
163
|
session.prefersEphemeralWebBrowserSession = false
|
|
166
164
|
|
|
167
|
-
//
|
|
168
|
-
if let
|
|
169
|
-
|
|
170
|
-
session.
|
|
165
|
+
// Provide a proper presentation context for the auth session
|
|
166
|
+
if let window = UIApplication.shared.vn_keyWindow {
|
|
167
|
+
let provider = WebAuthContextProvider(window: window)
|
|
168
|
+
objc_setAssociatedObject(session, &SocialAuthModule.webAuthProviderKey, provider, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
169
|
+
session.presentationContextProvider = provider
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
session.start()
|
|
@@ -227,14 +226,31 @@ final class SocialAuthModule: NSObject, NativeModule, ASAuthorizationControllerD
|
|
|
227
226
|
queue: .main
|
|
228
227
|
) { [weak self] _ in
|
|
229
228
|
UserDefaults.standard.removeObject(forKey: "vn_apple_userId")
|
|
230
|
-
self?.bridge?.
|
|
229
|
+
self?.bridge?.dispatchGlobalEvent("auth:appleCredentialRevoked", payload: [:])
|
|
231
230
|
}
|
|
232
231
|
}
|
|
233
232
|
|
|
233
|
+
private static var webAuthProviderKey: UInt8 = 0
|
|
234
|
+
|
|
234
235
|
deinit {
|
|
235
236
|
if let observer = credentialObserver {
|
|
236
237
|
NotificationCenter.default.removeObserver(observer)
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
}
|
|
241
|
+
|
|
242
|
+
// MARK: - ASWebAuthenticationSession presentation context
|
|
243
|
+
|
|
244
|
+
private final class WebAuthContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
|
|
245
|
+
private let window: UIWindow
|
|
246
|
+
|
|
247
|
+
init(window: UIWindow) {
|
|
248
|
+
self.window = window
|
|
249
|
+
super.init()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
|
253
|
+
return window
|
|
254
|
+
}
|
|
255
|
+
}
|
|
240
256
|
#endif
|
|
@@ -495,9 +495,9 @@ enum StyleEngine {
|
|
|
495
495
|
case "direction":
|
|
496
496
|
if let str = value as? String {
|
|
497
497
|
switch str {
|
|
498
|
-
case "ltr": flex.
|
|
499
|
-
case "rtl": flex.
|
|
500
|
-
case "inherit": flex.
|
|
498
|
+
case "ltr": flex.layoutDirection(.ltr)
|
|
499
|
+
case "rtl": flex.layoutDirection(.rtl)
|
|
500
|
+
case "inherit": flex.layoutDirection(.inherit)
|
|
501
501
|
default: break
|
|
502
502
|
}
|
|
503
503
|
}
|