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,8 +1,9 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Local notifications and remote push registration.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
``
|
|
3
|
+
Coroutines for requesting permission and scheduling / cancelling local
|
|
4
|
+
notifications, backed by the native ``Notifications`` module
|
|
5
|
+
(``UNUserNotificationCenter`` on iOS, ``NotificationManager`` on
|
|
6
|
+
Android).
|
|
6
7
|
|
|
7
8
|
On iOS you must ``await Notifications.request_permission()`` before
|
|
8
9
|
scheduling. On Android 13+ the ``POST_NOTIFICATIONS`` runtime
|
|
@@ -36,17 +37,9 @@ Example:
|
|
|
36
37
|
|
|
37
38
|
from __future__ import annotations
|
|
38
39
|
|
|
39
|
-
import
|
|
40
|
-
from typing import Any, Dict, List, Optional
|
|
40
|
+
from typing import Any, Optional
|
|
41
41
|
|
|
42
|
-
from
|
|
43
|
-
from ..runtime import reject_future, resolve_future
|
|
44
|
-
from ..utils import IS_ANDROID, IS_IOS
|
|
45
|
-
|
|
46
|
-
# Delayed Android deliveries in flight, keyed by identifier, so
|
|
47
|
-
# ``cancel`` can abort a notification that hasn't posted yet. (iOS
|
|
48
|
-
# delivers delays natively through UNTimeIntervalNotificationTrigger.)
|
|
49
|
-
_android_delayed: Dict[str, "asyncio.Task[Any]"] = {}
|
|
42
|
+
from .registry import NativeModuleError, native_module
|
|
50
43
|
|
|
51
44
|
|
|
52
45
|
class Notifications:
|
|
@@ -58,17 +51,17 @@ class Notifications:
|
|
|
58
51
|
|
|
59
52
|
On Android 12 and below the manifest declaration is sufficient
|
|
60
53
|
and this returns ``True`` without prompting. On Android 13+
|
|
61
|
-
(API 33) the ``POST_NOTIFICATIONS`` runtime permission
|
|
62
|
-
|
|
63
|
-
which shows the system prompt if the user hasn't decided yet.
|
|
54
|
+
(API 33) the ``POST_NOTIFICATIONS`` runtime permission prompt
|
|
55
|
+
is shown if the user hasn't decided yet.
|
|
64
56
|
|
|
65
57
|
Returns:
|
|
66
58
|
``True`` if granted (or no prompt is needed), ``False``
|
|
67
59
|
otherwise.
|
|
68
60
|
"""
|
|
69
|
-
|
|
70
|
-
return await
|
|
71
|
-
|
|
61
|
+
try:
|
|
62
|
+
return bool(await native_module("Notifications").call_async("request_permission"))
|
|
63
|
+
except Exception:
|
|
64
|
+
return False
|
|
72
65
|
|
|
73
66
|
@staticmethod
|
|
74
67
|
async def schedule(
|
|
@@ -89,42 +82,34 @@ class Notifications:
|
|
|
89
82
|
identifier: Stable ID used by
|
|
90
83
|
[`cancel`][pythonnative.native_modules.notifications.Notifications.cancel]
|
|
91
84
|
to target this notification.
|
|
92
|
-
**options:
|
|
93
|
-
``badge``,
|
|
85
|
+
**options: Forwarded to the native module (``sound``,
|
|
86
|
+
``badge``, ...). Unknown keys are ignored.
|
|
94
87
|
|
|
95
88
|
Returns:
|
|
96
89
|
``True`` on success, ``False`` if the underlying native
|
|
97
90
|
call failed or (on Android 13+) the user denied the
|
|
98
91
|
notification permission.
|
|
99
92
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return await asyncio.to_thread(_android_schedule, title, body, identifier)
|
|
113
|
-
return await asyncio.to_thread(_ios_schedule, title, body, delay_seconds, identifier)
|
|
93
|
+
try:
|
|
94
|
+
result = await native_module("Notifications").call_async(
|
|
95
|
+
"schedule",
|
|
96
|
+
title=title,
|
|
97
|
+
body=body,
|
|
98
|
+
delay_seconds=float(delay_seconds),
|
|
99
|
+
identifier=identifier,
|
|
100
|
+
**options,
|
|
101
|
+
)
|
|
102
|
+
except Exception:
|
|
103
|
+
return False
|
|
104
|
+
return bool(result)
|
|
114
105
|
|
|
115
106
|
@staticmethod
|
|
116
107
|
async def cancel(identifier: str = "default") -> None:
|
|
117
|
-
"""Cancel a pending notification by its identifier.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"""
|
|
123
|
-
if IS_ANDROID:
|
|
124
|
-
_cancel_android_delayed(identifier)
|
|
125
|
-
await asyncio.to_thread(_android_cancel, identifier)
|
|
126
|
-
return
|
|
127
|
-
await asyncio.to_thread(_ios_cancel, identifier)
|
|
108
|
+
"""Cancel a pending notification by its identifier."""
|
|
109
|
+
try:
|
|
110
|
+
await native_module("Notifications").call_async("cancel", identifier=identifier)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
128
113
|
|
|
129
114
|
@staticmethod
|
|
130
115
|
async def get_device_token() -> Optional[str]:
|
|
@@ -144,214 +129,8 @@ class Notifications:
|
|
|
144
129
|
RuntimeError: If APNs registration fails, with the native
|
|
145
130
|
error description.
|
|
146
131
|
"""
|
|
147
|
-
if not IS_IOS:
|
|
148
|
-
return None
|
|
149
|
-
return await _ios_get_device_token()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# ======================================================================
|
|
153
|
-
# Android implementation
|
|
154
|
-
# ======================================================================
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
async def _android_request_permission() -> bool:
|
|
158
|
-
"""Request ``POST_NOTIFICATIONS`` on Android 13+; older versions skip.
|
|
159
|
-
|
|
160
|
-
Delegates to the shared
|
|
161
|
-
[`Permissions`][pythonnative.Permissions] machinery, which routes
|
|
162
|
-
through the activity's ``requestPermissions`` and resolves once
|
|
163
|
-
``onRequestPermissionsResult`` arrives. Below API 33 there's no
|
|
164
|
-
runtime permission, so this resolves ``True`` immediately.
|
|
165
|
-
"""
|
|
166
|
-
try:
|
|
167
|
-
from java import jclass
|
|
168
|
-
|
|
169
|
-
if int(jclass("android.os.Build$VERSION").SDK_INT) < 33:
|
|
170
|
-
return True
|
|
171
|
-
except Exception:
|
|
172
|
-
return True
|
|
173
|
-
from .permissions import GRANTED, Permissions
|
|
174
|
-
|
|
175
|
-
return await Permissions.request("notifications") == GRANTED
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def _cancel_android_delayed(identifier: str) -> None:
|
|
179
|
-
pending = _android_delayed.pop(identifier, None)
|
|
180
|
-
if pending is not None and not pending.done():
|
|
181
|
-
pending.cancel()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
async def _android_schedule_later(title: str, body: str, delay_seconds: float, identifier: str) -> None:
|
|
185
|
-
"""Post an Android notification after ``delay_seconds`` on the framework loop.
|
|
186
|
-
|
|
187
|
-
The delay lives in the app process (matching the semantics of
|
|
188
|
-
``UNTimeIntervalNotificationTrigger`` closely enough for in-app
|
|
189
|
-
reminders); notifications whose delay outlives the process need
|
|
190
|
-
``AlarmManager``, which is out of scope for the built-in module.
|
|
191
|
-
"""
|
|
192
|
-
try:
|
|
193
|
-
await asyncio.sleep(delay_seconds)
|
|
194
|
-
await asyncio.to_thread(_android_schedule, title, body, identifier)
|
|
195
|
-
finally:
|
|
196
|
-
_android_delayed.pop(identifier, None)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def _android_schedule(title: str, body: str, identifier: str) -> bool:
|
|
200
|
-
try:
|
|
201
|
-
from java import jclass
|
|
202
|
-
|
|
203
|
-
from ..utils import get_android_context
|
|
204
|
-
|
|
205
|
-
ctx = get_android_context()
|
|
206
|
-
nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
|
|
207
|
-
channel_id = "pn_default"
|
|
208
|
-
NotificationChannel = jclass("android.app.NotificationChannel")
|
|
209
|
-
channel = NotificationChannel(channel_id, "PythonNative", 3) # IMPORTANCE_DEFAULT
|
|
210
|
-
nm.createNotificationChannel(channel)
|
|
211
|
-
|
|
212
|
-
Builder = jclass("android.app.Notification$Builder")
|
|
213
|
-
builder = Builder(ctx, channel_id)
|
|
214
|
-
builder.setContentTitle(title)
|
|
215
|
-
builder.setContentText(body)
|
|
216
|
-
builder.setSmallIcon(jclass("android.R$drawable").ic_dialog_info)
|
|
217
|
-
nm.notify(abs(hash(identifier)) % (2**31), builder.build())
|
|
218
|
-
return True
|
|
219
|
-
except Exception:
|
|
220
|
-
return False
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _android_cancel(identifier: str) -> None:
|
|
224
|
-
try:
|
|
225
|
-
from java import jclass
|
|
226
|
-
|
|
227
|
-
from ..utils import get_android_context
|
|
228
|
-
|
|
229
|
-
ctx = get_android_context()
|
|
230
|
-
nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
|
|
231
|
-
nm.cancel(abs(hash(identifier)) % (2**31))
|
|
232
|
-
except Exception:
|
|
233
|
-
diagnostics.swallowed("notifications._android_cancel")
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# ======================================================================
|
|
237
|
-
# iOS implementation
|
|
238
|
-
# ======================================================================
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
async def _ios_request_permission() -> bool:
|
|
242
|
-
loop = asyncio.get_running_loop()
|
|
243
|
-
future: asyncio.Future[bool] = loop.create_future()
|
|
244
|
-
try:
|
|
245
|
-
from rubicon.objc import Block, ObjCClass
|
|
246
|
-
|
|
247
|
-
center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
|
|
248
|
-
|
|
249
|
-
# Build a block that mirrors the UNNotificationCenter signature
|
|
250
|
-
# ``void(^)(BOOL granted, NSError* error)``.
|
|
251
|
-
def _completion(granted: bool, _error: Any) -> None:
|
|
252
|
-
resolve_future(future, bool(granted))
|
|
253
|
-
|
|
254
132
|
try:
|
|
255
|
-
|
|
256
|
-
except
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
resolve_future(future, True)
|
|
260
|
-
return await future
|
|
261
|
-
|
|
262
|
-
center.requestAuthorizationWithOptions_completionHandler_(0x07, block)
|
|
263
|
-
except Exception:
|
|
264
|
-
resolve_future(future, False)
|
|
265
|
-
return await future
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def _ios_schedule(title: str, body: str, delay_seconds: float, identifier: str) -> bool:
|
|
269
|
-
try:
|
|
270
|
-
from rubicon.objc import ObjCClass
|
|
271
|
-
|
|
272
|
-
content = ObjCClass("UNMutableNotificationContent").alloc().init()
|
|
273
|
-
content.setTitle_(title)
|
|
274
|
-
content.setBody_(body)
|
|
275
|
-
|
|
276
|
-
interval = float(delay_seconds) if delay_seconds > 0 else 1.0
|
|
277
|
-
trigger = ObjCClass("UNTimeIntervalNotificationTrigger").triggerWithTimeInterval_repeats_(interval, False)
|
|
278
|
-
request = ObjCClass("UNNotificationRequest").requestWithIdentifier_content_trigger_(
|
|
279
|
-
identifier, content, trigger
|
|
280
|
-
)
|
|
281
|
-
center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
|
|
282
|
-
center.addNotificationRequest_withCompletionHandler_(request, None)
|
|
283
|
-
return True
|
|
284
|
-
except Exception:
|
|
285
|
-
return False
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def _ios_cancel(identifier: str) -> None:
|
|
289
|
-
try:
|
|
290
|
-
from rubicon.objc import ObjCClass
|
|
291
|
-
|
|
292
|
-
center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
|
|
293
|
-
NSArray = ObjCClass("NSArray")
|
|
294
|
-
arr = NSArray.arrayWithObject_(identifier)
|
|
295
|
-
center.removePendingNotificationRequestsWithIdentifiers_(arr)
|
|
296
|
-
except Exception:
|
|
297
|
-
diagnostics.swallowed("notifications._ios_cancel")
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# ----------------------------------------------------------------------
|
|
301
|
-
# Remote notifications (APNs)
|
|
302
|
-
# ----------------------------------------------------------------------
|
|
303
|
-
#
|
|
304
|
-
# The registration result arrives through UIApplicationDelegate
|
|
305
|
-
# callbacks in the native template, which forward here via
|
|
306
|
-
# dispatch_device_token / dispatch_device_token_error. The token is
|
|
307
|
-
# cached so later get_device_token() calls resolve immediately.
|
|
308
|
-
|
|
309
|
-
_device_token: Optional[str] = None
|
|
310
|
-
_device_token_error: Optional[str] = None
|
|
311
|
-
_token_waiters: List["asyncio.Future[str]"] = []
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
async def _ios_get_device_token() -> str:
|
|
315
|
-
if _device_token is not None:
|
|
316
|
-
return _device_token
|
|
317
|
-
if _device_token_error is not None:
|
|
318
|
-
raise RuntimeError(f"APNs registration failed: {_device_token_error}")
|
|
319
|
-
|
|
320
|
-
loop = asyncio.get_running_loop()
|
|
321
|
-
future: asyncio.Future[str] = loop.create_future()
|
|
322
|
-
_token_waiters.append(future)
|
|
323
|
-
|
|
324
|
-
def _register() -> None:
|
|
325
|
-
from rubicon.objc import ObjCClass
|
|
326
|
-
|
|
327
|
-
ObjCClass("UIApplication").sharedApplication.registerForRemoteNotifications()
|
|
328
|
-
|
|
329
|
-
try:
|
|
330
|
-
# registerForRemoteNotifications must run on the main thread.
|
|
331
|
-
from ..runtime import call_on_main_thread
|
|
332
|
-
|
|
333
|
-
call_on_main_thread(_register)
|
|
334
|
-
except Exception as exc:
|
|
335
|
-
_token_waiters.remove(future)
|
|
336
|
-
raise RuntimeError(f"Could not start APNs registration: {exc}") from exc
|
|
337
|
-
return await future
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def dispatch_device_token(token: str) -> None:
|
|
341
|
-
"""Deliver the APNs device token from the native host."""
|
|
342
|
-
global _device_token, _device_token_error
|
|
343
|
-
_device_token = token
|
|
344
|
-
_device_token_error = None
|
|
345
|
-
waiters, _token_waiters[:] = list(_token_waiters), []
|
|
346
|
-
for future in waiters:
|
|
347
|
-
resolve_future(future, token)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
def dispatch_device_token_error(message: str) -> None:
|
|
351
|
-
"""Deliver an APNs registration failure from the native host."""
|
|
352
|
-
global _device_token_error
|
|
353
|
-
_device_token_error = message
|
|
354
|
-
waiters, _token_waiters[:] = list(_token_waiters), []
|
|
355
|
-
error = RuntimeError(f"APNs registration failed: {message}")
|
|
356
|
-
for future in waiters:
|
|
357
|
-
reject_future(future, error)
|
|
133
|
+
token = await native_module("Notifications").call_async("get_device_token")
|
|
134
|
+
except NativeModuleError as exc:
|
|
135
|
+
raise RuntimeError(f"APNs registration failed: {exc.message}") from exc
|
|
136
|
+
return str(token) if token else None
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Runtime permission checks and requests.
|
|
2
2
|
|
|
3
3
|
[`Permissions`][pythonnative.Permissions] normalizes the very different
|
|
4
|
-
iOS and Android permission models behind two calls
|
|
4
|
+
iOS and Android permission models behind two calls, both served by the
|
|
5
|
+
native ``Permissions`` module:
|
|
5
6
|
|
|
6
7
|
- ``check(permission)``: synchronous, returns a status string without
|
|
7
8
|
prompting.
|
|
@@ -18,12 +19,7 @@ Unknown names resolve to ``"undetermined"``.
|
|
|
18
19
|
|
|
19
20
|
from __future__ import annotations
|
|
20
21
|
|
|
21
|
-
import
|
|
22
|
-
from typing import Any, Callable, Dict
|
|
23
|
-
|
|
24
|
-
from .. import diagnostics
|
|
25
|
-
from ..runtime import resolve_future
|
|
26
|
-
from ..utils import IS_ANDROID, IS_IOS
|
|
22
|
+
from .registry import native_module
|
|
27
23
|
|
|
28
24
|
PermissionStatus = str
|
|
29
25
|
PermissionName = str
|
|
@@ -33,15 +29,15 @@ DENIED = "denied"
|
|
|
33
29
|
BLOCKED = "blocked"
|
|
34
30
|
UNDETERMINED = "undetermined"
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
_STATUSES = (GRANTED, DENIED, BLOCKED, UNDETERMINED)
|
|
33
|
+
|
|
34
|
+
PERMISSION_NAMES = ("camera", "microphone", "location", "photos", "notifications", "contacts")
|
|
35
|
+
"""Permission names understood by every platform."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _coerce(value: object) -> PermissionStatus:
|
|
39
|
+
status = str(value) if value is not None else UNDETERMINED
|
|
40
|
+
return status if status in _STATUSES else UNDETERMINED
|
|
45
41
|
|
|
46
42
|
|
|
47
43
|
class Permissions:
|
|
@@ -50,175 +46,15 @@ class Permissions:
|
|
|
50
46
|
@staticmethod
|
|
51
47
|
def check(permission: PermissionName) -> PermissionStatus:
|
|
52
48
|
"""Return the current status of ``permission`` without prompting."""
|
|
53
|
-
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
return
|
|
57
|
-
return UNDETERMINED
|
|
49
|
+
try:
|
|
50
|
+
return _coerce(native_module("Permissions").call("check", permission=permission))
|
|
51
|
+
except Exception:
|
|
52
|
+
return UNDETERMINED
|
|
58
53
|
|
|
59
54
|
@staticmethod
|
|
60
55
|
async def request(permission: PermissionName) -> PermissionStatus:
|
|
61
56
|
"""Prompt for ``permission`` (if needed) and return the result."""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
resolve_future(future, status)
|
|
67
|
-
|
|
68
|
-
if IS_ANDROID:
|
|
69
|
-
_android_request(permission, _done)
|
|
70
|
-
elif IS_IOS:
|
|
71
|
-
_ios_request(permission, _done)
|
|
72
|
-
else:
|
|
73
|
-
_done(UNDETERMINED)
|
|
74
|
-
|
|
75
|
-
return await future
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# ======================================================================
|
|
79
|
-
# Android
|
|
80
|
-
# ======================================================================
|
|
81
|
-
|
|
82
|
-
_android_pending: Dict[int, Callable[[PermissionStatus], None]] = {}
|
|
83
|
-
_android_next_code = 60001
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _android_check(permission: str) -> PermissionStatus:
|
|
87
|
-
manifest = _ANDROID_PERMS.get(permission)
|
|
88
|
-
if manifest is None:
|
|
89
|
-
return UNDETERMINED
|
|
90
|
-
try:
|
|
91
|
-
from java import jclass
|
|
92
|
-
|
|
93
|
-
from ..utils import get_android_context
|
|
94
|
-
|
|
95
|
-
PackageManager = jclass("android.content.pm.PackageManager")
|
|
96
|
-
ctx = get_android_context()
|
|
97
|
-
granted = ctx.checkSelfPermission(manifest) == PackageManager.PERMISSION_GRANTED
|
|
98
|
-
return GRANTED if granted else DENIED
|
|
99
|
-
except Exception:
|
|
100
|
-
return UNDETERMINED
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _android_request(permission: str, on_done: Callable[[PermissionStatus], None]) -> None:
|
|
104
|
-
manifest = _ANDROID_PERMS.get(permission)
|
|
105
|
-
if manifest is None:
|
|
106
|
-
on_done(UNDETERMINED)
|
|
107
|
-
return
|
|
108
|
-
if _android_check(permission) == GRANTED:
|
|
109
|
-
on_done(GRANTED)
|
|
110
|
-
return
|
|
111
|
-
try:
|
|
112
|
-
global _android_next_code
|
|
113
|
-
from java import jclass
|
|
114
|
-
|
|
115
|
-
from ..utils import get_android_context
|
|
116
|
-
|
|
117
|
-
ctx = get_android_context()
|
|
118
|
-
Activity = jclass("android.app.Activity")
|
|
119
|
-
if not Activity.isInstance(ctx):
|
|
120
|
-
on_done(_android_check(permission))
|
|
121
|
-
return
|
|
122
|
-
code = _android_next_code
|
|
123
|
-
_android_next_code += 1
|
|
124
|
-
_android_pending[code] = on_done
|
|
125
|
-
ctx.requestPermissions([manifest], code)
|
|
126
|
-
except Exception:
|
|
127
|
-
on_done(_android_check(permission))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def deliver_android_permission_result(request_code: int, permissions: Any, grant_results: Any) -> bool:
|
|
131
|
-
"""Forward an ``onRequestPermissionsResult`` to the pending coroutine."""
|
|
132
|
-
cb = _android_pending.pop(request_code, None)
|
|
133
|
-
if cb is None:
|
|
134
|
-
return False
|
|
135
|
-
status = DENIED
|
|
136
|
-
try:
|
|
137
|
-
del permissions
|
|
138
|
-
if grant_results is not None and len(grant_results) > 0 and grant_results[0] == 0:
|
|
139
|
-
status = GRANTED
|
|
140
|
-
except Exception:
|
|
141
|
-
status = DENIED
|
|
142
|
-
try:
|
|
143
|
-
cb(status)
|
|
144
|
-
except Exception:
|
|
145
|
-
diagnostics.swallowed("permissions.deliver_android_permission_result")
|
|
146
|
-
return True
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# ======================================================================
|
|
150
|
-
# iOS
|
|
151
|
-
# ======================================================================
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _ios_media_status(media_type: str) -> PermissionStatus:
|
|
155
|
-
from rubicon.objc import ObjCClass
|
|
156
|
-
|
|
157
|
-
AVCaptureDevice = ObjCClass("AVCaptureDevice")
|
|
158
|
-
status = AVCaptureDevice.authorizationStatusForMediaType_(media_type)
|
|
159
|
-
# 0 notDetermined, 1 restricted, 2 denied, 3 authorized
|
|
160
|
-
return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED}.get(int(status), UNDETERMINED)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _ios_check(permission: str) -> PermissionStatus:
|
|
164
|
-
try:
|
|
165
|
-
if permission == "camera":
|
|
166
|
-
return _ios_media_status("vide")
|
|
167
|
-
if permission == "microphone":
|
|
168
|
-
return _ios_media_status("soun")
|
|
169
|
-
if permission == "photos":
|
|
170
|
-
from rubicon.objc import ObjCClass
|
|
171
|
-
|
|
172
|
-
status = ObjCClass("PHPhotoLibrary").authorizationStatus()
|
|
173
|
-
return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED)
|
|
174
|
-
if permission == "location":
|
|
175
|
-
from rubicon.objc import ObjCClass
|
|
176
|
-
|
|
177
|
-
status = ObjCClass("CLLocationManager").authorizationStatus()
|
|
178
|
-
return {0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED)
|
|
179
|
-
except Exception:
|
|
180
|
-
return UNDETERMINED
|
|
181
|
-
return UNDETERMINED
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _ios_request(permission: str, on_done: Callable[[PermissionStatus], None]) -> None:
|
|
185
|
-
try:
|
|
186
|
-
from rubicon.objc import Block, ObjCClass
|
|
187
|
-
|
|
188
|
-
if permission in ("camera", "microphone"):
|
|
189
|
-
media = "vide" if permission == "camera" else "soun"
|
|
190
|
-
AVCaptureDevice = ObjCClass("AVCaptureDevice")
|
|
191
|
-
|
|
192
|
-
def _granted(ok: bool) -> None:
|
|
193
|
-
on_done(GRANTED if ok else DENIED)
|
|
194
|
-
|
|
195
|
-
AVCaptureDevice.requestAccessForMediaType_completionHandler_(media, Block(_granted, None, bool))
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
if permission == "photos":
|
|
199
|
-
PHPhotoLibrary = ObjCClass("PHPhotoLibrary")
|
|
200
|
-
|
|
201
|
-
def _photos_done(status: int) -> None:
|
|
202
|
-
# 0 notDetermined, 1 restricted, 2 denied, 3 authorized,
|
|
203
|
-
# 4 limited (counts as granted).
|
|
204
|
-
on_done({0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED))
|
|
205
|
-
|
|
206
|
-
PHPhotoLibrary.requestAuthorization_(Block(_photos_done, None, int))
|
|
207
|
-
return
|
|
208
|
-
|
|
209
|
-
if permission == "notifications":
|
|
210
|
-
center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
|
|
211
|
-
|
|
212
|
-
def _notif_done(granted: bool, _error: Any) -> None:
|
|
213
|
-
on_done(GRANTED if granted else DENIED)
|
|
214
|
-
|
|
215
|
-
# 0x07 = badge | sound | alert.
|
|
216
|
-
center.requestAuthorizationWithOptions_completionHandler_(0x07, Block(_notif_done, None, bool, object))
|
|
217
|
-
return
|
|
218
|
-
except Exception:
|
|
219
|
-
diagnostics.swallowed("permissions._ios_request")
|
|
220
|
-
# Fall back to reporting the current status for permissions whose
|
|
221
|
-
# request flow isn't wired up natively (location needs a
|
|
222
|
-
# CLLocationManager delegate; use the Location module's own
|
|
223
|
-
# request path for that).
|
|
224
|
-
on_done(_ios_check(permission))
|
|
57
|
+
try:
|
|
58
|
+
return _coerce(await native_module("Permissions").call_async("request", permission=permission))
|
|
59
|
+
except Exception:
|
|
60
|
+
return UNDETERMINED
|