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.
Files changed (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {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
+ }