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
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
[`Battery`][pythonnative.Battery] reports the current charge fraction
|
|
4
4
|
(``0.0``–``1.0``, or ``-1.0`` when unknown) and charging state, and
|
|
5
|
-
lets you subscribe to changes
|
|
5
|
+
lets you subscribe to changes. The native ``Battery`` module pushes a
|
|
6
|
+
``change`` event with ``{"level", "state"}``; off device, tests drive
|
|
7
|
+
the same path through
|
|
6
8
|
[`dispatch_battery`][pythonnative.native_modules.battery.dispatch_battery].
|
|
7
9
|
"""
|
|
8
10
|
|
|
9
11
|
from __future__ import annotations
|
|
10
12
|
|
|
11
|
-
from typing import Callable, Dict, List
|
|
13
|
+
from typing import Any, Callable, Dict, List
|
|
12
14
|
|
|
13
15
|
from .. import diagnostics
|
|
14
|
-
from
|
|
16
|
+
from .registry import native_module, on_event
|
|
15
17
|
|
|
16
18
|
BatteryState = str # "unknown" | "unplugged" | "charging" | "full"
|
|
17
19
|
|
|
@@ -24,20 +26,20 @@ class Battery:
|
|
|
24
26
|
@staticmethod
|
|
25
27
|
def get_level() -> float:
|
|
26
28
|
"""Return the charge fraction in ``[0, 1]`` (``-1.0`` if unknown)."""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
return -1.0
|
|
29
|
+
try:
|
|
30
|
+
level = float(native_module("Battery").call("get_level"))
|
|
31
|
+
except Exception:
|
|
32
|
+
return -1.0
|
|
33
|
+
return level if 0.0 <= level <= 1.0 else -1.0
|
|
32
34
|
|
|
33
35
|
@staticmethod
|
|
34
36
|
def get_state() -> BatteryState:
|
|
35
37
|
"""Return ``"charging"`` / ``"full"`` / ``"unplugged"`` / ``"unknown"``."""
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
return "unknown"
|
|
38
|
+
try:
|
|
39
|
+
state = str(native_module("Battery").call("get_state") or "unknown")
|
|
40
|
+
except Exception:
|
|
41
|
+
return "unknown"
|
|
42
|
+
return state if state in ("unknown", "unplugged", "charging", "full") else "unknown"
|
|
41
43
|
|
|
42
44
|
@staticmethod
|
|
43
45
|
def add_listener(callback: Callable[[Dict[str, object]], None]) -> Callable[[], None]:
|
|
@@ -57,7 +59,7 @@ class Battery:
|
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def dispatch_battery(level: float, state: BatteryState) -> None:
|
|
60
|
-
"""Notify listeners of a battery change
|
|
62
|
+
"""Notify listeners of a battery change."""
|
|
61
63
|
payload: Dict[str, object] = {"level": level, "state": state}
|
|
62
64
|
for listener in list(_listeners):
|
|
63
65
|
try:
|
|
@@ -66,75 +68,9 @@ def dispatch_battery(level: float, state: BatteryState) -> None:
|
|
|
66
68
|
diagnostics.swallowed("battery.dispatch_battery")
|
|
67
69
|
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _ios_device() -> object:
|
|
75
|
-
from rubicon.objc import ObjCClass
|
|
76
|
-
|
|
77
|
-
device = ObjCClass("UIDevice").currentDevice
|
|
78
|
-
try:
|
|
79
|
-
device.setBatteryMonitoringEnabled_(True)
|
|
80
|
-
except Exception:
|
|
81
|
-
diagnostics.swallowed("battery._ios_device")
|
|
82
|
-
return device
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _ios_level() -> float:
|
|
86
|
-
try:
|
|
87
|
-
level = float(_ios_device().batteryLevel)
|
|
88
|
-
return level if level >= 0 else -1.0
|
|
89
|
-
except Exception:
|
|
90
|
-
return -1.0
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _ios_state() -> BatteryState:
|
|
94
|
-
try:
|
|
95
|
-
# UIDeviceBatteryState: 0 unknown, 1 unplugged, 2 charging, 3 full
|
|
96
|
-
return {0: "unknown", 1: "unplugged", 2: "charging", 3: "full"}.get(int(_ios_device().batteryState), "unknown")
|
|
97
|
-
except Exception:
|
|
98
|
-
return "unknown"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
# ======================================================================
|
|
102
|
-
# Android: BatteryManager
|
|
103
|
-
# ======================================================================
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _android_level() -> float:
|
|
107
|
-
try:
|
|
108
|
-
from java import jclass
|
|
109
|
-
|
|
110
|
-
from ..utils import get_android_context
|
|
111
|
-
|
|
112
|
-
ctx = get_android_context()
|
|
113
|
-
Context = jclass("android.content.Context")
|
|
114
|
-
bm = ctx.getSystemService(Context.BATTERY_SERVICE)
|
|
115
|
-
BatteryManager = jclass("android.os.BatteryManager")
|
|
116
|
-
pct = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
|
117
|
-
return (pct / 100.0) if pct is not None and pct >= 0 else -1.0
|
|
118
|
-
except Exception:
|
|
119
|
-
return -1.0
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _android_state() -> BatteryState:
|
|
123
|
-
try:
|
|
124
|
-
from java import jclass
|
|
71
|
+
def _on_native_change(payload: Any) -> None:
|
|
72
|
+
if isinstance(payload, dict):
|
|
73
|
+
dispatch_battery(float(payload.get("level", -1.0)), str(payload.get("state", "unknown")))
|
|
125
74
|
|
|
126
|
-
from ..utils import get_android_context
|
|
127
75
|
|
|
128
|
-
|
|
129
|
-
Context = jclass("android.content.Context")
|
|
130
|
-
bm = ctx.getSystemService(Context.BATTERY_SERVICE)
|
|
131
|
-
BatteryManager = jclass("android.os.BatteryManager")
|
|
132
|
-
status = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
|
|
133
|
-
return {
|
|
134
|
-
BatteryManager.BATTERY_STATUS_CHARGING: "charging",
|
|
135
|
-
BatteryManager.BATTERY_STATUS_FULL: "full",
|
|
136
|
-
BatteryManager.BATTERY_STATUS_DISCHARGING: "unplugged",
|
|
137
|
-
BatteryManager.BATTERY_STATUS_NOT_CHARGING: "unplugged",
|
|
138
|
-
}.get(status, "unknown")
|
|
139
|
-
except Exception:
|
|
140
|
-
return "unknown"
|
|
76
|
+
on_event("Battery", "change", _on_native_change)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[`Biometrics`][pythonnative.Biometrics] gates an action behind the
|
|
4
4
|
device's biometric hardware via ``LAContext`` (iOS) and
|
|
5
|
-
``BiometricPrompt`` (Android)
|
|
5
|
+
``BiometricPrompt`` (Android), both implemented in the native
|
|
6
|
+
``Biometrics`` module.
|
|
6
7
|
|
|
7
8
|
``is_available`` is synchronous; ``authenticate`` is a coroutine that
|
|
8
9
|
presents the system prompt and resolves to ``True`` on success or
|
|
@@ -20,14 +21,7 @@ Example:
|
|
|
20
21
|
|
|
21
22
|
from __future__ import annotations
|
|
22
23
|
|
|
23
|
-
import
|
|
24
|
-
from typing import Any, Callable, Dict
|
|
25
|
-
|
|
26
|
-
from .. import diagnostics
|
|
27
|
-
from ..runtime import resolve_future
|
|
28
|
-
from ..utils import IS_ANDROID, IS_IOS
|
|
29
|
-
|
|
30
|
-
_pending: Dict[int, Any] = {}
|
|
24
|
+
from .registry import native_module
|
|
31
25
|
|
|
32
26
|
|
|
33
27
|
class Biometrics:
|
|
@@ -36,122 +30,15 @@ class Biometrics:
|
|
|
36
30
|
@staticmethod
|
|
37
31
|
def is_available() -> bool:
|
|
38
32
|
"""Return ``True`` when biometric auth can be attempted."""
|
|
39
|
-
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
return False
|
|
33
|
+
try:
|
|
34
|
+
return bool(native_module("Biometrics").call("is_available"))
|
|
35
|
+
except Exception:
|
|
36
|
+
return False
|
|
44
37
|
|
|
45
38
|
@staticmethod
|
|
46
39
|
async def authenticate(reason: str = "Authenticate") -> bool:
|
|
47
40
|
"""Present the biometric prompt; resolve ``True`` on success."""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
resolve_future(future, ok)
|
|
53
|
-
|
|
54
|
-
if IS_IOS:
|
|
55
|
-
_ios_authenticate(reason, _done)
|
|
56
|
-
elif IS_ANDROID:
|
|
57
|
-
_android_authenticate(reason, _done)
|
|
58
|
-
else:
|
|
59
|
-
_done(False)
|
|
60
|
-
|
|
61
|
-
return await future
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# ======================================================================
|
|
65
|
-
# iOS: LAContext
|
|
66
|
-
# ======================================================================
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _ios_context() -> Any:
|
|
70
|
-
from rubicon.objc import ObjCClass
|
|
71
|
-
|
|
72
|
-
return ObjCClass("LAContext").alloc().init()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _ios_available() -> bool:
|
|
76
|
-
try:
|
|
77
|
-
# LAPolicyDeviceOwnerAuthenticationWithBiometrics == 1
|
|
78
|
-
return bool(_ios_context().canEvaluatePolicy_error_(1, None))
|
|
79
|
-
except Exception:
|
|
80
|
-
return False
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _ios_authenticate(reason: str, on_done: Callable[[bool], None]) -> None:
|
|
84
|
-
try:
|
|
85
|
-
from rubicon.objc import Block, ObjCClass
|
|
86
|
-
|
|
87
|
-
context = _ios_context()
|
|
88
|
-
token = id(context)
|
|
89
|
-
_pending[token] = context
|
|
90
|
-
|
|
91
|
-
def _reply(success: bool, error: Any) -> None:
|
|
92
|
-
del error
|
|
93
|
-
_pending.pop(token, None)
|
|
94
|
-
try:
|
|
95
|
-
on_done(bool(success))
|
|
96
|
-
except Exception:
|
|
97
|
-
diagnostics.swallowed("biometrics._ios_authenticate._reply")
|
|
98
|
-
|
|
99
|
-
context.evaluatePolicy_localizedReason_reply_(1, reason, Block(_reply, None, bool, ObjCClass("NSError")))
|
|
100
|
-
except Exception:
|
|
101
|
-
on_done(False)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# ======================================================================
|
|
105
|
-
# Android: BiometricPrompt
|
|
106
|
-
# ======================================================================
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _android_available() -> bool:
|
|
110
|
-
try:
|
|
111
|
-
from java import jclass
|
|
112
|
-
|
|
113
|
-
from ..utils import get_android_context
|
|
114
|
-
|
|
115
|
-
BiometricManager = jclass("androidx.biometric.BiometricManager")
|
|
116
|
-
manager = BiometricManager.from_(get_android_context())
|
|
117
|
-
Authenticators = jclass("androidx.biometric.BiometricManager$Authenticators")
|
|
118
|
-
result = manager.canAuthenticate(Authenticators.BIOMETRIC_WEAK)
|
|
119
|
-
return result == BiometricManager.BIOMETRIC_SUCCESS
|
|
120
|
-
except Exception:
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _android_authenticate(reason: str, on_done: Callable[[bool], None]) -> None:
|
|
125
|
-
try:
|
|
126
|
-
from java import dynamic_proxy, jclass
|
|
127
|
-
|
|
128
|
-
from ..utils import get_android_context
|
|
129
|
-
|
|
130
|
-
ctx = get_android_context()
|
|
131
|
-
FragmentActivity = jclass("androidx.fragment.app.FragmentActivity")
|
|
132
|
-
if not FragmentActivity.isInstance(ctx):
|
|
133
|
-
on_done(False)
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
BiometricPrompt = jclass("androidx.biometric.BiometricPrompt")
|
|
137
|
-
ContextCompat = jclass("androidx.core.content.ContextCompat")
|
|
138
|
-
executor = ContextCompat.getMainExecutor(ctx)
|
|
139
|
-
|
|
140
|
-
class _Callback(dynamic_proxy(BiometricPrompt.AuthenticationCallback)): # type: ignore[misc]
|
|
141
|
-
def onAuthenticationSucceeded(self, result: Any) -> None: # noqa: N802, ARG002
|
|
142
|
-
on_done(True)
|
|
143
|
-
|
|
144
|
-
def onAuthenticationError(self, code: int, message: Any) -> None: # noqa: N802, ARG002
|
|
145
|
-
on_done(False)
|
|
146
|
-
|
|
147
|
-
def onAuthenticationFailed(self) -> None: # noqa: N802
|
|
148
|
-
# Fired on a non-matching attempt; the prompt stays up,
|
|
149
|
-
# so don't resolve yet.
|
|
150
|
-
pass
|
|
151
|
-
|
|
152
|
-
prompt = BiometricPrompt(ctx, executor, _Callback())
|
|
153
|
-
Builder = jclass("androidx.biometric.BiometricPrompt$PromptInfo$Builder")
|
|
154
|
-
info = Builder().setTitle(reason).setNegativeButtonText("Cancel").build()
|
|
155
|
-
prompt.authenticate(info)
|
|
156
|
-
except Exception:
|
|
157
|
-
on_done(False)
|
|
41
|
+
try:
|
|
42
|
+
return bool(await native_module("Biometrics").call_async("authenticate", reason=reason))
|
|
43
|
+
except Exception:
|
|
44
|
+
return False
|
|
@@ -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
|