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
pythonnative/__init__.py
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
PythonNative is a cross-platform toolkit that turns Python ``@component``
|
|
4
4
|
functions into real, native Android and iOS views. The component model
|
|
5
|
-
is React-like (function components plus hooks)
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
is React-like (function components plus hooks). Python owns the
|
|
6
|
+
component tree, reconciliation, and layout; each commit crosses once
|
|
7
|
+
into a native rendering core (Swift ``PythonNativeKit`` on iOS, the
|
|
8
|
+
Kotlin ``pythonnative`` module on Android) that owns every platform
|
|
9
|
+
view, gesture, animation, and device API. There is no JavaScript.
|
|
8
10
|
|
|
9
11
|
Key building blocks:
|
|
10
12
|
|
|
@@ -60,7 +62,7 @@ Example:
|
|
|
60
62
|
```
|
|
61
63
|
"""
|
|
62
64
|
|
|
63
|
-
__version__ = "0.
|
|
65
|
+
__version__ = "0.37.0"
|
|
64
66
|
|
|
65
67
|
from . import appearance, diagnostics, gestures, images, runtime, sdk
|
|
66
68
|
from .alerts import Alert
|
pythonnative/_ios_log.py
CHANGED
|
@@ -14,7 +14,7 @@ straight to fd 2 is a small, reliable fix: fd 2 *is* visible to
|
|
|
14
14
|
`simctl` (that is exactly how `NSLog` reaches the terminal), so
|
|
15
15
|
Python output lands next to the Swift logs with correct ordering.
|
|
16
16
|
|
|
17
|
-
This module is intentionally self-contained (no
|
|
17
|
+
This module is intentionally self-contained (no bridge or
|
|
18
18
|
platform-specific C bindings required), so it is safe to import
|
|
19
19
|
early during `pythonnative` package initialization.
|
|
20
20
|
"""
|
pythonnative/alerts.py
CHANGED
|
@@ -30,77 +30,42 @@ Example:
|
|
|
30
30
|
|
|
31
31
|
from __future__ import annotations
|
|
32
32
|
|
|
33
|
-
import asyncio
|
|
34
33
|
from typing import Any, Dict, List, Optional, Sequence
|
|
35
34
|
|
|
36
|
-
from .
|
|
37
|
-
from .runtime import resolve_future
|
|
35
|
+
from .native_modules.registry import native_module
|
|
38
36
|
|
|
39
37
|
# ======================================================================
|
|
40
38
|
# Internal dispatch helpers
|
|
41
39
|
# ======================================================================
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def
|
|
42
|
+
async def _present(
|
|
45
43
|
*,
|
|
46
44
|
title: str,
|
|
47
45
|
message: Optional[str],
|
|
48
46
|
buttons: List[Dict[str, Any]],
|
|
49
47
|
style: str,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"""Route an alert request to the active platform presenter.
|
|
48
|
+
) -> int:
|
|
49
|
+
"""Ask the native ``Alert`` module to present and await the chosen index.
|
|
53
50
|
|
|
54
51
|
``buttons`` is a list of ``{"label": str, "style":
|
|
55
|
-
"default"|"cancel"|"destructive"}`` dicts. The
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
"default"|"cancel"|"destructive"}`` dicts. The module resolves with
|
|
53
|
+
the index the user picked, or ``-1`` if the dialog was dismissed
|
|
54
|
+
without a selection. Off device the
|
|
55
|
+
[`DesktopAlert`][pythonnative.native_modules.desktop.DesktopAlert]
|
|
56
|
+
implementation records the call and answers from the queue set by
|
|
57
|
+
[`Alert.set_test_response`][pythonnative.alerts.Alert.set_test_response].
|
|
59
58
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
return
|
|
72
|
-
except Exception:
|
|
73
|
-
on_result(-1)
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
if Platform.is_android:
|
|
77
|
-
try:
|
|
78
|
-
from .native_views.android import _present_alert as _android_present_alert
|
|
79
|
-
|
|
80
|
-
_android_present_alert(
|
|
81
|
-
title=title,
|
|
82
|
-
message=message,
|
|
83
|
-
buttons=buttons,
|
|
84
|
-
style=style,
|
|
85
|
-
on_result=on_result,
|
|
86
|
-
)
|
|
87
|
-
return
|
|
88
|
-
except Exception:
|
|
89
|
-
on_result(-1)
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
# Test backend: record the call so unit tests can assert on it,
|
|
93
|
-
# then deliver the configured response.
|
|
94
|
-
Alert._test_log.append(
|
|
95
|
-
{
|
|
96
|
-
"title": title,
|
|
97
|
-
"message": message,
|
|
98
|
-
"buttons": list(buttons),
|
|
99
|
-
"style": style,
|
|
100
|
-
}
|
|
101
|
-
)
|
|
102
|
-
response = Alert._next_test_response()
|
|
103
|
-
on_result(response)
|
|
59
|
+
try:
|
|
60
|
+
result = await native_module("Alert").call_async(
|
|
61
|
+
"present", title=title, message=message, buttons=buttons, style=style
|
|
62
|
+
)
|
|
63
|
+
except Exception:
|
|
64
|
+
return -1
|
|
65
|
+
try:
|
|
66
|
+
return int(result)
|
|
67
|
+
except (TypeError, ValueError):
|
|
68
|
+
return -1
|
|
104
69
|
|
|
105
70
|
|
|
106
71
|
# ======================================================================
|
|
@@ -170,13 +135,14 @@ class Alert:
|
|
|
170
135
|
[`choose`][pythonnative.alerts.Alert.choose] and ``await``
|
|
171
136
|
the result.
|
|
172
137
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
138
|
+
try:
|
|
139
|
+
native_module("Alert").call(
|
|
140
|
+
"show", title=title, message=message, buttons=[{"label": button, "style": "default"}], style="alert"
|
|
141
|
+
)
|
|
142
|
+
except Exception:
|
|
143
|
+
from . import diagnostics
|
|
144
|
+
|
|
145
|
+
diagnostics.swallowed("alerts.Alert.show")
|
|
180
146
|
|
|
181
147
|
@staticmethod
|
|
182
148
|
async def confirm(
|
|
@@ -206,13 +172,7 @@ class Alert:
|
|
|
206
172
|
await save()
|
|
207
173
|
```
|
|
208
174
|
"""
|
|
209
|
-
|
|
210
|
-
future: asyncio.Future[bool] = loop.create_future()
|
|
211
|
-
|
|
212
|
-
def _on_result(index: int) -> None:
|
|
213
|
-
resolve_future(future, index == 1)
|
|
214
|
-
|
|
215
|
-
_dispatch_alert(
|
|
175
|
+
index = await _present(
|
|
216
176
|
title=title,
|
|
217
177
|
message=message,
|
|
218
178
|
buttons=[
|
|
@@ -220,9 +180,8 @@ class Alert:
|
|
|
220
180
|
{"label": confirm_label, "style": "default"},
|
|
221
181
|
],
|
|
222
182
|
style="alert",
|
|
223
|
-
on_result=_on_result,
|
|
224
183
|
)
|
|
225
|
-
return
|
|
184
|
+
return index == 1
|
|
226
185
|
|
|
227
186
|
@staticmethod
|
|
228
187
|
async def choose(
|
|
@@ -265,9 +224,6 @@ class Alert:
|
|
|
265
224
|
if not options:
|
|
266
225
|
raise ValueError("Alert.choose requires at least one option")
|
|
267
226
|
|
|
268
|
-
loop = asyncio.get_running_loop()
|
|
269
|
-
future: asyncio.Future[Optional[str]] = loop.create_future()
|
|
270
|
-
|
|
271
227
|
destructive = set(destructive_labels)
|
|
272
228
|
buttons: List[Dict[str, Any]] = [
|
|
273
229
|
{
|
|
@@ -279,20 +235,10 @@ class Alert:
|
|
|
279
235
|
if cancel_label is not None:
|
|
280
236
|
buttons.append({"label": cancel_label, "style": "cancel"})
|
|
281
237
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
resolve_future(future, None)
|
|
287
|
-
|
|
288
|
-
_dispatch_alert(
|
|
289
|
-
title=title,
|
|
290
|
-
message=message,
|
|
291
|
-
buttons=buttons,
|
|
292
|
-
style=style,
|
|
293
|
-
on_result=_on_result,
|
|
294
|
-
)
|
|
295
|
-
return await future
|
|
238
|
+
index = await _present(title=title, message=message, buttons=buttons, style=style)
|
|
239
|
+
if 0 <= index < len(options):
|
|
240
|
+
return options[index]
|
|
241
|
+
return None
|
|
296
242
|
|
|
297
243
|
|
|
298
244
|
__all__ = ["Alert"]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Entry point the native app templates run right after Python starts.
|
|
2
|
+
|
|
3
|
+
Both templates execute one line of Python once the interpreter is up:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import pythonnative.bootstrap; pythonnative.bootstrap.start()
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
[`start`][pythonnative.bootstrap.start] connects the two halves of the
|
|
10
|
+
bridge (installing the native -> Python callback on iOS), verifies the
|
|
11
|
+
protocol version, routes ``print()`` to the console on iOS, and warms
|
|
12
|
+
the asyncio runtime. From then on the native runtime drives everything
|
|
13
|
+
through ``callback("host", ...)``; see ``docs/concepts/bridge.md``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import traceback
|
|
21
|
+
from typing import Any, Dict
|
|
22
|
+
|
|
23
|
+
__all__ = ["start", "status"]
|
|
24
|
+
|
|
25
|
+
_started: Dict[str, Any] = {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def start(dev: bool = False, strict: bool = False) -> Dict[str, Any]:
|
|
29
|
+
"""Connect the bridge and prepare the runtime.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
dev: Enable dev mode (RedBox, validation warnings). Debug
|
|
33
|
+
templates pass ``True``; hot reload turns it on as well.
|
|
34
|
+
strict: Re-raise the failure after recording it. The templates
|
|
35
|
+
pass ``True`` so a broken bridge surfaces as a bootstrap
|
|
36
|
+
error screen with the full traceback.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A status dict (``{"protocol": 1, "platform": "ios"}``) that the
|
|
40
|
+
template logs. Unless ``strict`` is set this never raises:
|
|
41
|
+
failures are printed and reported in the dict under
|
|
42
|
+
``"error"``.
|
|
43
|
+
"""
|
|
44
|
+
global _started
|
|
45
|
+
if _started and "error" not in _started:
|
|
46
|
+
return dict(_started)
|
|
47
|
+
status_: Dict[str, Any] = {"platform": None, "protocol": None}
|
|
48
|
+
try:
|
|
49
|
+
from .utils import IS_ANDROID, IS_IOS
|
|
50
|
+
|
|
51
|
+
status_["platform"] = "ios" if IS_IOS else "android" if IS_ANDROID else "off-device"
|
|
52
|
+
if IS_IOS:
|
|
53
|
+
try:
|
|
54
|
+
from . import _ios_log
|
|
55
|
+
|
|
56
|
+
_ios_log.install()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
from . import bridge
|
|
60
|
+
|
|
61
|
+
status_["protocol"] = bridge.handshake()
|
|
62
|
+
if dev or os.environ.get("PN_DEV") in ("1", "true"):
|
|
63
|
+
from . import diagnostics
|
|
64
|
+
|
|
65
|
+
diagnostics.set_dev_mode(True)
|
|
66
|
+
# Create the guest loop now so the first pump request has a
|
|
67
|
+
# loop to drive and effects can schedule work during mount.
|
|
68
|
+
from .runtime import get_loop
|
|
69
|
+
|
|
70
|
+
get_loop()
|
|
71
|
+
# Import the view backend eagerly: on a slow device the first
|
|
72
|
+
# commit shouldn't also pay for importing the reconciler.
|
|
73
|
+
from .native_views import get_registry
|
|
74
|
+
|
|
75
|
+
get_registry()
|
|
76
|
+
except Exception as exc:
|
|
77
|
+
status_["error"] = f"{type(exc).__name__}: {exc}"
|
|
78
|
+
print(f"[pn.bootstrap] start failed: {exc!r}", file=sys.stderr)
|
|
79
|
+
traceback.print_exc()
|
|
80
|
+
_started = status_
|
|
81
|
+
if strict:
|
|
82
|
+
raise
|
|
83
|
+
return dict(status_)
|
|
84
|
+
_started = status_
|
|
85
|
+
return dict(status_)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def status() -> Dict[str, Any]:
|
|
89
|
+
"""Return the result of the last [`start`][pythonnative.bootstrap.start] (empty before it ran)."""
|
|
90
|
+
return dict(_started)
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""The native bridge: one channel between Python and Swift / Kotlin.
|
|
2
|
+
|
|
3
|
+
Everything that crosses into native code goes through a
|
|
4
|
+
[`Transport`][pythonnative.bridge.Transport] (``apply`` a transaction,
|
|
5
|
+
``measure`` a view, run a ``command``, drive an ``animate`` request, or
|
|
6
|
+
``call`` a native module), and everything native sends back arrives at
|
|
7
|
+
[`native_callback`][pythonnative.bridge.native_callback]. The protocol
|
|
8
|
+
is documented in ``docs/concepts/bridge.md``.
|
|
9
|
+
|
|
10
|
+
Off-device (tests, ``pn preview``) there is no transport; the desktop
|
|
11
|
+
registry renders with Tkinter and native modules fall back to their
|
|
12
|
+
Python implementations. Tests that want to exercise the bridge itself
|
|
13
|
+
install a [`FakeTransport`][pythonnative.bridge.fake.FakeTransport]
|
|
14
|
+
with [`set_transport`][pythonnative.bridge.set_transport].
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import threading
|
|
20
|
+
from collections import deque
|
|
21
|
+
from typing import Any, Callable, Deque, Dict, Optional, Protocol, Tuple
|
|
22
|
+
|
|
23
|
+
from . import codec
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"PROTOCOL_VERSION",
|
|
27
|
+
"Transport",
|
|
28
|
+
"get_transport",
|
|
29
|
+
"has_transport",
|
|
30
|
+
"handshake",
|
|
31
|
+
"native_callback",
|
|
32
|
+
"post_to_main",
|
|
33
|
+
"set_transport",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
PROTOCOL_VERSION = 1
|
|
37
|
+
"""Bridge protocol version this Python package speaks."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Transport(Protocol):
|
|
41
|
+
"""The Python -> native half of the bridge."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
|
|
45
|
+
def protocol_version(self) -> int:
|
|
46
|
+
"""Return the protocol version compiled into the native library."""
|
|
47
|
+
|
|
48
|
+
def apply(self, transaction_json: str) -> None:
|
|
49
|
+
"""Apply one serialized transaction (a JSON array of ops)."""
|
|
50
|
+
|
|
51
|
+
def measure(self, tag: int, max_width: float, max_height: float) -> Tuple[float, float]:
|
|
52
|
+
"""Return the intrinsic ``(width, height)`` of the view ``tag`` under the constraints."""
|
|
53
|
+
|
|
54
|
+
def command(self, tag: int, name: str, args_json: str) -> Optional[str]:
|
|
55
|
+
"""Run an imperative command on one view; returns its JSON result or ``None``."""
|
|
56
|
+
|
|
57
|
+
def animate(self, tag: int, request_json: str) -> Optional[str]:
|
|
58
|
+
"""Handle an animation request (``set`` / ``start`` / ``cancel``) for one view."""
|
|
59
|
+
|
|
60
|
+
def call(self, module: str, method: str, args_json: str) -> Optional[str]:
|
|
61
|
+
"""Call a native module method with a ``{"call_id", "args"}`` envelope."""
|
|
62
|
+
|
|
63
|
+
def set_callback(self, callback: Callable[[str, int, str, str], Optional[str]]) -> None:
|
|
64
|
+
"""Install ``callback`` as the native -> Python entry point."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ======================================================================
|
|
68
|
+
# Transport selection
|
|
69
|
+
# ======================================================================
|
|
70
|
+
|
|
71
|
+
_transport: Optional[Transport] = None
|
|
72
|
+
_transport_lock = threading.Lock()
|
|
73
|
+
_explicit = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _create_platform_transport() -> Optional[Transport]:
|
|
77
|
+
from ..utils import IS_ANDROID, IS_IOS
|
|
78
|
+
|
|
79
|
+
if IS_IOS:
|
|
80
|
+
from .ios import IOSTransport
|
|
81
|
+
|
|
82
|
+
transport: Transport = IOSTransport()
|
|
83
|
+
transport.set_callback(native_callback)
|
|
84
|
+
return transport
|
|
85
|
+
if IS_ANDROID:
|
|
86
|
+
from .android import AndroidTransport
|
|
87
|
+
|
|
88
|
+
return AndroidTransport()
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_transport() -> Transport:
|
|
93
|
+
"""Return the active transport, creating the platform one on first use.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
RuntimeError: Off-device, where no native runtime exists.
|
|
97
|
+
"""
|
|
98
|
+
global _transport
|
|
99
|
+
if _transport is not None:
|
|
100
|
+
return _transport
|
|
101
|
+
with _transport_lock:
|
|
102
|
+
if _transport is None:
|
|
103
|
+
created = _create_platform_transport()
|
|
104
|
+
if created is None:
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
"No native bridge is available on this platform (running off-device). "
|
|
107
|
+
"Use `pn preview` for the desktop renderer or install a FakeTransport in tests."
|
|
108
|
+
)
|
|
109
|
+
_transport = created
|
|
110
|
+
return _transport
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def has_transport() -> bool:
|
|
114
|
+
"""Whether a transport exists or can be created without raising."""
|
|
115
|
+
if _transport is not None:
|
|
116
|
+
return True
|
|
117
|
+
if _explicit:
|
|
118
|
+
return False
|
|
119
|
+
from ..utils import IS_ANDROID, IS_IOS
|
|
120
|
+
|
|
121
|
+
return bool(IS_IOS or IS_ANDROID)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def set_transport(transport: Optional[Transport]) -> None:
|
|
125
|
+
"""Install a transport explicitly (tests) or reset with ``None``."""
|
|
126
|
+
global _transport, _explicit
|
|
127
|
+
with _transport_lock:
|
|
128
|
+
_transport = transport
|
|
129
|
+
_explicit = transport is not None
|
|
130
|
+
if transport is not None:
|
|
131
|
+
transport.set_callback(native_callback)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def handshake() -> int:
|
|
135
|
+
"""Verify the native library speaks our protocol version.
|
|
136
|
+
|
|
137
|
+
Called by the native templates right after Python starts. Returns
|
|
138
|
+
the negotiated version.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: On a version mismatch, with a hint to rebuild.
|
|
142
|
+
"""
|
|
143
|
+
version = get_transport().protocol_version()
|
|
144
|
+
if version != PROTOCOL_VERSION:
|
|
145
|
+
raise RuntimeError(
|
|
146
|
+
f"Bridge protocol mismatch: the native runtime speaks v{version} but pythonnative "
|
|
147
|
+
f"expects v{PROTOCOL_VERSION}. Re-run 'pn build' so the staged template matches the "
|
|
148
|
+
"installed pythonnative package."
|
|
149
|
+
)
|
|
150
|
+
return version
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ======================================================================
|
|
154
|
+
# Main-queue posting
|
|
155
|
+
# ======================================================================
|
|
156
|
+
#
|
|
157
|
+
# The asyncio guest loop and ``call_on_main_thread`` need a way to run
|
|
158
|
+
# a callable on the platform main thread's next turn. Native provides
|
|
159
|
+
# it: ``Host.post()`` schedules ``callback("pump")``, which drains this
|
|
160
|
+
# queue. Keeping the queue in Python means one native crossing per
|
|
161
|
+
# batch of callables rather than one per callable.
|
|
162
|
+
|
|
163
|
+
_main_queue: Deque[Callable[[], None]] = deque()
|
|
164
|
+
_main_queue_lock = threading.Lock()
|
|
165
|
+
_pump_requested = False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def post_to_main(fn: Callable[[], None]) -> None:
|
|
169
|
+
"""Queue ``fn`` for the next main-thread turn (never runs inline)."""
|
|
170
|
+
global _pump_requested
|
|
171
|
+
with _main_queue_lock:
|
|
172
|
+
_main_queue.append(fn)
|
|
173
|
+
if _pump_requested:
|
|
174
|
+
return
|
|
175
|
+
_pump_requested = True
|
|
176
|
+
try:
|
|
177
|
+
get_transport().call("Host", "post", codec.dumps({"call_id": 0, "args": {}}))
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
with _main_queue_lock:
|
|
180
|
+
_pump_requested = False
|
|
181
|
+
print(f"[pn.bridge] Host.post failed; running {len(_main_queue)} queued callable(s) inline: {exc!r}")
|
|
182
|
+
_drain_main_queue()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _drain_main_queue() -> None:
|
|
186
|
+
global _pump_requested
|
|
187
|
+
while True:
|
|
188
|
+
with _main_queue_lock:
|
|
189
|
+
if not _main_queue:
|
|
190
|
+
_pump_requested = False
|
|
191
|
+
return
|
|
192
|
+
fn = _main_queue.popleft()
|
|
193
|
+
try:
|
|
194
|
+
fn()
|
|
195
|
+
except Exception as exc:
|
|
196
|
+
print(f"[pn.bridge] main-thread callable raised: {exc!r}")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ======================================================================
|
|
200
|
+
# native -> Python
|
|
201
|
+
# ======================================================================
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def native_callback(kind: str, tag: int, name: str, payload: str) -> Optional[str]:
|
|
205
|
+
"""Single entry point for every native -> Python message.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
kind: ``"event"``, ``"module"``, ``"host"``, ``"animation"``,
|
|
209
|
+
or ``"pump"``.
|
|
210
|
+
tag: View tag (events), screen id (host), otherwise ``0``.
|
|
211
|
+
name: Event name, module name, or host event.
|
|
212
|
+
payload: JSON text whose shape depends on ``kind``.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
A JSON string for request-style messages (a handler's return
|
|
216
|
+
value, ``"true"`` / ``"false"`` for ``back_pressed``), else
|
|
217
|
+
``None``. Never raises: failures are reported through
|
|
218
|
+
``diagnostics`` so nothing propagates into UIKit or the
|
|
219
|
+
Android looper.
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
if kind == "event":
|
|
223
|
+
return _on_event(int(tag), name, payload)
|
|
224
|
+
if kind == "module":
|
|
225
|
+
from ..native_modules.registry import dispatch_module_message
|
|
226
|
+
|
|
227
|
+
dispatch_module_message(name, codec.loads(payload) or {})
|
|
228
|
+
return None
|
|
229
|
+
if kind == "host":
|
|
230
|
+
from ..hosts.native import dispatch_host_event
|
|
231
|
+
|
|
232
|
+
return dispatch_host_event(int(tag), name, codec.loads(payload))
|
|
233
|
+
if kind == "animation":
|
|
234
|
+
from ..animated import native_animation_completed
|
|
235
|
+
|
|
236
|
+
data = codec.loads(payload) or {}
|
|
237
|
+
native_animation_completed(int(data.get("id", 0)), bool(data.get("finished", True)))
|
|
238
|
+
return None
|
|
239
|
+
if kind == "pump":
|
|
240
|
+
_drain_main_queue()
|
|
241
|
+
return None
|
|
242
|
+
print(f"[pn.bridge] unknown callback kind {kind!r}")
|
|
243
|
+
except Exception as exc:
|
|
244
|
+
from .. import diagnostics
|
|
245
|
+
|
|
246
|
+
if not diagnostics.report_error(exc, phase=f"bridge {kind}:{name}"):
|
|
247
|
+
import traceback
|
|
248
|
+
|
|
249
|
+
traceback.print_exc()
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _on_event(tag: int, name: str, payload: str) -> Optional[str]:
|
|
254
|
+
from ..events import get_event_registry
|
|
255
|
+
|
|
256
|
+
args = codec.loads(payload)
|
|
257
|
+
if args is None:
|
|
258
|
+
args = []
|
|
259
|
+
elif not isinstance(args, list):
|
|
260
|
+
args = [args]
|
|
261
|
+
callback = get_event_registry().get(tag, name)
|
|
262
|
+
if callback is None:
|
|
263
|
+
from ..native_views import get_registry
|
|
264
|
+
|
|
265
|
+
backend = get_registry()
|
|
266
|
+
internal = getattr(backend, "handle_internal_event", None)
|
|
267
|
+
if internal is not None:
|
|
268
|
+
result = internal(tag, name, args)
|
|
269
|
+
return None if result is None else codec.dumps(codec.to_jsonable(result))
|
|
270
|
+
return None
|
|
271
|
+
try:
|
|
272
|
+
result = callback(*args)
|
|
273
|
+
except Exception as exc:
|
|
274
|
+
from .. import diagnostics
|
|
275
|
+
|
|
276
|
+
if not diagnostics.report_error(exc, phase=f"event {name!r}"):
|
|
277
|
+
import traceback
|
|
278
|
+
|
|
279
|
+
traceback.print_exc()
|
|
280
|
+
return None
|
|
281
|
+
if result is None:
|
|
282
|
+
return None
|
|
283
|
+
try:
|
|
284
|
+
return codec.dumps(codec.to_jsonable(result))
|
|
285
|
+
except (TypeError, ValueError):
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _reset_for_tests() -> None:
|
|
290
|
+
"""Drop the transport and any queued main-thread work (test isolation)."""
|
|
291
|
+
global _transport, _explicit, _pump_requested
|
|
292
|
+
with _transport_lock:
|
|
293
|
+
_transport = None
|
|
294
|
+
_explicit = False
|
|
295
|
+
with _main_queue_lock:
|
|
296
|
+
_main_queue.clear()
|
|
297
|
+
_pump_requested = False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def transport_state() -> Dict[str, Any]:
|
|
301
|
+
"""Diagnostics snapshot (used by ``pn doctor`` and tests)."""
|
|
302
|
+
return {
|
|
303
|
+
"transport": None if _transport is None else _transport.name,
|
|
304
|
+
"protocol_version": PROTOCOL_VERSION,
|
|
305
|
+
"queued_main_callables": len(_main_queue),
|
|
306
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Android transport: the ``com.pythonnative.runtime.PNBridge`` class via Chaquopy.
|
|
2
|
+
|
|
3
|
+
``PNBridge`` is the only Java class Python touches. Its static methods
|
|
4
|
+
mirror the C entry points used on iOS (see ``docs/concepts/bridge.md``).
|
|
5
|
+
The reverse direction (native -> Python) is installed by the template's
|
|
6
|
+
``MainActivity``, which implements ``PythonHost`` by calling
|
|
7
|
+
[`pythonnative.bridge.native_callback`][pythonnative.bridge.native_callback]
|
|
8
|
+
through Chaquopy; this transport therefore has nothing to register.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Callable, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
__all__ = ["AndroidTransport"]
|
|
16
|
+
|
|
17
|
+
BRIDGE_CLASS = "com.pythonnative.runtime.PNBridge"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AndroidTransport:
|
|
21
|
+
"""Thin wrapper over the static ``PNBridge`` methods."""
|
|
22
|
+
|
|
23
|
+
name = "android"
|
|
24
|
+
|
|
25
|
+
def __init__(self, bridge_class: Any = None) -> None:
|
|
26
|
+
if bridge_class is None:
|
|
27
|
+
from java import jclass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
bridge_class = jclass(BRIDGE_CLASS)
|
|
31
|
+
except Exception as exc:
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
f"{BRIDGE_CLASS} is not on the classpath. Rebuild the app with 'pn run android' or "
|
|
34
|
+
"'pn build android' so the pythonnative Gradle module is included."
|
|
35
|
+
) from exc
|
|
36
|
+
self._bridge = bridge_class
|
|
37
|
+
|
|
38
|
+
def protocol_version(self) -> int:
|
|
39
|
+
"""Return the protocol version compiled into the native library."""
|
|
40
|
+
return int(self._bridge.protocolVersion())
|
|
41
|
+
|
|
42
|
+
def apply(self, transaction_json: str) -> None:
|
|
43
|
+
"""Apply one serialized transaction (a JSON array of ops)."""
|
|
44
|
+
self._bridge.apply(transaction_json)
|
|
45
|
+
|
|
46
|
+
def measure(self, tag: int, max_width: float, max_height: float) -> Tuple[float, float]:
|
|
47
|
+
"""Return the intrinsic ``(width, height)`` of the view ``tag`` under the constraints."""
|
|
48
|
+
packed = self._bridge.measure(int(tag), float(max_width), float(max_height))
|
|
49
|
+
if not packed:
|
|
50
|
+
return (0.0, 0.0)
|
|
51
|
+
text = str(packed)
|
|
52
|
+
w, _, h = text.partition(",")
|
|
53
|
+
try:
|
|
54
|
+
return (float(w), float(h))
|
|
55
|
+
except ValueError:
|
|
56
|
+
return (0.0, 0.0)
|
|
57
|
+
|
|
58
|
+
def command(self, tag: int, name: str, args_json: str) -> Optional[str]:
|
|
59
|
+
"""Run an imperative command on one view; returns its JSON result or ``None``."""
|
|
60
|
+
return _opt_str(self._bridge.command(int(tag), name, args_json))
|
|
61
|
+
|
|
62
|
+
def animate(self, tag: int, request_json: str) -> Optional[str]:
|
|
63
|
+
"""Handle an animation request (``set`` / ``start`` / ``cancel``) for one view."""
|
|
64
|
+
return _opt_str(self._bridge.animate(int(tag), request_json))
|
|
65
|
+
|
|
66
|
+
def call(self, module: str, method: str, args_json: str) -> Optional[str]:
|
|
67
|
+
"""Call a native module method with a ``{"call_id", "args"}`` envelope."""
|
|
68
|
+
return _opt_str(self._bridge.call(module, method, args_json))
|
|
69
|
+
|
|
70
|
+
def set_callback(self, callback: Callable[[str, int, str, str], Optional[str]]) -> None:
|
|
71
|
+
"""No-op: the Android template installs the host through ``PNBridge.setHost``."""
|
|
72
|
+
del callback
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _opt_str(value: Any) -> Optional[str]:
|
|
76
|
+
if value is None:
|
|
77
|
+
return None
|
|
78
|
+
return str(value)
|