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,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
+ )