pythonnative 0.36.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 (174) 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/components/layout.py +65 -3
  11. pythonnative/gestures.py +13 -10
  12. pythonnative/hooks.py +41 -26
  13. pythonnative/hosts/__init__.py +13 -36
  14. pythonnative/hosts/native.py +348 -0
  15. pythonnative/images.py +6 -5
  16. pythonnative/native_modules/__init__.py +30 -29
  17. pythonnative/native_modules/app_state.py +10 -6
  18. pythonnative/native_modules/battery.py +20 -84
  19. pythonnative/native_modules/biometrics.py +11 -124
  20. pythonnative/native_modules/camera.py +15 -291
  21. pythonnative/native_modules/clipboard.py +14 -109
  22. pythonnative/native_modules/desktop.py +322 -0
  23. pythonnative/native_modules/file_system.py +15 -21
  24. pythonnative/native_modules/haptics.py +19 -129
  25. pythonnative/native_modules/linking.py +24 -137
  26. pythonnative/native_modules/location.py +28 -180
  27. pythonnative/native_modules/net_info.py +25 -135
  28. pythonnative/native_modules/notifications.py +36 -257
  29. pythonnative/native_modules/permissions.py +20 -184
  30. pythonnative/native_modules/registry.py +513 -0
  31. pythonnative/native_modules/secure_store.py +18 -155
  32. pythonnative/native_modules/share.py +8 -113
  33. pythonnative/native_views/__init__.py +67 -86
  34. pythonnative/native_views/bridge_backend.py +264 -0
  35. pythonnative/platform.py +19 -15
  36. pythonnative/platform_metrics.py +3 -3
  37. pythonnative/project/android.py +0 -3
  38. pythonnative/project/builder.py +38 -16
  39. pythonnative/project/config.py +12 -2
  40. pythonnative/project/plugins.py +366 -0
  41. pythonnative/py.typed +0 -0
  42. pythonnative/runtime.py +35 -203
  43. pythonnative/sdk/__init__.py +48 -33
  44. pythonnative/sdk/_components.py +57 -76
  45. pythonnative/storage.py +18 -243
  46. pythonnative/templates/android_template/app/build.gradle +6 -9
  47. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  48. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  49. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  50. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  51. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  100. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  101. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  102. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  104. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  105. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  106. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  110. pythonnative/templates/android_template/settings.gradle +1 -0
  111. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  112. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  155. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  156. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  157. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  158. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  159. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  160. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  161. pythonnative/utils.py +4 -82
  162. pythonnative/virtual_rows.py +3 -3
  163. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  164. pythonnative-0.37.0.dist-info/RECORD +263 -0
  165. pythonnative/hosts/android.py +0 -269
  166. pythonnative/hosts/ios.py +0 -404
  167. pythonnative/native_views/android.py +0 -3702
  168. pythonnative/native_views/ios.py +0 -5253
  169. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  170. pythonnative-0.36.0.dist-info/RECORD +0 -155
  171. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  172. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  173. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  174. {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,233 @@
1
+ package com.pythonnative.runtime.gestures
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ /**
7
+ * Turn a raw pointer-event stream into arbitrated gesture payloads.
8
+ *
9
+ * One arbiter serves one view. Positions are in the view's coordinate
10
+ * space (dp); times are seconds on any monotonic clock. Beyond running
11
+ * each gesture's state machine, the arbiter enforces the relationships
12
+ * serialized by Python:
13
+ *
14
+ * - Two gestures not in each other's `simultaneous` sets race; when
15
+ * one activates, the other is force-failed for the interaction.
16
+ * - A gesture with a `wait_for` set may only activate after all of
17
+ * those gestures have failed; its output is buffered meanwhile and
18
+ * either flushed or discarded.
19
+ *
20
+ * This class is plain Kotlin (no Android types) so it is unit-testable
21
+ * with scripted pointer sequences.
22
+ */
23
+ class GestureArbiter(specs: List<GestureConfig>, private val emitOut: EmitFn) {
24
+ private enum class State { POSSIBLE, WAITING, ACTIVE, DONE, FAILED }
25
+
26
+ private val pointers = LinkedHashMap<Int, Point>()
27
+ private val recognizers = ArrayList<Recognizer>()
28
+ private val indices = ArrayList<Int>()
29
+ private val sim = HashMap<Int, Set<Int>?>()
30
+ private val waitFor = HashMap<Int, Set<Int>>()
31
+ private val states = HashMap<Int, State>()
32
+ private val buffers = HashMap<Int, ArrayList<Map<String, Any?>>>()
33
+ private var lastT = 0.0
34
+
35
+ init {
36
+ specs.forEachIndexed { i, spec ->
37
+ val recognizer = Recognizer.create(i, spec) { index, payload -> mediate(index, payload) } ?: return@forEachIndexed
38
+ recognizers.add(recognizer)
39
+ indices.add(i)
40
+ sim[i] = spec.simultaneous
41
+ waitFor[i] = spec.waitFor
42
+ states[i] = State.POSSIBLE
43
+ }
44
+ }
45
+
46
+ // -- pointer input ---------------------------------------------------
47
+
48
+ /** Record a pointer press and advance every recognizer. */
49
+ fun pointerDown(pointerId: Int, x: Double, y: Double, t: Double) {
50
+ lastT = t
51
+ if (pointers.isEmpty() && states.values.none { it == State.WAITING }) {
52
+ for (i in indices) states[i] = State.POSSIBLE
53
+ buffers.clear()
54
+ }
55
+ pointers[pointerId] = Point(x, y)
56
+ for (r in recognizers) r.down(pointers, t)
57
+ }
58
+
59
+ /** Record pointer travel and advance every recognizer. */
60
+ fun pointerMove(pointerId: Int, x: Double, y: Double, t: Double) {
61
+ lastT = t
62
+ if (!pointers.containsKey(pointerId)) return
63
+ pointers[pointerId] = Point(x, y)
64
+ for (r in recognizers) r.move(pointers, t)
65
+ }
66
+
67
+ /** Record a pointer release and advance every recognizer. */
68
+ fun pointerUp(pointerId: Int, x: Double, y: Double, t: Double) {
69
+ lastT = t
70
+ pointers.remove(pointerId)
71
+ for (r in recognizers) r.up(pointers, t, x, y)
72
+ }
73
+
74
+ /** Abort every in-flight gesture (for example, touch stolen by a scroll parent). */
75
+ fun cancel(t: Double) {
76
+ lastT = t
77
+ pointers.clear()
78
+ buffers.clear()
79
+ for (i in indices) if (states[i] == State.WAITING) states[i] = State.FAILED
80
+ for (r in recognizers) r.cancel(t)
81
+ }
82
+
83
+ /** Advance time-based recognizers (long press, multi-tap windows). */
84
+ fun poll(t: Double) {
85
+ lastT = t
86
+ for (r in recognizers) r.poll(t)
87
+ }
88
+
89
+ /** Earliest time [poll] should be called, or `null`. */
90
+ fun nextDeadline(): Double? = recognizers.mapNotNull { it.deadline() }.minOrNull()
91
+
92
+ /** Whether a pan gesture is currently activated. */
93
+ fun hasActivePan(): Boolean = recognizers.any { it is PanRecognizer && it.active }
94
+
95
+ // -- arbitration -------------------------------------------------------
96
+
97
+ private fun recognizerFor(index: Int): Recognizer? = recognizers.firstOrNull { it.index == index }
98
+
99
+ private fun isSimultaneous(a: Int, b: Int): Boolean {
100
+ val simA = sim[a] ?: return true
101
+ val simB = sim[b] ?: return true
102
+ return b in simA && a in simB
103
+ }
104
+
105
+ private fun mediate(index: Int, payload: Map<String, Any?>) {
106
+ val state = payload["state"]
107
+ val current = states[index] ?: State.POSSIBLE
108
+ if (state == GestureState.FAILED) {
109
+ if (current == State.POSSIBLE || current == State.WAITING) setFailed(index)
110
+ return
111
+ }
112
+ when (current) {
113
+ State.FAILED -> return
114
+ State.ACTIVE -> {
115
+ emitOut(index, payload)
116
+ if (state == GestureState.ENDED) {
117
+ states[index] = State.DONE
118
+ onResolved(index, succeeded = true)
119
+ } else if (state == GestureState.CANCELLED) {
120
+ states[index] = State.FAILED
121
+ onResolved(index, succeeded = false)
122
+ }
123
+ }
124
+ State.WAITING -> buffers.getOrPut(index) { ArrayList() }.add(payload)
125
+ State.DONE -> {}
126
+ State.POSSIBLE -> requestActivation(index, payload)
127
+ }
128
+ }
129
+
130
+ private fun requestActivation(index: Int, payload: Map<String, Any?>) {
131
+ for (j in indices) {
132
+ if (j == index) continue
133
+ val s = states[j]
134
+ if ((s == State.ACTIVE || s == State.DONE) && !isSimultaneous(index, j)) {
135
+ setFailed(index)
136
+ return
137
+ }
138
+ }
139
+ val targets = (waitFor[index] ?: emptySet()).filter { states.containsKey(it) }
140
+ if (targets.any { states[it] == State.ACTIVE || states[it] == State.DONE }) {
141
+ setFailed(index)
142
+ return
143
+ }
144
+ if (targets.any { states[it] == State.POSSIBLE || states[it] == State.WAITING }) {
145
+ states[index] = State.WAITING
146
+ buffers.getOrPut(index) { ArrayList() }.add(payload)
147
+ return
148
+ }
149
+ activate(index, listOf(payload))
150
+ }
151
+
152
+ private fun activate(index: Int, payloads: List<Map<String, Any?>>) {
153
+ val last = payloads.lastOrNull()?.get("state")
154
+ val discreteDone = last == GestureState.ENDED || last == GestureState.CANCELLED
155
+ states[index] = if (discreteDone) State.DONE else State.ACTIVE
156
+ for (j in indices) {
157
+ if (j == index) continue
158
+ val s = states[j]
159
+ if ((s == State.POSSIBLE || s == State.WAITING) && !isSimultaneous(index, j)) {
160
+ recognizerFor(j)?.forceFail(lastT)
161
+ setFailed(j)
162
+ }
163
+ }
164
+ for (p in payloads) emitOut(index, p)
165
+ if (states[index] == State.DONE) onResolved(index, succeeded = true)
166
+ }
167
+
168
+ private fun setFailed(index: Int) {
169
+ if (states[index] == State.FAILED) return
170
+ states[index] = State.FAILED
171
+ buffers.remove(index)
172
+ onResolved(index, succeeded = false)
173
+ }
174
+
175
+ private fun onResolved(index: Int, succeeded: Boolean) {
176
+ for (waiter in indices) {
177
+ if (states[waiter] != State.WAITING) continue
178
+ val wf = waitFor[waiter] ?: emptySet()
179
+ if (index !in wf) continue
180
+ if (succeeded) {
181
+ recognizerFor(waiter)?.forceFail(lastT)
182
+ setFailed(waiter)
183
+ continue
184
+ }
185
+ val targets = wf.filter { states.containsKey(it) }
186
+ if (targets.any { states[it] == State.POSSIBLE || states[it] == State.WAITING }) continue
187
+ if (targets.any { states[it] == State.ACTIVE || states[it] == State.DONE }) {
188
+ recognizerFor(waiter)?.forceFail(lastT)
189
+ setFailed(waiter)
190
+ continue
191
+ }
192
+ val payloads = buffers.remove(waiter) ?: ArrayList()
193
+ states[waiter] = State.POSSIBLE
194
+ activate(waiter, payloads)
195
+ }
196
+ }
197
+
198
+ companion object {
199
+ /** Decode the serialized `gestures` prop (a JSON array of spec objects). */
200
+ fun decodeSpecs(specs: JSONArray?): List<GestureConfig> {
201
+ if (specs == null) return emptyList()
202
+ val out = ArrayList<GestureConfig>(specs.length())
203
+ for (i in 0 until specs.length()) {
204
+ val spec = specs.optJSONObject(i)
205
+ out.add(if (spec == null) GestureConfig(kind = "") else decodeSpec(spec))
206
+ }
207
+ return out
208
+ }
209
+
210
+ /** Decode one spec object. */
211
+ fun decodeSpec(spec: JSONObject): GestureConfig {
212
+ return GestureConfig(
213
+ kind = spec.optString("kind", ""),
214
+ nTaps = spec.optInt("n_taps", 1),
215
+ maxDistance = spec.optDouble("max_distance", 12.0),
216
+ minDurationMs = spec.optDouble("min_duration_ms", 500.0),
217
+ minDistance = spec.optDouble("min_distance", 10.0),
218
+ minPointers = spec.optInt("min_pointers", 1),
219
+ direction = spec.optString("direction", "any"),
220
+ minVelocity = spec.optDouble("min_velocity", 300.0),
221
+ nPointers = spec.optInt("n_pointers", 1),
222
+ simultaneous = spec.optJSONArray("simultaneous")?.let { intSet(it) },
223
+ waitFor = spec.optJSONArray("wait_for")?.let { intSet(it) } ?: emptySet(),
224
+ )
225
+ }
226
+
227
+ private fun intSet(array: JSONArray): Set<Int> {
228
+ val out = HashSet<Int>()
229
+ for (i in 0 until array.length()) out.add(array.optInt(i))
230
+ return out
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,127 @@
1
+ package com.pythonnative.runtime.gestures
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.os.SystemClock
5
+ import android.view.MotionEvent
6
+ import android.view.View
7
+ import com.pythonnative.runtime.PNBridge
8
+ import com.pythonnative.runtime.bridge.JsonUtil
9
+ import com.pythonnative.runtime.bridge.MainThread
10
+ import com.pythonnative.runtime.bridge.PNLog
11
+ import com.pythonnative.runtime.bridge.ViewRecord
12
+ import com.pythonnative.runtime.components.PNEvents
13
+ import com.pythonnative.runtime.components.ViewStyler
14
+ import org.json.JSONArray
15
+ import kotlin.math.max
16
+
17
+ /**
18
+ * Binds `MotionEvent` streams on PythonNative views to a
19
+ * [GestureArbiter] and forwards its output as `gesture:<index>`
20
+ * events. Time-based deadlines (long press, multi-tap windows) are
21
+ * polled with the main `Handler`, scheduled at the arbiter's next
22
+ * deadline. When a pan activates, the parent is asked not to intercept
23
+ * so an enclosing scroll view can't steal the drag.
24
+ */
25
+ object GestureCoordinator {
26
+ private const val KEY_ARBITER = "arbiter"
27
+ private const val KEY_BOUND = "gestures_bound"
28
+ private const val KEY_POLL_DEADLINE = "poll_deadline"
29
+ private const val KEY_POLL_TOKEN = "poll_token"
30
+
31
+ /** Install (or replace) the arbiter for `record` from its `gestures` prop value. */
32
+ fun bind(record: ViewRecord, specs: Any?) {
33
+ val state = record.state
34
+ val array = specs as? JSONArray
35
+ if (array == null || array.length() == 0) {
36
+ state[KEY_ARBITER] = null
37
+ return
38
+ }
39
+ val tag = record.tag
40
+ val arbiter = GestureArbiter(GestureArbiter.decodeSpecs(array)) { index, payload ->
41
+ PNEvents.fireTag(tag, "gesture:$index", JsonUtil.args(payload))
42
+ }
43
+ state[KEY_ARBITER] = arbiter
44
+ if (state[KEY_BOUND] == true) return
45
+ state[KEY_BOUND] = true
46
+ bindTouchStream(record)
47
+ }
48
+
49
+ /** Drop the arbiter and any pending poll for `record`. */
50
+ fun unbind(record: ViewRecord) {
51
+ record.state[KEY_ARBITER] = null
52
+ record.state[KEY_POLL_DEADLINE] = null
53
+ record.state[KEY_POLL_TOKEN] = ((record.state[KEY_POLL_TOKEN] as? Int) ?: 0) + 1
54
+ }
55
+
56
+ /** Whether `record` currently has gestures wired. */
57
+ fun hasArbiter(record: ViewRecord): Boolean = record.state[KEY_ARBITER] is GestureArbiter
58
+
59
+ @SuppressLint("ClickableViewAccessibility")
60
+ private fun bindTouchStream(record: ViewRecord) {
61
+ record.view.setOnTouchListener { v, event ->
62
+ if (ViewStyler.pointerEventsBlocked(v)) return@setOnTouchListener false
63
+ feed(record, event)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Translate one `MotionEvent` into arbiter pointer calls. Returns
69
+ * `true` while any gesture spec is wired (to keep receiving the
70
+ * stream), `false` when there is nothing to feed.
71
+ */
72
+ fun feed(record: ViewRecord, event: MotionEvent): Boolean {
73
+ val arbiter = record.state[KEY_ARBITER] as? GestureArbiter ?: return false
74
+ try {
75
+ val t = now()
76
+ val density = PNBridge.density()
77
+ when (event.actionMasked) {
78
+ MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
79
+ val idx = event.actionIndex
80
+ arbiter.pointerDown(event.getPointerId(idx), (event.getX(idx) / density).toDouble(), (event.getY(idx) / density).toDouble(), t)
81
+ }
82
+ MotionEvent.ACTION_MOVE -> {
83
+ for (i in 0 until event.pointerCount) {
84
+ arbiter.pointerMove(event.getPointerId(i), (event.getX(i) / density).toDouble(), (event.getY(i) / density).toDouble(), t)
85
+ }
86
+ if (arbiter.hasActivePan()) record.view.parent?.requestDisallowInterceptTouchEvent(true)
87
+ }
88
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
89
+ val idx = event.actionIndex
90
+ arbiter.pointerUp(event.getPointerId(idx), (event.getX(idx) / density).toDouble(), (event.getY(idx) / density).toDouble(), t)
91
+ }
92
+ MotionEvent.ACTION_CANCEL -> arbiter.cancel(t)
93
+ }
94
+ schedulePoll(record)
95
+ } catch (e: Exception) {
96
+ PNLog.rateLimited("gesture-feed", "gesture feed failed", e)
97
+ }
98
+ return true
99
+ }
100
+
101
+ /** Seconds on the monotonic uptime clock. */
102
+ private fun now(): Double = SystemClock.uptimeMillis() / 1000.0
103
+
104
+ private fun schedulePoll(record: ViewRecord) {
105
+ val state = record.state
106
+ val arbiter = state[KEY_ARBITER] as? GestureArbiter ?: return
107
+ val deadline = arbiter.nextDeadline() ?: return
108
+ val pending = state[KEY_POLL_DEADLINE] as? Double
109
+ if (pending != null && pending <= deadline + 0.001) return
110
+ val delayMs = max(1L, ((deadline - now()) * 1000.0).toLong())
111
+ val token = ((state[KEY_POLL_TOKEN] as? Int) ?: 0) + 1
112
+ state[KEY_POLL_TOKEN] = token
113
+ state[KEY_POLL_DEADLINE] = deadline
114
+ MainThread.postDelayed({
115
+ if (state[KEY_POLL_TOKEN] == token) state[KEY_POLL_DEADLINE] = null
116
+ val live = state[KEY_ARBITER] as? GestureArbiter ?: return@postDelayed
117
+ live.poll(now())
118
+ schedulePoll(record)
119
+ }, delayMs)
120
+ }
121
+
122
+ /** Convenience for managers that own their own touch listener (Pressable). */
123
+ fun feed(view: View, event: MotionEvent): Boolean {
124
+ val record = PNBridge.registry.recordFor(view) ?: return false
125
+ return feed(record, event)
126
+ }
127
+ }