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
pythonnative/hosts/ios.py DELETED
@@ -1,404 +0,0 @@
1
- """iOS screen host: one ``UIViewController`` per screen, driven by ``ViewController.swift``."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import sys
7
- import threading
8
- from typing import Any, Callable, Dict, Optional, Sequence, Tuple
9
-
10
- from ..utils import IS_IOS
11
- from .base import ScreenHost, flush_hosts, log_pn
12
-
13
- __all__ = ["IOSScreenHost", "drain_ios_scheduled_renders", "forward_lifecycle"]
14
-
15
- try:
16
- from rubicon.objc import SEL, ObjCClass, ObjCInstance, objc_method
17
-
18
- _rubicon_available = True
19
- import gc as _gc
20
-
21
- _gc.disable()
22
- except ImportError: # pragma: no cover - only on hosts without the [ios] extra
23
- _rubicon_available = False
24
-
25
- # Redirect Python's stdout/stderr through fd 2 so ``print()`` output is
26
- # visible via ``xcrun simctl launch --console-pty``. Runs before any user
27
- # module is imported so their top-level prints are captured too.
28
- if IS_IOS:
29
- try:
30
- from .. import _ios_log
31
-
32
- _ios_log.install()
33
- except Exception:
34
- pass
35
-
36
- _REGISTRY: Dict[int, ScreenHost] = {}
37
- _SCHEDULED: Dict[int, ScreenHost] = {}
38
- _timer_target: Any = None
39
- _native_scheduler: Any = None
40
-
41
-
42
- def _objc_addr(obj: Any) -> Optional[int]:
43
- """Pointer of an ``ObjCInstance`` as an int (``ptr`` varies by rubicon release)."""
44
- ptr = getattr(obj, "ptr", None)
45
- if ptr is None:
46
- return None
47
- if isinstance(ptr, (bytes, bytearray)):
48
- try:
49
- return int.from_bytes(ptr, byteorder=sys.byteorder, signed=False)
50
- except Exception:
51
- return None
52
- if isinstance(ptr, int):
53
- return ptr
54
- value = getattr(ptr, "value", None)
55
- if isinstance(value, int):
56
- return value
57
- try:
58
- return int(ptr)
59
- except Exception:
60
- return None
61
-
62
-
63
- def _publish_color_scheme() -> None:
64
- try:
65
- from .. import appearance
66
-
67
- UITraitCollection = ObjCClass("UITraitCollection")
68
- style_value = int(UITraitCollection.currentTraitCollection.userInterfaceStyle)
69
- appearance.set_system_color_scheme("dark" if style_value == 2 else "light")
70
- except Exception:
71
- pass
72
-
73
-
74
- def _flush_scheduled() -> None:
75
- hosts = list(_SCHEDULED.values())
76
- _SCHEDULED.clear()
77
- if hosts:
78
- log_pn(f"render_scheduler: flushing {len(hosts)} host(s)")
79
- flush_hosts(hosts)
80
-
81
-
82
- def drain_ios_scheduled_renders() -> None:
83
- """Entry point used by the iOS template to drain pending renders on the main thread."""
84
- _flush_scheduled()
85
-
86
-
87
- def _wake_native_drain() -> bool:
88
- """Ask the template's ``pn_schedule_render_drain`` to drain on the main thread."""
89
- global _native_scheduler
90
- try:
91
- if _native_scheduler is None:
92
- import ctypes
93
-
94
- scheduler = ctypes.CDLL(None).pn_schedule_render_drain
95
- scheduler.restype = None
96
- scheduler.argtypes = []
97
- _native_scheduler = scheduler
98
- _native_scheduler()
99
- return True
100
- except Exception as exc:
101
- log_pn(f"render_scheduler: native iOS wake failed: {exc!r}")
102
- return False
103
-
104
-
105
- def forward_lifecycle(native_addr: int, event: str) -> None:
106
- """Forward a Swift ``UIViewController`` lifecycle event to its host.
107
-
108
- Args:
109
- native_addr: Pointer of the view controller, as registered by
110
- ``create_screen``.
111
- event: Host method name (``"on_resume"``, ``"on_layout"``, ...).
112
- """
113
- try:
114
- key = int(native_addr)
115
- except Exception as exc:
116
- log_pn(f"forward_lifecycle: bad native_addr={native_addr!r}: {exc!r}")
117
- return
118
- host = _REGISTRY.get(key)
119
- if host is None:
120
- log_pn(f"forward_lifecycle: no host for event={event!r} addr={key}")
121
- return
122
- handler = getattr(host, event, None)
123
- if handler is None:
124
- log_pn(f"forward_lifecycle: host has no {event!r}")
125
- return
126
- try:
127
- handler()
128
- except Exception as exc:
129
- log_pn(f"forward_lifecycle: {event!r} raised: {exc!r}")
130
-
131
-
132
- if _rubicon_available and IS_IOS:
133
- NSObject = ObjCClass("NSObject")
134
-
135
- class _RenderTimerTarget(NSObject): # type: ignore[misc, valid-type]
136
- @objc_method
137
- def onRenderTimer_(self, timer: object) -> None:
138
- _flush_scheduled()
139
-
140
- def _timer_target_instance() -> Any:
141
- global _timer_target
142
- if _timer_target is None:
143
- target = _RenderTimerTarget.new()
144
- try:
145
- target.retain()
146
- except Exception:
147
- pass
148
- _timer_target = target
149
- return _timer_target
150
-
151
-
152
- class IOSScreenHost(ScreenHost):
153
- """Host owned by a ``ViewController``; pushes onto its ``UINavigationController``."""
154
-
155
- def __init__(self, native_instance: Any, component_path: str, component: Any) -> None:
156
- if isinstance(native_instance, int):
157
- try:
158
- native_instance = ObjCInstance(native_instance)
159
- except Exception:
160
- native_instance = None
161
- super().__init__(native_instance, component_path, component)
162
- if self.native_instance is not None:
163
- addr = _objc_addr(self.native_instance)
164
- if addr is not None:
165
- _REGISTRY[addr] = self
166
-
167
- # -- lifecycle ------------------------------------------------------
168
-
169
- def on_create(self) -> None:
170
- """Publish the system color scheme, then mount the root component."""
171
- _publish_color_scheme()
172
- super().on_create()
173
-
174
- def on_layout(self) -> None:
175
- """Sync the root view's frame and viewport size after ``viewDidLayoutSubviews``."""
176
- # ``viewDidLayoutSubviews``: safe-area insets are valid now.
177
- _publish_color_scheme()
178
- if self.root_native_view is not None:
179
- self._sync_root_frame(self.root_native_view)
180
- self._push_viewport_from_root(self.root_native_view)
181
-
182
- def on_resume(self) -> None:
183
- """Mark the screen focused and refresh the color scheme, root frame, and viewport size."""
184
- _publish_color_scheme()
185
- super().on_resume()
186
- if self.root_native_view is not None:
187
- self._sync_root_frame(self.root_native_view)
188
- self._push_viewport_from_root(self.root_native_view)
189
-
190
- def on_destroy(self) -> None:
191
- """Drop this host from the view-controller registry, then tear down the tree."""
192
- if self.native_instance is not None:
193
- addr = _objc_addr(self.native_instance)
194
- if addr is not None:
195
- _REGISTRY.pop(addr, None)
196
- super().on_destroy()
197
-
198
- # -- platform primitives -------------------------------------------
199
-
200
- def _initial_viewport_size(self) -> Optional[Tuple[float, float]]:
201
- try:
202
- if self.native_instance is not None:
203
- bounds = self.native_instance.view.bounds
204
- if bounds.size.width > 0 and bounds.size.height > 0:
205
- return (float(bounds.size.width), float(bounds.size.height))
206
- except Exception:
207
- pass
208
- try:
209
- bounds = ObjCClass("UIScreen").mainScreen.bounds
210
- return (float(bounds.size.width), float(bounds.size.height))
211
- except Exception:
212
- return None
213
-
214
- def _schedule_render_async(self) -> bool:
215
- if self._render_scheduled:
216
- return True
217
- self._render_scheduled = True
218
- _SCHEDULED[id(self)] = self
219
- if threading.current_thread() is not threading.main_thread():
220
- if not _wake_native_drain():
221
- log_pn("request_render: native iOS scheduler unavailable; render remains queued")
222
- return True
223
- # Main-queue GCD blocks run in the common run-loop modes, so
224
- # renders land during scrolls and synthesized test touches; a
225
- # default-mode NSTimer is starved for the whole gesture.
226
- from ..runtime import _ensure_libdispatch_loaded, _ios_dispatch_async
227
-
228
- if _ensure_libdispatch_loaded():
229
- _ios_dispatch_async(_flush_scheduled)
230
- return True
231
- try:
232
- NSTimer = ObjCClass("NSTimer")
233
- NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
234
- 0.0, _timer_target_instance(), SEL("onRenderTimer:"), None, False
235
- )
236
- return True
237
- except Exception as exc:
238
- self._render_scheduled = False
239
- _SCHEDULED.pop(id(self), None)
240
- log_pn(f"request_render: iOS defer failed ({exc!r}); rendering synchronously")
241
- return False
242
-
243
- def _view_controller_class(self) -> Any:
244
- try:
245
- return ObjCClass("ViewController")
246
- except Exception:
247
- pass
248
- try:
249
- bundle = ObjCClass("NSBundle").mainBundle
250
- module_name = bundle.objectForInfoDictionaryKey_("CFBundleName") or bundle.objectForInfoDictionaryKey_(
251
- "CFBundleExecutable"
252
- )
253
- if module_name:
254
- return ObjCClass(f"{module_name}.ViewController")
255
- except Exception:
256
- pass
257
- raise NameError("ViewController class not found; ensure the Swift class is ObjC-visible")
258
-
259
- def _make_view_controller(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> Any:
260
- vc = self._view_controller_class().alloc().init()
261
- try:
262
- vc.setValue_forKey_(component_path, "requestedScreenPath")
263
- if args:
264
- vc.setValue_forKey_(json.dumps(args), "requestedScreenArgsJSON")
265
- except Exception:
266
- pass
267
- title = options.get("title")
268
- if title is not None:
269
- try:
270
- vc.setTitle_(str(title))
271
- except Exception:
272
- pass
273
- return vc
274
-
275
- def _run_nav_op(self, op: Callable[[Any], None]) -> None:
276
- """Run ``op(nav)`` unless a transition is mid-flight.
277
-
278
- UIKit doesn't queue navigation calls: one issued during an
279
- animation is ignored or misapplied. Dropping it keeps the stack
280
- consistent; a retry lands on a settled stack.
281
- """
282
- nav = getattr(self.native_instance, "navigationController", None)
283
- if nav is None:
284
- raise RuntimeError("No UINavigationController; the template must embed the root in one")
285
- try:
286
- coord = nav.transitionCoordinator
287
- if callable(coord):
288
- coord = coord()
289
- if coord is not None:
290
- log_pn("_run_nav_op: transition in flight; dropping nav op")
291
- return
292
- except Exception:
293
- pass
294
- op(nav)
295
-
296
- def _native_push(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
297
- vc = self._make_view_controller(component_path, args, options)
298
- self._run_nav_op(lambda nav: nav.pushViewController_animated_(vc, True))
299
-
300
- def _native_pop(self, count: int) -> None:
301
- def op(nav: Any) -> None:
302
- if count <= 1:
303
- nav.popViewControllerAnimated_(True)
304
- return
305
- controllers = nav.viewControllers
306
- n = int(controllers.count)
307
- target_index = max(0, n - 1 - count)
308
- nav.popToViewController_animated_(controllers.objectAtIndex_(target_index), True)
309
-
310
- self._run_nav_op(op)
311
-
312
- def _native_pop_to_root(self) -> None:
313
- self._run_nav_op(lambda nav: nav.popToRootViewControllerAnimated_(True))
314
-
315
- def _native_replace(self, component_path: str, args: Dict[str, Any], options: Dict[str, Any]) -> None:
316
- vc = self._make_view_controller(component_path, args, options)
317
-
318
- def op(nav: Any) -> None:
319
- controllers = list(nav.viewControllers)
320
- controllers[-1] = vc
321
- nav.setViewControllers_animated_(controllers, True)
322
-
323
- self._run_nav_op(op)
324
-
325
- def _native_reset(self, component_path: str, screens: Sequence[Tuple[Dict[str, Any], Dict[str, Any]]]) -> None:
326
- vcs = [self._make_view_controller(component_path, args, options) for args, options in screens]
327
-
328
- def op(nav: Any) -> None:
329
- root = nav.viewControllers.objectAtIndex_(0)
330
- nav.setViewControllers_animated_([root, *vcs], True)
331
-
332
- self._run_nav_op(op)
333
-
334
- def _native_set_options(self, options: Dict[str, Any]) -> None:
335
- title = options.get("title")
336
- if title is None or self.native_instance is None:
337
- return
338
- try:
339
- self.native_instance.setTitle_(str(title))
340
- except Exception as exc:
341
- log_pn(f"set_screen_options: setTitle failed: {exc!r}")
342
-
343
- def _attach_root(self, native_view: Any) -> None:
344
- self.native_instance.view.addSubview_(native_view)
345
- # Frame-based layout for the root so the layout engine's frames
346
- # aren't fought by Auto Layout.
347
- try:
348
- native_view.setTranslatesAutoresizingMaskIntoConstraints_(True)
349
- native_view.setAutoresizingMask_(2 | 16) # FlexibleWidth | FlexibleHeight
350
- except Exception:
351
- pass
352
- self._sync_root_frame(native_view)
353
- self._push_viewport_from_root(native_view)
354
-
355
- def _detach_root(self, native_view: Any) -> None:
356
- try:
357
- native_view.removeFromSuperview()
358
- except Exception:
359
- pass
360
-
361
- # -- viewport plumbing ---------------------------------------------
362
-
363
- def _sync_root_frame(self, native_view: Any) -> None:
364
- """Position the root below the top safe area, full-bleed at the bottom.
365
-
366
- The bottom inset is published to ``platform_metrics`` instead of
367
- being subtracted, so a tab bar can reach the home indicator;
368
- content that needs it opts in via ``SafeAreaView``.
369
- """
370
- root_view = self.native_instance.view
371
- if root_view is None:
372
- return
373
- try:
374
- bounds, insets = root_view.bounds, root_view.safeAreaInsets
375
- top, left, right, bottom = float(insets.top), float(insets.left), float(insets.right), float(insets.bottom)
376
- w = max(0.0, float(bounds.size.width) - left - right)
377
- h = max(0.0, float(bounds.size.height) - top)
378
- try:
379
- from .. import platform_metrics
380
-
381
- platform_metrics.set_safe_area_insets(0.0, left, bottom, right)
382
- except Exception:
383
- pass
384
- if w > 0 and h > 0:
385
- native_view.setFrame_(((left, top), (w, h)))
386
- return
387
- except Exception as exc:
388
- log_pn(f"sync_root_frame: insets path failed: {exc!r}")
389
- try:
390
- bounds = root_view.bounds
391
- native_view.setFrame_(((0, 0), (float(bounds.size.width), float(bounds.size.height))))
392
- except Exception as exc:
393
- log_pn(f"sync_root_frame: bounds fallback failed: {exc!r}")
394
-
395
- def _push_viewport_from_root(self, native_view: Any) -> None:
396
- try:
397
- bounds = native_view.bounds
398
- w, h = float(bounds.size.width), float(bounds.size.height)
399
- if w <= 0 or h <= 0:
400
- screen = ObjCClass("UIScreen").mainScreen.bounds
401
- w, h = float(screen.size.width), float(screen.size.height)
402
- self.set_viewport_size(w, h)
403
- except Exception as exc:
404
- log_pn(f"push_viewport: failed: {exc!r}")