craft-native 0.0.124 → 0.0.126
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/android/src/index.js +5 -3
- package/dist/android/templates/CraftNativeFlatList.kt.template +39 -2
- package/dist/android/templates/CraftNativeRefreshableFlatList.kt.template +29 -0
- package/dist/android/templates/MainActivityNative.kt.template +269 -26
- package/dist/android/templates/build.gradle.kts.app.template +1 -0
- package/dist/api/mobile.d.ts +43 -0
- package/dist/cli.js +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/ios/src/index.d.ts +49 -1
- package/dist/ios/src/index.js +150 -4
- package/dist/ios/templates/CraftApp.swift +111 -13
- package/dist/ios/templates/CraftHybrid.swift +1199 -0
- package/dist/ios/templates/CraftNativeActions.swift +213 -1
- package/dist/ios/templates/CraftNativeFlatList.swift +150 -21
- package/dist/ios/templates/CraftNativeScreen.swift +1683 -307
- package/dist/mobile.js +66 -0
- package/package.json +1 -1
|
@@ -425,10 +425,12 @@ async function init(options) {
|
|
|
425
425
|
if (config.renderer === "native") {
|
|
426
426
|
const mutationTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeMutation.kt.template"), "utf-8");
|
|
427
427
|
const flatListTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeFlatList.kt.template"), "utf-8");
|
|
428
|
+
const refreshableFlatListTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeRefreshableFlatList.kt.template"), "utf-8");
|
|
428
429
|
const capabilityTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeCapabilities.kt.template"), "utf-8");
|
|
429
430
|
const notificationTemplate = readFileSync(join(TEMPLATES_DIR, "CraftNativeNotificationReceiver.kt.template"), "utf-8");
|
|
430
431
|
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftNativeMutation.kt"), mutationTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
431
432
|
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftNativeFlatList.kt"), flatListTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
433
|
+
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftNativeRefreshableFlatList.kt"), refreshableFlatListTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
432
434
|
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftNativeCapabilities.kt"), capabilityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
433
435
|
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftNativeNotificationReceiver.kt"), notificationTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
434
436
|
}
|
|
@@ -475,7 +477,7 @@ async function init(options) {
|
|
|
475
477
|
const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.project.template"), "utf-8");
|
|
476
478
|
writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
|
|
477
479
|
const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.app.template"), "utf-8");
|
|
478
|
-
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, escapeKotlinString(config.version || "1.0.0")).replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk ?? ANDROID_MIN_SDK)).replace(/\{\{COMPILE_SDK\}\}/g, String(config.compileSdk || 36)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
|
|
480
|
+
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, escapeKotlinString(config.version || "1.0.0")).replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk ?? ANDROID_MIN_SDK)).replace(/\{\{COMPILE_SDK\}\}/g, String(config.compileSdk || 36)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{REFRESH_LAYOUT_DEPENDENCY\}\}/g, config.renderer === "native" ? ' implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
|
|
479
481
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "").replace(/\{\{JAVASCRIPT_ENGINE_DEPENDENCY\}\}/g, config.renderer === "native" ? ' implementation("androidx.javascriptengine:javascriptengine:1.1.0")' : "");
|
|
480
482
|
writeFileSync(join(output, "app/build.gradle.kts"), appGradle);
|
|
481
483
|
const proguardTemplate = readFileSync(join(TEMPLATES_DIR, "proguard-rules.pro.template"), "utf-8");
|
|
@@ -618,7 +620,7 @@ zipStorePath=wrapper/dists
|
|
|
618
620
|
console.log("Next steps:");
|
|
619
621
|
console.log(` 1. cd ${output}`);
|
|
620
622
|
if (config.renderer === "native") {
|
|
621
|
-
console.log(" 2. Compile your screen: stx
|
|
623
|
+
console.log(" 2. Compile your screen: stx native compile Screen.stx --format bundle --output screen.js");
|
|
622
624
|
console.log(" 3. Run: craft android build --native-bundle screen.js");
|
|
623
625
|
} else {
|
|
624
626
|
console.log(" 2. Add your web content to app/src/main/assets/index.html");
|
|
@@ -644,7 +646,7 @@ async function build(options) {
|
|
|
644
646
|
if (nativeBundlePath && resolve(nativeBundlePath) !== resolve(destination))
|
|
645
647
|
cpSync(nativeBundlePath, destination);
|
|
646
648
|
if (!existsSync(destination)) {
|
|
647
|
-
throw new Error("Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with stx
|
|
649
|
+
throw new Error("Native Android mode needs app/src/main/assets/native-screen.js. Compile a .stx screen with `stx native compile` first.");
|
|
648
650
|
}
|
|
649
651
|
} else if (nativeBundlePath) {
|
|
650
652
|
throw new Error('--native-bundle requires renderer: "native" in craft.config.json.');
|
|
@@ -14,7 +14,7 @@ import kotlin.math.ceil
|
|
|
14
14
|
|
|
15
15
|
internal typealias CraftNativeRenderItem = (node: JSONObject, identity: String, previous: View?) -> View
|
|
16
16
|
|
|
17
|
-
/** A stable-key RecyclerView host for stx
|
|
17
|
+
/** A stable-key RecyclerView host for stx native FlatList nodes. */
|
|
18
18
|
internal class CraftNativeFlatList(context: Context) : RecyclerView(context) {
|
|
19
19
|
private data class Item(
|
|
20
20
|
val identity: String,
|
|
@@ -90,6 +90,7 @@ internal class CraftNativeFlatList(context: Context) : RecyclerView(context) {
|
|
|
90
90
|
private var dataContentSignature = ""
|
|
91
91
|
private var horizontal = false
|
|
92
92
|
private var columns = 1
|
|
93
|
+
private var keyboardShouldPersistTaps = "never"
|
|
93
94
|
private var submissionGeneration = 0
|
|
94
95
|
private var isSubmittingList = false
|
|
95
96
|
private var pendingThemeRefresh = false
|
|
@@ -161,7 +162,7 @@ internal class CraftNativeFlatList(context: Context) : RecyclerView(context) {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
fun setKeyboardDismissMode(mode: String, dismissKeyboard: () -> Unit) {
|
|
164
|
-
if (mode !in setOf("on-drag", "interactive")) {
|
|
165
|
+
if (mode !in setOf("on-drag", "interactive") && keyboardShouldPersistTaps == "always") {
|
|
165
166
|
setOnTouchListener(null)
|
|
166
167
|
return
|
|
167
168
|
}
|
|
@@ -173,11 +174,47 @@ internal class CraftNativeFlatList(context: Context) : RecyclerView(context) {
|
|
|
173
174
|
dismissed = true
|
|
174
175
|
dismissKeyboard()
|
|
175
176
|
}
|
|
177
|
+
android.view.MotionEvent.ACTION_UP -> if (shouldDismissKeyboard(event)) dismissKeyboard()
|
|
176
178
|
}
|
|
177
179
|
false
|
|
178
180
|
}
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
fun setKeyboardShouldPersistTaps(value: String?) {
|
|
184
|
+
keyboardShouldPersistTaps = value?.takeIf { it in setOf("always", "handled", "never") } ?: "never"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun tapTarget(view: View, x: Float, y: Float): View? {
|
|
188
|
+
if (x < 0 || y < 0 || x >= view.width || y >= view.height || view.visibility != View.VISIBLE) return null
|
|
189
|
+
if (view is ViewGroup) {
|
|
190
|
+
for (index in view.childCount - 1 downTo 0) {
|
|
191
|
+
val child = view.getChildAt(index)
|
|
192
|
+
tapTarget(child, x - child.left, y - child.top)?.let { return it }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return view
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private fun shouldDismissKeyboard(event: android.view.MotionEvent): Boolean {
|
|
199
|
+
if (keyboardShouldPersistTaps == "always") return false
|
|
200
|
+
if (keyboardShouldPersistTaps == "never") return true
|
|
201
|
+
return tapTarget(this, event.x, event.y)?.isClickable != true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fun setContentContainerStyle(style: JSONObject?, density: Float) {
|
|
205
|
+
val padding = (style?.optDouble("padding", 0.0) ?: 0.0) * density
|
|
206
|
+
val horizontal = (style?.optDouble("paddingHorizontal", padding / density) ?: (padding / density)) * density
|
|
207
|
+
val vertical = (style?.optDouble("paddingVertical", padding / density) ?: (padding / density)) * density
|
|
208
|
+
setPadding(
|
|
209
|
+
((style?.optDouble("paddingLeft", horizontal / density) ?: (horizontal / density)) * density).toInt(),
|
|
210
|
+
((style?.optDouble("paddingTop", vertical / density) ?: (vertical / density)) * density).toInt(),
|
|
211
|
+
((style?.optDouble("paddingRight", horizontal / density) ?: (horizontal / density)) * density).toInt(),
|
|
212
|
+
((style?.optDouble("paddingBottom", vertical / density) ?: (vertical / density)) * density).toInt(),
|
|
213
|
+
)
|
|
214
|
+
clipToPadding = true
|
|
215
|
+
requestLayout()
|
|
216
|
+
}
|
|
217
|
+
|
|
181
218
|
fun apply(
|
|
182
219
|
nodes: List<JSONObject>,
|
|
183
220
|
horizontal: Boolean,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package {{PACKAGE_NAME}}
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
5
|
+
|
|
6
|
+
/** Swipe-to-refresh host for a native FlatList. */
|
|
7
|
+
internal class CraftNativeRefreshableFlatList(context: Context) : SwipeRefreshLayout(context) {
|
|
8
|
+
val list = CraftNativeFlatList(context)
|
|
9
|
+
private var refreshHandler: (() -> Unit)? = null
|
|
10
|
+
|
|
11
|
+
init {
|
|
12
|
+
addView(list, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
13
|
+
setOnRefreshListener { refreshHandler?.invoke() }
|
|
14
|
+
isEnabled = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fun configureRefresh(handler: (() -> Unit)?, refreshing: Boolean) {
|
|
18
|
+
refreshHandler = handler
|
|
19
|
+
isEnabled = handler != null
|
|
20
|
+
isRefreshing = handler != null && refreshing
|
|
21
|
+
if (handler == null) isRefreshing = false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun onDetachedFromWindow() {
|
|
25
|
+
refreshHandler = null
|
|
26
|
+
isRefreshing = false
|
|
27
|
+
super.onDetachedFromWindow()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -33,6 +33,8 @@ import android.text.InputType
|
|
|
33
33
|
import android.text.Layout
|
|
34
34
|
import android.view.View
|
|
35
35
|
import android.view.ViewGroup
|
|
36
|
+
import android.widget.AdapterView
|
|
37
|
+
import android.widget.ArrayAdapter
|
|
36
38
|
import android.widget.Button
|
|
37
39
|
import android.widget.EditText
|
|
38
40
|
import android.widget.FrameLayout
|
|
@@ -41,6 +43,7 @@ import android.widget.ImageView
|
|
|
41
43
|
import android.widget.LinearLayout
|
|
42
44
|
import android.widget.ScrollView
|
|
43
45
|
import android.widget.SeekBar
|
|
46
|
+
import android.widget.Spinner
|
|
44
47
|
import android.widget.Switch
|
|
45
48
|
import android.widget.TextView
|
|
46
49
|
import androidx.appcompat.app.AppCompatActivity
|
|
@@ -191,6 +194,16 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
191
194
|
val lp = child.layoutParams as? MarginLayoutParams
|
|
192
195
|
val horizontalMargins = (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
|
|
193
196
|
val verticalMargins = (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
|
197
|
+
if (child is CraftNativeModalView) {
|
|
198
|
+
val modalWidth = max(0, width - paddingLeft - paddingRight)
|
|
199
|
+
val modalHeight = max(0, height - paddingTop - paddingBottom)
|
|
200
|
+
child.measure(
|
|
201
|
+
View.MeasureSpec.makeMeasureSpec(modalWidth, View.MeasureSpec.EXACTLY),
|
|
202
|
+
View.MeasureSpec.makeMeasureSpec(modalHeight, View.MeasureSpec.EXACTLY),
|
|
203
|
+
)
|
|
204
|
+
absolute += Entry(child, style, modalWidth, modalHeight)
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
194
207
|
val childWidth = measureChildWidth(child, style, width - paddingLeft - paddingRight, horizontalMargins)
|
|
195
208
|
val childHeight = measureChildHeight(child, style, height - paddingTop - paddingBottom, verticalMargins)
|
|
196
209
|
val entry = Entry(child, style, childWidth, childHeight)
|
|
@@ -223,6 +236,16 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
223
236
|
val lp = child.layoutParams as? MarginLayoutParams
|
|
224
237
|
val horizontalMargins = (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
|
|
225
238
|
val verticalMargins = (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
|
239
|
+
if (child is CraftNativeModalView) {
|
|
240
|
+
val modalWidth = max(0, width - paddingLeft - paddingRight)
|
|
241
|
+
val modalHeight = max(0, height - paddingTop - paddingBottom)
|
|
242
|
+
child.measure(
|
|
243
|
+
View.MeasureSpec.makeMeasureSpec(modalWidth, View.MeasureSpec.EXACTLY),
|
|
244
|
+
View.MeasureSpec.makeMeasureSpec(modalHeight, View.MeasureSpec.EXACTLY),
|
|
245
|
+
)
|
|
246
|
+
absolute += Entry(child, style, modalWidth, modalHeight)
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
226
249
|
val childWidth = measureChildWidth(child, style, cellWidth, horizontalMargins)
|
|
227
250
|
val childHeight = measureChildHeight(child, style, height - paddingTop - paddingBottom, verticalMargins)
|
|
228
251
|
val entry = Entry(child, style, childWidth, childHeight)
|
|
@@ -250,7 +273,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
250
273
|
val child = getChildAt(index)
|
|
251
274
|
if (child.visibility == GONE) continue
|
|
252
275
|
val style = styles[child] ?: CraftNativeLayoutStyle()
|
|
253
|
-
if (style.position == "absolute") continue
|
|
276
|
+
if (style.position == "absolute" || child is CraftNativeModalView) continue
|
|
254
277
|
val lp = child.layoutParams as? MarginLayoutParams
|
|
255
278
|
val horizontalMargins = (lp?.leftMargin ?: 0) + (lp?.rightMargin ?: 0)
|
|
256
279
|
val verticalMargins = (lp?.topMargin ?: 0) + (lp?.bottomMargin ?: 0)
|
|
@@ -365,6 +388,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
365
388
|
val contentWidth = width - paddingLeft - paddingRight
|
|
366
389
|
val contentHeight = height - paddingTop - paddingBottom
|
|
367
390
|
for (entry in layout.absolute) {
|
|
391
|
+
if (entry.view is CraftNativeModalView && entry.view.visibility == View.VISIBLE) entry.view.bringToFront()
|
|
368
392
|
val childWidth = if (entry.style.left != null && entry.style.right != null) contentWidth - entry.style.left!! - entry.style.right!! else entry.width
|
|
369
393
|
val childHeight = if (entry.style.top != null && entry.style.bottom != null) contentHeight - entry.style.top!! - entry.style.bottom!! else entry.height
|
|
370
394
|
val x = paddingLeft + (entry.style.left ?: (entry.style.right?.let { contentWidth - it - childWidth } ?: 0))
|
|
@@ -435,6 +459,7 @@ private class CraftNativeFlexLayout(context: Context) : LinearLayout(context) {
|
|
|
435
459
|
crossOffset += line.cross + crossGap
|
|
436
460
|
}
|
|
437
461
|
for (entry in absolute) {
|
|
462
|
+
if (entry.view is CraftNativeModalView && entry.view.visibility == View.VISIBLE) entry.view.bringToFront()
|
|
438
463
|
val widthValue = if (entry.style.left != null && entry.style.right != null) contentWidth - entry.style.left!! - entry.style.right!! else entry.width
|
|
439
464
|
val heightValue = if (entry.style.top != null && entry.style.bottom != null) contentHeight - entry.style.top!! - entry.style.bottom!! else entry.height
|
|
440
465
|
val x = paddingLeft + (entry.style.left ?: (entry.style.right?.let { contentWidth - it - widthValue } ?: 0))
|
|
@@ -463,6 +488,7 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
463
488
|
private var bounces = true
|
|
464
489
|
private var alwaysBounceVertical = true
|
|
465
490
|
private var alwaysBounceHorizontal = true
|
|
491
|
+
private var keyboardShouldPersistTaps = "never"
|
|
466
492
|
|
|
467
493
|
init {
|
|
468
494
|
vertical.isNestedScrollingEnabled = true
|
|
@@ -496,6 +522,10 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
496
522
|
horizontal.isEnabled = value
|
|
497
523
|
}
|
|
498
524
|
|
|
525
|
+
fun setKeyboardShouldPersistTaps(value: String?) {
|
|
526
|
+
keyboardShouldPersistTaps = value?.takeIf { it in setOf("always", "handled", "never") } ?: "never"
|
|
527
|
+
}
|
|
528
|
+
|
|
499
529
|
fun setBounces(value: Boolean) {
|
|
500
530
|
bounces = value
|
|
501
531
|
applyBounceModes()
|
|
@@ -512,6 +542,25 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
512
542
|
horizontal.overScrollMode = if (bounces && alwaysBounceHorizontal) View.OVER_SCROLL_ALWAYS else View.OVER_SCROLL_NEVER
|
|
513
543
|
}
|
|
514
544
|
|
|
545
|
+
private fun tapTarget(view: View, x: Float, y: Float): View? {
|
|
546
|
+
if (x < 0 || y < 0 || x >= view.width || y >= view.height || view.visibility != View.VISIBLE) return null
|
|
547
|
+
if (view is ViewGroup) {
|
|
548
|
+
for (index in view.childCount - 1 downTo 0) {
|
|
549
|
+
val child = view.getChildAt(index)
|
|
550
|
+
tapTarget(child, x - child.left, y - child.top)?.let { return it }
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return view
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private fun shouldDismissKeyboard(scroller: View, event: android.view.MotionEvent): Boolean {
|
|
557
|
+
if (keyboardShouldPersistTaps == "always") return false
|
|
558
|
+
return when (keyboardShouldPersistTaps) {
|
|
559
|
+
"handled" -> (scroller as? ViewGroup)?.let { tapTarget(it, event.x, event.y)?.isClickable != true } ?: true
|
|
560
|
+
else -> true
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
515
564
|
fun setPagingEnabled(value: Boolean) {
|
|
516
565
|
pagingEnabled = value
|
|
517
566
|
}
|
|
@@ -584,6 +633,9 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
584
633
|
}
|
|
585
634
|
}
|
|
586
635
|
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
|
|
636
|
+
if (event.actionMasked == android.view.MotionEvent.ACTION_UP && shouldDismissKeyboard(scroller, event)) {
|
|
637
|
+
dismissKeyboard?.invoke()
|
|
638
|
+
}
|
|
587
639
|
onEndDrag?.invoke()
|
|
588
640
|
if (pagingEnabled) snapToPage(scroller)
|
|
589
641
|
if (moved && !momentumActive) {
|
|
@@ -605,6 +657,46 @@ private class CraftNativeScrollView(context: Context) : FrameLayout(context) {
|
|
|
605
657
|
|
|
606
658
|
private class CraftNativeFlexSpacer(context: Context) : View(context)
|
|
607
659
|
|
|
660
|
+
private class CraftNativeModalView(context: Context) : FrameLayout(context) {
|
|
661
|
+
val content = CraftNativeFlexLayout(context)
|
|
662
|
+
private val blocker = View(context)
|
|
663
|
+
private var renderedVisible = false
|
|
664
|
+
var onRequestClose: (() -> Unit)? = null
|
|
665
|
+
|
|
666
|
+
var transparent: Boolean = false
|
|
667
|
+
set(value) {
|
|
668
|
+
field = value
|
|
669
|
+
blocker.setBackgroundColor(if (value) Color.TRANSPARENT else Color.argb(82, 0, 0, 0))
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
init {
|
|
673
|
+
isClickable = true
|
|
674
|
+
blocker.isClickable = true
|
|
675
|
+
blocker.setOnTouchListener { _, _ -> true }
|
|
676
|
+
blocker.setBackgroundColor(Color.argb(82, 0, 0, 0))
|
|
677
|
+
addView(blocker, LayoutParams(-1, -1))
|
|
678
|
+
addView(content, LayoutParams(-1, -1))
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
fun setVisible(visible: Boolean, opacity: Float, animationType: String?) {
|
|
682
|
+
if (renderedVisible == visible && visibility == if (visible) View.VISIBLE else View.GONE) return
|
|
683
|
+
renderedVisible = visible
|
|
684
|
+
if (!visible) {
|
|
685
|
+
animate().cancel()
|
|
686
|
+
alpha = 0f
|
|
687
|
+
visibility = View.GONE
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
visibility = View.VISIBLE
|
|
691
|
+
if (animationType == "none") {
|
|
692
|
+
alpha = opacity
|
|
693
|
+
} else {
|
|
694
|
+
alpha = 0f
|
|
695
|
+
animate().alpha(opacity).setDuration(if (animationType == "slide") 240L else 180L).start()
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
608
700
|
private data class LayoutBounds(
|
|
609
701
|
val x: Double,
|
|
610
702
|
val y: Double,
|
|
@@ -612,7 +704,7 @@ private data class LayoutBounds(
|
|
|
612
704
|
val height: Double,
|
|
613
705
|
)
|
|
614
706
|
|
|
615
|
-
/** WebView-free host for a compiled stx
|
|
707
|
+
/** WebView-free host for a route bundle compiled by `stx native compile`. */
|
|
616
708
|
class MainActivity : AppCompatActivity() {
|
|
617
709
|
private class Screen(
|
|
618
710
|
val route: String?,
|
|
@@ -626,6 +718,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
626
718
|
val suppressedTextChanges = mutableSetOf<EditText>()
|
|
627
719
|
val controls = mutableMapOf<String, View>()
|
|
628
720
|
val handlers = mutableMapOf<View, String>()
|
|
721
|
+
val pressInHandlers = mutableMapOf<View, String>()
|
|
722
|
+
val pressOutHandlers = mutableMapOf<View, String>()
|
|
629
723
|
val longPressHandlers = mutableMapOf<View, String>()
|
|
630
724
|
val scrollHandlers = mutableMapOf<View, String>()
|
|
631
725
|
val scrollBeginHandlers = mutableMapOf<View, String>()
|
|
@@ -642,6 +736,10 @@ class MainActivity : AppCompatActivity() {
|
|
|
642
736
|
val imageLoadHandlers = mutableMapOf<ImageView, String>()
|
|
643
737
|
val imageLoadEndHandlers = mutableMapOf<ImageView, String>()
|
|
644
738
|
val imageErrorHandlers = mutableMapOf<ImageView, String>()
|
|
739
|
+
val pressActiveOpacities = mutableMapOf<View, Float>()
|
|
740
|
+
val pressBaseOpacities = mutableMapOf<View, Float>()
|
|
741
|
+
val modalVisibility = mutableMapOf<View, Boolean>()
|
|
742
|
+
val modalCloseHandlers = mutableMapOf<View, String>()
|
|
645
743
|
val layoutHandlers = mutableMapOf<View, String>()
|
|
646
744
|
val lastLayoutBounds = mutableMapOf<View, LayoutBounds>()
|
|
647
745
|
val flatListRows = mutableMapOf<CraftNativeFlatList, MutableMap<String, MutableMap<String, View>>>()
|
|
@@ -695,6 +793,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
695
793
|
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
|
|
696
794
|
val safe = insets.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
|
|
697
795
|
view.setPadding(safe.left, safe.top, safe.right, safe.bottom)
|
|
796
|
+
view.requestLayout()
|
|
797
|
+
view.post { screens.lastOrNull()?.takeIf { it.active }?.let(::emitLayoutEvents) }
|
|
698
798
|
insets
|
|
699
799
|
}
|
|
700
800
|
ViewCompat.requestApplyInsets(root)
|
|
@@ -896,7 +996,13 @@ class MainActivity : AppCompatActivity() {
|
|
|
896
996
|
|
|
897
997
|
private fun refreshScreenTheme(screen: Screen) {
|
|
898
998
|
screen.mutations.node("root")?.let { renderCommitted(screen, it) }
|
|
899
|
-
screen.controls.values.
|
|
999
|
+
screen.controls.values.mapNotNull {
|
|
1000
|
+
when (it) {
|
|
1001
|
+
is CraftNativeRefreshableFlatList -> it.list
|
|
1002
|
+
is CraftNativeFlatList -> it
|
|
1003
|
+
else -> null
|
|
1004
|
+
}
|
|
1005
|
+
}.forEach { it.refreshThemeDefaults() }
|
|
900
1006
|
}
|
|
901
1007
|
|
|
902
1008
|
private fun refreshThemeDefaults() {
|
|
@@ -1169,6 +1275,60 @@ class MainActivity : AppCompatActivity() {
|
|
|
1169
1275
|
override fun afterTextChanged(s: Editable?) {}
|
|
1170
1276
|
})
|
|
1171
1277
|
}
|
|
1278
|
+
"Picker" -> (previous as? Spinner ?: Spinner(this)).apply {
|
|
1279
|
+
val options = (0 until children.length()).mapNotNull { index ->
|
|
1280
|
+
val option = children.optJSONObject(index) ?: return@mapNotNull null
|
|
1281
|
+
if (option.optString("type") != "Text") return@mapNotNull null
|
|
1282
|
+
val label = (option.optJSONArray("children") ?: JSONArray()).let { text ->
|
|
1283
|
+
(0 until text.length()).joinToString("") { text.optString(it) }
|
|
1284
|
+
}
|
|
1285
|
+
if (label.isBlank()) return@mapNotNull null
|
|
1286
|
+
val optionProps = option.optJSONObject("props")
|
|
1287
|
+
val value = optionProps?.optString("value").takeIf { !it.isNullOrBlank() } ?: label
|
|
1288
|
+
value to label
|
|
1289
|
+
}
|
|
1290
|
+
adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_spinner_item, options.map { it.second }).also {
|
|
1291
|
+
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
1292
|
+
}
|
|
1293
|
+
val requested = props.optString("selectedValue").ifBlank { props.optString("value") }
|
|
1294
|
+
val selected = options.indexOfFirst { it.first == requested }.takeIf { it >= 0 } ?: 0
|
|
1295
|
+
isEnabled = !props.optBoolean("disabled", false)
|
|
1296
|
+
val handler = events.optString("onValueChange").ifBlank { events.optString("onChange") }
|
|
1297
|
+
if (handler.isNotBlank()) screen.handlers[this] = handler else screen.handlers.remove(this)
|
|
1298
|
+
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
|
1299
|
+
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
|
1300
|
+
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
|
1301
|
+
val current = screen.handlers[this@apply] ?: return
|
|
1302
|
+
val option = options.getOrNull(position) ?: return
|
|
1303
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()
|
|
1304
|
+
.put("value", option.first).put("index", position)))
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
setSelection(selected, false)
|
|
1308
|
+
}
|
|
1309
|
+
"Modal" -> (previous as? CraftNativeModalView ?: CraftNativeModalView(this)).apply {
|
|
1310
|
+
transparent = props.optBoolean("transparent", false)
|
|
1311
|
+
content.configure(style, resources.displayMetrics.density)
|
|
1312
|
+
reconcileChildren(screen, content, children, path, next, style, previousControls)
|
|
1313
|
+
val visible = props.optBoolean("visible", false)
|
|
1314
|
+
val wasVisible = screen.modalVisibility[this]
|
|
1315
|
+
screen.modalVisibility[this] = visible
|
|
1316
|
+
val requestClose = events.optString("onRequestClose")
|
|
1317
|
+
if (requestClose.isNotBlank()) screen.modalCloseHandlers[this] = requestClose else screen.modalCloseHandlers.remove(this)
|
|
1318
|
+
setVisible(visible, style.optDouble("opacity", 1.0).toFloat(), props.optString("animationType").ifBlank { null })
|
|
1319
|
+
if (wasVisible != visible && (visible || wasVisible == true)) {
|
|
1320
|
+
val lifecycleHandler = if (visible) events.optString("onShow") else events.optString("onDismiss")
|
|
1321
|
+
if (lifecycleHandler.isNotBlank()) {
|
|
1322
|
+
send(screen, "EVENT", JSONObject().put("handlerName", lifecycleHandler).put("nativeEvent", JSONObject()))
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
"TouchableOpacity", "TouchableHighlight", "Pressable" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
1327
|
+
configureContainer(this, style)
|
|
1328
|
+
screen.pressActiveOpacities[this] = props.optDouble("activeOpacity", 0.5).toFloat()
|
|
1329
|
+
screen.pressBaseOpacities[this] = style.optDouble("opacity", 1.0).toFloat()
|
|
1330
|
+
reconcileChildren(screen, this, children, path, next, style, previousControls)
|
|
1331
|
+
}
|
|
1172
1332
|
"Switch" -> (previous as? Switch ?: Switch(this)).apply {
|
|
1173
1333
|
setOnCheckedChangeListener(null)
|
|
1174
1334
|
val requestedValue = props.opt("value")
|
|
@@ -1286,6 +1446,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
1286
1446
|
props.optBoolean("showsHorizontalScrollIndicator", true),
|
|
1287
1447
|
)
|
|
1288
1448
|
setScrollEnabled(props.optBoolean("scrollEnabled", true))
|
|
1449
|
+
setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
1289
1450
|
setBounces(props.optBoolean("bounces", true))
|
|
1290
1451
|
setAlwaysBounce(
|
|
1291
1452
|
props.optBoolean("alwaysBounceVertical", !horizontal),
|
|
@@ -1339,16 +1500,27 @@ class MainActivity : AppCompatActivity() {
|
|
|
1339
1500
|
?.hideSoftInputFromWindow(nativeScroll.windowToken, 0)
|
|
1340
1501
|
},
|
|
1341
1502
|
)
|
|
1342
|
-
|
|
1343
|
-
|
|
1503
|
+
val contentStyle = JSONObject(style.toString()).apply {
|
|
1504
|
+
props.optJSONObject("contentContainerStyle")?.let { override ->
|
|
1505
|
+
override.keys().forEach { key -> put(key, override.get(key)) }
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
configureContainer(content, contentStyle)
|
|
1509
|
+
reconcileChildren(screen, content, children, path, next, contentStyle, previousControls)
|
|
1344
1510
|
}
|
|
1345
|
-
"FlatList" -> (previous as?
|
|
1346
|
-
|
|
1511
|
+
"FlatList" -> (previous as? CraftNativeRefreshableFlatList ?: CraftNativeRefreshableFlatList(this)).apply {
|
|
1512
|
+
val nativeList = list
|
|
1513
|
+
configureRefresh(events.optString("onRefresh").takeIf { it.isNotBlank() }?.let { handler ->
|
|
1514
|
+
{ send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject())) }
|
|
1515
|
+
}, props.optBoolean("refreshing", false))
|
|
1516
|
+
nativeList.setKeyboardShouldPersistTaps(props.optString("keyboardShouldPersistTaps", "never"))
|
|
1517
|
+
nativeList.setContentContainerStyle(props.optJSONObject("contentContainerStyle"), resources.displayMetrics.density)
|
|
1518
|
+
nativeList.setKeyboardDismissMode(props.optString("keyboardDismissMode")) {
|
|
1347
1519
|
(getSystemService(INPUT_METHOD_SERVICE) as? android.view.inputmethod.InputMethodManager)
|
|
1348
1520
|
?.hideSoftInputFromWindow(windowToken, 0)
|
|
1349
1521
|
}
|
|
1350
|
-
overScrollMode = if (props.optBoolean("bounces", true)) View.OVER_SCROLL_ALWAYS else View.OVER_SCROLL_NEVER
|
|
1351
|
-
reconcileFlatList(screen,
|
|
1522
|
+
nativeList.overScrollMode = if (props.optBoolean("bounces", true)) View.OVER_SCROLL_ALWAYS else View.OVER_SCROLL_NEVER
|
|
1523
|
+
reconcileFlatList(screen, nativeList, children, path, protocolId ?: path, props, events)
|
|
1352
1524
|
}
|
|
1353
1525
|
"View", "SafeAreaView" -> (previous as? CraftNativeFlexLayout ?: CraftNativeFlexLayout(this)).apply {
|
|
1354
1526
|
configureContainer(this, style)
|
|
@@ -1359,22 +1531,60 @@ class MainActivity : AppCompatActivity() {
|
|
|
1359
1531
|
reconcileChildren(screen, this, children, path, next, style, previousControls)
|
|
1360
1532
|
}
|
|
1361
1533
|
}
|
|
1362
|
-
|
|
1534
|
+
val pressInHandler = events.optString("onPressIn").takeIf { it.isNotBlank() }
|
|
1535
|
+
val pressOutHandler = events.optString("onPressOut").takeIf { it.isNotBlank() }
|
|
1536
|
+
if (pressInHandler != null) screen.pressInHandlers[control] = pressInHandler else screen.pressInHandlers.remove(control)
|
|
1537
|
+
if (pressOutHandler != null) screen.pressOutHandlers[control] = pressOutHandler else screen.pressOutHandlers.remove(control)
|
|
1538
|
+
if (type != "Button" && type != "Link" && type != "TextInput" && type != "Picker" && type != "Modal" && type != "Switch" && type != "Slider" && type != "ActivityIndicator") {
|
|
1363
1539
|
val handler = events.optString("onPress").ifBlank { events.optString("onClick") }
|
|
1364
|
-
|
|
1365
|
-
|
|
1540
|
+
val disabled = props.optBoolean("disabled", false)
|
|
1541
|
+
val hasPressLifecycle = pressInHandler != null || pressOutHandler != null
|
|
1542
|
+
if ((handler.isNotBlank() || (type in setOf("TouchableOpacity", "TouchableHighlight", "Pressable") && hasPressLifecycle)) && !disabled) {
|
|
1543
|
+
if (handler.isNotBlank()) screen.handlers[control] = handler else screen.handlers.remove(control)
|
|
1366
1544
|
control.isClickable = true
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1545
|
+
if (type in setOf("TouchableOpacity", "TouchableHighlight", "Pressable")) {
|
|
1546
|
+
control.setOnClickListener(null)
|
|
1547
|
+
control.setOnTouchListener { view, event ->
|
|
1548
|
+
when (event.actionMasked) {
|
|
1549
|
+
android.view.MotionEvent.ACTION_DOWN -> {
|
|
1550
|
+
val active = screen.pressActiveOpacities[view] ?: 0.5f
|
|
1551
|
+
val base = screen.pressBaseOpacities[view] ?: 1f
|
|
1552
|
+
view.alpha = base * active
|
|
1553
|
+
screen.pressInHandlers[view]?.let { current ->
|
|
1554
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
1555
|
+
}
|
|
1556
|
+
true
|
|
1557
|
+
}
|
|
1558
|
+
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
|
|
1559
|
+
view.alpha = screen.pressBaseOpacities[view] ?: 1f
|
|
1560
|
+
screen.pressOutHandlers[view]?.let { current ->
|
|
1561
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
1562
|
+
}
|
|
1563
|
+
if (event.actionMasked == android.view.MotionEvent.ACTION_UP) {
|
|
1564
|
+
val current = screen.handlers[view] ?: return@setOnTouchListener true
|
|
1565
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
1566
|
+
}
|
|
1567
|
+
true
|
|
1568
|
+
}
|
|
1569
|
+
else -> true
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
} else {
|
|
1573
|
+
control.setOnTouchListener(null)
|
|
1574
|
+
control.setOnClickListener {
|
|
1575
|
+
val current = screen.handlers[control] ?: return@setOnClickListener
|
|
1576
|
+
send(screen, "EVENT", JSONObject().put("handlerName", current).put("nativeEvent", JSONObject()))
|
|
1577
|
+
}
|
|
1370
1578
|
}
|
|
1371
1579
|
} else {
|
|
1372
1580
|
screen.handlers.remove(control)
|
|
1373
1581
|
control.isClickable = false
|
|
1374
1582
|
control.setOnClickListener(null)
|
|
1583
|
+
control.setOnTouchListener(null)
|
|
1375
1584
|
}
|
|
1585
|
+
control.isEnabled = !disabled
|
|
1376
1586
|
}
|
|
1377
|
-
val longPressHandler = events.optString("onLongPress")
|
|
1587
|
+
val longPressHandler = events.optString("onLongPress").takeUnless { props.optBoolean("disabled", false) } ?: ""
|
|
1378
1588
|
if (longPressHandler.isNotBlank()) {
|
|
1379
1589
|
screen.longPressHandlers[control] = longPressHandler
|
|
1380
1590
|
control.setOnLongClickListener {
|
|
@@ -1403,6 +1613,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
1403
1613
|
}
|
|
1404
1614
|
next[identity] = control
|
|
1405
1615
|
applyViewStyle(control, style)
|
|
1616
|
+
if (type == "Modal" && !props.optBoolean("visible", false)) control.visibility = View.GONE
|
|
1406
1617
|
val width = style.optDouble("width", Double.NaN).takeUnless { it.isNaN() }?.let(::dp)
|
|
1407
1618
|
val height = style.optDouble("height", Double.NaN).takeUnless { it.isNaN() }?.let(::dp)
|
|
1408
1619
|
control.layoutParams = LinearLayout.LayoutParams(
|
|
@@ -1751,7 +1962,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
1751
1962
|
val role = props.optString("accessibilityRole").ifBlank { type.lowercase() }
|
|
1752
1963
|
val state = props.optJSONObject("accessibilityState")
|
|
1753
1964
|
val disabled = state?.optBoolean("disabled", false) == true
|
|
1754
|
-
if (type !in setOf("Button", "Link", "TextInput", "Switch", "Slider", "ActivityIndicator")) {
|
|
1965
|
+
if (type !in setOf("Button", "Link", "TextInput", "Picker", "Modal", "Switch", "Slider", "ActivityIndicator")) {
|
|
1755
1966
|
view.isEnabled = !disabled
|
|
1756
1967
|
} else if (disabled) {
|
|
1757
1968
|
view.isEnabled = false
|
|
@@ -1997,19 +2208,33 @@ class MainActivity : AppCompatActivity() {
|
|
|
1997
2208
|
screen.flatListRows.values.forEach { rows -> rows.values.forEach { addAll(it.values) } }
|
|
1998
2209
|
}
|
|
1999
2210
|
|
|
2211
|
+
private fun releaseFlatList(
|
|
2212
|
+
screen: Screen,
|
|
2213
|
+
list: CraftNativeFlatList,
|
|
2214
|
+
preservingInputDrafts: Boolean,
|
|
2215
|
+
) {
|
|
2216
|
+
val owner = screen.flatListOwnerByView.remove(list)
|
|
2217
|
+
list.discardAll()
|
|
2218
|
+
screen.flatListRows.remove(list)?.values?.forEach {
|
|
2219
|
+
releaseViewState(screen, it.values, preservingInputDrafts)
|
|
2220
|
+
}
|
|
2221
|
+
if (owner != null) screen.flatListOwners.entries.removeAll { it.value == owner }
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2000
2224
|
private fun releaseViewState(
|
|
2001
2225
|
screen: Screen,
|
|
2002
2226
|
views: Collection<View>,
|
|
2003
2227
|
preservingInputDrafts: Boolean = false,
|
|
2004
2228
|
) {
|
|
2005
|
-
val released = views.
|
|
2006
|
-
released.filterIsInstance<
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2229
|
+
val released = views.toMutableSet()
|
|
2230
|
+
val refreshableLists = released.filterIsInstance<CraftNativeRefreshableFlatList>()
|
|
2231
|
+
val nestedLists = refreshableLists.map { it.list }.toSet()
|
|
2232
|
+
released.addAll(nestedLists)
|
|
2233
|
+
refreshableLists.forEach {
|
|
2234
|
+
releaseFlatList(screen, it.list, preservingInputDrafts)
|
|
2235
|
+
}
|
|
2236
|
+
released.filterIsInstance<CraftNativeFlatList>().filterNot { nestedLists.contains(it) }.forEach {
|
|
2237
|
+
releaseFlatList(screen, it, preservingInputDrafts)
|
|
2013
2238
|
}
|
|
2014
2239
|
releaseImages(released)
|
|
2015
2240
|
released.filterIsInstance<EditText>().forEach {
|
|
@@ -2021,6 +2246,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
2021
2246
|
}
|
|
2022
2247
|
released.forEach(defaultBackgrounds::remove)
|
|
2023
2248
|
screen.handlers.keys.removeAll(released)
|
|
2249
|
+
screen.pressInHandlers.keys.removeAll(released)
|
|
2250
|
+
screen.pressOutHandlers.keys.removeAll(released)
|
|
2024
2251
|
screen.longPressHandlers.keys.removeAll(released)
|
|
2025
2252
|
screen.layoutHandlers.keys.removeAll(released)
|
|
2026
2253
|
screen.lastLayoutBounds.keys.removeAll(released)
|
|
@@ -2039,6 +2266,10 @@ class MainActivity : AppCompatActivity() {
|
|
|
2039
2266
|
screen.imageLoadHandlers.keys.removeAll(released)
|
|
2040
2267
|
screen.imageLoadEndHandlers.keys.removeAll(released)
|
|
2041
2268
|
screen.imageErrorHandlers.keys.removeAll(released)
|
|
2269
|
+
screen.pressActiveOpacities.keys.removeAll(released)
|
|
2270
|
+
screen.pressBaseOpacities.keys.removeAll(released)
|
|
2271
|
+
screen.modalVisibility.keys.removeAll(released)
|
|
2272
|
+
screen.modalCloseHandlers.keys.removeAll(released)
|
|
2042
2273
|
}
|
|
2043
2274
|
|
|
2044
2275
|
private fun closeScreen(screen: Screen) {
|
|
@@ -2049,6 +2280,8 @@ class MainActivity : AppCompatActivity() {
|
|
|
2049
2280
|
releaseViewState(screen, retainedViews(screen).toList())
|
|
2050
2281
|
screen.controls.clear()
|
|
2051
2282
|
screen.handlers.clear()
|
|
2283
|
+
screen.pressInHandlers.clear()
|
|
2284
|
+
screen.pressOutHandlers.clear()
|
|
2052
2285
|
screen.layoutHandlers.clear()
|
|
2053
2286
|
screen.lastLayoutBounds.clear()
|
|
2054
2287
|
screen.scrollHandlers.clear()
|
|
@@ -2073,7 +2306,17 @@ class MainActivity : AppCompatActivity() {
|
|
|
2073
2306
|
}
|
|
2074
2307
|
|
|
2075
2308
|
@Deprecated("Uses the activity back callback for native route navigation")
|
|
2076
|
-
override fun onBackPressed()
|
|
2309
|
+
override fun onBackPressed() {
|
|
2310
|
+
val screen = screens.lastOrNull() ?: return back()
|
|
2311
|
+
val modal = screen.modalVisibility.entries.lastOrNull { (view, visible) -> visible && view.visibility == View.VISIBLE }
|
|
2312
|
+
if (modal != null) {
|
|
2313
|
+
screen.modalCloseHandlers[modal.key]?.let { handler ->
|
|
2314
|
+
send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject()))
|
|
2315
|
+
}
|
|
2316
|
+
return
|
|
2317
|
+
}
|
|
2318
|
+
back()
|
|
2319
|
+
}
|
|
2077
2320
|
|
|
2078
2321
|
private fun showError(message: String) {
|
|
2079
2322
|
root.removeAllViews()
|