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,75 @@
1
+ package com.pythonnative.runtime
2
+
3
+ import com.pythonnative.runtime.animation.AnimationSpecs
4
+ import org.json.JSONArray
5
+ import org.json.JSONObject
6
+ import org.junit.Assert.assertArrayEquals
7
+ import org.junit.Assert.assertEquals
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Test
10
+ import kotlin.math.exp
11
+ import kotlin.math.sqrt
12
+
13
+ class AnimationSpecsTest {
14
+ @Test
15
+ fun springDampingRatio() {
16
+ // Critically damped when damping = 2 * sqrt(k * m).
17
+ val k = 100.0
18
+ val m = 2.0
19
+ val critical = 2 * sqrt(k * m)
20
+ assertEquals(1.0, AnimationSpecs.dampingRatio(k, critical, m), 1e-9)
21
+ val spec = JSONObject("""{"kind":"spring","from":0,"to":1,"stiffness":100,"damping":10,"mass":1}""")
22
+ val spring = AnimationSpecs.spring(spec)
23
+ assertEquals(100.0, spring.stiffness, 1e-9)
24
+ assertEquals(0.5, spring.dampingRatio, 1e-9)
25
+ // Mass scales the effective stiffness so the natural frequency matches.
26
+ val heavy = AnimationSpecs.spring(JSONObject("""{"kind":"spring","stiffness":100,"damping":10,"mass":4}"""))
27
+ assertEquals(25.0, heavy.stiffness, 1e-9)
28
+ assertEquals(10.0 / (2 * sqrt(400.0)), heavy.dampingRatio, 1e-9)
29
+ }
30
+
31
+ @Test
32
+ fun decayFrictionReproducesPythonDecay() {
33
+ val k = 0.002
34
+ val friction = AnimationSpecs.decayFriction(k)
35
+ // FlingAnimation: v(t) = v0 * exp(-4.2 * friction * t); Python: v0 * exp(-k * 1000 * t).
36
+ val t = 0.25
37
+ assertEquals(exp(-k * 1000 * t), exp(-AnimationSpecs.FLING_FRICTION_SCALE * friction * t), 1e-9)
38
+ val decay = AnimationSpecs.decay(JSONObject("""{"kind":"decay","from":10,"velocity":2,"deceleration":0.002}"""))
39
+ assertEquals(2000.0, decay.startVelocity, 1e-9)
40
+ assertEquals(10.0 + 2.0 / (0.002 * 1000.0), decay.projectedFinal, 1e-9)
41
+ }
42
+
43
+ @Test
44
+ fun timingDefaults() {
45
+ val timing = AnimationSpecs.timing(JSONObject("""{"kind":"timing","from":0,"to":1}"""))
46
+ assertEquals(300L, timing.durationMs)
47
+ assertEquals(1.0, timing.to, 0.0)
48
+ }
49
+
50
+ @Test
51
+ fun easingNamesMatchPythonCurves() {
52
+ @Suppress("UNCHECKED_CAST")
53
+ val easeInOut = AnimationSpecs.resolveEasing("ease_in_out") as (Float) -> Float
54
+ assertEquals(0.5f, easeInOut(0.5f), 1e-6f)
55
+ assertEquals(0.15625f, easeInOut(0.25f), 1e-6f)
56
+ @Suppress("UNCHECKED_CAST")
57
+ val linear = AnimationSpecs.resolveEasing("linear") as (Float) -> Float
58
+ assertEquals(0.3f, linear(0.3f), 1e-6f)
59
+ @Suppress("UNCHECKED_CAST")
60
+ val fallback = AnimationSpecs.resolveEasing("does-not-exist") as (Float) -> Float
61
+ assertEquals(easeInOut(0.3f), fallback(0.3f), 1e-6f)
62
+ @Suppress("UNCHECKED_CAST")
63
+ val bounce = AnimationSpecs.resolveEasing("bounce") as (Float) -> Float
64
+ assertEquals(1f, bounce(1f), 1e-6f)
65
+ assertTrue(bounce(0.5f) in 0f..1f)
66
+ }
67
+
68
+ @Test
69
+ fun cubicBezierArraysAreControlPoints() {
70
+ val pts = AnimationSpecs.resolveEasing(JSONArray("[0.25, 0.1, 0.25, 1.0]")) as DoubleArray
71
+ assertArrayEquals(doubleArrayOf(0.25, 0.1, 0.25, 1.0), pts, 1e-9)
72
+ val css = AnimationSpecs.resolveEasing("cubic-bezier(0.42, 0, 1, 1)") as DoubleArray
73
+ assertArrayEquals(doubleArrayOf(0.42, 0.0, 1.0, 1.0), css, 1e-9)
74
+ }
75
+ }
@@ -0,0 +1,197 @@
1
+ package com.pythonnative.runtime
2
+
3
+ import com.pythonnative.runtime.gestures.GestureArbiter
4
+ import com.pythonnative.runtime.gestures.GestureConfig
5
+ import org.json.JSONArray
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertFalse
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Test
10
+
11
+ class GestureArbiterTest {
12
+ private class Recorder {
13
+ val events = ArrayList<Pair<Int, Map<String, Any?>>>()
14
+ fun emit(index: Int, payload: Map<String, Any?>) {
15
+ events.add(index to payload)
16
+ }
17
+ fun states(index: Int) = events.filter { it.first == index }.map { it.second["state"] }
18
+ }
19
+
20
+ private fun tap(nTaps: Int = 1, sim: Set<Int>? = null, waitFor: Set<Int> = emptySet()) =
21
+ GestureConfig(kind = "tap", nTaps = nTaps, simultaneous = sim, waitFor = waitFor)
22
+
23
+ @Test
24
+ fun singleTapEmitsEnded() {
25
+ val rec = Recorder()
26
+ val arbiter = GestureArbiter(listOf(tap()), rec::emit)
27
+ arbiter.pointerDown(0, 10.0, 10.0, 0.0)
28
+ arbiter.pointerUp(0, 11.0, 10.0, 0.1)
29
+ assertEquals(listOf<Any?>("ended"), rec.states(0))
30
+ assertEquals(11.0, rec.events[0].second["x"])
31
+ }
32
+
33
+ @Test
34
+ fun tapFailsWhenPointerTravels() {
35
+ val rec = Recorder()
36
+ val arbiter = GestureArbiter(listOf(tap()), rec::emit)
37
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
38
+ arbiter.pointerMove(0, 50.0, 0.0, 0.05)
39
+ arbiter.pointerUp(0, 50.0, 0.0, 0.1)
40
+ assertTrue(rec.events.isEmpty())
41
+ }
42
+
43
+ @Test
44
+ fun doubleTapNeedsTwoTapsInsideTheWindow() {
45
+ val rec = Recorder()
46
+ val arbiter = GestureArbiter(listOf(tap(nTaps = 2)), rec::emit)
47
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
48
+ arbiter.pointerUp(0, 0.0, 0.0, 0.05)
49
+ assertTrue(rec.events.isEmpty())
50
+ arbiter.pointerDown(0, 0.0, 0.0, 0.2)
51
+ arbiter.pointerUp(0, 0.0, 0.0, 0.25)
52
+ assertEquals(listOf<Any?>("ended"), rec.states(0))
53
+ }
54
+
55
+ @Test
56
+ fun exclusiveSingleTapWaitsForDoubleTapWindow() {
57
+ // Exclusive(double_tap, single_tap): index 1 waits for index 0.
58
+ val rec = Recorder()
59
+ val specs = listOf(
60
+ tap(nTaps = 2, sim = emptySet()),
61
+ tap(nTaps = 1, sim = emptySet(), waitFor = setOf(0)),
62
+ )
63
+ val arbiter = GestureArbiter(specs, rec::emit)
64
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
65
+ arbiter.pointerUp(0, 0.0, 0.0, 0.05)
66
+ // Single tap is buffered until the double-tap gap expires.
67
+ assertTrue(rec.events.isEmpty())
68
+ val deadline = arbiter.nextDeadline()
69
+ assertTrue(deadline != null && deadline > 0.05)
70
+ arbiter.poll(deadline!! + 0.001)
71
+ assertEquals(listOf<Any?>("ended"), rec.states(1))
72
+ assertTrue(rec.states(0).isEmpty())
73
+ }
74
+
75
+ @Test
76
+ fun exclusiveDoubleTapSuppressesSingleTap() {
77
+ val rec = Recorder()
78
+ val specs = listOf(
79
+ tap(nTaps = 2, sim = emptySet()),
80
+ tap(nTaps = 1, sim = emptySet(), waitFor = setOf(0)),
81
+ )
82
+ val arbiter = GestureArbiter(specs, rec::emit)
83
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
84
+ arbiter.pointerUp(0, 0.0, 0.0, 0.05)
85
+ arbiter.pointerDown(0, 0.0, 0.0, 0.15)
86
+ arbiter.pointerUp(0, 0.0, 0.0, 0.2)
87
+ assertEquals(listOf<Any?>("ended"), rec.states(0))
88
+ assertTrue(rec.states(1).isEmpty())
89
+ }
90
+
91
+ @Test
92
+ fun longPressActivatesOnPollAndEndsOnRelease() {
93
+ val rec = Recorder()
94
+ val arbiter = GestureArbiter(listOf(GestureConfig(kind = "long_press", minDurationMs = 500.0)), rec::emit)
95
+ arbiter.pointerDown(0, 5.0, 5.0, 0.0)
96
+ assertEquals(0.5, arbiter.nextDeadline()!!, 1e-9)
97
+ arbiter.poll(0.3)
98
+ assertTrue(rec.events.isEmpty())
99
+ arbiter.poll(0.5)
100
+ assertEquals(listOf<Any?>("began"), rec.states(0))
101
+ arbiter.pointerUp(0, 5.0, 5.0, 0.7)
102
+ assertEquals(listOf<Any?>("began", "ended"), rec.states(0))
103
+ }
104
+
105
+ @Test
106
+ fun panReportsTranslationAndVelocity() {
107
+ val rec = Recorder()
108
+ val arbiter = GestureArbiter(listOf(GestureConfig(kind = "pan", minDistance = 10.0)), rec::emit)
109
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
110
+ arbiter.pointerMove(0, 5.0, 0.0, 0.01)
111
+ assertTrue(rec.events.isEmpty())
112
+ arbiter.pointerMove(0, 20.0, 0.0, 0.02)
113
+ assertEquals(listOf<Any?>("began"), rec.states(0))
114
+ assertTrue(arbiter.hasActivePan())
115
+ arbiter.pointerMove(0, 30.0, 5.0, 0.03)
116
+ val changed = rec.events.last().second
117
+ assertEquals("changed", changed["state"])
118
+ assertEquals(10.0, changed["translation_x"] as Double, 1e-9)
119
+ assertEquals(5.0, changed["translation_y"] as Double, 1e-9)
120
+ assertTrue((changed["velocity_x"] as Double) > 0)
121
+ arbiter.pointerUp(0, 40.0, 5.0, 0.04)
122
+ val ended = rec.events.last().second
123
+ assertEquals("ended", ended["state"])
124
+ assertEquals(20.0, ended["translation_x"] as Double, 1e-9)
125
+ assertFalse(arbiter.hasActivePan())
126
+ }
127
+
128
+ @Test
129
+ fun raceLetsFirstActivationWin() {
130
+ // Pan and long press not simultaneous: the pan activates first, long press never fires.
131
+ val rec = Recorder()
132
+ val specs = listOf(
133
+ GestureConfig(kind = "pan", minDistance = 10.0, simultaneous = emptySet()),
134
+ GestureConfig(kind = "long_press", minDurationMs = 500.0, simultaneous = emptySet()),
135
+ )
136
+ val arbiter = GestureArbiter(specs, rec::emit)
137
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
138
+ arbiter.pointerMove(0, 3.0, 0.0, 0.05)
139
+ arbiter.pointerMove(0, 6.0, 0.0, 0.1)
140
+ arbiter.pointerMove(0, 20.0, 0.0, 0.15)
141
+ assertEquals(listOf<Any?>("began"), rec.states(0))
142
+ arbiter.poll(0.6)
143
+ assertTrue(rec.states(1).isEmpty())
144
+ }
145
+
146
+ @Test
147
+ fun simultaneousGesturesBothRun() {
148
+ val rec = Recorder()
149
+ val specs = listOf(
150
+ GestureConfig(kind = "pan", minDistance = 5.0, simultaneous = setOf(1)),
151
+ GestureConfig(kind = "pinch", simultaneous = setOf(0)),
152
+ )
153
+ val arbiter = GestureArbiter(specs, rec::emit)
154
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
155
+ arbiter.pointerDown(1, 100.0, 0.0, 0.01)
156
+ assertEquals(listOf<Any?>("began"), rec.states(1))
157
+ arbiter.pointerMove(1, 120.0, 0.0, 0.02)
158
+ assertEquals(listOf<Any?>("began"), rec.states(0))
159
+ assertEquals(listOf<Any?>("began", "changed"), rec.states(1))
160
+ assertEquals(1.2, rec.events.last().second["scale"] as Double, 1e-9)
161
+ }
162
+
163
+ @Test
164
+ fun swipeResolvesDirectionAndVelocity() {
165
+ val rec = Recorder()
166
+ val arbiter = GestureArbiter(listOf(GestureConfig(kind = "swipe", direction = "right", minVelocity = 300.0)), rec::emit)
167
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
168
+ arbiter.pointerMove(0, 50.0, 0.0, 0.05)
169
+ arbiter.pointerUp(0, 100.0, 0.0, 0.1)
170
+ val ended = rec.events.single().second
171
+ assertEquals("ended", ended["state"])
172
+ assertEquals("right", ended["direction"])
173
+ assertEquals(1000.0, ended["velocity_x"] as Double, 1e-6)
174
+ }
175
+
176
+ @Test
177
+ fun cancelAbortsActiveGestures() {
178
+ val rec = Recorder()
179
+ val arbiter = GestureArbiter(listOf(GestureConfig(kind = "pan", minDistance = 1.0)), rec::emit)
180
+ arbiter.pointerDown(0, 0.0, 0.0, 0.0)
181
+ arbiter.pointerMove(0, 10.0, 0.0, 0.01)
182
+ arbiter.cancel(0.02)
183
+ assertEquals(listOf<Any?>("began", "cancelled"), rec.states(0))
184
+ }
185
+
186
+ @Test
187
+ fun decodesSerializedSpecs() {
188
+ val specs = GestureArbiter.decodeSpecs(
189
+ JSONArray("""[{"kind":"tap","n_taps":2,"simultaneous":[1],"wait_for":[]},{"kind":"pan","min_distance":4}]"""),
190
+ )
191
+ assertEquals(2, specs.size)
192
+ assertEquals(2, specs[0].nTaps)
193
+ assertEquals(setOf(1), specs[0].simultaneous)
194
+ assertEquals(4.0, specs[1].minDistance, 0.0)
195
+ assertEquals(null, specs[1].simultaneous)
196
+ }
197
+ }
@@ -0,0 +1,47 @@
1
+ package com.pythonnative.runtime
2
+
3
+ import com.pythonnative.runtime.components.PNColor
4
+ import org.json.JSONArray
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertNull
7
+ import org.junit.Test
8
+
9
+ class PNColorTest {
10
+ private fun hex(v: Int?): String = if (v == null) "null" else String.format("%08X", v)
11
+
12
+ @Test
13
+ fun parsesHexForms() {
14
+ assertEquals("FFFF0000", hex(PNColor.parse("#f00")))
15
+ assertEquals("FF112233", hex(PNColor.parse("#112233")))
16
+ assertEquals("80112233", hex(PNColor.parse("#80112233")))
17
+ assertEquals("FF112233", hex(PNColor.parse("112233")))
18
+ }
19
+
20
+ @Test
21
+ fun parsesFunctionalForms() {
22
+ assertEquals("FF0A141E", hex(PNColor.parse("rgb(10, 20, 30)")))
23
+ assertEquals("800A141E", hex(PNColor.parse("rgba(10, 20, 30, 0.5)")))
24
+ }
25
+
26
+ @Test
27
+ fun parsesNamedColors() {
28
+ assertEquals("FF000000", hex(PNColor.parse("black")))
29
+ assertEquals("FFFFFFFF", hex(PNColor.parse("White")))
30
+ assertEquals("00000000", hex(PNColor.parse("transparent")))
31
+ }
32
+
33
+ @Test
34
+ fun parsesIntegersAndArrays() {
35
+ assertEquals("FF112233", hex(PNColor.parse(0x112233)))
36
+ assertEquals("80112233", hex(PNColor.parse(0x80112233L)))
37
+ assertEquals("FF0A141E", hex(PNColor.parse(JSONArray("[10, 20, 30]"))))
38
+ assertEquals("800A141E", hex(PNColor.parse(JSONArray("[10, 20, 30, 0.5]"))))
39
+ }
40
+
41
+ @Test
42
+ fun rejectsGarbage() {
43
+ assertNull(PNColor.parse("not a color"))
44
+ assertNull(PNColor.parse(null))
45
+ assertEquals(7, PNColor.parseOr("nope", 7))
46
+ }
47
+ }
@@ -0,0 +1,78 @@
1
+ package com.pythonnative.runtime
2
+
3
+ import com.pythonnative.runtime.bridge.JsonUtil
4
+ import com.pythonnative.runtime.bridge.Op
5
+ import com.pythonnative.runtime.bridge.PNTransaction
6
+ import com.pythonnative.runtime.bridge.value
7
+ import org.json.JSONObject
8
+ import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertNull
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Test
12
+
13
+ class PNTransactionTest {
14
+ @Test
15
+ fun decodesEveryOpcode() {
16
+ val json = """
17
+ [
18
+ ["c", 1, "View", {"background_color": "#fff"}],
19
+ ["u", 1, {"opacity": 0.5, "background_color": null}],
20
+ ["i", 1, 2, 0],
21
+ ["f", 2, 10, 20.5, 100, 40],
22
+ ["d", 2]
23
+ ]
24
+ """.trimIndent()
25
+ val ops = PNTransaction.decode(json)
26
+ assertEquals(5, ops.size)
27
+ val create = ops[0] as Op.Create
28
+ assertEquals(1L, create.tag)
29
+ assertEquals("View", create.typeName)
30
+ assertEquals("#fff", create.props.getString("background_color"))
31
+ val update = ops[1] as Op.Update
32
+ assertEquals(0.5, update.changed.getDouble("opacity"), 1e-9)
33
+ assertTrue(update.changed.isNull("background_color"))
34
+ assertEquals(Op.Insert(1, 2, 0), ops[2])
35
+ assertEquals(Op.Frame(2, 10.0, 20.5, 100.0, 40.0), ops[3])
36
+ assertEquals(Op.Destroy(2), ops[4])
37
+ }
38
+
39
+ @Test
40
+ fun infinityStringsBecomeInfinity() {
41
+ val ops = PNTransaction.decode("""[["f", 1, 0, 0, "inf", "-inf"]]""")
42
+ val frame = ops[0] as Op.Frame
43
+ assertEquals(Double.POSITIVE_INFINITY, frame.width, 0.0)
44
+ assertEquals(Double.NEGATIVE_INFINITY, frame.height, 0.0)
45
+ assertEquals(Double.POSITIVE_INFINITY, JsonUtil.toDouble("Infinity"), 0.0)
46
+ }
47
+
48
+ @Test
49
+ fun malformedOpsAreIsolated() {
50
+ val errors = ArrayList<Int>()
51
+ val ops = PNTransaction.decode(
52
+ """[["c", 1, "View", {}], ["zz", 1], "not-an-array", ["i"], ["d", 1]]""",
53
+ ) { index, _ -> errors.add(index) }
54
+ assertEquals(listOf(1, 2, 3), errors)
55
+ assertEquals(2, ops.size)
56
+ assertTrue(ops[0] is Op.Create)
57
+ assertEquals(Op.Destroy(1), ops[1])
58
+ }
59
+
60
+ @Test
61
+ fun mergeKeepsExplicitNulls() {
62
+ val target = JSONObject("""{"a": 1, "b": 2}""")
63
+ JsonUtil.merge(target, JSONObject("""{"b": null, "c": "x"}"""))
64
+ assertEquals(1, target.getInt("a"))
65
+ assertTrue(target.isNull("b"))
66
+ assertNull(target.value("b"))
67
+ assertEquals("x", target.getString("c"))
68
+ }
69
+
70
+ @Test
71
+ fun encodesResultsAsJson() {
72
+ assertNull(JsonUtil.encode(null))
73
+ assertEquals("\"hi\"", JsonUtil.encode("hi"))
74
+ assertEquals("true", JsonUtil.encode(true))
75
+ assertEquals("""{"x":1.5}""", JsonUtil.encode(mapOf("x" to 1.5)))
76
+ assertEquals("[1,2]", JsonUtil.encode(listOf(1, 2)))
77
+ }
78
+ }
@@ -0,0 +1,75 @@
1
+ package com.pythonnative.runtime
2
+
3
+ import com.pythonnative.runtime.modules.ModuleEnvelope
4
+ import com.pythonnative.runtime.modules.Promise
5
+ import org.json.JSONObject
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertFalse
8
+ import org.junit.Assert.assertTrue
9
+ import org.junit.Test
10
+
11
+ class PromiseTest {
12
+ @Test
13
+ fun decodesCallEnvelope() {
14
+ val (callId, args) = ModuleEnvelope.decodeCall("""{"call_id": 7, "args": {"url": "x"}}""")
15
+ assertEquals(7L, callId)
16
+ assertEquals("x", args.getString("url"))
17
+ val (emptyId, emptyArgs) = ModuleEnvelope.decodeCall("")
18
+ assertEquals(0L, emptyId)
19
+ assertEquals(0, emptyArgs.length())
20
+ }
21
+
22
+ @Test
23
+ fun encodesResultEnvelopes() {
24
+ assertEquals("""{"ok":true,"value":3}""", ModuleEnvelope.ok(3))
25
+ val err = JSONObject(ModuleEnvelope.error("boom", "code_x"))
26
+ assertFalse(err.getBoolean("ok"))
27
+ assertEquals("boom", err.getString("error"))
28
+ assertEquals("code_x", err.getString("code"))
29
+ assertEquals("""{"pending":true}""", ModuleEnvelope.pending())
30
+ val event = JSONObject(ModuleEnvelope.event("change", mapOf("state" to "active")))
31
+ assertEquals("change", event.getString("event"))
32
+ assertEquals("active", event.getJSONObject("payload").getString("state"))
33
+ }
34
+
35
+ @Test
36
+ fun synchronousSettlementIsReturnedInline() {
37
+ val posted = ArrayList<Runnable>()
38
+ val promise = Promise(1, "Test") { posted.add(it) }
39
+ promise.resolve(mapOf("a" to 1))
40
+ assertTrue(promise.isSettled)
41
+ assertEquals("""{"ok":true,"value":{"a":1}}""", promise.markReturned())
42
+ assertTrue(posted.isEmpty())
43
+ }
44
+
45
+ @Test
46
+ fun asynchronousSettlementIsPosted() {
47
+ val posted = ArrayList<Runnable>()
48
+ val promise = Promise(9, "Test") { posted.add(it) }
49
+ assertEquals("""{"pending":true}""", promise.markReturned())
50
+ promise.resolve("later")
51
+ assertEquals(1, posted.size)
52
+ // Second settlement is ignored.
53
+ promise.reject("nope")
54
+ assertEquals(1, posted.size)
55
+ }
56
+
57
+ @Test
58
+ fun rejectionEnvelopeCarriesCode() {
59
+ val promise = Promise(2, "Test") { }
60
+ promise.reject("bad", "bad_request")
61
+ val obj = JSONObject(promise.markReturned())
62
+ assertFalse(obj.getBoolean("ok"))
63
+ assertEquals("bad_request", obj.getString("code"))
64
+ }
65
+
66
+ @Test
67
+ fun settledEnvelopesCarryCallId() {
68
+ val ok = JSONObject(ModuleEnvelope.settled(5, true))
69
+ assertEquals(5L, ok.getLong("call_id"))
70
+ assertTrue(ok.getBoolean("ok"))
71
+ val err = JSONObject(ModuleEnvelope.settledError(6, "x", null))
72
+ assertEquals(6L, err.getLong("call_id"))
73
+ assertFalse(err.has("code"))
74
+ }
75
+ }
@@ -14,3 +14,4 @@ dependencyResolutionManagement {
14
14
  }
15
15
  rootProject.name = "android_template"
16
16
  include ':app'
17
+ include ':pythonnative'
@@ -0,0 +1,21 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "PythonNativeKit",
6
+ platforms: [.iOS(.v13)],
7
+ products: [
8
+ .library(name: "PythonNativeKit", targets: ["PythonNativeKit"]),
9
+ ],
10
+ targets: [
11
+ .target(
12
+ name: "PythonNativeKit",
13
+ path: "Sources/PythonNativeKit"
14
+ ),
15
+ .testTarget(
16
+ name: "PythonNativeKitTests",
17
+ dependencies: ["PythonNativeKit"],
18
+ path: "Tests/PythonNativeKitTests"
19
+ ),
20
+ ]
21
+ )