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
@@ -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)
@@ -1,8 +1,9 @@
1
- """Cross-platform local notifications and remote push registration.
1
+ """Local notifications and remote push registration.
2
2
 
3
- Provides coroutines for requesting permission and scheduling /
4
- cancelling local push notifications. Uses Android's
5
- ``NotificationManager`` or iOS's ``UNUserNotificationCenter``.
3
+ Coroutines for requesting permission and scheduling / cancelling local
4
+ notifications, backed by the native ``Notifications`` module
5
+ (``UNUserNotificationCenter`` on iOS, ``NotificationManager`` on
6
+ Android).
6
7
 
7
8
  On iOS you must ``await Notifications.request_permission()`` before
8
9
  scheduling. On Android 13+ the ``POST_NOTIFICATIONS`` runtime
@@ -36,17 +37,9 @@ Example:
36
37
 
37
38
  from __future__ import annotations
38
39
 
39
- import asyncio
40
- from typing import Any, Dict, List, Optional
40
+ from typing import Any, Optional
41
41
 
42
- from .. import diagnostics
43
- from ..runtime import reject_future, resolve_future
44
- from ..utils import IS_ANDROID, IS_IOS
45
-
46
- # Delayed Android deliveries in flight, keyed by identifier, so
47
- # ``cancel`` can abort a notification that hasn't posted yet. (iOS
48
- # delivers delays natively through UNTimeIntervalNotificationTrigger.)
49
- _android_delayed: Dict[str, "asyncio.Task[Any]"] = {}
42
+ from .registry import NativeModuleError, native_module
50
43
 
51
44
 
52
45
  class Notifications:
@@ -58,17 +51,17 @@ class Notifications:
58
51
 
59
52
  On Android 12 and below the manifest declaration is sufficient
60
53
  and this returns ``True`` without prompting. On Android 13+
61
- (API 33) the ``POST_NOTIFICATIONS`` runtime permission is
62
- requested through [`Permissions`][pythonnative.Permissions],
63
- which shows the system prompt if the user hasn't decided yet.
54
+ (API 33) the ``POST_NOTIFICATIONS`` runtime permission prompt
55
+ is shown if the user hasn't decided yet.
64
56
 
65
57
  Returns:
66
58
  ``True`` if granted (or no prompt is needed), ``False``
67
59
  otherwise.
68
60
  """
69
- if IS_ANDROID:
70
- return await _android_request_permission()
71
- return await _ios_request_permission()
61
+ try:
62
+ return bool(await native_module("Notifications").call_async("request_permission"))
63
+ except Exception:
64
+ return False
72
65
 
73
66
  @staticmethod
74
67
  async def schedule(
@@ -89,42 +82,34 @@ class Notifications:
89
82
  identifier: Stable ID used by
90
83
  [`cancel`][pythonnative.native_modules.notifications.Notifications.cancel]
91
84
  to target this notification.
92
- **options: Reserved for future tuning (e.g., ``sound``,
93
- ``badge``, ``category``).
85
+ **options: Forwarded to the native module (``sound``,
86
+ ``badge``, ...). Unknown keys are ignored.
94
87
 
95
88
  Returns:
96
89
  ``True`` on success, ``False`` if the underlying native
97
90
  call failed or (on Android 13+) the user denied the
98
91
  notification permission.
99
92
  """
100
- del options
101
- if IS_ANDROID:
102
- # Android 13+ silently drops notifications posted without
103
- # the POST_NOTIFICATIONS grant, so prompt before posting.
104
- if not await _android_request_permission():
105
- return False
106
- if delay_seconds > 0:
107
- _cancel_android_delayed(identifier)
108
- _android_delayed[identifier] = asyncio.ensure_future(
109
- _android_schedule_later(title, body, delay_seconds, identifier)
110
- )
111
- return True
112
- return await asyncio.to_thread(_android_schedule, title, body, identifier)
113
- return await asyncio.to_thread(_ios_schedule, title, body, delay_seconds, identifier)
93
+ try:
94
+ result = await native_module("Notifications").call_async(
95
+ "schedule",
96
+ title=title,
97
+ body=body,
98
+ delay_seconds=float(delay_seconds),
99
+ identifier=identifier,
100
+ **options,
101
+ )
102
+ except Exception:
103
+ return False
104
+ return bool(result)
114
105
 
115
106
  @staticmethod
116
107
  async def cancel(identifier: str = "default") -> None:
117
- """Cancel a pending notification by its identifier.
118
-
119
- Args:
120
- identifier: The same string passed to
121
- [`schedule`][pythonnative.native_modules.notifications.Notifications.schedule].
122
- """
123
- if IS_ANDROID:
124
- _cancel_android_delayed(identifier)
125
- await asyncio.to_thread(_android_cancel, identifier)
126
- return
127
- await asyncio.to_thread(_ios_cancel, identifier)
108
+ """Cancel a pending notification by its identifier."""
109
+ try:
110
+ await native_module("Notifications").call_async("cancel", identifier=identifier)
111
+ except Exception:
112
+ pass
128
113
 
129
114
  @staticmethod
130
115
  async def get_device_token() -> Optional[str]:
@@ -144,214 +129,8 @@ class Notifications:
144
129
  RuntimeError: If APNs registration fails, with the native
145
130
  error description.
146
131
  """
147
- if not IS_IOS:
148
- return None
149
- return await _ios_get_device_token()
150
-
151
-
152
- # ======================================================================
153
- # Android implementation
154
- # ======================================================================
155
-
156
-
157
- async def _android_request_permission() -> bool:
158
- """Request ``POST_NOTIFICATIONS`` on Android 13+; older versions skip.
159
-
160
- Delegates to the shared
161
- [`Permissions`][pythonnative.Permissions] machinery, which routes
162
- through the activity's ``requestPermissions`` and resolves once
163
- ``onRequestPermissionsResult`` arrives. Below API 33 there's no
164
- runtime permission, so this resolves ``True`` immediately.
165
- """
166
- try:
167
- from java import jclass
168
-
169
- if int(jclass("android.os.Build$VERSION").SDK_INT) < 33:
170
- return True
171
- except Exception:
172
- return True
173
- from .permissions import GRANTED, Permissions
174
-
175
- return await Permissions.request("notifications") == GRANTED
176
-
177
-
178
- def _cancel_android_delayed(identifier: str) -> None:
179
- pending = _android_delayed.pop(identifier, None)
180
- if pending is not None and not pending.done():
181
- pending.cancel()
182
-
183
-
184
- async def _android_schedule_later(title: str, body: str, delay_seconds: float, identifier: str) -> None:
185
- """Post an Android notification after ``delay_seconds`` on the framework loop.
186
-
187
- The delay lives in the app process (matching the semantics of
188
- ``UNTimeIntervalNotificationTrigger`` closely enough for in-app
189
- reminders); notifications whose delay outlives the process need
190
- ``AlarmManager``, which is out of scope for the built-in module.
191
- """
192
- try:
193
- await asyncio.sleep(delay_seconds)
194
- await asyncio.to_thread(_android_schedule, title, body, identifier)
195
- finally:
196
- _android_delayed.pop(identifier, None)
197
-
198
-
199
- def _android_schedule(title: str, body: str, identifier: str) -> bool:
200
- try:
201
- from java import jclass
202
-
203
- from ..utils import get_android_context
204
-
205
- ctx = get_android_context()
206
- nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
207
- channel_id = "pn_default"
208
- NotificationChannel = jclass("android.app.NotificationChannel")
209
- channel = NotificationChannel(channel_id, "PythonNative", 3) # IMPORTANCE_DEFAULT
210
- nm.createNotificationChannel(channel)
211
-
212
- Builder = jclass("android.app.Notification$Builder")
213
- builder = Builder(ctx, channel_id)
214
- builder.setContentTitle(title)
215
- builder.setContentText(body)
216
- builder.setSmallIcon(jclass("android.R$drawable").ic_dialog_info)
217
- nm.notify(abs(hash(identifier)) % (2**31), builder.build())
218
- return True
219
- except Exception:
220
- return False
221
-
222
-
223
- def _android_cancel(identifier: str) -> None:
224
- try:
225
- from java import jclass
226
-
227
- from ..utils import get_android_context
228
-
229
- ctx = get_android_context()
230
- nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
231
- nm.cancel(abs(hash(identifier)) % (2**31))
232
- except Exception:
233
- diagnostics.swallowed("notifications._android_cancel")
234
-
235
-
236
- # ======================================================================
237
- # iOS implementation
238
- # ======================================================================
239
-
240
-
241
- async def _ios_request_permission() -> bool:
242
- loop = asyncio.get_running_loop()
243
- future: asyncio.Future[bool] = loop.create_future()
244
- try:
245
- from rubicon.objc import Block, ObjCClass
246
-
247
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
248
-
249
- # Build a block that mirrors the UNNotificationCenter signature
250
- # ``void(^)(BOOL granted, NSError* error)``.
251
- def _completion(granted: bool, _error: Any) -> None:
252
- resolve_future(future, bool(granted))
253
-
254
132
  try:
255
- block = Block(_completion, None, bool, object)
256
- except Exception:
257
- # If Block ctor signature changed across rubicon versions,
258
- # fall back to optimistic "granted" without polling.
259
- resolve_future(future, True)
260
- return await future
261
-
262
- center.requestAuthorizationWithOptions_completionHandler_(0x07, block)
263
- except Exception:
264
- resolve_future(future, False)
265
- return await future
266
-
267
-
268
- def _ios_schedule(title: str, body: str, delay_seconds: float, identifier: str) -> bool:
269
- try:
270
- from rubicon.objc import ObjCClass
271
-
272
- content = ObjCClass("UNMutableNotificationContent").alloc().init()
273
- content.setTitle_(title)
274
- content.setBody_(body)
275
-
276
- interval = float(delay_seconds) if delay_seconds > 0 else 1.0
277
- trigger = ObjCClass("UNTimeIntervalNotificationTrigger").triggerWithTimeInterval_repeats_(interval, False)
278
- request = ObjCClass("UNNotificationRequest").requestWithIdentifier_content_trigger_(
279
- identifier, content, trigger
280
- )
281
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
282
- center.addNotificationRequest_withCompletionHandler_(request, None)
283
- return True
284
- except Exception:
285
- return False
286
-
287
-
288
- def _ios_cancel(identifier: str) -> None:
289
- try:
290
- from rubicon.objc import ObjCClass
291
-
292
- center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
293
- NSArray = ObjCClass("NSArray")
294
- arr = NSArray.arrayWithObject_(identifier)
295
- center.removePendingNotificationRequestsWithIdentifiers_(arr)
296
- except Exception:
297
- diagnostics.swallowed("notifications._ios_cancel")
298
-
299
-
300
- # ----------------------------------------------------------------------
301
- # Remote notifications (APNs)
302
- # ----------------------------------------------------------------------
303
- #
304
- # The registration result arrives through UIApplicationDelegate
305
- # callbacks in the native template, which forward here via
306
- # dispatch_device_token / dispatch_device_token_error. The token is
307
- # cached so later get_device_token() calls resolve immediately.
308
-
309
- _device_token: Optional[str] = None
310
- _device_token_error: Optional[str] = None
311
- _token_waiters: List["asyncio.Future[str]"] = []
312
-
313
-
314
- async def _ios_get_device_token() -> str:
315
- if _device_token is not None:
316
- return _device_token
317
- if _device_token_error is not None:
318
- raise RuntimeError(f"APNs registration failed: {_device_token_error}")
319
-
320
- loop = asyncio.get_running_loop()
321
- future: asyncio.Future[str] = loop.create_future()
322
- _token_waiters.append(future)
323
-
324
- def _register() -> None:
325
- from rubicon.objc import ObjCClass
326
-
327
- ObjCClass("UIApplication").sharedApplication.registerForRemoteNotifications()
328
-
329
- try:
330
- # registerForRemoteNotifications must run on the main thread.
331
- from ..runtime import call_on_main_thread
332
-
333
- call_on_main_thread(_register)
334
- except Exception as exc:
335
- _token_waiters.remove(future)
336
- raise RuntimeError(f"Could not start APNs registration: {exc}") from exc
337
- return await future
338
-
339
-
340
- def dispatch_device_token(token: str) -> None:
341
- """Deliver the APNs device token from the native host."""
342
- global _device_token, _device_token_error
343
- _device_token = token
344
- _device_token_error = None
345
- waiters, _token_waiters[:] = list(_token_waiters), []
346
- for future in waiters:
347
- resolve_future(future, token)
348
-
349
-
350
- def dispatch_device_token_error(message: str) -> None:
351
- """Deliver an APNs registration failure from the native host."""
352
- global _device_token_error
353
- _device_token_error = message
354
- waiters, _token_waiters[:] = list(_token_waiters), []
355
- error = RuntimeError(f"APNs registration failed: {message}")
356
- for future in waiters:
357
- reject_future(future, error)
133
+ token = await native_module("Notifications").call_async("get_device_token")
134
+ except NativeModuleError as exc:
135
+ raise RuntimeError(f"APNs registration failed: {exc.message}") from exc
136
+ return str(token) if token else None