pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
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.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/cli/pn.py +24 -6
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/mutations.py +21 -0
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helpers for the JSON shapes the bridge exchanges with Python.
|
|
8
|
+
*
|
|
9
|
+
* Python normalizes values before encoding: `math.inf` arrives as the
|
|
10
|
+
* string `"inf"`, removed props arrive as JSON `null`, and tuples and
|
|
11
|
+
* frozensets arrive as arrays. These helpers hide those conventions.
|
|
12
|
+
*/
|
|
13
|
+
object JsonUtil {
|
|
14
|
+
/** Whether `value` is absent or an explicit JSON null. */
|
|
15
|
+
fun isNull(value: Any?): Boolean = value == null || value === JSONObject.NULL
|
|
16
|
+
|
|
17
|
+
/** Coerce a JSON value to a double; `"inf"` / `"-inf"` become infinities. */
|
|
18
|
+
fun toDoubleOrNull(value: Any?): Double? {
|
|
19
|
+
return when (value) {
|
|
20
|
+
null, JSONObject.NULL -> null
|
|
21
|
+
is Number -> value.toDouble()
|
|
22
|
+
is Boolean -> if (value) 1.0 else 0.0
|
|
23
|
+
is String -> {
|
|
24
|
+
val s = value.trim()
|
|
25
|
+
when (s.lowercase()) {
|
|
26
|
+
"inf", "+inf", "infinity", "+infinity" -> Double.POSITIVE_INFINITY
|
|
27
|
+
"-inf", "-infinity" -> Double.NEGATIVE_INFINITY
|
|
28
|
+
"nan" -> Double.NaN
|
|
29
|
+
else -> s.toDoubleOrNull()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else -> null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Coerce a JSON value to a double with `default` for missing or unparseable input. */
|
|
37
|
+
fun toDouble(value: Any?, default: Double = 0.0): Double = toDoubleOrNull(value) ?: default
|
|
38
|
+
|
|
39
|
+
/** Coerce a JSON value to an int (truncating), with `default` for bad input. */
|
|
40
|
+
fun toInt(value: Any?, default: Int = 0): Int {
|
|
41
|
+
val d = toDoubleOrNull(value) ?: return default
|
|
42
|
+
if (d.isNaN() || d.isInfinite()) return default
|
|
43
|
+
return d.toInt()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Coerce a JSON value to a long (truncating), with `default` for bad input. */
|
|
47
|
+
fun toLong(value: Any?, default: Long = 0L): Long {
|
|
48
|
+
return when (value) {
|
|
49
|
+
is Number -> value.toLong()
|
|
50
|
+
else -> {
|
|
51
|
+
val d = toDoubleOrNull(value) ?: return default
|
|
52
|
+
if (d.isNaN() || d.isInfinite()) default else d.toLong()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Python-style truthiness for JSON values. */
|
|
58
|
+
fun truthy(value: Any?): Boolean {
|
|
59
|
+
return when (value) {
|
|
60
|
+
null, JSONObject.NULL -> false
|
|
61
|
+
is Boolean -> value
|
|
62
|
+
is Number -> value.toDouble() != 0.0
|
|
63
|
+
is String -> value.isNotEmpty()
|
|
64
|
+
is JSONArray -> value.length() > 0
|
|
65
|
+
is JSONObject -> value.length() > 0
|
|
66
|
+
else -> true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `value` as a string, or `null` when absent or JSON null. */
|
|
71
|
+
fun stringOrNull(value: Any?): String? = if (isNull(value)) null else value.toString()
|
|
72
|
+
|
|
73
|
+
/** Copy `changed` into `target`, keeping explicit nulls as [JSONObject.NULL]. */
|
|
74
|
+
fun merge(target: JSONObject, changed: JSONObject) {
|
|
75
|
+
val keys = changed.keys()
|
|
76
|
+
while (keys.hasNext()) {
|
|
77
|
+
val key = keys.next()
|
|
78
|
+
target.put(key, changed.opt(key) ?: JSONObject.NULL)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Convert a [JSONArray] to a Kotlin list (JSON nulls become `null`). */
|
|
83
|
+
fun toList(array: JSONArray?): List<Any?> {
|
|
84
|
+
if (array == null) return emptyList()
|
|
85
|
+
val out = ArrayList<Any?>(array.length())
|
|
86
|
+
for (i in 0 until array.length()) {
|
|
87
|
+
val v = array.opt(i)
|
|
88
|
+
out.add(if (v === JSONObject.NULL) null else v)
|
|
89
|
+
}
|
|
90
|
+
return out
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Convert a [JSONObject] to a Kotlin map (JSON nulls become `null`). */
|
|
94
|
+
fun toMap(obj: JSONObject?): Map<String, Any?> {
|
|
95
|
+
if (obj == null) return emptyMap()
|
|
96
|
+
val out = LinkedHashMap<String, Any?>()
|
|
97
|
+
val keys = obj.keys()
|
|
98
|
+
while (keys.hasNext()) {
|
|
99
|
+
val key = keys.next()
|
|
100
|
+
val v = obj.opt(key)
|
|
101
|
+
out[key] = if (v === JSONObject.NULL) null else v
|
|
102
|
+
}
|
|
103
|
+
return out
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Wrap a Kotlin value into something `org.json` can hold (maps, lists, nulls). */
|
|
107
|
+
fun wrap(value: Any?): Any {
|
|
108
|
+
return when (value) {
|
|
109
|
+
null -> JSONObject.NULL
|
|
110
|
+
is JSONObject, is JSONArray, is String, is Boolean -> value
|
|
111
|
+
is Float -> if (value.isFinite()) value.toDouble() else JSONObject.NULL
|
|
112
|
+
is Double -> if (value.isFinite()) value else JSONObject.NULL
|
|
113
|
+
is Number -> value
|
|
114
|
+
is Map<*, *> -> {
|
|
115
|
+
val obj = JSONObject()
|
|
116
|
+
for ((k, v) in value) obj.put(k.toString(), wrap(v))
|
|
117
|
+
obj
|
|
118
|
+
}
|
|
119
|
+
is Iterable<*> -> {
|
|
120
|
+
val arr = JSONArray()
|
|
121
|
+
for (v in value) arr.put(wrap(v))
|
|
122
|
+
arr
|
|
123
|
+
}
|
|
124
|
+
is Array<*> -> wrap(value.asList())
|
|
125
|
+
is DoubleArray -> wrap(value.asList())
|
|
126
|
+
is FloatArray -> wrap(value.asList())
|
|
127
|
+
is IntArray -> wrap(value.asList())
|
|
128
|
+
else -> value.toString()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Build a JSON array of positional arguments. */
|
|
133
|
+
fun args(vararg values: Any?): JSONArray {
|
|
134
|
+
val arr = JSONArray()
|
|
135
|
+
for (v in values) arr.put(wrap(v))
|
|
136
|
+
return arr
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Encode a command or module result as a JSON string; `null` stays `null`. */
|
|
140
|
+
fun encode(value: Any?): String? {
|
|
141
|
+
if (value == null || value === JSONObject.NULL) return null
|
|
142
|
+
return when (val wrapped = wrap(value)) {
|
|
143
|
+
is JSONObject, is JSONArray -> wrapped.toString()
|
|
144
|
+
is String -> JSONObject.quote(wrapped)
|
|
145
|
+
else -> wrapped.toString()
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The value under `key`, or `null` for missing keys and JSON nulls. */
|
|
151
|
+
fun JSONObject.value(key: String): Any? {
|
|
152
|
+
val v = opt(key) ?: return null
|
|
153
|
+
return if (v === JSONObject.NULL) null else v
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** String prop or `null`. */
|
|
157
|
+
fun JSONObject.str(key: String): String? = JsonUtil.stringOrNull(opt(key))
|
|
158
|
+
|
|
159
|
+
/** Numeric prop (accepting `"inf"`) or `null`. */
|
|
160
|
+
fun JSONObject.num(key: String): Double? = JsonUtil.toDoubleOrNull(opt(key))
|
|
161
|
+
|
|
162
|
+
/** Boolean prop using Python truthiness, or `null` when absent. */
|
|
163
|
+
fun JSONObject.bool(key: String): Boolean? {
|
|
164
|
+
val v = value(key) ?: return null
|
|
165
|
+
return JsonUtil.truthy(v)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Object prop or `null`. */
|
|
169
|
+
fun JSONObject.obj(key: String): JSONObject? = value(key) as? JSONObject
|
|
170
|
+
|
|
171
|
+
/** Array prop or `null`. */
|
|
172
|
+
fun JSONObject.arr(key: String): JSONArray? = value(key) as? JSONArray
|
|
173
|
+
|
|
174
|
+
/** Whether the prop is present with a non-null value. */
|
|
175
|
+
fun JSONObject.present(key: String): Boolean = value(key) != null
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
|
|
6
|
+
/** Main-looper helpers shared by the bridge, modules, and managers. */
|
|
7
|
+
object MainThread {
|
|
8
|
+
private val handler: Handler by lazy { Handler(Looper.getMainLooper()) }
|
|
9
|
+
|
|
10
|
+
/** Whether the current thread is the main thread. */
|
|
11
|
+
fun isMain(): Boolean = Looper.myLooper() == Looper.getMainLooper()
|
|
12
|
+
|
|
13
|
+
/** Post `runnable` to the main looper. */
|
|
14
|
+
fun post(runnable: Runnable) {
|
|
15
|
+
handler.post(runnable)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Post `runnable` to the main looper after `delayMs`. */
|
|
19
|
+
fun postDelayed(runnable: Runnable, delayMs: Long) {
|
|
20
|
+
handler.postDelayed(runnable, delayMs)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Remove a pending `runnable`. */
|
|
24
|
+
fun remove(runnable: Runnable) {
|
|
25
|
+
handler.removeCallbacks(runnable)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Run `runnable` inline when already on the main thread, otherwise post it. */
|
|
29
|
+
fun runOnMain(runnable: Runnable) {
|
|
30
|
+
if (isMain()) runnable.run() else post(runnable)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
|
|
5
|
+
/** Rate-limited logging so a hot failing op cannot flood logcat. */
|
|
6
|
+
object PNLog {
|
|
7
|
+
const val TAG = "PythonNative"
|
|
8
|
+
|
|
9
|
+
private const val BURST = 5
|
|
10
|
+
private const val EVERY = 100
|
|
11
|
+
private val counts = HashMap<String, Int>()
|
|
12
|
+
private val once = HashSet<String>()
|
|
13
|
+
|
|
14
|
+
/** Log `message` for `key`, letting the first few through and then every 100th. */
|
|
15
|
+
fun rateLimited(key: String, message: String, error: Throwable? = null) {
|
|
16
|
+
val n = (counts[key] ?: 0) + 1
|
|
17
|
+
counts[key] = n
|
|
18
|
+
if (n <= BURST || n % EVERY == 0) {
|
|
19
|
+
val suffix = if (n > BURST) " (x$n)" else ""
|
|
20
|
+
if (error != null) Log.e(TAG, message + suffix, error) else Log.e(TAG, message + suffix)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Log `message` at most once per `key`. */
|
|
25
|
+
fun once(key: String, message: String) {
|
|
26
|
+
if (once.add(key)) Log.w(TAG, message)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Log a swallowed exception at debug level. */
|
|
30
|
+
fun swallowed(where: String, error: Throwable) {
|
|
31
|
+
Log.d(TAG, "$where: ${error.javaClass.simpleName}: ${error.message}")
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.pythonnative.runtime.components.BuiltinComponents
|
|
5
|
+
import com.pythonnative.runtime.components.ComponentManager
|
|
6
|
+
import com.pythonnative.runtime.modules.BuiltinModules
|
|
7
|
+
import com.pythonnative.runtime.modules.NativeModule
|
|
8
|
+
import com.pythonnative.runtime.plugins.GeneratedPlugins
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A native extension: registers component managers and native modules.
|
|
12
|
+
*
|
|
13
|
+
* `pn build` generates a call to each plugin entry's `register` in
|
|
14
|
+
* `plugins/GeneratedPlugins.kt`.
|
|
15
|
+
*/
|
|
16
|
+
interface PNPlugin {
|
|
17
|
+
fun register(registry: PNRegistry)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registry of component managers (by element type) and native modules
|
|
22
|
+
* (by module name). Built-ins are registered on first use, followed by
|
|
23
|
+
* the generated plugin registrations.
|
|
24
|
+
*/
|
|
25
|
+
object PNRegistry {
|
|
26
|
+
private val componentFactories = HashMap<String, () -> ComponentManager>()
|
|
27
|
+
private val managers = HashMap<String, ComponentManager>()
|
|
28
|
+
private val moduleFactories = HashMap<String, () -> NativeModule>()
|
|
29
|
+
private val modules = HashMap<String, NativeModule>()
|
|
30
|
+
private var builtinsRegistered = false
|
|
31
|
+
|
|
32
|
+
/** Register (or override) the manager factory for element type `name`. */
|
|
33
|
+
fun registerComponent(name: String, factory: () -> ComponentManager) {
|
|
34
|
+
componentFactories[name] = factory
|
|
35
|
+
managers.remove(name)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register a native module. The factory is invoked once to read the
|
|
40
|
+
* module's `name`; the same instance then serves every call.
|
|
41
|
+
*/
|
|
42
|
+
fun registerModule(factory: () -> NativeModule) {
|
|
43
|
+
val module = factory()
|
|
44
|
+
moduleFactories[module.name] = factory
|
|
45
|
+
modules[module.name] = module
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The manager for element type `name`, or `null` when unknown. */
|
|
49
|
+
fun managerFor(name: String): ComponentManager? {
|
|
50
|
+
ensureBuiltins()
|
|
51
|
+
managers[name]?.let { return it }
|
|
52
|
+
val factory = componentFactories[name] ?: return null
|
|
53
|
+
val manager = factory()
|
|
54
|
+
managers[name] = manager
|
|
55
|
+
return manager
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The module named `name`, or `null`. */
|
|
59
|
+
fun module(name: String): NativeModule? {
|
|
60
|
+
ensureBuiltins()
|
|
61
|
+
return modules[name]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Registered element type names. */
|
|
65
|
+
fun componentNames(): Set<String> {
|
|
66
|
+
ensureBuiltins()
|
|
67
|
+
return componentFactories.keys
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Register built-in components and modules plus generated plugins (idempotent). */
|
|
71
|
+
fun ensureBuiltins() {
|
|
72
|
+
if (builtinsRegistered) return
|
|
73
|
+
builtinsRegistered = true
|
|
74
|
+
BuiltinComponents.register(this)
|
|
75
|
+
BuiltinModules.register(this)
|
|
76
|
+
try {
|
|
77
|
+
GeneratedPlugins.registerAll(this)
|
|
78
|
+
} catch (e: Exception) {
|
|
79
|
+
Log.e(PNLog.TAG, "plugin registration failed", e)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONException
|
|
5
|
+
import org.json.JSONObject
|
|
6
|
+
|
|
7
|
+
/** One decoded mutation op. Geometry is in dp (points). */
|
|
8
|
+
sealed class Op {
|
|
9
|
+
/** `["c", tag, "Type", {props}]` */
|
|
10
|
+
data class Create(val tag: Long, val typeName: String, val props: JSONObject) : Op()
|
|
11
|
+
|
|
12
|
+
/** `["u", tag, {changed}]` (a `null` value means the prop was removed) */
|
|
13
|
+
data class Update(val tag: Long, val changed: JSONObject) : Op()
|
|
14
|
+
|
|
15
|
+
/** `["i", parent, child, index]` (move-aware) */
|
|
16
|
+
data class Insert(val parent: Long, val child: Long, val index: Int) : Op()
|
|
17
|
+
|
|
18
|
+
/** `["d", tag]` */
|
|
19
|
+
data class Destroy(val tag: Long) : Op()
|
|
20
|
+
|
|
21
|
+
/** `["f", tag, x, y, w, h]` relative to the parent's content origin */
|
|
22
|
+
data class Frame(val tag: Long, val x: Double, val y: Double, val width: Double, val height: Double) : Op()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Decoder for the transaction wire format: a JSON array of ops, each an
|
|
27
|
+
* array whose first element is a one-letter opcode.
|
|
28
|
+
*
|
|
29
|
+
* Decoding is isolated per op: a malformed entry is reported through
|
|
30
|
+
* `onError` and skipped so the rest of the commit still applies.
|
|
31
|
+
*/
|
|
32
|
+
object PNTransaction {
|
|
33
|
+
/** Decode `json`; invalid ops are skipped after `onError(index, error)`. */
|
|
34
|
+
fun decode(json: String, onError: (Int, Throwable) -> Unit = { _, _ -> }): List<Op> {
|
|
35
|
+
val array = JSONArray(json)
|
|
36
|
+
val ops = ArrayList<Op>(array.length())
|
|
37
|
+
for (i in 0 until array.length()) {
|
|
38
|
+
try {
|
|
39
|
+
val raw = array.get(i) as? JSONArray
|
|
40
|
+
?: throw JSONException("op $i is not an array")
|
|
41
|
+
ops.add(decodeOp(raw))
|
|
42
|
+
} catch (e: Exception) {
|
|
43
|
+
onError(i, e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return ops
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Decode a single op array. */
|
|
50
|
+
fun decodeOp(raw: JSONArray): Op {
|
|
51
|
+
val code = raw.optString(0, "")
|
|
52
|
+
return when (code) {
|
|
53
|
+
"c" -> Op.Create(
|
|
54
|
+
tag(raw, 1),
|
|
55
|
+
raw.optString(2, ""),
|
|
56
|
+
raw.optJSONObject(3) ?: JSONObject(),
|
|
57
|
+
)
|
|
58
|
+
"u" -> Op.Update(tag(raw, 1), raw.optJSONObject(2) ?: JSONObject())
|
|
59
|
+
"i" -> Op.Insert(tag(raw, 1), tag(raw, 2), JsonUtil.toInt(raw.opt(3), 0))
|
|
60
|
+
"d" -> Op.Destroy(tag(raw, 1))
|
|
61
|
+
"f" -> Op.Frame(
|
|
62
|
+
tag(raw, 1),
|
|
63
|
+
JsonUtil.toDouble(raw.opt(2)),
|
|
64
|
+
JsonUtil.toDouble(raw.opt(3)),
|
|
65
|
+
JsonUtil.toDouble(raw.opt(4)),
|
|
66
|
+
JsonUtil.toDouble(raw.opt(5)),
|
|
67
|
+
)
|
|
68
|
+
else -> throw JSONException("unknown opcode '$code'")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun tag(raw: JSONArray, index: Int): Long {
|
|
73
|
+
val v = raw.opt(index) ?: throw JSONException("missing tag at $index")
|
|
74
|
+
return when (v) {
|
|
75
|
+
is Number -> v.toLong()
|
|
76
|
+
is String -> v.toLongOrNull() ?: throw JSONException("bad tag '$v'")
|
|
77
|
+
else -> throw JSONException("bad tag $v")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.pythonnative.runtime.PNBridge
|
|
5
|
+
import com.pythonnative.runtime.components.ComponentManager
|
|
6
|
+
import com.pythonnative.runtime.components.PlaceholderManager
|
|
7
|
+
import com.pythonnative.runtime.gestures.GestureCoordinator
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Applies decoded transactions to the view tree, in order, with per-op
|
|
12
|
+
* error isolation. Unknown element types create a placeholder view and
|
|
13
|
+
* log once.
|
|
14
|
+
*/
|
|
15
|
+
class TransactionApplier(private val registry: ViewRegistry) {
|
|
16
|
+
private val placeholder = PlaceholderManager()
|
|
17
|
+
|
|
18
|
+
/** Decode and apply `transactionJson`. */
|
|
19
|
+
fun apply(transactionJson: String) {
|
|
20
|
+
val ops = try {
|
|
21
|
+
PNTransaction.decode(transactionJson) { index, error ->
|
|
22
|
+
PNLog.rateLimited("decode", "transaction op $index could not be decoded", error)
|
|
23
|
+
}
|
|
24
|
+
} catch (e: Exception) {
|
|
25
|
+
PNLog.rateLimited("decode-top", "transaction is not a JSON array", e)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
for (op in ops) {
|
|
29
|
+
try {
|
|
30
|
+
applyOp(op)
|
|
31
|
+
} catch (e: Exception) {
|
|
32
|
+
PNLog.rateLimited("op:" + op.javaClass.simpleName, "failed to apply $op", e)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Apply a single op. */
|
|
38
|
+
fun applyOp(op: Op) {
|
|
39
|
+
when (op) {
|
|
40
|
+
is Op.Create -> create(op)
|
|
41
|
+
is Op.Update -> update(op)
|
|
42
|
+
is Op.Insert -> insert(op)
|
|
43
|
+
is Op.Destroy -> destroy(op)
|
|
44
|
+
is Op.Frame -> frame(op)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private fun create(op: Op.Create) {
|
|
49
|
+
registry.get(op.tag)?.let {
|
|
50
|
+
PNLog.rateLimited("dup-create", "tag ${op.tag} already exists (${it.typeName}); replacing")
|
|
51
|
+
destroyRecord(it)
|
|
52
|
+
}
|
|
53
|
+
val manager = PNRegistry.managerFor(op.typeName) ?: run {
|
|
54
|
+
PNLog.once("unknown-type:" + op.typeName, "unknown element type '${op.typeName}'; using a placeholder view")
|
|
55
|
+
placeholder
|
|
56
|
+
}
|
|
57
|
+
val view = manager.createView(PNBridge.context(), op.tag, op.props)
|
|
58
|
+
val record = ViewRecord(op.tag, op.typeName, view, manager)
|
|
59
|
+
JsonUtil.merge(record.props, op.props)
|
|
60
|
+
registry.register(record)
|
|
61
|
+
try {
|
|
62
|
+
manager.applyProps(view, op.props, true)
|
|
63
|
+
} finally {
|
|
64
|
+
record.initialized = true
|
|
65
|
+
}
|
|
66
|
+
if (op.props.has("gestures")) {
|
|
67
|
+
GestureCoordinator.bind(record, op.props.opt("gestures"))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun update(op: Op.Update) {
|
|
72
|
+
val record = registry.get(op.tag) ?: throw IllegalStateException("update: unknown tag ${op.tag}")
|
|
73
|
+
JsonUtil.merge(record.props, op.changed)
|
|
74
|
+
record.manager.update(record.view, op.changed)
|
|
75
|
+
if (op.changed.has("gestures")) {
|
|
76
|
+
GestureCoordinator.bind(record, op.changed.opt("gestures"))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private fun insert(op: Op.Insert) {
|
|
81
|
+
val parent = registry.get(op.parent) ?: throw IllegalStateException("insert: unknown parent ${op.parent}")
|
|
82
|
+
val child = registry.get(op.child) ?: throw IllegalStateException("insert: unknown child ${op.child}")
|
|
83
|
+
parent.manager.insertChild(parent.view, child.view, op.index)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private fun destroy(op: Op.Destroy) {
|
|
87
|
+
val record = registry.get(op.tag) ?: return
|
|
88
|
+
destroyRecord(record)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun destroyRecord(record: ViewRecord) {
|
|
92
|
+
GestureCoordinator.unbind(record)
|
|
93
|
+
try {
|
|
94
|
+
val parentRecord = (record.view.parent as? View)?.let { registry.recordFor(it) }
|
|
95
|
+
if (parentRecord != null) {
|
|
96
|
+
parentRecord.manager.removeChild(parentRecord.view, record.view)
|
|
97
|
+
}
|
|
98
|
+
record.manager.destroy(record.view)
|
|
99
|
+
} finally {
|
|
100
|
+
registry.unregister(record.tag)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private fun frame(op: Op.Frame) {
|
|
105
|
+
val record = registry.get(op.tag) ?: throw IllegalStateException("frame: unknown tag ${op.tag}")
|
|
106
|
+
record.frame = doubleArrayOf(op.x, op.y, op.width, op.height)
|
|
107
|
+
record.manager.setFrame(record.view, op.x, op.y, op.width, op.height)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Whether `manager` is the fallback placeholder manager. */
|
|
111
|
+
fun isPlaceholder(manager: ComponentManager): Boolean = manager === placeholder
|
|
112
|
+
|
|
113
|
+
/** The props recorded for `tag`, mainly for diagnostics. */
|
|
114
|
+
fun propsOf(tag: Long): JSONObject? = registry.get(tag)?.props
|
|
115
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.pythonnative.runtime.bridge
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.pythonnative.runtime.components.ComponentManager
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
import java.util.IdentityHashMap
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Per-view bookkeeping: the reconciler tag, the element type, the
|
|
11
|
+
* manager that owns it, the merged props, and free-form manager state.
|
|
12
|
+
*/
|
|
13
|
+
class ViewRecord(
|
|
14
|
+
val tag: Long,
|
|
15
|
+
val typeName: String,
|
|
16
|
+
val view: View,
|
|
17
|
+
val manager: ComponentManager,
|
|
18
|
+
) {
|
|
19
|
+
/** Every prop applied so far (removed props stay as JSON null). */
|
|
20
|
+
val props = JSONObject()
|
|
21
|
+
|
|
22
|
+
/** Manager-private state (suppress flags, wrapped widgets, etc.). */
|
|
23
|
+
val state = HashMap<String, Any?>()
|
|
24
|
+
|
|
25
|
+
/** Last frame applied by `setFrame`, in dp (`x, y, w, h`). */
|
|
26
|
+
var frame: DoubleArray? = null
|
|
27
|
+
|
|
28
|
+
/** Whether the initial props have been applied. */
|
|
29
|
+
var initialized = false
|
|
30
|
+
|
|
31
|
+
/** Whether the element wired a callback named `name` this render. */
|
|
32
|
+
fun hasEvent(name: String): Boolean {
|
|
33
|
+
val events = props.opt("_pn_events") as? JSONArray ?: return false
|
|
34
|
+
for (i in 0 until events.length()) {
|
|
35
|
+
if (events.optString(i) == name) return true
|
|
36
|
+
}
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Tag to [ViewRecord] map with a reverse index by view identity. */
|
|
42
|
+
class ViewRegistry {
|
|
43
|
+
private val byTag = HashMap<Long, ViewRecord>()
|
|
44
|
+
private val byView = IdentityHashMap<View, ViewRecord>()
|
|
45
|
+
|
|
46
|
+
/** Number of live records. */
|
|
47
|
+
val size: Int get() = byTag.size
|
|
48
|
+
|
|
49
|
+
/** Register `record`; replaces any earlier record with the same tag. */
|
|
50
|
+
fun register(record: ViewRecord) {
|
|
51
|
+
byTag.remove(record.tag)?.let { byView.remove(it.view) }
|
|
52
|
+
byTag[record.tag] = record
|
|
53
|
+
byView[record.view] = record
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Remove and return the record for `tag`. */
|
|
57
|
+
fun unregister(tag: Long): ViewRecord? {
|
|
58
|
+
val record = byTag.remove(tag) ?: return null
|
|
59
|
+
byView.remove(record.view)
|
|
60
|
+
return record
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The record for `tag`, or `null`. */
|
|
64
|
+
fun get(tag: Long): ViewRecord? = byTag[tag]
|
|
65
|
+
|
|
66
|
+
/** The record whose view is `view`, or `null`. */
|
|
67
|
+
fun recordFor(view: View): ViewRecord? = byView[view]
|
|
68
|
+
|
|
69
|
+
/** The tag of `view`, or `null` when it is not a PythonNative view. */
|
|
70
|
+
fun tagOf(view: View): Long? = byView[view]?.tag
|
|
71
|
+
|
|
72
|
+
/** Snapshot of all records. */
|
|
73
|
+
fun all(): List<ViewRecord> = ArrayList(byTag.values)
|
|
74
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import com.pythonnative.runtime.bridge.PNRegistry
|
|
4
|
+
|
|
5
|
+
/** Registers the built-in element types with the registry. */
|
|
6
|
+
object BuiltinComponents {
|
|
7
|
+
fun register(registry: PNRegistry) {
|
|
8
|
+
registry.registerComponent("View") { ViewManager() }
|
|
9
|
+
registry.registerComponent("Column") { ViewManager() }
|
|
10
|
+
registry.registerComponent("Row") { ViewManager() }
|
|
11
|
+
registry.registerComponent("Text") { TextManager() }
|
|
12
|
+
registry.registerComponent("Button") { ButtonManager() }
|
|
13
|
+
registry.registerComponent("TextInput") { TextInputManager() }
|
|
14
|
+
registry.registerComponent("Image") { ImageManager() }
|
|
15
|
+
registry.registerComponent("Switch") { SwitchManager() }
|
|
16
|
+
registry.registerComponent("Checkbox") { CheckboxManager() }
|
|
17
|
+
registry.registerComponent("ProgressBar") { ProgressBarManager() }
|
|
18
|
+
registry.registerComponent("ActivityIndicator") { ActivityIndicatorManager() }
|
|
19
|
+
registry.registerComponent("Slider") { SliderManager() }
|
|
20
|
+
registry.registerComponent("WebView") { WebViewManager() }
|
|
21
|
+
registry.registerComponent("Spacer") { SpacerManager() }
|
|
22
|
+
registry.registerComponent("ScrollView") { ScrollViewManager() }
|
|
23
|
+
registry.registerComponent("SafeAreaView") { SafeAreaViewManager() }
|
|
24
|
+
registry.registerComponent("KeyboardAvoidingView") { KeyboardAvoidingViewManager() }
|
|
25
|
+
registry.registerComponent("Modal") { ModalManager() }
|
|
26
|
+
registry.registerComponent("Portal") { PortalManager() }
|
|
27
|
+
registry.registerComponent("TabBar") { TabBarManager() }
|
|
28
|
+
registry.registerComponent("Pressable") { PressableManager() }
|
|
29
|
+
registry.registerComponent("StatusBar") { StatusBarManager() }
|
|
30
|
+
registry.registerComponent("Picker") { PickerManager() }
|
|
31
|
+
registry.registerComponent("SegmentedControl") { SegmentedControlManager() }
|
|
32
|
+
registry.registerComponent("DatePicker") { DatePickerManager() }
|
|
33
|
+
registry.registerComponent("VirtualList") { VirtualListManager() }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.pythonnative.runtime.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.widget.Button
|
|
6
|
+
import com.pythonnative.runtime.bridge.JsonUtil
|
|
7
|
+
import com.pythonnative.runtime.bridge.num
|
|
8
|
+
import com.pythonnative.runtime.bridge.str
|
|
9
|
+
import com.pythonnative.runtime.bridge.value
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
|
|
12
|
+
/** `Button` element: a platform `Button` firing `on_press`. */
|
|
13
|
+
class ButtonManager : ComponentManager() {
|
|
14
|
+
override fun createView(context: Context, tag: Long, props: JSONObject): View {
|
|
15
|
+
val button = Button(context)
|
|
16
|
+
button.setOnClickListener { fire(it, "on_press") }
|
|
17
|
+
return button
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
|
|
21
|
+
val button = view as Button
|
|
22
|
+
if (props.has("title")) button.text = props.str("title") ?: ""
|
|
23
|
+
props.num("font_size")?.let { button.textSize = it.toFloat() }
|
|
24
|
+
PNColor.parse(props.value("color"))?.let { button.setTextColor(it) }
|
|
25
|
+
if (props.has("enabled")) button.isEnabled = JsonUtil.truthy(props.value("enabled"))
|
|
26
|
+
if (props.has("disabled")) button.isEnabled = !JsonUtil.truthy(props.value("disabled"))
|
|
27
|
+
if (props.has("all_caps")) button.isAllCaps = JsonUtil.truthy(props.value("all_caps"))
|
|
28
|
+
if (listOf("font_family", "font_weight", "italic", "bold").any { props.has(it) }) {
|
|
29
|
+
TextStyle.applyTypeface(button, propsOf(button))
|
|
30
|
+
}
|
|
31
|
+
ViewStyler.apply(button, props)
|
|
32
|
+
}
|
|
33
|
+
}
|