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,213 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import android.view.View
6
+ import android.view.ViewGroup
7
+ import android.widget.FrameLayout
8
+ import com.pythonnative.runtime.PNBridge
9
+ import com.pythonnative.runtime.animation.PNAnimator
10
+ import com.pythonnative.runtime.bridge.JsonUtil
11
+ import com.pythonnative.runtime.bridge.ViewRecord
12
+ import com.pythonnative.runtime.bridge.present
13
+ import org.json.JSONArray
14
+ import org.json.JSONObject
15
+ import kotlin.math.max
16
+ import kotlin.math.min
17
+ import kotlin.math.roundToInt
18
+
19
+ /**
20
+ * Base class for one element type's native implementation.
21
+ *
22
+ * Subclasses implement [createView] (construct the widget) and
23
+ * [applyProps] (apply visual props); the base class owns frame
24
+ * application, intrinsic measurement, child management, and the
25
+ * animation hooks. All geometry crossing this API is in dp.
26
+ */
27
+ abstract class ComponentManager {
28
+ /** Construct the widget for `tag`; `props` may be consulted for creation-time choices. */
29
+ abstract fun createView(context: Context, tag: Long, props: JSONObject): View
30
+
31
+ /**
32
+ * Apply `props` to `view`. `initial` is `true` for the create op;
33
+ * on updates `props` holds only the changed keys (JSON null means
34
+ * removed). The merged props are always available via [propsOf].
35
+ */
36
+ open fun applyProps(view: View, props: JSONObject, initial: Boolean) {
37
+ ViewStyler.apply(view, props)
38
+ }
39
+
40
+ /** Apply changed props (the `u` op). */
41
+ fun update(view: View, changed: JSONObject) = applyProps(view, changed, false)
42
+
43
+ /** Ensure `child` sits at `index` under `parent` (move-aware, clamped). */
44
+ open fun insertChild(parent: View, child: View, index: Int) {
45
+ val group = parent as? ViewGroup ?: return
46
+ ViewChildren.insert(group, child, index)
47
+ }
48
+
49
+ /** Detach `child` from `parent` without destroying it. */
50
+ open fun removeChild(parent: View, child: View) {
51
+ (parent as? ViewGroup)?.removeView(child)
52
+ }
53
+
54
+ /** Release resources and detach from the parent. */
55
+ open fun destroy(view: View) {
56
+ try {
57
+ teardown(view)
58
+ } catch (e: Exception) {
59
+ Log.d(PNBridge.TAG, "teardown failed for ${view.javaClass.simpleName}", e)
60
+ }
61
+ (view.parent as? ViewGroup)?.removeView(view)
62
+ }
63
+
64
+ /** Subclass hook for extra cleanup before the view is released. */
65
+ protected open fun teardown(view: View) {}
66
+
67
+ /**
68
+ * Position `view` inside its parent's `FrameLayout` via margin layout
69
+ * params. Non-finite values are clamped; sizes are never negative.
70
+ */
71
+ open fun setFrame(view: View, x: Double, y: Double, width: Double, height: Double) {
72
+ val pxX = px(finite(x))
73
+ val pxY = px(finite(y))
74
+ val pxW = max(0, px(finite(width)))
75
+ val pxH = max(0, px(finite(height)))
76
+ val lp = view.layoutParams
77
+ val params: ViewGroup.LayoutParams = if (lp == null) {
78
+ FrameLayout.LayoutParams(pxW, pxH)
79
+ } else {
80
+ lp.width = pxW
81
+ lp.height = pxH
82
+ lp
83
+ }
84
+ if (params is ViewGroup.MarginLayoutParams) {
85
+ params.leftMargin = pxX
86
+ params.topMargin = pxY
87
+ params.rightMargin = 0
88
+ params.bottomMargin = 0
89
+ }
90
+ view.layoutParams = params
91
+ val record = recordOf(view)
92
+ if (record != null) {
93
+ record.frame = doubleArrayOf(x, y, width, height)
94
+ if (record.props.present("hit_slop") || record.state["hit_slop_active"] == true) {
95
+ ViewStyler.updateHitSlop(view)
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Natural size of `view` under the constraints, in dp. Either
102
+ * constraint may be `1e6` or infinite meaning unconstrained.
103
+ */
104
+ open fun measure(view: View, maxWidth: Double, maxHeight: Double): FloatArray {
105
+ val density = PNBridge.density()
106
+ val wSpec = measureSpec(maxWidth, density)
107
+ val hSpec = measureSpec(maxHeight, density)
108
+ view.measure(wSpec, hSpec)
109
+ return floatArrayOf(view.measuredWidth / density, view.measuredHeight / density)
110
+ }
111
+
112
+ /** Run an imperative command; unknown names return `null`. */
113
+ open fun command(view: View, name: String, args: JSONObject): Any? = null
114
+
115
+ /** Apply one Python-driven animation frame immediately. */
116
+ open fun setAnimatedProperty(view: View, prop: String, value: Any?) {
117
+ PNAnimator.setProperty(view, prop, value)
118
+ }
119
+
120
+ /** Start a natively driven animation; `false` asks Python to tick it instead. */
121
+ open fun startAnimation(view: View, id: Long, prop: String, spec: JSONObject): Boolean =
122
+ PNAnimator.start(view, id, prop, spec)
123
+
124
+ /** Cancel a native animation and return the presentation value, if readable. */
125
+ open fun cancelAnimation(view: View, id: Long): Any? = PNAnimator.cancel(id)
126
+
127
+ // ------------------------------------------------------------------
128
+ // Helpers for subclasses
129
+ // ------------------------------------------------------------------
130
+
131
+ /** The registry record for `view`, or `null` for foreign views. */
132
+ protected fun recordOf(view: View): ViewRecord? = PNBridge.registry.recordFor(view)
133
+
134
+ /** Merged props for `view` (empty for foreign views). */
135
+ protected fun propsOf(view: View): JSONObject = recordOf(view)?.props ?: JSONObject()
136
+
137
+ /** Manager-private state map for `view`. */
138
+ protected fun stateOf(view: View): MutableMap<String, Any?> = recordOf(view)?.state ?: HashMap()
139
+
140
+ /** Dispatch event `name` with positional `args` for `view`'s tag. */
141
+ protected fun fire(view: View, name: String, vararg args: Any?): Boolean = PNEvents.fire(view, name, *args)
142
+
143
+ /** Dispatch event `name` and return Python's (JSON) reply. */
144
+ protected fun fireForResult(view: View, name: String, vararg args: Any?): String? =
145
+ PNEvents.fireForResult(view, name, *args)
146
+
147
+ /** Whether the element wired a callback named `name`. */
148
+ protected fun hasEvent(view: View, name: String): Boolean = recordOf(view)?.hasEvent(name) ?: false
149
+
150
+ /** dp to whole pixels. */
151
+ protected fun px(dp: Double): Int = (dp * PNBridge.density()).roundToInt()
152
+
153
+ /** dp to pixels as a float. */
154
+ protected fun pxF(dp: Double): Float = (dp * PNBridge.density()).toFloat()
155
+
156
+ /** pixels to dp. */
157
+ protected fun dp(px: Number): Double = px.toDouble() / PNBridge.density()
158
+
159
+ private fun finite(v: Double): Double = if (v.isNaN() || v.isInfinite()) 0.0 else v
160
+
161
+ private fun measureSpec(limit: Double, density: Float): Int {
162
+ if (limit.isNaN() || limit.isInfinite() || limit >= 1e6) {
163
+ return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
164
+ }
165
+ val px = min((max(0.0, limit) * density).toInt(), (1 shl 29))
166
+ return View.MeasureSpec.makeMeasureSpec(px, View.MeasureSpec.AT_MOST)
167
+ }
168
+ }
169
+
170
+ /** Move-aware indexed insertion into any `ViewGroup`. */
171
+ object ViewChildren {
172
+ fun insert(parent: ViewGroup, child: View, index: Int) {
173
+ val current = child.parent as? ViewGroup
174
+ if (current === parent) {
175
+ val currentIndex = parent.indexOfChild(child)
176
+ val target = max(0, min(index, parent.childCount - 1))
177
+ if (currentIndex == target) return
178
+ parent.removeView(child)
179
+ parent.addView(child, max(0, min(target, parent.childCount)))
180
+ return
181
+ }
182
+ current?.removeView(child)
183
+ if (child.layoutParams == null) {
184
+ child.layoutParams = FrameLayout.LayoutParams(0, 0)
185
+ }
186
+ parent.addView(child, max(0, min(index, parent.childCount)))
187
+ }
188
+ }
189
+
190
+ /** Event dispatch keyed by view identity. */
191
+ object PNEvents {
192
+ /** Dispatch `name` for `view` with positional `args`; `false` when the view is unknown. */
193
+ fun fire(view: View, name: String, vararg args: Any?): Boolean {
194
+ val tag = PNBridge.registry.tagOf(view) ?: return false
195
+ fireTag(tag, name, JsonUtil.args(*args))
196
+ return true
197
+ }
198
+
199
+ /** Dispatch `name` for `view` and return Python's JSON reply. */
200
+ fun fireForResult(view: View, name: String, vararg args: Any?): String? {
201
+ val tag = PNBridge.registry.tagOf(view) ?: return null
202
+ return fireTag(tag, name, JsonUtil.args(*args))
203
+ }
204
+
205
+ /** Dispatch `name` for `tag` with an already built argument array. */
206
+ fun fireTag(tag: Long, name: String, args: JSONArray): String? =
207
+ PNBridge.callPython("event", tag, name, args.toString())
208
+ }
209
+
210
+ /** Fallback for unknown element types: an empty container that hosts children. */
211
+ class PlaceholderManager : ComponentManager() {
212
+ override fun createView(context: Context, tag: Long, props: JSONObject): View = FrameLayout(context)
213
+ }
@@ -0,0 +1,69 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+ import androidx.core.view.ViewCompat
6
+ import androidx.core.view.WindowInsetsCompat
7
+ import com.pythonnative.runtime.PNBridge
8
+ import com.pythonnative.runtime.modules.ModuleEvents
9
+ import com.pythonnative.runtime.views.PNFrameLayout
10
+ import org.json.JSONObject
11
+ import kotlin.math.max
12
+
13
+ /**
14
+ * Flex container (shared by `View`, `Column`, and `Row`): a bare
15
+ * [PNFrameLayout]. All flex semantics are computed by the Python layout
16
+ * engine and applied through `setFrame`; the container is just a
17
+ * positioning surface with `pointer_events` interception.
18
+ */
19
+ open class ViewManager : ComponentManager() {
20
+ override fun createView(context: Context, tag: Long, props: JSONObject): View = PNFrameLayout(context)
21
+ }
22
+
23
+ /** Empty layout placeholder used as a flexible gap. */
24
+ class SpacerManager : ComponentManager() {
25
+ override fun createView(context: Context, tag: Long, props: JSONObject): View = View(context)
26
+
27
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
28
+ // Spacers carry no visual props of their own.
29
+ }
30
+ }
31
+
32
+ /** Safe-area container: a flex container with `fitsSystemWindows`. */
33
+ class SafeAreaViewManager : ViewManager() {
34
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
35
+ val view = PNFrameLayout(context)
36
+ view.fitsSystemWindows = true
37
+ return view
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Vanilla container that publishes the keyboard height (from window
43
+ * insets) as a `Host` module `keyboard` event so the Python-side
44
+ * component can compute its offset.
45
+ */
46
+ class KeyboardAvoidingViewManager : ViewManager() {
47
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
48
+ val view = PNFrameLayout(context)
49
+ ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
50
+ publishKeyboard(v, insets)
51
+ insets
52
+ }
53
+ view.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
54
+ ViewCompat.getRootWindowInsets(v)?.let { publishKeyboard(v, it) }
55
+ }
56
+ return view
57
+ }
58
+
59
+ private fun publishKeyboard(view: View, insets: WindowInsetsCompat) {
60
+ val density = PNBridge.density()
61
+ val ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
62
+ val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
63
+ val height = max(0, ime - bars) / density
64
+ val last = stateOf(view)["keyboard_height"] as? Double
65
+ if (last != null && last == height.toDouble()) return
66
+ stateOf(view)["keyboard_height"] = height.toDouble()
67
+ ModuleEvents.emit("Host", "keyboard", JSONObject().put("height", height.toDouble()))
68
+ }
69
+ }
@@ -0,0 +1,186 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.content.Context
4
+ import android.content.res.ColorStateList
5
+ import android.view.View
6
+ import android.widget.CheckBox
7
+ import android.widget.CompoundButton
8
+ import android.widget.ProgressBar
9
+ import android.widget.SeekBar
10
+ import android.widget.Switch
11
+ import com.pythonnative.runtime.bridge.JsonUtil
12
+ import com.pythonnative.runtime.bridge.num
13
+ import com.pythonnative.runtime.bridge.str
14
+ import com.pythonnative.runtime.bridge.value
15
+ import org.json.JSONObject
16
+
17
+ /** Base for `CompoundButton` widgets: `on_change` wiring with a suppress guard. */
18
+ abstract class CheckedManager : ComponentManager() {
19
+ protected fun bindChecked(button: CompoundButton) {
20
+ button.setOnCheckedChangeListener { b, checked ->
21
+ if (stateOf(b)["suppress"] == true) return@setOnCheckedChangeListener
22
+ fire(b, "on_change", checked)
23
+ }
24
+ }
25
+ }
26
+
27
+ /** `Switch` element. */
28
+ class SwitchManager : CheckedManager() {
29
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
30
+ val sw = Switch(context)
31
+ bindChecked(sw)
32
+ return sw
33
+ }
34
+
35
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
36
+ val sw = view as Switch
37
+ if (props.has("value")) {
38
+ val state = stateOf(sw)
39
+ state["suppress"] = true
40
+ try {
41
+ sw.isChecked = JsonUtil.truthy(props.value("value"))
42
+ } finally {
43
+ state["suppress"] = false
44
+ }
45
+ }
46
+ if (props.has("disabled")) sw.isEnabled = !JsonUtil.truthy(props.value("disabled"))
47
+ if (props.has("enabled")) sw.isEnabled = JsonUtil.truthy(props.value("enabled"))
48
+ PNColor.parse(props.value("on_tint_color") ?: props.value("tint_color"))?.let {
49
+ sw.thumbTintList = ColorStateList(
50
+ arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
51
+ intArrayOf(it, 0xFFFAFAFA.toInt()),
52
+ )
53
+ sw.trackTintList = ColorStateList(
54
+ arrayOf(intArrayOf(android.R.attr.state_checked), intArrayOf()),
55
+ intArrayOf(PNColor.withAlpha(it, 0.5), 0x61000000),
56
+ )
57
+ }
58
+ PNColor.parse(props.value("thumb_color"))?.let { sw.thumbTintList = ColorStateList.valueOf(it) }
59
+ ViewStyler.applyAccessibility(sw, props)
60
+ }
61
+ }
62
+
63
+ /** `Checkbox` element with an optional inline label. */
64
+ class CheckboxManager : CheckedManager() {
65
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
66
+ val cb = CheckBox(context)
67
+ bindChecked(cb)
68
+ return cb
69
+ }
70
+
71
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
72
+ val cb = view as CheckBox
73
+ if (props.has("label")) cb.text = props.str("label") ?: ""
74
+ if (props.has("value")) {
75
+ val state = stateOf(cb)
76
+ state["suppress"] = true
77
+ try {
78
+ cb.isChecked = JsonUtil.truthy(props.value("value"))
79
+ } finally {
80
+ state["suppress"] = false
81
+ }
82
+ }
83
+ if (props.has("disabled")) cb.isEnabled = !JsonUtil.truthy(props.value("disabled"))
84
+ PNColor.parse(props.value("color"))?.let { cb.buttonTintList = ColorStateList.valueOf(it) }
85
+ ViewStyler.applyAccessibility(cb, props)
86
+ }
87
+ }
88
+
89
+ /** `ProgressBar` element: a horizontal determinate bar with a 0..1 `value`. */
90
+ class ProgressBarManager : ComponentManager() {
91
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
92
+ val pb = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal)
93
+ pb.max = 1000
94
+ return pb
95
+ }
96
+
97
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
98
+ val pb = view as ProgressBar
99
+ props.num("value")?.let { pb.progress = (it.coerceIn(0.0, 1.0) * 1000).toInt() }
100
+ PNColor.parse(props.value("color"))?.let { pb.progressTintList = ColorStateList.valueOf(it) }
101
+ PNColor.parse(props.value("track_color"))?.let {
102
+ val track = ColorStateList.valueOf(it)
103
+ pb.progressBackgroundTintList = track
104
+ pb.secondaryProgressTintList = track
105
+ }
106
+ if (props.has("indeterminate")) pb.isIndeterminate = JsonUtil.truthy(props.value("indeterminate"))
107
+ ViewStyler.apply(pb, props)
108
+ }
109
+ }
110
+
111
+ /** `ActivityIndicator` element: an indeterminate spinner. */
112
+ class ActivityIndicatorManager : ComponentManager() {
113
+ override fun createView(context: Context, tag: Long, props: JSONObject): View = ProgressBar(context)
114
+
115
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
116
+ val pb = view as ProgressBar
117
+ if (props.has("animating")) {
118
+ pb.visibility = if (JsonUtil.truthy(props.value("animating"))) View.VISIBLE else View.GONE
119
+ }
120
+ if (props.has("hides_when_stopped") && !JsonUtil.truthy(props.value("hides_when_stopped"))) {
121
+ pb.visibility = View.VISIBLE
122
+ }
123
+ PNColor.parse(props.value("color"))?.let { pb.indeterminateTintList = ColorStateList.valueOf(it) }
124
+ props.str("size")?.let {
125
+ // The framework ProgressBar has no runtime size switch; scale "large".
126
+ val scale = if (it == "large") 1.5f else 1f
127
+ pb.scaleX = scale
128
+ pb.scaleY = scale
129
+ }
130
+ ViewStyler.apply(pb, props)
131
+ }
132
+ }
133
+
134
+ /** `Slider` element: a `SeekBar` mapped onto `min_value..max_value`. */
135
+ class SliderManager : ComponentManager() {
136
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
137
+ val sb = SeekBar(context)
138
+ sb.max = 1000
139
+ sb.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
140
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
141
+ if (!fromUser) return
142
+ fire(seekBar, "on_change", valueFor(seekBar, progress))
143
+ }
144
+
145
+ override fun onStartTrackingTouch(seekBar: SeekBar) {
146
+ fire(seekBar, "on_sliding_start", valueFor(seekBar, seekBar.progress))
147
+ }
148
+
149
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
150
+ fire(seekBar, "on_sliding_complete", valueFor(seekBar, seekBar.progress))
151
+ }
152
+ })
153
+ return sb
154
+ }
155
+
156
+ private fun range(view: View): Pair<Double, Double> {
157
+ val merged = propsOf(view)
158
+ val mn = merged.num("min_value") ?: merged.num("minimum_value") ?: 0.0
159
+ val mx = merged.num("max_value") ?: merged.num("maximum_value") ?: 1.0
160
+ return Pair(mn, mx)
161
+ }
162
+
163
+ private fun valueFor(view: View, progress: Int): Double {
164
+ val (mn, mx) = range(view)
165
+ val span = if (mx != mn) mx - mn else 1.0
166
+ var value = mn + (progress / 1000.0) * span
167
+ propsOf(view).num("step")?.takeIf { it > 0 }?.let { step ->
168
+ value = mn + Math.round((value - mn) / step) * step
169
+ }
170
+ return value
171
+ }
172
+
173
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
174
+ val sb = view as SeekBar
175
+ val (mn, mx) = range(sb)
176
+ val span = if (mx != mn) mx - mn else 1.0
177
+ props.num("value")?.let { sb.progress = (((it - mn) / span) * 1000).toInt().coerceIn(0, 1000) }
178
+ if (props.has("disabled")) sb.isEnabled = !JsonUtil.truthy(props.value("disabled"))
179
+ PNColor.parse(props.value("minimum_track_tint_color") ?: props.value("tint_color"))?.let {
180
+ sb.progressTintList = ColorStateList.valueOf(it)
181
+ }
182
+ PNColor.parse(props.value("maximum_track_tint_color"))?.let { sb.progressBackgroundTintList = ColorStateList.valueOf(it) }
183
+ PNColor.parse(props.value("thumb_tint_color"))?.let { sb.thumbTintList = ColorStateList.valueOf(it) }
184
+ ViewStyler.applyAccessibility(sb, props)
185
+ }
186
+ }
@@ -0,0 +1,123 @@
1
+ package com.pythonnative.runtime.components
2
+
3
+ import android.app.DatePickerDialog
4
+ import android.app.TimePickerDialog
5
+ import android.content.Context
6
+ import android.view.View
7
+ import android.widget.Button
8
+ import com.pythonnative.runtime.bridge.PNLog
9
+ import com.pythonnative.runtime.bridge.str
10
+ import com.pythonnative.runtime.bridge.value
11
+ import org.json.JSONObject
12
+ import java.text.SimpleDateFormat
13
+ import java.util.Calendar
14
+ import java.util.Locale
15
+
16
+ /**
17
+ * `DatePicker` element: a trigger `Button` opening the native
18
+ * `DatePickerDialog` (`mode` `"date"`), `TimePickerDialog` (`"time"`),
19
+ * or a chained date-then-time flow (`"datetime"`). Values are ISO
20
+ * strings; the confirmed value is reported through `on_change`.
21
+ */
22
+ class DatePickerManager : ComponentManager() {
23
+ override fun createView(context: Context, tag: Long, props: JSONObject): View {
24
+ val btn = Button(context)
25
+ btn.isAllCaps = false
26
+ btn.setOnClickListener { v ->
27
+ if (propsOf(v).value("enabled") == false) return@setOnClickListener
28
+ openDialog(v as Button)
29
+ }
30
+ return btn
31
+ }
32
+
33
+ override fun applyProps(view: View, props: JSONObject, initial: Boolean) {
34
+ val btn = view as Button
35
+ if (props.has("enabled")) btn.isEnabled = props.value("enabled") != false
36
+ if (props.has("value") || props.has("mode") || initial) refreshLabel(btn)
37
+ ViewStyler.applyAccessibility(btn, props)
38
+ }
39
+
40
+ private fun mode(btn: Button): String = propsOf(btn).str("mode") ?: "date"
41
+
42
+ private fun refreshLabel(btn: Button) {
43
+ val value = propsOf(btn).str("value")
44
+ btn.text = if (!value.isNullOrEmpty()) value else PLACEHOLDERS[mode(btn)] ?: "Select"
45
+ }
46
+
47
+ private fun openDialog(btn: Button) {
48
+ val mode = mode(btn)
49
+ val cal = parse(propsOf(btn).str("value"), mode)
50
+ when (mode) {
51
+ "time" -> openTime(btn, cal)
52
+ "datetime" -> openDate(btn, cal, thenTime = true)
53
+ else -> openDate(btn, cal, thenTime = false)
54
+ }
55
+ }
56
+
57
+ private fun openDate(btn: Button, cal: Calendar, thenTime: Boolean) {
58
+ val dialog = DatePickerDialog(
59
+ btn.context,
60
+ { _, year, month, day ->
61
+ cal.set(Calendar.YEAR, year)
62
+ cal.set(Calendar.MONTH, month)
63
+ cal.set(Calendar.DAY_OF_MONTH, day)
64
+ if (thenTime) openTime(btn, cal) else commit(btn, cal)
65
+ },
66
+ cal.get(Calendar.YEAR),
67
+ cal.get(Calendar.MONTH),
68
+ cal.get(Calendar.DAY_OF_MONTH),
69
+ )
70
+ try {
71
+ val merged = propsOf(btn)
72
+ merged.str("minimum")?.takeIf { it.isNotEmpty() }?.let { dialog.datePicker.minDate = parse(it, mode(btn)).timeInMillis }
73
+ merged.str("maximum")?.takeIf { it.isNotEmpty() }?.let { dialog.datePicker.maxDate = parse(it, mode(btn)).timeInMillis }
74
+ } catch (e: Exception) {
75
+ PNLog.swallowed("DatePickerManager.minMax", e)
76
+ }
77
+ dialog.show()
78
+ }
79
+
80
+ private fun openTime(btn: Button, cal: Calendar) {
81
+ TimePickerDialog(
82
+ btn.context,
83
+ { _, hour, minute ->
84
+ cal.set(Calendar.HOUR_OF_DAY, hour)
85
+ cal.set(Calendar.MINUTE, minute)
86
+ commit(btn, cal)
87
+ },
88
+ cal.get(Calendar.HOUR_OF_DAY),
89
+ cal.get(Calendar.MINUTE),
90
+ true,
91
+ ).show()
92
+ }
93
+
94
+ private fun format(mode: String): SimpleDateFormat = SimpleDateFormat(PATTERNS[mode] ?: PATTERNS["date"]!!, Locale.US)
95
+
96
+ private fun parse(value: String?, mode: String): Calendar {
97
+ val cal = Calendar.getInstance()
98
+ if (!value.isNullOrEmpty()) {
99
+ try {
100
+ format(mode).parse(value)?.let { cal.time = it }
101
+ } catch (e: Exception) {
102
+ PNLog.swallowed("DatePickerManager.parse", e)
103
+ }
104
+ }
105
+ return cal
106
+ }
107
+
108
+ private fun commit(btn: Button, cal: Calendar) {
109
+ val iso = try {
110
+ format(mode(btn)).format(cal.time)
111
+ } catch (e: Exception) {
112
+ return
113
+ }
114
+ propsOf(btn).put("value", iso)
115
+ btn.text = iso
116
+ fire(btn, "on_change", iso)
117
+ }
118
+
119
+ private companion object {
120
+ val PATTERNS = mapOf("date" to "yyyy-MM-dd", "time" to "HH:mm", "datetime" to "yyyy-MM-dd'T'HH:mm")
121
+ val PLACEHOLDERS = mapOf("date" to "Select date", "time" to "Select time", "datetime" to "Select date & time")
122
+ }
123
+ }