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,11 +1,21 @@
1
1
  """Native API modules for device capabilities.
2
2
 
3
- Provides cross-platform Python interfaces to common device APIs. Each
4
- module auto-detects the platform at import time and dispatches to the
5
- appropriate native APIs via Chaquopy (Android) or rubicon-objc (iOS).
6
- On a desktop machine without either runtime, modules fall back to safe
7
- defaults (in-memory buffers, ``"unknown"`` states, no-op feedback) so
8
- the same code stays runnable in the desktop mock and unit tests.
3
+ Cross-platform Python interfaces to common device APIs. Every module
4
+ here is a thin facade over a *native module*: a Swift class in
5
+ ``PythonNativeKit`` and a Kotlin class in the ``pythonnative`` Gradle
6
+ module registered under the same name (``"Camera"``, ``"Haptics"``,
7
+ ...). Facades reach them through
8
+ [`native_module`][pythonnative.native_modules.registry.native_module]
9
+ and the bridge described in ``docs/concepts/bridge.md``; there is no
10
+ Python-side Objective-C or JNI anywhere in this package.
11
+
12
+ On a desktop machine (``pn preview``, unit tests) the same names
13
+ resolve to plain Python implementations in
14
+ [`pythonnative.native_modules.desktop`][pythonnative.native_modules.desktop]
15
+ with safe defaults (in-memory buffers, ``"unknown"`` states, no-op
16
+ feedback), so the same code stays runnable off device. Third-party
17
+ packages ship their own native modules the same way; see
18
+ ``docs/guides/native-modules.md``.
9
19
 
10
20
  Hardware / media:
11
21
 
@@ -43,8 +53,6 @@ Reactive state (with hooks):
43
53
  [`use_net_info`][pythonnative.use_net_info]: connectivity.
44
54
  """
45
55
 
46
- from typing import Any, Sequence
47
-
48
56
  from .app_state import AppState, use_app_state
49
57
  from .battery import Battery
50
58
  from .biometrics import Biometrics
@@ -57,46 +65,39 @@ from .location import Location
57
65
  from .net_info import NetInfo, use_net_info
58
66
  from .notifications import Notifications
59
67
  from .permissions import Permissions
68
+ from .registry import (
69
+ BridgeModule,
70
+ NativeModule,
71
+ NativeModuleError,
72
+ PythonModule,
73
+ native_module,
74
+ register_python_module,
75
+ )
60
76
  from .secure_store import SecureStore
61
77
  from .share import Share
62
78
 
63
-
64
- def dispatch_activity_result(request_code: int, result_code: int, data: Any) -> bool:
65
- """Route an Android ``onActivityResult`` to whichever module is waiting on it.
66
-
67
- Called by the screen host. Returns ``True`` when a pending request
68
- consumed the result.
69
- """
70
- from .camera import deliver_android_activity_result
71
-
72
- return deliver_android_activity_result(request_code, result_code, data)
73
-
74
-
75
- def dispatch_permissions_result(request_code: int, permissions: Sequence[str], grant_results: Sequence[int]) -> bool:
76
- """Route an Android ``onRequestPermissionsResult`` to the pending ``Permissions.request``."""
77
- from .permissions import deliver_android_permission_result
78
-
79
- return deliver_android_permission_result(request_code, permissions, grant_results)
80
-
81
-
82
79
  __all__ = [
83
80
  "AppState",
84
81
  "Battery",
85
82
  "Biometrics",
83
+ "BridgeModule",
86
84
  "Camera",
87
85
  "Clipboard",
88
86
  "FileSystem",
89
87
  "Haptics",
90
88
  "Linking",
91
89
  "Location",
90
+ "NativeModule",
91
+ "NativeModuleError",
92
92
  "NetInfo",
93
93
  "Notifications",
94
94
  "Permissions",
95
+ "PythonModule",
95
96
  "SecureStore",
96
97
  "Share",
97
98
  "Vibration",
98
- "dispatch_activity_result",
99
- "dispatch_permissions_result",
99
+ "native_module",
100
+ "register_python_module",
100
101
  "use_app_state",
101
102
  "use_net_info",
102
103
  ]
@@ -2,10 +2,10 @@
2
2
 
3
3
  [`AppState`][pythonnative.AppState] exposes the current lifecycle phase
4
4
  (``"active"``, ``"inactive"``, or ``"background"``) and lets you
5
- subscribe to transitions. The native host (the iOS app delegate /
6
- Android ``Activity``) forwards lifecycle callbacks by calling
7
- [`dispatch_app_state`][pythonnative.native_modules.app_state.dispatch_app_state],
8
- so the same listener machinery works on every platform and in tests.
5
+ subscribe to transitions. The native ``AppState`` module pushes a
6
+ ``change`` event on every transition; off device, tests drive the same
7
+ path through
8
+ [`dispatch_app_state`][pythonnative.native_modules.app_state.dispatch_app_state].
9
9
 
10
10
  Prefer the [`use_app_state`][pythonnative.use_app_state] hook inside
11
11
  components; use the imperative API for non-UI code.
@@ -27,6 +27,7 @@ from typing import Callable, List
27
27
 
28
28
  from .. import diagnostics
29
29
  from ..hooks import use_effect, use_state
30
+ from .registry import on_event
30
31
 
31
32
  AppStateStatus = str # "active" | "inactive" | "background"
32
33
 
@@ -64,8 +65,8 @@ class AppState:
64
65
  def dispatch_app_state(state: AppStateStatus) -> None:
65
66
  """Update the current state and notify every listener.
66
67
 
67
- Called by the native host on lifecycle transitions. Unknown values
68
- are ignored so a misbehaving host can't push garbage into the tree.
68
+ Unknown values are ignored so a misbehaving host can't push garbage
69
+ into the tree.
69
70
  """
70
71
  global _current
71
72
  if state not in _VALID or state == _current:
@@ -78,6 +79,9 @@ def dispatch_app_state(state: AppStateStatus) -> None:
78
79
  diagnostics.swallowed("app_state.dispatch_app_state")
79
80
 
80
81
 
82
+ on_event("AppState", "change", lambda payload: dispatch_app_state(str(payload)))
83
+
84
+
81
85
  def use_app_state() -> AppStateStatus:
82
86
  """Subscribe a component to [`AppState`][pythonnative.AppState].
83
87
 
@@ -2,16 +2,18 @@
2
2
 
3
3
  [`Battery`][pythonnative.Battery] reports the current charge fraction
4
4
  (``0.0``–``1.0``, or ``-1.0`` when unknown) and charging state, and
5
- lets you subscribe to changes that the native host forwards via
5
+ lets you subscribe to changes. The native ``Battery`` module pushes a
6
+ ``change`` event with ``{"level", "state"}``; off device, tests drive
7
+ the same path through
6
8
  [`dispatch_battery`][pythonnative.native_modules.battery.dispatch_battery].
7
9
  """
8
10
 
9
11
  from __future__ import annotations
10
12
 
11
- from typing import Callable, Dict, List
13
+ from typing import Any, Callable, Dict, List
12
14
 
13
15
  from .. import diagnostics
14
- from ..utils import IS_ANDROID, IS_IOS
16
+ from .registry import native_module, on_event
15
17
 
16
18
  BatteryState = str # "unknown" | "unplugged" | "charging" | "full"
17
19
 
@@ -24,20 +26,20 @@ class Battery:
24
26
  @staticmethod
25
27
  def get_level() -> float:
26
28
  """Return the charge fraction in ``[0, 1]`` (``-1.0`` if unknown)."""
27
- if IS_IOS:
28
- return _ios_level()
29
- if IS_ANDROID:
30
- return _android_level()
31
- return -1.0
29
+ try:
30
+ level = float(native_module("Battery").call("get_level"))
31
+ except Exception:
32
+ return -1.0
33
+ return level if 0.0 <= level <= 1.0 else -1.0
32
34
 
33
35
  @staticmethod
34
36
  def get_state() -> BatteryState:
35
37
  """Return ``"charging"`` / ``"full"`` / ``"unplugged"`` / ``"unknown"``."""
36
- if IS_IOS:
37
- return _ios_state()
38
- if IS_ANDROID:
39
- return _android_state()
40
- return "unknown"
38
+ try:
39
+ state = str(native_module("Battery").call("get_state") or "unknown")
40
+ except Exception:
41
+ return "unknown"
42
+ return state if state in ("unknown", "unplugged", "charging", "full") else "unknown"
41
43
 
42
44
  @staticmethod
43
45
  def add_listener(callback: Callable[[Dict[str, object]], None]) -> Callable[[], None]:
@@ -57,7 +59,7 @@ class Battery:
57
59
 
58
60
 
59
61
  def dispatch_battery(level: float, state: BatteryState) -> None:
60
- """Notify listeners of a battery change (called by the native host)."""
62
+ """Notify listeners of a battery change."""
61
63
  payload: Dict[str, object] = {"level": level, "state": state}
62
64
  for listener in list(_listeners):
63
65
  try:
@@ -66,75 +68,9 @@ def dispatch_battery(level: float, state: BatteryState) -> None:
66
68
  diagnostics.swallowed("battery.dispatch_battery")
67
69
 
68
70
 
69
- # ======================================================================
70
- # iOS: UIDevice
71
- # ======================================================================
72
-
73
-
74
- def _ios_device() -> object:
75
- from rubicon.objc import ObjCClass
76
-
77
- device = ObjCClass("UIDevice").currentDevice
78
- try:
79
- device.setBatteryMonitoringEnabled_(True)
80
- except Exception:
81
- diagnostics.swallowed("battery._ios_device")
82
- return device
83
-
84
-
85
- def _ios_level() -> float:
86
- try:
87
- level = float(_ios_device().batteryLevel)
88
- return level if level >= 0 else -1.0
89
- except Exception:
90
- return -1.0
91
-
92
-
93
- def _ios_state() -> BatteryState:
94
- try:
95
- # UIDeviceBatteryState: 0 unknown, 1 unplugged, 2 charging, 3 full
96
- return {0: "unknown", 1: "unplugged", 2: "charging", 3: "full"}.get(int(_ios_device().batteryState), "unknown")
97
- except Exception:
98
- return "unknown"
99
-
100
-
101
- # ======================================================================
102
- # Android: BatteryManager
103
- # ======================================================================
104
-
105
-
106
- def _android_level() -> float:
107
- try:
108
- from java import jclass
109
-
110
- from ..utils import get_android_context
111
-
112
- ctx = get_android_context()
113
- Context = jclass("android.content.Context")
114
- bm = ctx.getSystemService(Context.BATTERY_SERVICE)
115
- BatteryManager = jclass("android.os.BatteryManager")
116
- pct = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
117
- return (pct / 100.0) if pct is not None and pct >= 0 else -1.0
118
- except Exception:
119
- return -1.0
120
-
121
-
122
- def _android_state() -> BatteryState:
123
- try:
124
- from java import jclass
71
+ def _on_native_change(payload: Any) -> None:
72
+ if isinstance(payload, dict):
73
+ dispatch_battery(float(payload.get("level", -1.0)), str(payload.get("state", "unknown")))
125
74
 
126
- from ..utils import get_android_context
127
75
 
128
- ctx = get_android_context()
129
- Context = jclass("android.content.Context")
130
- bm = ctx.getSystemService(Context.BATTERY_SERVICE)
131
- BatteryManager = jclass("android.os.BatteryManager")
132
- status = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
133
- return {
134
- BatteryManager.BATTERY_STATUS_CHARGING: "charging",
135
- BatteryManager.BATTERY_STATUS_FULL: "full",
136
- BatteryManager.BATTERY_STATUS_DISCHARGING: "unplugged",
137
- BatteryManager.BATTERY_STATUS_NOT_CHARGING: "unplugged",
138
- }.get(status, "unknown")
139
- except Exception:
140
- return "unknown"
76
+ on_event("Battery", "change", _on_native_change)
@@ -2,7 +2,8 @@
2
2
 
3
3
  [`Biometrics`][pythonnative.Biometrics] gates an action behind the
4
4
  device's biometric hardware via ``LAContext`` (iOS) and
5
- ``BiometricPrompt`` (Android).
5
+ ``BiometricPrompt`` (Android), both implemented in the native
6
+ ``Biometrics`` module.
6
7
 
7
8
  ``is_available`` is synchronous; ``authenticate`` is a coroutine that
8
9
  presents the system prompt and resolves to ``True`` on success or
@@ -20,14 +21,7 @@ Example:
20
21
 
21
22
  from __future__ import annotations
22
23
 
23
- import asyncio
24
- from typing import Any, Callable, Dict
25
-
26
- from .. import diagnostics
27
- from ..runtime import resolve_future
28
- from ..utils import IS_ANDROID, IS_IOS
29
-
30
- _pending: Dict[int, Any] = {}
24
+ from .registry import native_module
31
25
 
32
26
 
33
27
  class Biometrics:
@@ -36,122 +30,15 @@ class Biometrics:
36
30
  @staticmethod
37
31
  def is_available() -> bool:
38
32
  """Return ``True`` when biometric auth can be attempted."""
39
- if IS_IOS:
40
- return _ios_available()
41
- if IS_ANDROID:
42
- return _android_available()
43
- return False
33
+ try:
34
+ return bool(native_module("Biometrics").call("is_available"))
35
+ except Exception:
36
+ return False
44
37
 
45
38
  @staticmethod
46
39
  async def authenticate(reason: str = "Authenticate") -> bool:
47
40
  """Present the biometric prompt; resolve ``True`` on success."""
48
- loop = asyncio.get_running_loop()
49
- future: asyncio.Future[bool] = loop.create_future()
50
-
51
- def _done(ok: bool) -> None:
52
- resolve_future(future, ok)
53
-
54
- if IS_IOS:
55
- _ios_authenticate(reason, _done)
56
- elif IS_ANDROID:
57
- _android_authenticate(reason, _done)
58
- else:
59
- _done(False)
60
-
61
- return await future
62
-
63
-
64
- # ======================================================================
65
- # iOS: LAContext
66
- # ======================================================================
67
-
68
-
69
- def _ios_context() -> Any:
70
- from rubicon.objc import ObjCClass
71
-
72
- return ObjCClass("LAContext").alloc().init()
73
-
74
-
75
- def _ios_available() -> bool:
76
- try:
77
- # LAPolicyDeviceOwnerAuthenticationWithBiometrics == 1
78
- return bool(_ios_context().canEvaluatePolicy_error_(1, None))
79
- except Exception:
80
- return False
81
-
82
-
83
- def _ios_authenticate(reason: str, on_done: Callable[[bool], None]) -> None:
84
- try:
85
- from rubicon.objc import Block, ObjCClass
86
-
87
- context = _ios_context()
88
- token = id(context)
89
- _pending[token] = context
90
-
91
- def _reply(success: bool, error: Any) -> None:
92
- del error
93
- _pending.pop(token, None)
94
- try:
95
- on_done(bool(success))
96
- except Exception:
97
- diagnostics.swallowed("biometrics._ios_authenticate._reply")
98
-
99
- context.evaluatePolicy_localizedReason_reply_(1, reason, Block(_reply, None, bool, ObjCClass("NSError")))
100
- except Exception:
101
- on_done(False)
102
-
103
-
104
- # ======================================================================
105
- # Android: BiometricPrompt
106
- # ======================================================================
107
-
108
-
109
- def _android_available() -> bool:
110
- try:
111
- from java import jclass
112
-
113
- from ..utils import get_android_context
114
-
115
- BiometricManager = jclass("androidx.biometric.BiometricManager")
116
- manager = BiometricManager.from_(get_android_context())
117
- Authenticators = jclass("androidx.biometric.BiometricManager$Authenticators")
118
- result = manager.canAuthenticate(Authenticators.BIOMETRIC_WEAK)
119
- return result == BiometricManager.BIOMETRIC_SUCCESS
120
- except Exception:
121
- return False
122
-
123
-
124
- def _android_authenticate(reason: str, on_done: Callable[[bool], None]) -> None:
125
- try:
126
- from java import dynamic_proxy, jclass
127
-
128
- from ..utils import get_android_context
129
-
130
- ctx = get_android_context()
131
- FragmentActivity = jclass("androidx.fragment.app.FragmentActivity")
132
- if not FragmentActivity.isInstance(ctx):
133
- on_done(False)
134
- return
135
-
136
- BiometricPrompt = jclass("androidx.biometric.BiometricPrompt")
137
- ContextCompat = jclass("androidx.core.content.ContextCompat")
138
- executor = ContextCompat.getMainExecutor(ctx)
139
-
140
- class _Callback(dynamic_proxy(BiometricPrompt.AuthenticationCallback)): # type: ignore[misc]
141
- def onAuthenticationSucceeded(self, result: Any) -> None: # noqa: N802, ARG002
142
- on_done(True)
143
-
144
- def onAuthenticationError(self, code: int, message: Any) -> None: # noqa: N802, ARG002
145
- on_done(False)
146
-
147
- def onAuthenticationFailed(self) -> None: # noqa: N802
148
- # Fired on a non-matching attempt; the prompt stays up,
149
- # so don't resolve yet.
150
- pass
151
-
152
- prompt = BiometricPrompt(ctx, executor, _Callback())
153
- Builder = jclass("androidx.biometric.BiometricPrompt$PromptInfo$Builder")
154
- info = Builder().setTitle(reason).setNegativeButtonText("Cancel").build()
155
- prompt.authenticate(info)
156
- except Exception:
157
- on_done(False)
41
+ try:
42
+ return bool(await native_module("Biometrics").call_async("authenticate", reason=reason))
43
+ except Exception:
44
+ return False