pythonnative 0.24.0__py3-none-any.whl → 0.25.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 +16 -4
- pythonnative/animated.py +2 -2
- pythonnative/components.py +59 -0
- pythonnative/hooks.py +364 -81
- pythonnative/native_modules/notifications.py +36 -4
- pythonnative/native_modules/permissions.py +24 -1
- pythonnative/native_views/android.py +7 -2
- pythonnative/native_views/ios.py +12 -1
- pythonnative/reconciler.py +518 -37
- pythonnative/runtime.py +407 -215
- pythonnative/screen.py +53 -7
- pythonnative/storage.py +2 -2
- pythonnative/suspense.py +417 -0
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/METADATA +2 -1
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/RECORD +19 -18
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/WHEEL +1 -1
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.24.0.dist-info → pythonnative-0.25.0.dist-info}/top_level.txt +0 -0
|
@@ -28,11 +28,16 @@ Example:
|
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
|
|
30
30
|
import asyncio
|
|
31
|
-
from typing import Any
|
|
31
|
+
from typing import Any, Dict
|
|
32
32
|
|
|
33
33
|
from ..runtime import resolve_future
|
|
34
34
|
from ..utils import IS_ANDROID
|
|
35
35
|
|
|
36
|
+
# Delayed Android deliveries in flight, keyed by identifier, so
|
|
37
|
+
# ``cancel`` can abort a notification that hasn't posted yet. (iOS
|
|
38
|
+
# delivers delays natively through UNTimeIntervalNotificationTrigger.)
|
|
39
|
+
_android_delayed: Dict[str, "asyncio.Task[Any]"] = {}
|
|
40
|
+
|
|
36
41
|
|
|
37
42
|
class Notifications:
|
|
38
43
|
"""Local notification interface."""
|
|
@@ -82,7 +87,13 @@ class Notifications:
|
|
|
82
87
|
"""
|
|
83
88
|
del options
|
|
84
89
|
if IS_ANDROID:
|
|
85
|
-
|
|
90
|
+
if delay_seconds > 0:
|
|
91
|
+
_cancel_android_delayed(identifier)
|
|
92
|
+
_android_delayed[identifier] = asyncio.ensure_future(
|
|
93
|
+
_android_schedule_later(title, body, delay_seconds, identifier)
|
|
94
|
+
)
|
|
95
|
+
return True
|
|
96
|
+
return await asyncio.to_thread(_android_schedule, title, body, identifier)
|
|
86
97
|
return await asyncio.to_thread(_ios_schedule, title, body, delay_seconds, identifier)
|
|
87
98
|
|
|
88
99
|
@staticmethod
|
|
@@ -94,6 +105,7 @@ class Notifications:
|
|
|
94
105
|
[`schedule`][pythonnative.native_modules.notifications.Notifications.schedule].
|
|
95
106
|
"""
|
|
96
107
|
if IS_ANDROID:
|
|
108
|
+
_cancel_android_delayed(identifier)
|
|
97
109
|
await asyncio.to_thread(_android_cancel, identifier)
|
|
98
110
|
return
|
|
99
111
|
await asyncio.to_thread(_ios_cancel, identifier)
|
|
@@ -104,8 +116,28 @@ class Notifications:
|
|
|
104
116
|
# ======================================================================
|
|
105
117
|
|
|
106
118
|
|
|
107
|
-
def
|
|
108
|
-
|
|
119
|
+
def _cancel_android_delayed(identifier: str) -> None:
|
|
120
|
+
pending = _android_delayed.pop(identifier, None)
|
|
121
|
+
if pending is not None and not pending.done():
|
|
122
|
+
pending.cancel()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def _android_schedule_later(title: str, body: str, delay_seconds: float, identifier: str) -> None:
|
|
126
|
+
"""Post an Android notification after ``delay_seconds`` on the framework loop.
|
|
127
|
+
|
|
128
|
+
The delay lives in the app process (matching the semantics of
|
|
129
|
+
``UNTimeIntervalNotificationTrigger`` closely enough for in-app
|
|
130
|
+
reminders); notifications whose delay outlives the process need
|
|
131
|
+
``AlarmManager``, which is out of scope for the built-in module.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
await asyncio.sleep(delay_seconds)
|
|
135
|
+
await asyncio.to_thread(_android_schedule, title, body, identifier)
|
|
136
|
+
finally:
|
|
137
|
+
_android_delayed.pop(identifier, None)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _android_schedule(title: str, body: str, identifier: str) -> bool:
|
|
109
141
|
try:
|
|
110
142
|
from java import jclass
|
|
111
143
|
|
|
@@ -193,8 +193,31 @@ def _ios_request(permission: str, on_done: Callable[[PermissionStatus], None]) -
|
|
|
193
193
|
|
|
194
194
|
AVCaptureDevice.requestAccessForMediaType_completionHandler_(media, Block(_granted, None, bool))
|
|
195
195
|
return
|
|
196
|
+
|
|
197
|
+
if permission == "photos":
|
|
198
|
+
PHPhotoLibrary = ObjCClass("PHPhotoLibrary")
|
|
199
|
+
|
|
200
|
+
def _photos_done(status: int) -> None:
|
|
201
|
+
# 0 notDetermined, 1 restricted, 2 denied, 3 authorized,
|
|
202
|
+
# 4 limited (counts as granted).
|
|
203
|
+
on_done({0: UNDETERMINED, 1: BLOCKED, 2: DENIED, 3: GRANTED, 4: GRANTED}.get(int(status), UNDETERMINED))
|
|
204
|
+
|
|
205
|
+
PHPhotoLibrary.requestAuthorization_(Block(_photos_done, None, int))
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if permission == "notifications":
|
|
209
|
+
center = ObjCClass("UNUserNotificationCenter").currentNotificationCenter()
|
|
210
|
+
|
|
211
|
+
def _notif_done(granted: bool, _error: Any) -> None:
|
|
212
|
+
on_done(GRANTED if granted else DENIED)
|
|
213
|
+
|
|
214
|
+
# 0x07 = badge | sound | alert.
|
|
215
|
+
center.requestAuthorizationWithOptions_completionHandler_(0x07, Block(_notif_done, None, bool, object))
|
|
216
|
+
return
|
|
196
217
|
except Exception:
|
|
197
218
|
pass
|
|
198
219
|
# Fall back to reporting the current status for permissions whose
|
|
199
|
-
# request flow isn't wired up natively
|
|
220
|
+
# request flow isn't wired up natively (location needs a
|
|
221
|
+
# CLLocationManager delegate; use the Location module's own
|
|
222
|
+
# request path for that).
|
|
200
223
|
on_done(_ios_check(permission))
|
|
@@ -612,11 +612,16 @@ def _make_end_listener(anim_id: int) -> Any:
|
|
|
612
612
|
AnimatorListener = jclass("android.animation.Animator$AnimatorListener")
|
|
613
613
|
|
|
614
614
|
class _EndProxy(dynamic_proxy(AnimatorListener)):
|
|
615
|
+
# ``onAnimationStart`` / ``onAnimationEnd`` accept an optional
|
|
616
|
+
# ``isReverse``: API 26 added default two-arg overloads to
|
|
617
|
+
# ``AnimatorListener``, and ``ValueAnimator`` invokes those, so
|
|
618
|
+
# the java.lang.reflect.Proxy forwards two arguments here.
|
|
619
|
+
|
|
615
620
|
def __init__(self) -> None:
|
|
616
621
|
super().__init__()
|
|
617
622
|
self._cancelled = False
|
|
618
623
|
|
|
619
|
-
def onAnimationStart(self, animation: Any) -> None:
|
|
624
|
+
def onAnimationStart(self, animation: Any, isReverse: bool = False) -> None:
|
|
620
625
|
pass
|
|
621
626
|
|
|
622
627
|
def onAnimationRepeat(self, animation: Any) -> None:
|
|
@@ -625,7 +630,7 @@ def _make_end_listener(anim_id: int) -> Any:
|
|
|
625
630
|
def onAnimationCancel(self, animation: Any) -> None:
|
|
626
631
|
self._cancelled = True
|
|
627
632
|
|
|
628
|
-
def onAnimationEnd(self, animation: Any) -> None:
|
|
633
|
+
def onAnimationEnd(self, animation: Any, isReverse: bool = False) -> None:
|
|
629
634
|
entry = _native_anims.pop(anim_id, None)
|
|
630
635
|
if entry is None:
|
|
631
636
|
return
|
pythonnative/native_views/ios.py
CHANGED
|
@@ -3251,7 +3251,18 @@ class PortalHandler(IOSViewHandler):
|
|
|
3251
3251
|
return
|
|
3252
3252
|
current = overlay.superview
|
|
3253
3253
|
if current is not None and _objc_ptr(current) == _objc_ptr(host):
|
|
3254
|
-
host.
|
|
3254
|
+
# Already attached to the right host. Only reorder when
|
|
3255
|
+
# the overlay is not frontmost: reordering the subview
|
|
3256
|
+
# array during an active touch sequence (a re-render can
|
|
3257
|
+
# flush between touch-down and touch-up) can cancel the
|
|
3258
|
+
# touch and leave the portal's controls stuck
|
|
3259
|
+
# highlighted with their action never fired.
|
|
3260
|
+
try:
|
|
3261
|
+
front = host.subviews.lastObject()
|
|
3262
|
+
if front is None or _objc_ptr(front) != _objc_ptr(overlay):
|
|
3263
|
+
host.bringSubviewToFront_(overlay)
|
|
3264
|
+
except Exception:
|
|
3265
|
+
host.bringSubviewToFront_(overlay)
|
|
3255
3266
|
self._sync_overlay_frame(overlay, host)
|
|
3256
3267
|
return
|
|
3257
3268
|
if current is not None:
|