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
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""Cross-platform clipboard access.
|
|
2
2
|
|
|
3
3
|
[`Clipboard`][pythonnative.Clipboard] reads and writes the system
|
|
4
|
-
pasteboard
|
|
5
|
-
``ClipboardManager``
|
|
6
|
-
need for a coroutine.
|
|
4
|
+
pasteboard through the native ``Clipboard`` module (``UIPasteboard``
|
|
5
|
+
on iOS, ``ClipboardManager`` on Android). All methods are synchronous.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
mock target and unit tests instead of raising.
|
|
7
|
+
Off device the module is a process-local string buffer, which keeps it
|
|
8
|
+
usable in ``pn preview`` and unit tests.
|
|
11
9
|
|
|
12
10
|
Example:
|
|
13
11
|
```python
|
|
@@ -20,13 +18,8 @@ Example:
|
|
|
20
18
|
|
|
21
19
|
from __future__ import annotations
|
|
22
20
|
|
|
23
|
-
from typing import Optional
|
|
24
|
-
|
|
25
21
|
from .. import diagnostics
|
|
26
|
-
from
|
|
27
|
-
|
|
28
|
-
# Desktop fallback buffer so the API is usable off-device.
|
|
29
|
-
_desktop_buffer: str = ""
|
|
22
|
+
from .registry import native_module
|
|
30
23
|
|
|
31
24
|
|
|
32
25
|
class Clipboard:
|
|
@@ -35,109 +28,21 @@ class Clipboard:
|
|
|
35
28
|
@staticmethod
|
|
36
29
|
def set_string(text: str) -> None:
|
|
37
30
|
"""Copy ``text`` onto the system clipboard."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
elif IS_IOS:
|
|
43
|
-
_ios_set(value)
|
|
44
|
-
else:
|
|
45
|
-
_desktop_buffer = value
|
|
31
|
+
try:
|
|
32
|
+
native_module("Clipboard").call("set_string", text="" if text is None else str(text))
|
|
33
|
+
except Exception:
|
|
34
|
+
diagnostics.swallowed("clipboard.set_string")
|
|
46
35
|
|
|
47
36
|
@staticmethod
|
|
48
37
|
def get_string() -> str:
|
|
49
38
|
"""Return the current clipboard string (``""`` when empty)."""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
54
|
-
return
|
|
39
|
+
try:
|
|
40
|
+
value = native_module("Clipboard").call("get_string")
|
|
41
|
+
except Exception:
|
|
42
|
+
return ""
|
|
43
|
+
return "" if value is None else str(value)
|
|
55
44
|
|
|
56
45
|
@staticmethod
|
|
57
46
|
def has_string() -> bool:
|
|
58
47
|
"""Return ``True`` when the clipboard holds non-empty text."""
|
|
59
48
|
return bool(Clipboard.get_string())
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# ======================================================================
|
|
63
|
-
# iOS: UIPasteboard
|
|
64
|
-
# ======================================================================
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _ios_pasteboard() -> Optional[object]:
|
|
68
|
-
try:
|
|
69
|
-
from rubicon.objc import ObjCClass
|
|
70
|
-
|
|
71
|
-
return ObjCClass("UIPasteboard").generalPasteboard
|
|
72
|
-
except Exception:
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _ios_set(text: str) -> None:
|
|
77
|
-
pb = _ios_pasteboard()
|
|
78
|
-
if pb is None:
|
|
79
|
-
return
|
|
80
|
-
try:
|
|
81
|
-
pb.string = text
|
|
82
|
-
except Exception:
|
|
83
|
-
diagnostics.swallowed("clipboard._ios_set")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _ios_get() -> str:
|
|
87
|
-
pb = _ios_pasteboard()
|
|
88
|
-
if pb is None:
|
|
89
|
-
return ""
|
|
90
|
-
try:
|
|
91
|
-
value = pb.string
|
|
92
|
-
return str(value) if value is not None else ""
|
|
93
|
-
except Exception:
|
|
94
|
-
return ""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# ======================================================================
|
|
98
|
-
# Android: ClipboardManager
|
|
99
|
-
# ======================================================================
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _android_manager() -> Optional[object]:
|
|
103
|
-
try:
|
|
104
|
-
from java import jclass
|
|
105
|
-
|
|
106
|
-
from ..utils import get_android_context
|
|
107
|
-
|
|
108
|
-
ctx = get_android_context()
|
|
109
|
-
Context = jclass("android.content.Context")
|
|
110
|
-
return ctx.getSystemService(Context.CLIPBOARD_SERVICE)
|
|
111
|
-
except Exception:
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _android_set(text: str) -> None:
|
|
116
|
-
manager = _android_manager()
|
|
117
|
-
if manager is None:
|
|
118
|
-
return
|
|
119
|
-
try:
|
|
120
|
-
from java import jclass
|
|
121
|
-
|
|
122
|
-
ClipData = jclass("android.content.ClipData")
|
|
123
|
-
clip = ClipData.newPlainText("pythonnative", text)
|
|
124
|
-
manager.setPrimaryClip(clip)
|
|
125
|
-
except Exception:
|
|
126
|
-
diagnostics.swallowed("clipboard._android_set")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _android_get() -> str:
|
|
130
|
-
manager = _android_manager()
|
|
131
|
-
if manager is None:
|
|
132
|
-
return ""
|
|
133
|
-
try:
|
|
134
|
-
if not manager.hasPrimaryClip():
|
|
135
|
-
return ""
|
|
136
|
-
clip = manager.getPrimaryClip()
|
|
137
|
-
if clip is None or clip.getItemCount() == 0:
|
|
138
|
-
return ""
|
|
139
|
-
item = clip.getItemAt(0)
|
|
140
|
-
text = item.getText()
|
|
141
|
-
return str(text) if text is not None else ""
|
|
142
|
-
except Exception:
|
|
143
|
-
return ""
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Off-device implementations of the built-in native modules.
|
|
2
|
+
|
|
3
|
+
On iOS and Android every module in this package is implemented in
|
|
4
|
+
Swift and Kotlin (``PythonNativeKit`` and the ``pythonnative`` Gradle
|
|
5
|
+
module). Off device (``pn preview``, unit tests) the same module names
|
|
6
|
+
resolve to the plain Python classes below, which keep the API usable
|
|
7
|
+
without a device: in-memory buffers, ``"unknown"`` states, and no-op
|
|
8
|
+
feedback.
|
|
9
|
+
|
|
10
|
+
Each class has the same method names and argument shapes as its native
|
|
11
|
+
counterpart, so a facade in this package never branches on platform.
|
|
12
|
+
Apps and tests may swap any of these with
|
|
13
|
+
[`register_python_module`][pythonnative.native_modules.registry.register_python_module].
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import platform as _platform
|
|
21
|
+
import sys
|
|
22
|
+
import threading
|
|
23
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
24
|
+
|
|
25
|
+
__all__ = ["default_implementation"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ======================================================================
|
|
29
|
+
# Device / host information
|
|
30
|
+
# ======================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DesktopDevice:
|
|
34
|
+
"""Static device information for the desktop preview."""
|
|
35
|
+
|
|
36
|
+
def info(self) -> Dict[str, Any]:
|
|
37
|
+
home = os.path.expanduser("~")
|
|
38
|
+
app_dir = os.path.join(home, ".pythonnative_data")
|
|
39
|
+
return {
|
|
40
|
+
"platform": "desktop",
|
|
41
|
+
"os": sys.platform,
|
|
42
|
+
"os_version": _platform.release(),
|
|
43
|
+
"model": _platform.machine(),
|
|
44
|
+
"app_dir": app_dir,
|
|
45
|
+
"cache_dir": os.path.join(app_dir, "cache"),
|
|
46
|
+
"temp_dir": os.path.join(app_dir, "tmp"),
|
|
47
|
+
"locale": os.environ.get("LANG", "en_US").split(".")[0],
|
|
48
|
+
"app_version": "0.0.0",
|
|
49
|
+
"build_number": "0",
|
|
50
|
+
"bundle_id": "com.pythonnative.preview",
|
|
51
|
+
"python_version": _platform.python_version(),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DesktopAppState:
|
|
56
|
+
def current_state(self) -> str:
|
|
57
|
+
return "active"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ======================================================================
|
|
61
|
+
# Storage
|
|
62
|
+
# ======================================================================
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DesktopStorage:
|
|
66
|
+
"""Dict-backed ``AsyncStorage`` with optional JSON persistence.
|
|
67
|
+
|
|
68
|
+
Set ``PN_STORAGE_DIR`` to persist between runs (``pn preview`` does
|
|
69
|
+
this); leave it unset in tests for a purely in-memory store.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self) -> None:
|
|
73
|
+
self._lock = threading.Lock()
|
|
74
|
+
self._store: Dict[str, str] = {}
|
|
75
|
+
self._loaded = False
|
|
76
|
+
|
|
77
|
+
def _path(self) -> Optional[str]:
|
|
78
|
+
base = os.environ.get("PN_STORAGE_DIR")
|
|
79
|
+
if not base:
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
os.makedirs(base, exist_ok=True)
|
|
83
|
+
except OSError:
|
|
84
|
+
return None
|
|
85
|
+
return os.path.join(base, "pn_async_storage.json")
|
|
86
|
+
|
|
87
|
+
def _load(self) -> None:
|
|
88
|
+
if self._loaded:
|
|
89
|
+
return
|
|
90
|
+
self._loaded = True
|
|
91
|
+
path = self._path()
|
|
92
|
+
if path is None or not os.path.exists(path):
|
|
93
|
+
return
|
|
94
|
+
try:
|
|
95
|
+
with open(path, encoding="utf-8") as f:
|
|
96
|
+
data = json.load(f)
|
|
97
|
+
except (OSError, json.JSONDecodeError):
|
|
98
|
+
return
|
|
99
|
+
if isinstance(data, dict):
|
|
100
|
+
self._store.update({str(k): str(v) for k, v in data.items()})
|
|
101
|
+
|
|
102
|
+
def _persist(self) -> None:
|
|
103
|
+
path = self._path()
|
|
104
|
+
if path is None:
|
|
105
|
+
return
|
|
106
|
+
try:
|
|
107
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
108
|
+
json.dump(self._store, f)
|
|
109
|
+
except OSError:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def get(self, key: str) -> Optional[str]:
|
|
113
|
+
with self._lock:
|
|
114
|
+
self._load()
|
|
115
|
+
return self._store.get(key)
|
|
116
|
+
|
|
117
|
+
def set(self, key: str, value: str) -> None:
|
|
118
|
+
with self._lock:
|
|
119
|
+
self._load()
|
|
120
|
+
self._store[key] = value
|
|
121
|
+
self._persist()
|
|
122
|
+
|
|
123
|
+
def delete(self, key: str) -> None:
|
|
124
|
+
with self._lock:
|
|
125
|
+
self._load()
|
|
126
|
+
self._store.pop(key, None)
|
|
127
|
+
self._persist()
|
|
128
|
+
|
|
129
|
+
def all_keys(self) -> List[str]:
|
|
130
|
+
with self._lock:
|
|
131
|
+
self._load()
|
|
132
|
+
return list(self._store.keys())
|
|
133
|
+
|
|
134
|
+
def clear(self) -> None:
|
|
135
|
+
with self._lock:
|
|
136
|
+
self._store.clear()
|
|
137
|
+
self._persist()
|
|
138
|
+
|
|
139
|
+
def _reset(self) -> None:
|
|
140
|
+
"""Forget everything, including the on-disk snapshot (tests)."""
|
|
141
|
+
with self._lock:
|
|
142
|
+
self._store.clear()
|
|
143
|
+
self._loaded = False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class DesktopSecureStore:
|
|
147
|
+
def __init__(self) -> None:
|
|
148
|
+
self._store: Dict[str, str] = {}
|
|
149
|
+
|
|
150
|
+
def set_item(self, key: str, value: str) -> bool:
|
|
151
|
+
self._store[key] = value
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
def get_item(self, key: str) -> Optional[str]:
|
|
155
|
+
return self._store.get(key)
|
|
156
|
+
|
|
157
|
+
def delete_item(self, key: str) -> bool:
|
|
158
|
+
return self._store.pop(key, None) is not None
|
|
159
|
+
|
|
160
|
+
def clear(self) -> None:
|
|
161
|
+
self._store.clear()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DesktopClipboard:
|
|
165
|
+
def __init__(self) -> None:
|
|
166
|
+
self._buffer = ""
|
|
167
|
+
|
|
168
|
+
def set_string(self, text: str) -> None:
|
|
169
|
+
self._buffer = "" if text is None else str(text)
|
|
170
|
+
|
|
171
|
+
def get_string(self) -> str:
|
|
172
|
+
return self._buffer
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ======================================================================
|
|
176
|
+
# System integration
|
|
177
|
+
# ======================================================================
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class DesktopAlert:
|
|
181
|
+
"""Records alerts and answers with scripted responses.
|
|
182
|
+
|
|
183
|
+
The log and response queue live on
|
|
184
|
+
[`Alert`][pythonnative.alerts.Alert] (``Alert._test_log`` and
|
|
185
|
+
``Alert.set_test_response``) so tests have one place to look.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _record(title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> Any:
|
|
190
|
+
from ..alerts import Alert
|
|
191
|
+
|
|
192
|
+
Alert._test_log.append({"title": title, "message": message, "buttons": list(buttons), "style": style})
|
|
193
|
+
return Alert
|
|
194
|
+
|
|
195
|
+
def show(self, title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> None:
|
|
196
|
+
self._record(title, message, buttons, style)
|
|
197
|
+
|
|
198
|
+
def present(self, title: str, message: Optional[str], buttons: List[Dict[str, Any]], style: str) -> int:
|
|
199
|
+
return int(self._record(title, message, buttons, style)._next_test_response())
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class DesktopShare:
|
|
203
|
+
def share(self, message: Optional[str] = None, url: Optional[str] = None, title: Optional[str] = None) -> bool:
|
|
204
|
+
del message, url, title
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DesktopLinking:
|
|
209
|
+
def open_url(self, url: str) -> bool:
|
|
210
|
+
del url
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
def can_open_url(self, url: str) -> bool:
|
|
214
|
+
del url
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def open_settings(self) -> bool:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class DesktopHaptics:
|
|
222
|
+
def impact(self, style: str = "medium") -> None:
|
|
223
|
+
del style
|
|
224
|
+
|
|
225
|
+
def notification(self, type: str = "success") -> None: # noqa: A002 - wire name
|
|
226
|
+
del type
|
|
227
|
+
|
|
228
|
+
def selection(self) -> None:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
def vibrate(self, duration_ms: int = 400) -> None:
|
|
232
|
+
del duration_ms
|
|
233
|
+
|
|
234
|
+
def cancel(self) -> None:
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class DesktopBattery:
|
|
239
|
+
def get_level(self) -> float:
|
|
240
|
+
return -1.0
|
|
241
|
+
|
|
242
|
+
def get_state(self) -> str:
|
|
243
|
+
return "unknown"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class DesktopNetInfo:
|
|
247
|
+
def fetch(self) -> Optional[Dict[str, Any]]:
|
|
248
|
+
# ``None`` means "no fresh reading"; the facade keeps whatever
|
|
249
|
+
# snapshot was last dispatched (tests push those directly).
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class DesktopPermissions:
|
|
254
|
+
def check(self, permission: str) -> str:
|
|
255
|
+
del permission
|
|
256
|
+
return "undetermined"
|
|
257
|
+
|
|
258
|
+
def request(self, permission: str) -> str:
|
|
259
|
+
del permission
|
|
260
|
+
return "undetermined"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class DesktopNotifications:
|
|
264
|
+
def request_permission(self) -> bool:
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
def schedule(self, title: str, body: str = "", delay_seconds: float = 0, identifier: str = "default") -> bool:
|
|
268
|
+
del title, body, delay_seconds, identifier
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
def cancel(self, identifier: str = "default") -> None:
|
|
272
|
+
del identifier
|
|
273
|
+
|
|
274
|
+
def get_device_token(self) -> Optional[str]:
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DesktopCamera:
|
|
279
|
+
def take_photo(self) -> Optional[str]:
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
def pick_from_gallery(self) -> Optional[str]:
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class DesktopLocation:
|
|
287
|
+
def get_current(self) -> Optional[Dict[str, float]]:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class DesktopBiometrics:
|
|
292
|
+
def is_available(self) -> bool:
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
def authenticate(self, reason: str = "Authenticate") -> bool:
|
|
296
|
+
del reason
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
_DEFAULTS: Dict[str, Callable[[], Any]] = {
|
|
301
|
+
"Device": DesktopDevice,
|
|
302
|
+
"AppState": DesktopAppState,
|
|
303
|
+
"Storage": DesktopStorage,
|
|
304
|
+
"SecureStore": DesktopSecureStore,
|
|
305
|
+
"Clipboard": DesktopClipboard,
|
|
306
|
+
"Alert": DesktopAlert,
|
|
307
|
+
"Share": DesktopShare,
|
|
308
|
+
"Linking": DesktopLinking,
|
|
309
|
+
"Haptics": DesktopHaptics,
|
|
310
|
+
"Battery": DesktopBattery,
|
|
311
|
+
"NetInfo": DesktopNetInfo,
|
|
312
|
+
"Permissions": DesktopPermissions,
|
|
313
|
+
"Notifications": DesktopNotifications,
|
|
314
|
+
"Camera": DesktopCamera,
|
|
315
|
+
"Location": DesktopLocation,
|
|
316
|
+
"Biometrics": DesktopBiometrics,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def default_implementation(name: str) -> Optional[Callable[[], Any]]:
|
|
321
|
+
"""Return the factory for the built-in desktop implementation of ``name``."""
|
|
322
|
+
return _DEFAULTS.get(name)
|
|
@@ -17,8 +17,9 @@ Example:
|
|
|
17
17
|
import os
|
|
18
18
|
from typing import Any, Optional
|
|
19
19
|
|
|
20
|
-
from
|
|
21
|
-
|
|
20
|
+
from .registry import native_module
|
|
21
|
+
|
|
22
|
+
_app_dir_cache: Optional[str] = None
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class FileSystem:
|
|
@@ -39,31 +40,24 @@ class FileSystem:
|
|
|
39
40
|
On Android the result is `Context.getFilesDir()`. On iOS it is
|
|
40
41
|
the user's Documents directory. On a desktop machine without
|
|
41
42
|
either runtime, a `.pythonnative_data` directory is created
|
|
42
|
-
under the user's home folder.
|
|
43
|
+
under the user's home folder. The value comes from the native
|
|
44
|
+
``Device`` module's ``info()`` and is cached after the first
|
|
45
|
+
call.
|
|
43
46
|
|
|
44
47
|
Returns:
|
|
45
48
|
Absolute path to the app's data directory.
|
|
46
49
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return str(get_android_context().getFilesDir().getAbsolutePath())
|
|
52
|
-
except Exception:
|
|
53
|
-
diagnostics.swallowed("file_system.FileSystem.app_dir")
|
|
54
|
-
else:
|
|
50
|
+
global _app_dir_cache
|
|
51
|
+
if _app_dir_cache is None:
|
|
52
|
+
path: Optional[str] = None
|
|
55
53
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"NSFileManager"
|
|
60
|
-
).defaultManager.URLsForDirectory_inDomains_
|
|
61
|
-
docs = NSSearchPathForDirectoriesInDomains(9, 1) # NSDocumentDirectory, NSUserDomainMask
|
|
62
|
-
if docs and docs.count > 0:
|
|
63
|
-
return str(docs.objectAtIndex_(0).path)
|
|
54
|
+
info = native_module("Device").call("info")
|
|
55
|
+
if isinstance(info, dict):
|
|
56
|
+
path = info.get("app_dir")
|
|
64
57
|
except Exception:
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
path = None
|
|
59
|
+
_app_dir_cache = str(path) if path else os.path.join(os.path.expanduser("~"), ".pythonnative_data")
|
|
60
|
+
return _app_dir_cache
|
|
67
61
|
|
|
68
62
|
@staticmethod
|
|
69
63
|
def read_text(path: str, encoding: str = "utf-8") -> Optional[str]:
|