pythonnative 0.36.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/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/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.36.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.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Cross-platform camera and gallery access.
|
|
2
2
|
|
|
3
3
|
Both entry points are coroutines: ``await Camera.take_photo()`` returns
|
|
4
|
-
the saved image path (a ``str``) or ``None`` if the user cancels.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
``Intent(MediaStore.ACTION_IMAGE_CAPTURE)`` (Android).
|
|
4
|
+
the saved image path (a ``str``) or ``None`` if the user cancels. The
|
|
5
|
+
native ``Camera`` module presents ``UIImagePickerController`` (iOS) or
|
|
6
|
+
launches ``MediaStore.ACTION_IMAGE_CAPTURE`` / ``ACTION_PICK``
|
|
7
|
+
(Android) and resolves the call when the picker finishes; Python only
|
|
8
|
+
awaits the promise.
|
|
10
9
|
|
|
11
10
|
Example:
|
|
12
11
|
```python
|
|
@@ -22,316 +21,41 @@ Example:
|
|
|
22
21
|
|
|
23
22
|
from __future__ import annotations
|
|
24
23
|
|
|
25
|
-
import
|
|
26
|
-
from typing import Any, Callable, Dict, Optional
|
|
24
|
+
from typing import Any, Optional
|
|
27
25
|
|
|
28
|
-
from
|
|
29
|
-
from ..runtime import resolve_future
|
|
30
|
-
from ..utils import IS_ANDROID, IS_IOS
|
|
31
|
-
|
|
32
|
-
# Retain pool keyed by ``id(delegate)`` so iOS UIImagePickerController
|
|
33
|
-
# delegates aren't garbage-collected before the picker calls back.
|
|
34
|
-
_pending_delegates: Dict[int, Any] = {}
|
|
26
|
+
from .registry import native_module
|
|
35
27
|
|
|
36
28
|
|
|
37
29
|
class Camera:
|
|
38
|
-
"""Camera and image-picker interface.
|
|
39
|
-
|
|
40
|
-
All methods are static coroutines. They dispatch to the iOS or
|
|
41
|
-
Android implementation at call time based on the runtime
|
|
42
|
-
platform.
|
|
43
|
-
"""
|
|
30
|
+
"""Camera and image-picker interface (static coroutines)."""
|
|
44
31
|
|
|
45
32
|
@staticmethod
|
|
46
33
|
async def take_photo(**options: Any) -> Optional[str]:
|
|
47
34
|
"""Launch the device camera to capture a photo.
|
|
48
35
|
|
|
49
36
|
Args:
|
|
50
|
-
**options:
|
|
51
|
-
|
|
52
|
-
``flash_mode``) will land here.
|
|
37
|
+
**options: Forwarded to the native module (``quality``,
|
|
38
|
+
``allow_editing``, ...). Unknown keys are ignored.
|
|
53
39
|
|
|
54
40
|
Returns:
|
|
55
41
|
The saved image path, or ``None`` if the user cancelled or
|
|
56
42
|
no camera is available.
|
|
57
43
|
"""
|
|
58
|
-
return await _launch("
|
|
44
|
+
return await _launch("take_photo", options)
|
|
59
45
|
|
|
60
46
|
@staticmethod
|
|
61
47
|
async def pick_from_gallery(**options: Any) -> Optional[str]:
|
|
62
48
|
"""Open the system gallery picker.
|
|
63
49
|
|
|
64
|
-
Args:
|
|
65
|
-
**options: Reserved for platform-specific tuning.
|
|
66
|
-
|
|
67
50
|
Returns:
|
|
68
51
|
The selected image path, or ``None`` if the user cancelled.
|
|
69
52
|
"""
|
|
70
|
-
return await _launch("
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
async def _launch(source: str, **options: Any) -> Optional[str]:
|
|
74
|
-
del options
|
|
75
|
-
loop = asyncio.get_running_loop()
|
|
76
|
-
future: asyncio.Future[Optional[str]] = loop.create_future()
|
|
77
|
-
|
|
78
|
-
def _on_result(path: Optional[str]) -> None:
|
|
79
|
-
resolve_future(future, path)
|
|
80
|
-
|
|
81
|
-
if IS_ANDROID:
|
|
82
|
-
_android_launch_picker(_on_result, source=source)
|
|
83
|
-
elif IS_IOS:
|
|
84
|
-
_ios_launch_picker(_on_result, source=source)
|
|
85
|
-
else:
|
|
86
|
-
resolve_future(future, None)
|
|
87
|
-
|
|
88
|
-
return await future
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# ======================================================================
|
|
92
|
-
# iOS implementation: UIImagePickerControllerDelegate
|
|
93
|
-
# ======================================================================
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _ios_launch_picker(on_result: Callable[[Optional[str]], None], source: str) -> None:
|
|
97
|
-
try:
|
|
98
|
-
from rubicon.objc import SEL, ObjCClass, objc_method
|
|
99
|
-
|
|
100
|
-
UIImagePickerController = ObjCClass("UIImagePickerController")
|
|
101
|
-
NSObject = ObjCClass("NSObject")
|
|
102
|
-
|
|
103
|
-
class _PNImagePickerDelegate(NSObject): # type: ignore[misc,valid-type]
|
|
104
|
-
_callback: Optional[Callable[[Optional[str]], None]] = None
|
|
105
|
-
_picker: Any = None
|
|
106
|
-
|
|
107
|
-
@objc_method
|
|
108
|
-
def imagePickerController_didFinishPickingMediaWithInfo_(self, picker: Any, info: Any) -> None:
|
|
109
|
-
path = _ios_extract_path(info)
|
|
110
|
-
try:
|
|
111
|
-
picker.dismissViewControllerAnimated_completion_(True, None)
|
|
112
|
-
except Exception:
|
|
113
|
-
diagnostics.swallowed(
|
|
114
|
-
"camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
|
|
115
|
-
)
|
|
116
|
-
cb = self._callback
|
|
117
|
-
self._callback = None
|
|
118
|
-
_pending_delegates.pop(id(self), None)
|
|
119
|
-
if cb is not None:
|
|
120
|
-
try:
|
|
121
|
-
cb(path)
|
|
122
|
-
except Exception:
|
|
123
|
-
diagnostics.swallowed(
|
|
124
|
-
"camera._ios_launch_picker._PNImagePickerDelegate.imagePickerController_didFinishPickingMediaWithInfo_"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
@objc_method
|
|
128
|
-
def imagePickerControllerDidCancel_(self, picker: Any) -> None:
|
|
129
|
-
try:
|
|
130
|
-
picker.dismissViewControllerAnimated_completion_(True, None)
|
|
131
|
-
except Exception:
|
|
132
|
-
diagnostics.swallowed(
|
|
133
|
-
"camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
|
|
134
|
-
)
|
|
135
|
-
cb = self._callback
|
|
136
|
-
self._callback = None
|
|
137
|
-
_pending_delegates.pop(id(self), None)
|
|
138
|
-
if cb is not None:
|
|
139
|
-
try:
|
|
140
|
-
cb(None)
|
|
141
|
-
except Exception:
|
|
142
|
-
diagnostics.swallowed(
|
|
143
|
-
"camera._ios_launch_picker._PNImagePickerDelegate.imagePickerControllerDidCancel_"
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
delegate = _PNImagePickerDelegate.new()
|
|
147
|
-
delegate._callback = on_result
|
|
148
|
-
_pending_delegates[id(delegate)] = delegate
|
|
149
|
-
|
|
150
|
-
picker = UIImagePickerController.alloc().init()
|
|
151
|
-
picker.setSourceType_(1 if source == "camera" else 0)
|
|
152
|
-
picker.setDelegate_(delegate)
|
|
153
|
-
|
|
154
|
-
UIApplication = ObjCClass("UIApplication")
|
|
155
|
-
top = UIApplication.sharedApplication.keyWindow.rootViewController
|
|
156
|
-
while top is not None and top.presentedViewController is not None:
|
|
157
|
-
top = top.presentedViewController
|
|
158
|
-
if top is not None:
|
|
159
|
-
top.presentViewController_animated_completion_(picker, True, None)
|
|
160
|
-
else:
|
|
161
|
-
_pending_delegates.pop(id(delegate), None)
|
|
162
|
-
on_result(None)
|
|
163
|
-
|
|
164
|
-
# Reference SEL/objc_method so the lint pass keeps the import;
|
|
165
|
-
# they're needed for the delegate class above.
|
|
166
|
-
_ = (SEL, objc_method)
|
|
167
|
-
except Exception:
|
|
168
|
-
on_result(None)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def _ios_extract_path(info: Any) -> Optional[str]:
|
|
172
|
-
"""Best-effort extraction of an image path from picker ``info``."""
|
|
173
|
-
try:
|
|
174
|
-
url = info.objectForKey_("UIImagePickerControllerImageURL")
|
|
175
|
-
if url is not None:
|
|
176
|
-
try:
|
|
177
|
-
return str(url.absoluteString)
|
|
178
|
-
except Exception:
|
|
179
|
-
try:
|
|
180
|
-
return str(url.path)
|
|
181
|
-
except Exception:
|
|
182
|
-
diagnostics.swallowed("camera._ios_extract_path")
|
|
183
|
-
image = info.objectForKey_("UIImagePickerControllerOriginalImage")
|
|
184
|
-
if image is not None:
|
|
185
|
-
return _ios_write_image_to_tmp(image)
|
|
186
|
-
except Exception:
|
|
187
|
-
return None
|
|
188
|
-
return None
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def _ios_write_image_to_tmp(image: Any) -> Optional[str]:
|
|
192
|
-
"""Encode a UIImage to JPEG and write it to NSCachesDirectory."""
|
|
193
|
-
try:
|
|
194
|
-
from rubicon.objc import ObjCClass
|
|
195
|
-
|
|
196
|
-
try:
|
|
197
|
-
data = image.jpegDataWithCompressionQuality_(0.85)
|
|
198
|
-
except Exception:
|
|
199
|
-
return None
|
|
200
|
-
if data is None:
|
|
201
|
-
return None
|
|
202
|
-
NSString = ObjCClass("NSString")
|
|
203
|
-
NSFileManager = ObjCClass("NSFileManager")
|
|
204
|
-
manager = NSFileManager.defaultManager
|
|
205
|
-
urls = manager.URLsForDirectory_inDomains_(13, 1) # NSCachesDirectory, NSUserDomainMask
|
|
206
|
-
if urls is None or urls.count == 0:
|
|
207
|
-
return None
|
|
208
|
-
cache_dir = urls.firstObject
|
|
209
|
-
import time as _time
|
|
210
|
-
|
|
211
|
-
filename = NSString.stringWithFormat_("pn-camera-%d.jpg", int(_time.time() * 1000))
|
|
212
|
-
target = cache_dir.URLByAppendingPathComponent_(filename)
|
|
213
|
-
if not data.writeToURL_atomically_(target, True):
|
|
214
|
-
return None
|
|
215
|
-
try:
|
|
216
|
-
return str(target.path)
|
|
217
|
-
except Exception:
|
|
218
|
-
return str(target.absoluteString)
|
|
219
|
-
except Exception:
|
|
220
|
-
return None
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# ======================================================================
|
|
224
|
-
# Android implementation: ActivityResultLauncher / startActivityForResult
|
|
225
|
-
# ======================================================================
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
_android_pending_results: Dict[int, Callable[[Optional[str]], None]] = {}
|
|
229
|
-
_android_next_request_code: int = 50001
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _android_next_code() -> int:
|
|
233
|
-
global _android_next_request_code
|
|
234
|
-
code = _android_next_request_code
|
|
235
|
-
_android_next_request_code += 1
|
|
236
|
-
return code
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def _android_launch_picker(on_result: Callable[[Optional[str]], None], source: str) -> None:
|
|
240
|
-
try:
|
|
241
|
-
from java import jclass
|
|
242
|
-
|
|
243
|
-
from ..utils import get_android_context
|
|
53
|
+
return await _launch("pick_from_gallery", options)
|
|
244
54
|
|
|
245
|
-
Intent = jclass("android.content.Intent")
|
|
246
|
-
MediaStore = jclass("android.provider.MediaStore")
|
|
247
|
-
ctx = get_android_context()
|
|
248
55
|
|
|
249
|
-
|
|
250
|
-
intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
|
251
|
-
else:
|
|
252
|
-
intent = Intent(Intent.ACTION_PICK)
|
|
253
|
-
intent.setType("image/*")
|
|
254
|
-
|
|
255
|
-
# ``startActivityForResult`` requires the host to be an
|
|
256
|
-
# Activity; in unit tests we may only have an Application
|
|
257
|
-
# context, in which case we fall back to ``startActivity`` and
|
|
258
|
-
# report ``None`` (the caller has no Activity to dispatch the
|
|
259
|
-
# result back through).
|
|
260
|
-
Activity = jclass("android.app.Activity")
|
|
261
|
-
if not Activity.isInstance(ctx):
|
|
262
|
-
ctx.startActivity(intent)
|
|
263
|
-
on_result(None)
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
request_code = _android_next_code()
|
|
267
|
-
_android_pending_results[request_code] = on_result
|
|
268
|
-
try:
|
|
269
|
-
ctx.startActivityForResult(intent, request_code)
|
|
270
|
-
except Exception:
|
|
271
|
-
_android_pending_results.pop(request_code, None)
|
|
272
|
-
on_result(None)
|
|
273
|
-
except Exception:
|
|
274
|
-
on_result(None)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def deliver_android_activity_result(request_code: int, result_code: int, data: Any) -> bool:
|
|
278
|
-
"""Forward an Activity result to the registered camera coroutine.
|
|
279
|
-
|
|
280
|
-
The host Activity should call this from ``onActivityResult`` so
|
|
281
|
-
the pending
|
|
282
|
-
[`take_photo`][pythonnative.native_modules.camera.Camera.take_photo]
|
|
283
|
-
/
|
|
284
|
-
[`pick_from_gallery`][pythonnative.native_modules.camera.Camera.pick_from_gallery]
|
|
285
|
-
awaitable receives a path. Returns ``True`` if a Python callback
|
|
286
|
-
was invoked (so the host can short-circuit further handlers).
|
|
287
|
-
"""
|
|
288
|
-
cb = _android_pending_results.pop(request_code, None)
|
|
289
|
-
if cb is None:
|
|
290
|
-
return False
|
|
291
|
-
path: Optional[str] = None
|
|
292
|
-
try:
|
|
293
|
-
if result_code == -1 and data is not None: # RESULT_OK
|
|
294
|
-
uri = data.getData()
|
|
295
|
-
if uri is not None:
|
|
296
|
-
path = str(uri)
|
|
297
|
-
else:
|
|
298
|
-
try:
|
|
299
|
-
extras = data.getExtras()
|
|
300
|
-
if extras is not None:
|
|
301
|
-
thumb = extras.get("data")
|
|
302
|
-
if thumb is not None:
|
|
303
|
-
path = _android_write_bitmap_to_cache(thumb)
|
|
304
|
-
except Exception:
|
|
305
|
-
diagnostics.swallowed("camera.deliver_android_activity_result")
|
|
306
|
-
except Exception:
|
|
307
|
-
path = None
|
|
56
|
+
async def _launch(method: str, options: Any) -> Optional[str]:
|
|
308
57
|
try:
|
|
309
|
-
|
|
310
|
-
except Exception:
|
|
311
|
-
diagnostics.swallowed("camera.deliver_android_activity_result")
|
|
312
|
-
return True
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _android_write_bitmap_to_cache(bitmap: Any) -> Optional[str]:
|
|
316
|
-
"""Persist a Bitmap to the app cache directory and return its path."""
|
|
317
|
-
try:
|
|
318
|
-
from java import jclass
|
|
319
|
-
|
|
320
|
-
from ..utils import get_android_context
|
|
321
|
-
|
|
322
|
-
ctx = get_android_context()
|
|
323
|
-
cache_dir = ctx.getCacheDir()
|
|
324
|
-
File = jclass("java.io.File")
|
|
325
|
-
FileOutputStream = jclass("java.io.FileOutputStream")
|
|
326
|
-
Bitmap = jclass("android.graphics.Bitmap")
|
|
327
|
-
import time as _time
|
|
328
|
-
|
|
329
|
-
target = File(cache_dir, f"pn-camera-{int(_time.time() * 1000)}.jpg")
|
|
330
|
-
out = FileOutputStream(target)
|
|
331
|
-
try:
|
|
332
|
-
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, out)
|
|
333
|
-
finally:
|
|
334
|
-
out.close()
|
|
335
|
-
return str(target.getAbsolutePath())
|
|
58
|
+
result = await native_module("Camera").call_async(method, **options)
|
|
336
59
|
except Exception:
|
|
337
60
|
return None
|
|
61
|
+
return str(result) if result else None
|
|
@@ -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 ""
|