pythonnative 0.25.0__py3-none-any.whl → 0.27.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 +1 -1
- pythonnative/animated.py +697 -62
- pythonnative/cli/pn.py +172 -12
- pythonnative/components.py +61 -7
- pythonnative/diagnostics.py +27 -0
- pythonnative/gestures.py +446 -34
- pythonnative/native_modules/app_state.py +2 -1
- pythonnative/native_modules/battery.py +3 -2
- pythonnative/native_modules/biometrics.py +2 -1
- pythonnative/native_modules/camera.py +16 -7
- pythonnative/native_modules/clipboard.py +3 -2
- pythonnative/native_modules/file_system.py +3 -2
- pythonnative/native_modules/haptics.py +7 -6
- pythonnative/native_modules/linking.py +62 -6
- pythonnative/native_modules/location.py +11 -8
- pythonnative/native_modules/net_info.py +75 -5
- pythonnative/native_modules/notifications.py +134 -15
- pythonnative/native_modules/permissions.py +3 -2
- pythonnative/native_modules/share.py +2 -1
- pythonnative/native_views/android.py +461 -137
- pythonnative/native_views/desktop.py +816 -96
- pythonnative/native_views/ios.py +750 -193
- pythonnative/project/android.py +15 -3
- pythonnative/project/builder.py +172 -70
- pythonnative/project/config.py +28 -6
- pythonnative/project/devices.py +291 -0
- pythonnative/project/doctor.py +5 -4
- pythonnative/project/ios.py +51 -77
- pythonnative/project/permissions.py +19 -1
- pythonnative/project/runtime_assets.py +78 -166
- pythonnative/reconciler.py +28 -0
- pythonnative/style.py +19 -0
- pythonnative/templates/android_template/app/build.gradle +9 -0
- pythonnative/templates/android_template/app/src/main/AndroidManifest.xml +5 -0
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +148 -12
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/PNFrameLayout.kt +50 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +41 -17
- pythonnative/templates/ios_template/ios_template/BridgingHeader.h +14 -0
- pythonnative/templates/ios_template/ios_template/Info.plist +5 -2
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +397 -0
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +31 -20
- pythonnative/templates/ios_template/ios_template/ViewController.swift +77 -198
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +72 -47
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/METADATA +3 -3
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/RECORD +49 -46
- pythonnative/templates/ios_template/ios_template/Base.lproj/Main.storyboard +0 -24
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.25.0.dist-info → pythonnative-0.27.0.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
"""Cross-platform local notifications.
|
|
1
|
+
"""Cross-platform local notifications and remote push registration.
|
|
2
2
|
|
|
3
3
|
Provides coroutines for requesting permission and scheduling /
|
|
4
4
|
cancelling local push notifications. Uses Android's
|
|
5
5
|
``NotificationManager`` or iOS's ``UNUserNotificationCenter``.
|
|
6
6
|
|
|
7
7
|
On iOS you must ``await Notifications.request_permission()`` before
|
|
8
|
-
scheduling. On Android 13+ the runtime
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
scheduling. On Android 13+ the ``POST_NOTIFICATIONS`` runtime
|
|
9
|
+
permission is requested automatically the first time you schedule a
|
|
10
|
+
notification (the template manifest declares it); earlier Android
|
|
11
|
+
versions don't prompt at all.
|
|
12
|
+
|
|
13
|
+
For remote (server-sent) pushes, enable the ``remote_notifications``
|
|
14
|
+
capability in ``pythonnative.toml`` and call
|
|
15
|
+
``Notifications.get_device_token()`` to register with APNs and receive
|
|
16
|
+
the device token your server needs. Android remote push requires
|
|
17
|
+
Firebase Cloud Messaging, which needs a per-app ``google-services.json``
|
|
18
|
+
and is not wired up by the built-in module; ``get_device_token`` returns
|
|
19
|
+
``None`` there.
|
|
11
20
|
|
|
12
21
|
Example:
|
|
13
22
|
```python
|
|
@@ -28,10 +37,11 @@ Example:
|
|
|
28
37
|
from __future__ import annotations
|
|
29
38
|
|
|
30
39
|
import asyncio
|
|
31
|
-
from typing import Any, Dict
|
|
40
|
+
from typing import Any, Dict, List, Optional
|
|
32
41
|
|
|
33
|
-
from ..
|
|
34
|
-
from ..
|
|
42
|
+
from .. import diagnostics
|
|
43
|
+
from ..runtime import reject_future, resolve_future
|
|
44
|
+
from ..utils import IS_ANDROID, IS_IOS
|
|
35
45
|
|
|
36
46
|
# Delayed Android deliveries in flight, keyed by identifier, so
|
|
37
47
|
# ``cancel`` can abort a notification that hasn't posted yet. (iOS
|
|
@@ -46,17 +56,18 @@ class Notifications:
|
|
|
46
56
|
async def request_permission() -> bool:
|
|
47
57
|
"""Request notification permission from the user.
|
|
48
58
|
|
|
49
|
-
On Android the manifest declaration is
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
On Android 12 and below the manifest declaration is sufficient
|
|
60
|
+
and this returns ``True`` without prompting. On Android 13+
|
|
61
|
+
(API 33) the ``POST_NOTIFICATIONS`` runtime permission is
|
|
62
|
+
requested through [`Permissions`][pythonnative.Permissions],
|
|
63
|
+
which shows the system prompt if the user hasn't decided yet.
|
|
53
64
|
|
|
54
65
|
Returns:
|
|
55
66
|
``True`` if granted (or no prompt is needed), ``False``
|
|
56
67
|
otherwise.
|
|
57
68
|
"""
|
|
58
69
|
if IS_ANDROID:
|
|
59
|
-
return
|
|
70
|
+
return await _android_request_permission()
|
|
60
71
|
return await _ios_request_permission()
|
|
61
72
|
|
|
62
73
|
@staticmethod
|
|
@@ -83,10 +94,15 @@ class Notifications:
|
|
|
83
94
|
|
|
84
95
|
Returns:
|
|
85
96
|
``True`` on success, ``False`` if the underlying native
|
|
86
|
-
call failed
|
|
97
|
+
call failed or (on Android 13+) the user denied the
|
|
98
|
+
notification permission.
|
|
87
99
|
"""
|
|
88
100
|
del options
|
|
89
101
|
if IS_ANDROID:
|
|
102
|
+
# Android 13+ silently drops notifications posted without
|
|
103
|
+
# the POST_NOTIFICATIONS grant, so prompt before posting.
|
|
104
|
+
if not await _android_request_permission():
|
|
105
|
+
return False
|
|
90
106
|
if delay_seconds > 0:
|
|
91
107
|
_cancel_android_delayed(identifier)
|
|
92
108
|
_android_delayed[identifier] = asyncio.ensure_future(
|
|
@@ -110,12 +126,55 @@ class Notifications:
|
|
|
110
126
|
return
|
|
111
127
|
await asyncio.to_thread(_ios_cancel, identifier)
|
|
112
128
|
|
|
129
|
+
@staticmethod
|
|
130
|
+
async def get_device_token() -> Optional[str]:
|
|
131
|
+
"""Register for remote notifications and return the device token.
|
|
132
|
+
|
|
133
|
+
On iOS this calls ``registerForRemoteNotifications`` and waits
|
|
134
|
+
for the APNs callback; the token is a lowercase hex string your
|
|
135
|
+
server passes to APNs. Requires the ``remote_notifications``
|
|
136
|
+
capability (which adds the ``aps-environment`` entitlement) and
|
|
137
|
+
a real device (the simulator has no APNs connection).
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The APNs token, or ``None`` on platforms without built-in
|
|
141
|
+
remote push support (Android and desktop).
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
RuntimeError: If APNs registration fails, with the native
|
|
145
|
+
error description.
|
|
146
|
+
"""
|
|
147
|
+
if not IS_IOS:
|
|
148
|
+
return None
|
|
149
|
+
return await _ios_get_device_token()
|
|
150
|
+
|
|
113
151
|
|
|
114
152
|
# ======================================================================
|
|
115
153
|
# Android implementation
|
|
116
154
|
# ======================================================================
|
|
117
155
|
|
|
118
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
|
+
|
|
119
178
|
def _cancel_android_delayed(identifier: str) -> None:
|
|
120
179
|
pending = _android_delayed.pop(identifier, None)
|
|
121
180
|
if pending is not None and not pending.done():
|
|
@@ -171,7 +230,7 @@ def _android_cancel(identifier: str) -> None:
|
|
|
171
230
|
nm = ctx.getSystemService(jclass("android.content.Context").NOTIFICATION_SERVICE)
|
|
172
231
|
nm.cancel(abs(hash(identifier)) % (2**31))
|
|
173
232
|
except Exception:
|
|
174
|
-
|
|
233
|
+
diagnostics.swallowed("notifications._android_cancel")
|
|
175
234
|
|
|
176
235
|
|
|
177
236
|
# ======================================================================
|
|
@@ -235,4 +294,64 @@ def _ios_cancel(identifier: str) -> None:
|
|
|
235
294
|
arr = NSArray.arrayWithObject_(identifier)
|
|
236
295
|
center.removePendingNotificationRequestsWithIdentifiers_(arr)
|
|
237
296
|
except Exception:
|
|
238
|
-
|
|
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)
|
|
@@ -21,6 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
import asyncio
|
|
22
22
|
from typing import Any, Callable, Dict
|
|
23
23
|
|
|
24
|
+
from .. import diagnostics
|
|
24
25
|
from ..runtime import resolve_future
|
|
25
26
|
from ..utils import IS_ANDROID, IS_IOS
|
|
26
27
|
|
|
@@ -141,7 +142,7 @@ def deliver_android_permission_result(request_code: int, permissions: Any, grant
|
|
|
141
142
|
try:
|
|
142
143
|
cb(status)
|
|
143
144
|
except Exception:
|
|
144
|
-
|
|
145
|
+
diagnostics.swallowed("permissions.deliver_android_permission_result")
|
|
145
146
|
return True
|
|
146
147
|
|
|
147
148
|
|
|
@@ -215,7 +216,7 @@ def _ios_request(permission: str, on_done: Callable[[PermissionStatus], None]) -
|
|
|
215
216
|
center.requestAuthorizationWithOptions_completionHandler_(0x07, Block(_notif_done, None, bool, object))
|
|
216
217
|
return
|
|
217
218
|
except Exception:
|
|
218
|
-
|
|
219
|
+
diagnostics.swallowed("permissions._ios_request")
|
|
219
220
|
# Fall back to reporting the current status for permissions whose
|
|
220
221
|
# request flow isn't wired up natively (location needs a
|
|
221
222
|
# CLLocationManager delegate; use the Location module's own
|
|
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|
|
22
22
|
import asyncio
|
|
23
23
|
from typing import Any, Callable, Dict, Optional
|
|
24
24
|
|
|
25
|
+
from .. import diagnostics
|
|
25
26
|
from ..runtime import resolve_future
|
|
26
27
|
from ..utils import IS_ANDROID, IS_IOS
|
|
27
28
|
|
|
@@ -102,7 +103,7 @@ def _ios_share(
|
|
|
102
103
|
try:
|
|
103
104
|
on_done(bool(completed))
|
|
104
105
|
except Exception:
|
|
105
|
-
|
|
106
|
+
diagnostics.swallowed("share._ios_share._completion")
|
|
106
107
|
|
|
107
108
|
controller.setCompletionWithItemsHandler_(
|
|
108
109
|
Block(_completion, None, ObjCClass("NSString"), Block, ObjCClass("NSArray"), ObjCClass("NSError"))
|