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.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/cli/pn.py +24 -6
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/mutations.py +21 -0
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.35.0.dist-info/RECORD +0 -155
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.35.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {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 .
|
|
43
|
+
if IS_ANDROID or IS_IOS:
|
|
44
|
+
from .native_modules.registry import native_module
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
"
|
|
99
|
-
"
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
|