pythonnative 0.36.0__py3-none-any.whl → 0.38.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 (187) hide show
  1. pythonnative/__init__.py +14 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +24 -86
  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 +96 -10
  11. pythonnative/components/_base.py +31 -0
  12. pythonnative/components/controls.py +19 -16
  13. pythonnative/components/layout.py +70 -11
  14. pythonnative/components/lists.py +6 -6
  15. pythonnative/gestures.py +40 -16
  16. pythonnative/hooks.py +41 -26
  17. pythonnative/hosts/__init__.py +13 -36
  18. pythonnative/hosts/native.py +348 -0
  19. pythonnative/images.py +6 -5
  20. pythonnative/native_modules/__init__.py +49 -29
  21. pythonnative/native_modules/app_state.py +10 -6
  22. pythonnative/native_modules/battery.py +22 -87
  23. pythonnative/native_modules/biometrics.py +15 -130
  24. pythonnative/native_modules/camera.py +24 -296
  25. pythonnative/native_modules/clipboard.py +14 -111
  26. pythonnative/native_modules/desktop.py +322 -0
  27. pythonnative/native_modules/file_system.py +90 -187
  28. pythonnative/native_modules/haptics.py +29 -135
  29. pythonnative/native_modules/linking.py +29 -141
  30. pythonnative/native_modules/location.py +32 -186
  31. pythonnative/native_modules/net_info.py +22 -135
  32. pythonnative/native_modules/notifications.py +37 -265
  33. pythonnative/native_modules/permissions.py +78 -194
  34. pythonnative/native_modules/registry.py +513 -0
  35. pythonnative/native_modules/secure_store.py +23 -164
  36. pythonnative/native_modules/share.py +9 -114
  37. pythonnative/native_views/__init__.py +67 -86
  38. pythonnative/native_views/bridge_backend.py +264 -0
  39. pythonnative/navigation/__init__.py +2 -1
  40. pythonnative/navigation/hooks.py +46 -7
  41. pythonnative/navigation/state.py +30 -9
  42. pythonnative/platform.py +19 -15
  43. pythonnative/platform_metrics.py +3 -3
  44. pythonnative/project/android.py +23 -3
  45. pythonnative/project/builder.py +99 -48
  46. pythonnative/project/config.py +96 -26
  47. pythonnative/project/deps.py +703 -0
  48. pythonnative/project/doctor.py +53 -4
  49. pythonnative/project/plugins.py +366 -0
  50. pythonnative/project/runtime_assets.py +13 -14
  51. pythonnative/py.typed +0 -0
  52. pythonnative/runtime.py +35 -203
  53. pythonnative/sdk/__init__.py +48 -33
  54. pythonnative/sdk/_components.py +57 -76
  55. pythonnative/storage.py +18 -243
  56. pythonnative/style.py +94 -44
  57. pythonnative/templates/android_template/app/build.gradle +11 -11
  58. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  59. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  60. pythonnative/templates/android_template/build.gradle +7 -4
  61. pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties +1 -1
  62. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  102. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  103. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  105. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  106. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +99 -0
  107. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  108. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  109. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  110. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  111. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  112. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  113. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  114. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  115. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  116. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  117. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  118. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  119. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  120. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  121. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  122. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  123. pythonnative/templates/android_template/settings.gradle +1 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +266 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  157. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  158. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  159. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  160. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  161. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  162. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  163. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  164. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  165. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  166. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  167. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  168. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  169. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  170. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +41 -48
  171. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  172. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  173. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +82 -10
  174. pythonnative/utils.py +26 -109
  175. pythonnative/virtual_rows.py +13 -23
  176. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/METADATA +10 -12
  177. pythonnative-0.38.0.dist-info/RECORD +264 -0
  178. pythonnative/hosts/android.py +0 -269
  179. pythonnative/hosts/ios.py +0 -404
  180. pythonnative/native_views/android.py +0 -3702
  181. pythonnative/native_views/ios.py +0 -5253
  182. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  183. pythonnative-0.36.0.dist-info/RECORD +0 -155
  184. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/WHEEL +0 -0
  185. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/entry_points.txt +0 -0
  186. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/licenses/LICENSE +0 -0
  187. {pythonnative-0.36.0.dist-info → pythonnative-0.38.0.dist-info}/top_level.txt +0 -0
@@ -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
 
@@ -1,17 +1,20 @@
1
1
  """Battery level and charging state.
2
2
 
3
3
  [`Battery`][pythonnative.Battery] reports the current charge fraction
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
4
+ (``0.0`` to ``1.0``, or ``-1.0`` when the platform doesn't know) and
5
+ charging state, and lets you subscribe to changes. Both getters read a
6
+ value the OS already holds, so they are synchronous. The native
7
+ ``Battery`` module pushes a ``change`` event with ``{"level", "state"}``;
8
+ off device, tests drive the same path through
6
9
  [`dispatch_battery`][pythonnative.native_modules.battery.dispatch_battery].
7
10
  """
8
11
 
9
12
  from __future__ import annotations
10
13
 
11
- from typing import Callable, Dict, List
14
+ from typing import Any, Callable, Dict, List
12
15
 
13
16
  from .. import diagnostics
14
- from ..utils import IS_ANDROID, IS_IOS
17
+ from .registry import native_module, on_event
15
18
 
16
19
  BatteryState = str # "unknown" | "unplugged" | "charging" | "full"
17
20
 
@@ -19,25 +22,23 @@ _listeners: List[Callable[[Dict[str, object]], None]] = []
19
22
 
20
23
 
21
24
  class Battery:
22
- """Battery interface (synchronous getters + change listener)."""
25
+ """Battery interface (synchronous getters + change listener).
26
+
27
+ Raises:
28
+ NativeModuleError: If the native module fails.
29
+ """
23
30
 
24
31
  @staticmethod
25
32
  def get_level() -> float:
26
- """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
33
+ """Return the charge fraction in ``[0, 1]``, or ``-1.0`` when the platform can't report it."""
34
+ level = float(native_module("Battery").call("get_level"))
35
+ return level if 0.0 <= level <= 1.0 else -1.0
32
36
 
33
37
  @staticmethod
34
38
  def get_state() -> BatteryState:
35
39
  """Return ``"charging"`` / ``"full"`` / ``"unplugged"`` / ``"unknown"``."""
36
- if IS_IOS:
37
- return _ios_state()
38
- if IS_ANDROID:
39
- return _android_state()
40
- return "unknown"
40
+ state = str(native_module("Battery").call("get_state") or "unknown")
41
+ return state if state in ("unknown", "unplugged", "charging", "full") else "unknown"
41
42
 
42
43
  @staticmethod
43
44
  def add_listener(callback: Callable[[Dict[str, object]], None]) -> Callable[[], None]:
@@ -57,7 +58,7 @@ class Battery:
57
58
 
58
59
 
59
60
  def dispatch_battery(level: float, state: BatteryState) -> None:
60
- """Notify listeners of a battery change (called by the native host)."""
61
+ """Notify listeners of a battery change."""
61
62
  payload: Dict[str, object] = {"level": level, "state": state}
62
63
  for listener in list(_listeners):
63
64
  try:
@@ -66,75 +67,9 @@ def dispatch_battery(level: float, state: BatteryState) -> None:
66
67
  diagnostics.swallowed("battery.dispatch_battery")
67
68
 
68
69
 
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
70
+ def _on_native_change(payload: Any) -> None:
71
+ if isinstance(payload, dict):
72
+ dispatch_battery(float(payload.get("level", -1.0)), str(payload.get("state", "unknown")))
125
73
 
126
- from ..utils import get_android_context
127
74
 
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"
75
+ on_event("Battery", "change", _on_native_change)
@@ -2,11 +2,12 @@
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
- ``is_available`` is synchronous; ``authenticate`` is a coroutine that
8
- presents the system prompt and resolves to ``True`` on success or
9
- ``False`` on failure / cancellation.
8
+ ``is_available`` is synchronous (a capability lookup); ``authenticate``
9
+ is a coroutine that presents the system prompt and resolves to ``True``
10
+ on success or ``False`` when the user fails or cancels the prompt.
10
11
 
11
12
  Example:
12
13
  ```python
@@ -20,138 +21,22 @@ 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:
34
- """Biometric authentication interface."""
28
+ """Biometric authentication interface.
29
+
30
+ Raises:
31
+ NativeModuleError: If the native module fails.
32
+ """
35
33
 
36
34
  @staticmethod
37
35
  def is_available() -> bool:
38
- """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
36
+ """Return ``True`` when biometric auth can be attempted (enrolled hardware; ``False`` on desktop)."""
37
+ return bool(native_module("Biometrics").call("is_available"))
44
38
 
45
39
  @staticmethod
46
40
  async def authenticate(reason: str = "Authenticate") -> bool:
47
- """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
+ """Present the biometric prompt; resolve ``True`` on success, ``False`` on failure or cancel."""
42
+ return bool(await native_module("Biometrics").call_async("authenticate", reason=reason))