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
@@ -1,19 +1,21 @@
1
1
  """Open URLs, deep links, and the system settings page.
2
2
 
3
3
  [`Linking`][pythonnative.Linking] wraps ``UIApplication.openURL`` /
4
- ``Intent(ACTION_VIEW)`` so a Python app can hand a URL (``https:``,
5
- ``mailto:``, ``tel:``, a custom scheme, …) to the OS.
4
+ ``Intent(ACTION_VIEW)`` (in the native ``Linking`` module) so a Python
5
+ app can hand a URL (``https:``, ``mailto:``, ``tel:``, a custom
6
+ scheme, ...) to the OS.
6
7
 
7
8
  Outbound methods are synchronous and return a ``bool`` describing
8
9
  whether the platform accepted the request. On desktop they return
9
10
  ``False``.
10
11
 
11
12
  Inbound deep links flow the other way: declare your schemes in
12
- ``pythonnative.toml`` (``app.url_schemes``) and the native host calls
13
- [`dispatch_url`][pythonnative.native_modules.linking.dispatch_url] for
14
- every URL that opens the app. The URL that cold-started the app is kept
15
- and returned by ``get_initial_url``; later URLs reach subscribers added
16
- with ``add_listener``.
13
+ ``pythonnative.toml`` (``app.url_schemes``) and the native module
14
+ pushes a ``url`` event for every URL that opens the app, which lands in
15
+ [`dispatch_url`][pythonnative.native_modules.linking.dispatch_url]. The
16
+ URL that cold-started the app is kept and returned by
17
+ ``get_initial_url``; later URLs reach subscribers added with
18
+ ``add_listener``.
17
19
 
18
20
  Example:
19
21
  ```python
@@ -30,43 +32,36 @@ from __future__ import annotations
30
32
 
31
33
  from typing import Any, Callable, List, Optional
32
34
 
33
- from ..utils import IS_ANDROID, IS_IOS
35
+ from .registry import native_module, on_event
34
36
 
35
- # Populated by the native host when the app is launched from a deep
36
- # link; ``get_initial_url`` returns it.
37
37
  _initial_url: Optional[str] = None
38
38
  _url_listeners: List[Callable[[str], None]] = []
39
39
 
40
40
 
41
+ def _call_bool(method: str, **args: Any) -> bool:
42
+ try:
43
+ return bool(native_module("Linking").call(method, **args))
44
+ except Exception:
45
+ return False
46
+
47
+
41
48
  class Linking:
42
49
  """System URL / deep-link interface (synchronous)."""
43
50
 
44
51
  @staticmethod
45
52
  def open_url(url: str) -> bool:
46
53
  """Hand ``url`` to the OS. Returns ``True`` if it was accepted."""
47
- if IS_IOS:
48
- return _ios_open(url)
49
- if IS_ANDROID:
50
- return _android_open(url)
51
- return False
54
+ return _call_bool("open_url", url=url)
52
55
 
53
56
  @staticmethod
54
57
  def can_open_url(url: str) -> bool:
55
58
  """Return ``True`` when some installed app can handle ``url``."""
56
- if IS_IOS:
57
- return _ios_can_open(url)
58
- if IS_ANDROID:
59
- return _android_can_open(url)
60
- return False
59
+ return _call_bool("can_open_url", url=url)
61
60
 
62
61
  @staticmethod
63
62
  def open_settings() -> bool:
64
63
  """Open this app's entry in the system Settings app."""
65
- if IS_IOS:
66
- return _ios_open_settings()
67
- if IS_ANDROID:
68
- return _android_open_settings()
69
- return False
64
+ return _call_bool("open_settings")
70
65
 
71
66
  @staticmethod
72
67
  def get_initial_url() -> Optional[str]:
@@ -97,18 +92,17 @@ class Linking:
97
92
 
98
93
 
99
94
  def set_initial_url(url: Optional[str]) -> None:
100
- """Record the launch URL (called by the native host on cold start)."""
95
+ """Record the launch URL (or clear it with ``None``)."""
101
96
  global _initial_url
102
97
  _initial_url = url
103
98
 
104
99
 
105
100
  def dispatch_url(url: str) -> None:
106
- """Deliver an inbound deep link from the native host.
101
+ """Deliver an inbound deep link.
107
102
 
108
103
  The first URL ever dispatched is also recorded as the initial URL
109
104
  (a cold start from a deep link reaches Python only after the
110
- interpreter boots, so the host can't call ``set_initial_url``
111
- earlier than this).
105
+ interpreter boots, so native can't report it earlier than this).
112
106
 
113
107
  Args:
114
108
  url: The full URL string that opened the app.
@@ -125,111 +119,4 @@ def dispatch_url(url: str) -> None:
125
119
  diagnostics.warn(f"Linking listener raised while handling {url!r}: {exc!r}")
126
120
 
127
121
 
128
- # ======================================================================
129
- # iOS
130
- # ======================================================================
131
-
132
-
133
- def _ios_url(url: str) -> Optional[Any]:
134
- try:
135
- from rubicon.objc import ObjCClass
136
-
137
- return ObjCClass("NSURL").URLWithString_(url)
138
- except Exception:
139
- return None
140
-
141
-
142
- def _ios_open(url: str) -> bool:
143
- nsurl = _ios_url(url)
144
- if nsurl is None:
145
- return False
146
- try:
147
- from rubicon.objc import ObjCClass
148
-
149
- app = ObjCClass("UIApplication").sharedApplication
150
- if app.canOpenURL_(nsurl):
151
- app.openURL_options_completionHandler_(nsurl, None, None)
152
- return True
153
- return False
154
- except Exception:
155
- return False
156
-
157
-
158
- def _ios_can_open(url: str) -> bool:
159
- nsurl = _ios_url(url)
160
- if nsurl is None:
161
- return False
162
- try:
163
- from rubicon.objc import ObjCClass
164
-
165
- return bool(ObjCClass("UIApplication").sharedApplication.canOpenURL_(nsurl))
166
- except Exception:
167
- return False
168
-
169
-
170
- def _ios_open_settings() -> bool:
171
- try:
172
- from rubicon.objc import ObjCClass
173
-
174
- settings = ObjCClass("UIApplicationOpenSettingsURLString")
175
- return _ios_open(str(settings))
176
- except Exception:
177
- try:
178
- return _ios_open("app-settings:")
179
- except Exception:
180
- return False
181
-
182
-
183
- # ======================================================================
184
- # Android
185
- # ======================================================================
186
-
187
-
188
- def _android_open(url: str) -> bool:
189
- try:
190
- from java import jclass
191
-
192
- from ..utils import get_android_context
193
-
194
- Intent = jclass("android.content.Intent")
195
- Uri = jclass("android.net.Uri")
196
- intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
197
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
198
- get_android_context().startActivity(intent)
199
- return True
200
- except Exception:
201
- return False
202
-
203
-
204
- def _android_can_open(url: str) -> bool:
205
- try:
206
- from java import jclass
207
-
208
- from ..utils import get_android_context
209
-
210
- Intent = jclass("android.content.Intent")
211
- Uri = jclass("android.net.Uri")
212
- intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
213
- pm = get_android_context().getPackageManager()
214
- return intent.resolveActivity(pm) is not None
215
- except Exception:
216
- return False
217
-
218
-
219
- def _android_open_settings() -> bool:
220
- try:
221
- from java import jclass
222
-
223
- from ..utils import get_android_context
224
-
225
- Intent = jclass("android.content.Intent")
226
- Settings = jclass("android.provider.Settings")
227
- Uri = jclass("android.net.Uri")
228
- ctx = get_android_context()
229
- intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
230
- intent.setData(Uri.fromParts("package", ctx.getPackageName(), None))
231
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
232
- ctx.startActivity(intent)
233
- return True
234
- except Exception:
235
- return False
122
+ on_event("Linking", "url", lambda payload: dispatch_url(str(payload)))
@@ -3,6 +3,9 @@
3
3
  [`Location.get_current`][pythonnative.native_modules.location.Location.get_current]
4
4
  is a coroutine that resolves to a ``(latitude, longitude)`` tuple, or
5
5
  ``None`` if no recent fix is available or the user denies permission.
6
+ The native ``Location`` module owns the ``CLLocationManager`` /
7
+ ``LocationManager`` session and resolves the call with
8
+ ``{"latitude", "longitude", "accuracy", "altitude", "timestamp"}``.
6
9
 
7
10
  Permission prompts are triggered the first time a location-using API
8
11
  is called; ensure the appropriate manifest entries
@@ -24,16 +27,9 @@ Example:
24
27
 
25
28
  from __future__ import annotations
26
29
 
27
- import asyncio
28
- from typing import Any, Callable, Dict, Optional, Tuple
30
+ from typing import Any, Dict, Optional, Tuple
29
31
 
30
- from .. import diagnostics
31
- from ..runtime import resolve_future
32
- from ..utils import IS_ANDROID, IS_IOS
33
-
34
- # Retain pool keyed by ``id(delegate)`` so iOS / Android listeners
35
- # aren't garbage-collected before the platform calls back.
36
- _pending_delegates: Dict[int, Any] = {}
32
+ from .registry import native_module
37
33
 
38
34
  Coords = Tuple[float, float]
39
35
 
@@ -46,184 +42,36 @@ class Location:
46
42
  """Request the device's current location.
47
43
 
48
44
  Args:
49
- **options: Reserved for platform-specific tuning (e.g.,
50
- ``accuracy``, ``timeout``).
45
+ **options: Forwarded to the native module (``accuracy``,
46
+ ``timeout``). Unknown keys are ignored.
51
47
 
52
48
  Returns:
53
49
  ``(latitude, longitude)`` if a fix was obtained, otherwise
54
50
  ``None``.
55
51
  """
56
- del options
57
- loop = asyncio.get_running_loop()
58
- future: asyncio.Future[Optional[Coords]] = loop.create_future()
59
-
60
- def _on_result(coords: Optional[Coords]) -> None:
61
- resolve_future(future, coords)
62
-
63
- if IS_ANDROID:
64
- _android_get(_on_result)
65
- elif IS_IOS:
66
- _ios_get(_on_result)
67
- else:
68
- resolve_future(future, None)
69
-
70
- return await future
71
-
72
-
73
- # ======================================================================
74
- # iOS implementation: CLLocationManagerDelegate
75
- # ======================================================================
76
-
77
-
78
- def _ios_get(on_result: Callable[[Optional[Coords]], None]) -> None:
79
- try:
80
- from rubicon.objc import ObjCClass, objc_method
81
-
82
- CLLocationManager = ObjCClass("CLLocationManager")
83
- NSObject = ObjCClass("NSObject")
52
+ fix = await Location.get_current_fix(**options)
53
+ if fix is None:
54
+ return None
55
+ return (fix["latitude"], fix["longitude"])
84
56
 
85
- class _PNLocationDelegate(NSObject): # type: ignore[misc,valid-type]
86
- _callback: Optional[Callable[[Optional[Coords]], None]] = None
87
- _manager: Any = None
88
- _delivered: bool = False
89
-
90
- def _finish(self, coords: Optional[Coords]) -> None:
91
- if self._delivered:
92
- return
93
- self._delivered = True
94
- try:
95
- if self._manager is not None:
96
- self._manager.stopUpdatingLocation()
97
- except Exception:
98
- diagnostics.swallowed("location._ios_get._PNLocationDelegate._finish")
99
- cb = self._callback
100
- self._callback = None
101
- _pending_delegates.pop(id(self), None)
102
- if cb is not None:
103
- try:
104
- cb(coords)
105
- except Exception:
106
- diagnostics.swallowed("location._ios_get._PNLocationDelegate._finish")
107
-
108
- @objc_method
109
- def locationManager_didUpdateLocations_(self, manager: Any, locations: Any) -> None:
110
- del manager
111
- try:
112
- last = locations.lastObject
113
- if last is None:
114
- return
115
- coord = last.coordinate
116
- self._finish((float(coord.latitude), float(coord.longitude)))
117
- except Exception:
118
- self._finish(None)
119
-
120
- @objc_method
121
- def locationManager_didFailWithError_(self, manager: Any, error: Any) -> None:
122
- del manager, error
123
- self._finish(None)
124
-
125
- @objc_method
126
- def locationManagerDidChangeAuthorization_(self, manager: Any) -> None:
127
- try:
128
- status = int(manager.authorizationStatus)
129
- except Exception:
130
- status = 0
131
- if status in (3, 4): # AuthorizedAlways, AuthorizedWhenInUse
132
- try:
133
- manager.startUpdatingLocation()
134
- except Exception:
135
- diagnostics.swallowed(
136
- "location._ios_get._PNLocationDelegate.locationManagerDidChangeAuthorization_"
137
- )
138
-
139
- delegate = _PNLocationDelegate.new()
140
- delegate._callback = on_result
141
- _pending_delegates[id(delegate)] = delegate
142
-
143
- manager = CLLocationManager.alloc().init()
144
- manager.setDelegate_(delegate)
145
- delegate._manager = manager
146
- try:
147
- manager.requestWhenInUseAuthorization()
148
- except Exception:
149
- diagnostics.swallowed("location._ios_get")
150
- try:
151
- manager.startUpdatingLocation()
152
- except Exception:
153
- diagnostics.swallowed("location._ios_get")
154
- except Exception:
155
- on_result(None)
156
-
157
-
158
- # ======================================================================
159
- # Android implementation: LocationListener
160
- # ======================================================================
161
-
162
-
163
- def _android_get(on_result: Callable[[Optional[Coords]], None]) -> None:
164
- try:
165
- from java import dynamic_proxy, jclass
166
-
167
- from ..utils import get_android_context
168
-
169
- ctx = get_android_context()
170
- Context = jclass("android.content.Context")
171
- lm = ctx.getSystemService(Context.LOCATION_SERVICE)
57
+ @staticmethod
58
+ async def get_current_fix(**options: Any) -> Optional[Dict[str, float]]:
59
+ """Like ``get_current`` but returns the full fix dict.
172
60
 
173
- # Try the most recent known fix first; it's instant and avoids
174
- # the GPS warm-up delay.
61
+ Keys: ``latitude``, ``longitude``, and when the platform reports
62
+ them ``accuracy`` (meters), ``altitude`` (meters), ``speed``
63
+ (m/s), ``heading`` (degrees), ``timestamp`` (Unix seconds).
64
+ """
175
65
  try:
176
- for provider in ("gps", "network", "passive"):
177
- loc = lm.getLastKnownLocation(provider)
178
- if loc is not None:
179
- on_result((float(loc.getLatitude()), float(loc.getLongitude())))
180
- return
66
+ result = await native_module("Location").call_async("get_current", **options)
181
67
  except Exception:
182
- diagnostics.swallowed("location._android_get")
183
-
184
- LocationListener = jclass("android.location.LocationListener")
185
- delivered = [False]
186
-
187
- def _finish(coords: Optional[Coords], listener: Any) -> None:
188
- if delivered[0]:
189
- return
190
- delivered[0] = True
191
- try:
192
- lm.removeUpdates(listener)
193
- except Exception:
194
- diagnostics.swallowed("location._android_get._finish")
195
- _pending_delegates.pop(id(listener), None)
196
- try:
197
- on_result(coords)
198
- except Exception:
199
- diagnostics.swallowed("location._android_get._finish")
200
-
201
- class _PNLocationListener(dynamic_proxy(LocationListener)): # type: ignore[misc]
202
- def onLocationChanged(self, loc: Any) -> None:
203
- try:
204
- coords = (float(loc.getLatitude()), float(loc.getLongitude()))
205
- except Exception:
206
- coords = None
207
- _finish(coords, self)
208
-
209
- def onStatusChanged(self, provider: Any, status: int, extras: Any) -> None:
210
- del provider, status, extras
211
-
212
- def onProviderEnabled(self, provider: Any) -> None:
213
- del provider
214
-
215
- def onProviderDisabled(self, provider: Any) -> None:
216
- del provider
217
- _finish(None, self)
218
-
219
- listener = _PNLocationListener()
220
- _pending_delegates[id(listener)] = listener
68
+ return None
69
+ if not isinstance(result, dict):
70
+ return None
221
71
  try:
222
- lm.requestSingleUpdate("gps", listener, None)
223
- except Exception:
224
- try:
225
- lm.requestLocationUpdates("network", 1000, 0.0, listener)
226
- except Exception:
227
- _finish(None, listener)
228
- except Exception:
229
- on_result(None)
72
+ fix: Dict[str, float] = {k: float(v) for k, v in result.items() if v is not None}
73
+ fix["latitude"]
74
+ fix["longitude"]
75
+ except (KeyError, TypeError, ValueError):
76
+ return None
77
+ return fix
@@ -3,10 +3,9 @@
3
3
  [`NetInfo`][pythonnative.NetInfo] reports whether the device is online
4
4
  and over what kind of connection. ``fetch`` returns a snapshot dict;
5
5
  ``add_listener`` (and the [`use_net_info`][pythonnative.use_net_info]
6
- hook) deliver live updates: while at least one listener is subscribed,
7
- a lightweight background watcher polls the platform's connectivity
8
- state and dispatches a fresh snapshot whenever it changes. Native
9
- hosts may also push changes directly through
6
+ hook) deliver live updates pushed by the native ``NetInfo`` module
7
+ (``NWPathMonitor`` on iOS, ``ConnectivityManager.NetworkCallback`` on
8
+ Android) as ``change`` events. Off device, tests push snapshots through
10
9
  [`dispatch_net_info`][pythonnative.native_modules.net_info.dispatch_net_info].
11
10
 
12
11
  A snapshot looks like::
@@ -19,12 +18,11 @@ A snapshot looks like::
19
18
 
20
19
  from __future__ import annotations
21
20
 
22
- import threading
23
- from typing import Callable, Dict, List, Optional
21
+ from typing import Any, Callable, Dict, List
24
22
 
25
23
  from .. import diagnostics
26
24
  from ..hooks import use_effect, use_state
27
- from ..utils import IS_ANDROID, IS_IOS
25
+ from .registry import native_module, on_event
28
26
 
29
27
  NetInfoState = Dict[str, object]
30
28
 
@@ -35,11 +33,6 @@ _last_state: NetInfoState = {
35
33
  "is_internet_reachable": True,
36
34
  }
37
35
 
38
- # Background watcher driving live updates while listeners exist.
39
- _WATCH_INTERVAL_S = 2.0
40
- _watcher_lock = threading.Lock()
41
- _watcher_stop: Optional[threading.Event] = None
42
-
43
36
 
44
37
  class NetInfo:
45
38
  """Network connectivity interface."""
@@ -48,66 +41,34 @@ class NetInfo:
48
41
  def fetch() -> NetInfoState:
49
42
  """Return a fresh snapshot of connectivity state."""
50
43
  global _last_state
51
- if IS_ANDROID:
52
- _last_state = _android_state()
53
- elif IS_IOS:
54
- _last_state = _ios_state()
44
+ try:
45
+ snapshot = native_module("NetInfo").call("fetch")
46
+ except Exception:
47
+ snapshot = None
48
+ if isinstance(snapshot, dict):
49
+ _last_state = _normalize(snapshot)
55
50
  return dict(_last_state)
56
51
 
57
52
  @staticmethod
58
53
  def add_listener(callback: Callable[[NetInfoState], None]) -> Callable[[], None]:
59
- """Subscribe to connectivity changes; returns an unsubscribe fn.
60
-
61
- The first subscription starts a background watcher that polls
62
- the platform connectivity state every couple of seconds and
63
- dispatches a snapshot on change; the watcher stops when the
64
- last listener unsubscribes.
65
- """
54
+ """Subscribe to connectivity changes; returns an unsubscribe fn."""
66
55
  _listeners.append(callback)
67
- _ensure_watcher()
68
56
 
69
57
  def _unsubscribe() -> None:
70
58
  try:
71
59
  _listeners.remove(callback)
72
60
  except ValueError:
73
61
  pass
74
- if not _listeners:
75
- _stop_watcher()
76
62
 
77
63
  return _unsubscribe
78
64
 
79
65
 
80
- def _ensure_watcher() -> None:
81
- """Start the connectivity watcher if a platform backend exists."""
82
- global _watcher_stop
83
- if not (IS_ANDROID or IS_IOS):
84
- return
85
- with _watcher_lock:
86
- if _watcher_stop is not None:
87
- return
88
- stop = threading.Event()
89
- _watcher_stop = stop
90
-
91
- def _watch() -> None:
92
- previous = dict(_last_state)
93
- while not stop.wait(_WATCH_INTERVAL_S):
94
- try:
95
- current = _android_state() if IS_ANDROID else _ios_state()
96
- except Exception:
97
- continue
98
- if current != previous:
99
- previous = dict(current)
100
- dispatch_net_info(current)
101
-
102
- threading.Thread(target=_watch, daemon=True, name="pn-netinfo").start()
103
-
104
-
105
- def _stop_watcher() -> None:
106
- global _watcher_stop
107
- with _watcher_lock:
108
- if _watcher_stop is not None:
109
- _watcher_stop.set()
110
- _watcher_stop = None
66
+ def _normalize(snapshot: Dict[str, Any]) -> NetInfoState:
67
+ return {
68
+ "is_connected": bool(snapshot.get("is_connected", False)),
69
+ "type": str(snapshot.get("type", "unknown")),
70
+ "is_internet_reachable": bool(snapshot.get("is_internet_reachable", False)),
71
+ }
111
72
 
112
73
 
113
74
  def dispatch_net_info(state: NetInfoState) -> None:
@@ -121,25 +82,18 @@ def dispatch_net_info(state: NetInfoState) -> None:
121
82
  diagnostics.swallowed("net_info.dispatch_net_info")
122
83
 
123
84
 
124
- def dispatch_android_change() -> None:
125
- """Re-read Android connectivity and notify listeners on change.
126
-
127
- Called by the template's ``MainActivity`` from the
128
- ``ConnectivityManager.NetworkCallback`` it registers via
129
- ``registerDefaultNetworkCallback`` whenever the default network
130
- appears, drops, or changes capabilities. The callback lives in
131
- Kotlin because Chaquopy's ``dynamic_proxy`` implements interfaces
132
- only and ``NetworkCallback`` is a class. Re-reading the snapshot
133
- here keeps the transport-type mapping in one place, and identical
134
- consecutive snapshots are dropped so paired ``onAvailable`` /
135
- ``onCapabilitiesChanged`` callbacks don't double-notify.
136
- """
137
- state = _android_state()
85
+ def _on_native_change(payload: Any) -> None:
86
+ if not isinstance(payload, dict):
87
+ return
88
+ state = _normalize(payload)
138
89
  if state == _last_state:
139
90
  return
140
91
  dispatch_net_info(state)
141
92
 
142
93
 
94
+ on_event("NetInfo", "change", _on_native_change)
95
+
96
+
143
97
  def use_net_info() -> NetInfoState:
144
98
  """Subscribe a component to [`NetInfo`][pythonnative.NetInfo].
145
99
 
@@ -159,67 +113,3 @@ def use_net_info() -> NetInfoState:
159
113
 
160
114
  use_effect(_subscribe, [])
161
115
  return state
162
-
163
-
164
- # ======================================================================
165
- # iOS: SCNetworkReachability
166
- # ======================================================================
167
-
168
-
169
- def _ios_state() -> NetInfoState:
170
- try:
171
- from ctypes import CDLL, byref, c_uint32, c_void_p, util
172
-
173
- sc = CDLL(util.find_library("SystemConfiguration"))
174
- sc.SCNetworkReachabilityCreateWithName.restype = c_void_p
175
- ref = sc.SCNetworkReachabilityCreateWithName(None, b"8.8.8.8")
176
- if not ref:
177
- return dict(_last_state)
178
- flags = c_uint32(0)
179
- ok = sc.SCNetworkReachabilityGetFlags(c_void_p(ref), byref(flags))
180
- if not ok:
181
- return {"is_connected": False, "type": "none", "is_internet_reachable": False}
182
- reachable = bool(flags.value & 0x2) # kSCNetworkReachabilityFlagsReachable
183
- is_wwan = bool(flags.value & (1 << 18)) # kSCNetworkReachabilityFlagsIsWWAN
184
- return {
185
- "is_connected": reachable,
186
- "type": ("cellular" if is_wwan else "wifi") if reachable else "none",
187
- "is_internet_reachable": reachable,
188
- }
189
- except Exception:
190
- return dict(_last_state)
191
-
192
-
193
- # ======================================================================
194
- # Android: ConnectivityManager
195
- # ======================================================================
196
-
197
-
198
- def _android_state() -> NetInfoState:
199
- try:
200
- from java import jclass
201
-
202
- from ..utils import get_android_context
203
-
204
- ctx = get_android_context()
205
- Context = jclass("android.content.Context")
206
- manager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE)
207
- network = manager.getActiveNetwork()
208
- if network is None:
209
- return {"is_connected": False, "type": "none", "is_internet_reachable": False}
210
- caps = manager.getNetworkCapabilities(network)
211
- if caps is None:
212
- return {"is_connected": False, "type": "none", "is_internet_reachable": False}
213
- NetworkCapabilities = jclass("android.net.NetworkCapabilities")
214
- if caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI):
215
- kind = "wifi"
216
- elif caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR):
217
- kind = "cellular"
218
- elif caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET):
219
- kind = "ethernet"
220
- else:
221
- kind = "unknown"
222
- reachable = bool(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))
223
- return {"is_connected": True, "type": kind, "is_internet_reachable": reachable}
224
- except Exception:
225
- return dict(_last_state)