pythonnative 0.35.0__py3-none-any.whl → 0.37.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. pythonnative/__init__.py +6 -4
  2. pythonnative/_ios_log.py +1 -1
  3. pythonnative/alerts.py +34 -88
  4. pythonnative/bootstrap.py +90 -0
  5. pythonnative/bridge/__init__.py +306 -0
  6. pythonnative/bridge/android.py +78 -0
  7. pythonnative/bridge/codec.py +164 -0
  8. pythonnative/bridge/fake.py +236 -0
  9. pythonnative/bridge/ios.py +138 -0
  10. pythonnative/cli/pn.py +24 -6
  11. pythonnative/components/layout.py +65 -3
  12. pythonnative/gestures.py +13 -10
  13. pythonnative/hooks.py +41 -26
  14. pythonnative/hosts/__init__.py +13 -36
  15. pythonnative/hosts/native.py +348 -0
  16. pythonnative/images.py +6 -5
  17. pythonnative/mutations.py +21 -0
  18. pythonnative/native_modules/__init__.py +30 -29
  19. pythonnative/native_modules/app_state.py +10 -6
  20. pythonnative/native_modules/battery.py +20 -84
  21. pythonnative/native_modules/biometrics.py +11 -124
  22. pythonnative/native_modules/camera.py +15 -291
  23. pythonnative/native_modules/clipboard.py +14 -109
  24. pythonnative/native_modules/desktop.py +322 -0
  25. pythonnative/native_modules/file_system.py +15 -21
  26. pythonnative/native_modules/haptics.py +19 -129
  27. pythonnative/native_modules/linking.py +24 -137
  28. pythonnative/native_modules/location.py +28 -180
  29. pythonnative/native_modules/net_info.py +25 -135
  30. pythonnative/native_modules/notifications.py +36 -257
  31. pythonnative/native_modules/permissions.py +20 -184
  32. pythonnative/native_modules/registry.py +513 -0
  33. pythonnative/native_modules/secure_store.py +18 -155
  34. pythonnative/native_modules/share.py +8 -113
  35. pythonnative/native_views/__init__.py +67 -86
  36. pythonnative/native_views/bridge_backend.py +264 -0
  37. pythonnative/platform.py +19 -15
  38. pythonnative/platform_metrics.py +3 -3
  39. pythonnative/project/android.py +0 -3
  40. pythonnative/project/builder.py +38 -16
  41. pythonnative/project/config.py +12 -2
  42. pythonnative/project/plugins.py +366 -0
  43. pythonnative/py.typed +0 -0
  44. pythonnative/runtime.py +35 -203
  45. pythonnative/sdk/__init__.py +48 -33
  46. pythonnative/sdk/_components.py +57 -76
  47. pythonnative/storage.py +18 -243
  48. pythonnative/templates/android_template/app/build.gradle +6 -9
  49. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
  50. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
  51. pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
  52. pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
  53. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
  54. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
  55. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
  56. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
  57. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
  58. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
  59. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
  60. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
  61. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
  62. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
  63. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
  64. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
  65. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
  66. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
  67. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
  68. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
  69. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
  70. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
  71. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
  72. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
  73. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
  74. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
  75. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
  76. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
  77. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
  78. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
  79. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
  80. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
  81. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
  82. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
  83. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
  84. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
  85. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
  86. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
  87. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
  88. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
  89. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
  90. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
  91. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
  92. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
  93. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
  94. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
  95. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
  96. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
  97. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
  98. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
  99. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
  100. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
  101. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
  102. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
  103. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
  104. pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
  105. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
  106. pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
  107. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
  108. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
  109. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
  110. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
  111. pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
  112. pythonnative/templates/android_template/settings.gradle +1 -0
  113. pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
  114. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
  115. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
  116. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
  117. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
  118. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
  119. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
  120. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
  121. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
  122. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
  123. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
  124. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
  125. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
  126. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
  127. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
  128. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
  129. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
  130. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
  131. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
  132. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
  133. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
  134. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
  135. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
  136. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
  137. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
  138. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
  139. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
  140. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
  141. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
  142. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
  143. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
  144. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
  145. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
  146. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
  147. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
  148. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
  149. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
  150. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
  151. pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
  152. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
  153. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
  154. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
  155. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
  156. pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
  157. pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
  158. pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
  159. pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
  160. pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
  161. pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
  162. pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
  163. pythonnative/utils.py +4 -82
  164. pythonnative/virtual_rows.py +3 -3
  165. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
  166. pythonnative-0.37.0.dist-info/RECORD +263 -0
  167. pythonnative/hosts/android.py +0 -269
  168. pythonnative/hosts/ios.py +0 -404
  169. pythonnative/native_views/android.py +0 -3702
  170. pythonnative/native_views/ios.py +0 -5253
  171. pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
  172. pythonnative-0.35.0.dist-info/RECORD +0 -155
  173. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
  174. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
  175. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
  176. {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,348 @@
1
+ """On-device screen host, shared by iOS and Android.
2
+
3
+ One [`NativeScreenHost`][pythonnative.hosts.native.NativeScreenHost]
4
+ exists per native screen (``UIViewController`` / ``Fragment``). The
5
+ native side never holds Python objects: screens are addressed by an
6
+ integer id it assigns, lifecycle arrives through
7
+ ``callback("host", screen_id, event, payload)`` (routed here by
8
+ [`dispatch_host_event`][pythonnative.hosts.native.dispatch_host_event]),
9
+ and everything the host needs from the platform goes out through the
10
+ ``Host`` native module (``attach_root``, ``push``, ``pop``, ...). See
11
+ ``docs/concepts/bridge.md`` for the payloads.
12
+
13
+ Because native reports viewport size, safe-area insets, keyboard
14
+ height, and color scheme *with* each lifecycle event, this module
15
+ never queries the platform; it only publishes what it is told.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import threading
22
+ from typing import Any, Dict, Optional, Sequence, Tuple
23
+
24
+ from .. import appearance, diagnostics, platform_metrics
25
+ from ..bridge import codec
26
+ from ..native_modules.registry import native_module
27
+ from .base import ScreenHost, flush_hosts, log_pn
28
+
29
+ __all__ = ["NativeScreenHost", "dispatch_host_event", "host_for_screen", "live_hosts"]
30
+
31
+ _HOSTS: Dict[int, "NativeScreenHost"] = {}
32
+ _SCHEDULED: Dict[int, ScreenHost] = {}
33
+ _flush_pending = False
34
+
35
+
36
+ def host_for_screen(screen_id: int) -> Optional["NativeScreenHost"]:
37
+ """Return the host registered for ``screen_id`` (``None`` if unknown)."""
38
+ return _HOSTS.get(int(screen_id))
39
+
40
+
41
+ def live_hosts() -> Sequence["NativeScreenHost"]:
42
+ """Every host currently registered, in creation order."""
43
+ return list(_HOSTS.values())
44
+
45
+
46
+ def _host_module() -> Any:
47
+ return native_module("Host")
48
+
49
+
50
+ # ======================================================================
51
+ # Deferred renders
52
+ # ======================================================================
53
+
54
+
55
+ def _flush_scheduled() -> None:
56
+ global _flush_pending
57
+ _flush_pending = False
58
+ hosts = list(_SCHEDULED.values())
59
+ _SCHEDULED.clear()
60
+ if hosts:
61
+ log_pn(f"render_scheduler: flushing {len(hosts)} host(s)")
62
+ flush_hosts(hosts)
63
+
64
+
65
+ def _request_flush() -> None:
66
+ """Queue one main-thread flush of every scheduled host."""
67
+ global _flush_pending
68
+ if _flush_pending:
69
+ return
70
+ _flush_pending = True
71
+ from ..bridge import post_to_main
72
+
73
+ post_to_main(_flush_scheduled)
74
+
75
+
76
+ # ======================================================================
77
+ # Payload helpers
78
+ # ======================================================================
79
+
80
+
81
+ def _publish_metrics(payload: Any) -> Tuple[float, float]:
82
+ """Publish viewport, insets, keyboard, and color scheme from a host payload.
83
+
84
+ Returns the ``(width, height)`` it found (``0, 0`` when absent).
85
+ """
86
+ if not isinstance(payload, dict):
87
+ return (0.0, 0.0)
88
+ width = float(payload.get("width") or 0.0)
89
+ height = float(payload.get("height") or 0.0)
90
+ insets = payload.get("insets")
91
+ if isinstance(insets, dict):
92
+ platform_metrics.set_safe_area_insets(
93
+ float(insets.get("top") or 0.0),
94
+ float(insets.get("left") or 0.0),
95
+ float(insets.get("bottom") or 0.0),
96
+ float(insets.get("right") or 0.0),
97
+ )
98
+ if "keyboard_height" in payload:
99
+ platform_metrics.set_keyboard_height(float(payload.get("keyboard_height") or 0.0))
100
+ scheme = payload.get("color_scheme")
101
+ if scheme in ("light", "dark"):
102
+ appearance.set_system_color_scheme(str(scheme))
103
+ if width > 0 and height > 0:
104
+ platform_metrics.set_window_dimensions(width, height)
105
+ return (width, height)
106
+
107
+
108
+ # ======================================================================
109
+ # Host
110
+ # ======================================================================
111
+
112
+
113
+ class NativeScreenHost(ScreenHost):
114
+ """Screen host addressed by an integer ``screen_id`` on the bridge."""
115
+
116
+ def __init__(self, screen_id: int, component_path: str, component: Any) -> None:
117
+ super().__init__(int(screen_id), component_path, component)
118
+ self.screen_id = int(screen_id)
119
+ self._pending_viewport: Optional[Tuple[float, float]] = None
120
+ _HOSTS[self.screen_id] = self
121
+
122
+ # -- lifecycle ------------------------------------------------------
123
+
124
+ def on_destroy(self) -> None:
125
+ """Forget the screen id, then tear down the tree."""
126
+ _HOSTS.pop(self.screen_id, None)
127
+ _SCHEDULED.pop(id(self), None)
128
+ super().on_destroy()
129
+
130
+ def apply_metrics(self, payload: Any) -> None:
131
+ """Publish the metrics carried by a host event and resize the viewport."""
132
+ width, height = _publish_metrics(payload)
133
+ if width > 0 and height > 0:
134
+ self._pending_viewport = (width, height)
135
+ self.set_viewport_size(width, height)
136
+
137
+ # -- platform primitives -------------------------------------------
138
+
139
+ def _initial_viewport_size(self) -> Optional[Tuple[float, float]]:
140
+ if self._pending_viewport is not None:
141
+ return self._pending_viewport
142
+ dims = platform_metrics.get_window_dimensions()
143
+ if dims.width > 0 and dims.height > 0:
144
+ return (dims.width, dims.height)
145
+ return None
146
+
147
+ def _schedule_render_async(self) -> bool:
148
+ if self._render_scheduled:
149
+ return True
150
+ self._render_scheduled = True
151
+ _SCHEDULED[id(self)] = self
152
+ try:
153
+ _request_flush()
154
+ except Exception as exc:
155
+ self._render_scheduled = False
156
+ _SCHEDULED.pop(id(self), None)
157
+ log_pn(f"request_render: bridge defer failed ({exc!r}); rendering synchronously")
158
+ return False
159
+ return True
160
+
161
+ def _attach_root(self, native_view: Any) -> None:
162
+ tag = getattr(native_view, "tag", None)
163
+ if tag is None:
164
+ return
165
+ # Native answers with the viewport the root now occupies, which
166
+ # is the earliest exact size available on iOS.
167
+ result = _host_module().call("attach_root", screen=self.screen_id, tag=int(tag))
168
+ if isinstance(result, dict):
169
+ self.apply_metrics(result)
170
+
171
+ def _detach_root(self, native_view: Any) -> None:
172
+ tag = getattr(native_view, "tag", None)
173
+ if tag is None:
174
+ return
175
+ try:
176
+ _host_module().call("detach_root", screen=self.screen_id, tag=int(tag))
177
+ except Exception:
178
+ diagnostics.swallowed("hosts.native.detach_root")
179
+
180
+ def _native_push(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
181
+ _host_module().call(
182
+ "push",
183
+ screen=self.screen_id,
184
+ path=component_path,
185
+ args=json.dumps(args) if args else None,
186
+ options=codec.to_jsonable(options or {}),
187
+ )
188
+
189
+ def _native_pop(self, count: int) -> None:
190
+ _host_module().call("pop", screen=self.screen_id, count=int(count))
191
+
192
+ def _native_pop_to_root(self) -> None:
193
+ _host_module().call("pop_to_root", screen=self.screen_id)
194
+
195
+ def _native_replace(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
196
+ _host_module().call(
197
+ "replace",
198
+ screen=self.screen_id,
199
+ path=component_path,
200
+ args=json.dumps(args) if args else None,
201
+ options=codec.to_jsonable(options or {}),
202
+ )
203
+
204
+ def _native_reset(self, component_path: str, screens: Sequence[Tuple[Dict[str, Any], Dict[str, Any]]]) -> None:
205
+ _host_module().call(
206
+ "reset",
207
+ screen=self.screen_id,
208
+ path=component_path,
209
+ screens=[
210
+ {
211
+ "path": component_path,
212
+ "args": json.dumps(args) if args else None,
213
+ "options": codec.to_jsonable(options or {}),
214
+ }
215
+ for args, options in screens
216
+ ],
217
+ )
218
+
219
+ def _native_set_options(self, options: Dict[str, Any]) -> None:
220
+ _host_module().call("set_options", screen=self.screen_id, options=codec.to_jsonable(options or {}))
221
+
222
+
223
+ # ======================================================================
224
+ # Inbound host events
225
+ # ======================================================================
226
+
227
+ _create_lock = threading.Lock()
228
+
229
+
230
+ def dispatch_host_event(screen_id: int, event: str, payload: Any) -> Optional[str]:
231
+ """Route ``callback("host", screen_id, event, payload)`` to a host.
232
+
233
+ Events (payloads are what ``PNViewController`` and
234
+ ``PNScreenFragment`` send):
235
+
236
+ - ``create`` ``{"path", "args", "dev_root", "restored_state",
237
+ ...metrics}``: import the component, create the host, and mount.
238
+ ``path`` is the dotted component path (``None`` means the app
239
+ entry module), ``args`` a JSON string, ``dev_root`` the hot-reload
240
+ overlay directory for debug builds. Returns ``{"root": tag}``.
241
+ - ``start`` / ``resume`` / ``pause`` / ``stop`` / ``destroy``:
242
+ lifecycle. ``resume`` and ``layout`` carry metrics.
243
+ - ``layout`` ``{width, height, insets, keyboard_height, color_scheme}``.
244
+ - ``appearance`` ``{"color_scheme"}``.
245
+ - ``back_pressed``: returns ``"true"`` when a handler consumed it.
246
+ - ``hot_reload_tick``: poll the reload manifest; returns ``"true"``
247
+ when a reload ran.
248
+ - ``save_state`` / ``restore_state``: instance-state hooks.
249
+ - ``flush``: run deferred renders now.
250
+
251
+ Returns a JSON string for request-style events, else ``None``.
252
+ """
253
+ screen_id = int(screen_id)
254
+ if event == "create":
255
+ return _create(screen_id, payload if isinstance(payload, dict) else {})
256
+ if event == "flush":
257
+ _flush_scheduled()
258
+ return None
259
+ if event == "appearance":
260
+ _publish_metrics(payload)
261
+ return None
262
+ host = _HOSTS.get(screen_id)
263
+ if host is None:
264
+ log_pn(f"dispatch_host_event: no host for screen={screen_id} event={event!r}")
265
+ return None
266
+ if event == "layout":
267
+ host.apply_metrics(payload)
268
+ host.on_layout()
269
+ return None
270
+ if event == "resume":
271
+ host.apply_metrics(payload)
272
+ host.on_resume()
273
+ return None
274
+ if event == "back_pressed":
275
+ return "true" if host.on_back_pressed() else "false"
276
+ if event in ("hot_reload_tick", "tick"):
277
+ return "true" if host.hot_reload_tick() else "false"
278
+ if event == "save_state":
279
+ host.on_save_instance_state()
280
+ return None
281
+ if event == "restore_state":
282
+ host.on_restore_instance_state()
283
+ return None
284
+ if event == "destroy":
285
+ host.on_destroy()
286
+ return None
287
+ handler = getattr(host, f"on_{event}", None)
288
+ if handler is None:
289
+ log_pn(f"dispatch_host_event: unknown event {event!r}")
290
+ return None
291
+ handler()
292
+ return None
293
+
294
+
295
+ def _create(screen_id: int, payload: Dict[str, Any]) -> Optional[str]:
296
+ from . import import_component
297
+
298
+ component_path = str(payload.get("path") or payload.get("component") or _entry_module())
299
+ with _create_lock:
300
+ existing = _HOSTS.get(screen_id)
301
+ if existing is not None:
302
+ existing.apply_metrics(payload)
303
+ existing.on_create()
304
+ return _root_json(existing)
305
+ component = import_component(component_path)
306
+ host = NativeScreenHost(screen_id, component_path, component)
307
+ args = payload.get("args")
308
+ if args:
309
+ host.set_args(args)
310
+ dev_root = payload.get("dev_root")
311
+ if dev_root:
312
+ from .. import hot_reload
313
+
314
+ host.enable_hot_reload(hot_reload.manifest_path_for(str(dev_root)), str(dev_root))
315
+ if not (payload.get("width") and payload.get("height")):
316
+ # ``create`` is sent before the first layout pass; ask native for
317
+ # its best guess so the first commit lays out at the right size.
318
+ try:
319
+ viewport = _host_module().call("viewport", screen=screen_id)
320
+ except Exception:
321
+ viewport = None
322
+ if isinstance(viewport, dict):
323
+ payload = {**viewport, **payload}
324
+ _publish_metrics(payload)
325
+ width = float(payload.get("width") or 0.0)
326
+ height = float(payload.get("height") or 0.0)
327
+ if width > 0 and height > 0:
328
+ host._pending_viewport = (width, height)
329
+ host.on_create()
330
+ return _root_json(host)
331
+
332
+
333
+ def _entry_module() -> str:
334
+ import os
335
+
336
+ return os.environ.get("PN_ENTRY_MODULE") or "app.main"
337
+
338
+
339
+ def _root_json(host: ScreenHost) -> str:
340
+ tag = getattr(host.root_native_view, "tag", None)
341
+ return codec.dumps({"root": None if tag is None else int(tag)})
342
+
343
+
344
+ def _reset_for_tests() -> None:
345
+ global _flush_pending
346
+ _HOSTS.clear()
347
+ _SCHEDULED.clear()
348
+ _flush_pending = False
pythonnative/images.py CHANGED
@@ -38,13 +38,14 @@ def _default_cache_dir() -> str:
38
38
  backups). Desktop: a per-user directory under the system temp dir.
39
39
  """
40
40
  try:
41
- from .utils import IS_ANDROID
41
+ from .utils import IS_ANDROID, IS_IOS
42
42
 
43
- if IS_ANDROID:
44
- from .utils import get_android_context
43
+ if IS_ANDROID or IS_IOS:
44
+ from .native_modules.registry import native_module
45
45
 
46
- base = str(get_android_context().getCacheDir().getAbsolutePath())
47
- return os.path.join(base, "pn_images")
46
+ info = native_module("Device").call("info")
47
+ if isinstance(info, dict) and info.get("cache_dir"):
48
+ return os.path.join(str(info["cache_dir"]), "pn_images")
48
49
  except Exception:
49
50
  pass
50
51
  home = os.path.expanduser("~")
pythonnative/mutations.py CHANGED
@@ -64,6 +64,11 @@ class UpdateOp:
64
64
 
65
65
  Removed props are signaled with a value of ``None``, matching the
66
66
  pre-existing handler contract.
67
+
68
+ Attributes:
69
+ tag: Unique integer identity of the target view.
70
+ changed_props: Mapping of modified prop names to their new values.
71
+ Removed props appear with a value of ``None``.
67
72
  """
68
73
 
69
74
  tag: int
@@ -78,6 +83,12 @@ class InsertOp:
78
83
  attached to the parent at a different position, it is moved rather
79
84
  than duplicated. ``index`` is clamped by handlers to the current
80
85
  child count.
86
+
87
+ Attributes:
88
+ parent_tag: Integer tag of the container view.
89
+ child_tag: Integer tag of the child view to insert or move.
90
+ index: Zero-based insertion index, clamped by handlers to the
91
+ current child count.
81
92
  """
82
93
 
83
94
  parent_tag: int
@@ -92,6 +103,9 @@ class DestroyOp:
92
103
  The registry drops its tag record and calls the handler's
93
104
  ``destroy`` hook so platform resources (listeners, timers, image
94
105
  loads) can be released eagerly instead of waiting for GC.
106
+
107
+ Attributes:
108
+ tag: Unique integer identity of the view to destroy.
95
109
  """
96
110
 
97
111
  tag: int
@@ -103,6 +117,13 @@ class SetFrameOp:
103
117
 
104
118
  Coordinates are points relative to the parent's content origin,
105
119
  exactly as computed by the layout engine.
120
+
121
+ Attributes:
122
+ tag: Unique integer identity of the target view.
123
+ x: Horizontal origin offset in points relative to the parent.
124
+ y: Vertical origin offset in points relative to the parent.
125
+ width: View width in points.
126
+ height: View height in points.
106
127
  """
107
128
 
108
129
  tag: int
@@ -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