pythonnative 0.16.0__py3-none-any.whl → 0.17.1__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 +26 -2
- pythonnative/alerts.py +254 -68
- pythonnative/animated.py +224 -180
- pythonnative/hooks.py +271 -1
- pythonnative/layout.py +35 -1
- pythonnative/native_modules/camera.py +118 -121
- pythonnative/native_modules/file_system.py +3 -3
- pythonnative/native_modules/location.py +90 -109
- pythonnative/native_modules/notifications.py +148 -126
- pythonnative/native_views/android.py +187 -66
- pythonnative/native_views/ios.py +133 -42
- pythonnative/navigation.py +57 -2
- pythonnative/net.py +244 -0
- pythonnative/reconciler.py +38 -0
- pythonnative/runtime.py +487 -0
- pythonnative/screen.py +52 -3
- pythonnative/storage.py +409 -0
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/METADATA +3 -1
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/RECORD +23 -20
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/WHEEL +0 -0
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.16.0.dist-info → pythonnative-0.17.1.dist-info}/top_level.txt +0 -0
pythonnative/__init__.py
CHANGED
|
@@ -51,9 +51,9 @@ Example:
|
|
|
51
51
|
```
|
|
52
52
|
"""
|
|
53
53
|
|
|
54
|
-
__version__ = "0.
|
|
54
|
+
__version__ = "0.17.1"
|
|
55
55
|
|
|
56
|
-
from . import sdk
|
|
56
|
+
from . import runtime, sdk
|
|
57
57
|
from .alerts import Alert
|
|
58
58
|
from .animated import Animated, AnimatedValue, use_animated_value
|
|
59
59
|
from .components import (
|
|
@@ -103,17 +103,23 @@ from .components import (
|
|
|
103
103
|
)
|
|
104
104
|
from .element import Element
|
|
105
105
|
from .hooks import (
|
|
106
|
+
MutationCall,
|
|
107
|
+
MutationState,
|
|
106
108
|
Provider,
|
|
109
|
+
QueryResult,
|
|
107
110
|
batch_updates,
|
|
108
111
|
component,
|
|
109
112
|
create_context,
|
|
110
113
|
memo,
|
|
114
|
+
use_async_effect,
|
|
111
115
|
use_callback,
|
|
112
116
|
use_context,
|
|
113
117
|
use_effect,
|
|
114
118
|
use_keyboard_height,
|
|
115
119
|
use_memo,
|
|
120
|
+
use_mutation,
|
|
116
121
|
use_navigation,
|
|
122
|
+
use_query,
|
|
117
123
|
use_reducer,
|
|
118
124
|
use_ref,
|
|
119
125
|
use_safe_area_insets,
|
|
@@ -129,7 +135,9 @@ from .navigation import (
|
|
|
129
135
|
use_focus_effect,
|
|
130
136
|
use_route,
|
|
131
137
|
)
|
|
138
|
+
from .net import HTTPError, Response, fetch
|
|
132
139
|
from .platform import Platform
|
|
140
|
+
from .runtime import run_async
|
|
133
141
|
from .screen import create_screen
|
|
134
142
|
from .sdk import (
|
|
135
143
|
Props,
|
|
@@ -138,6 +146,7 @@ from .sdk import (
|
|
|
138
146
|
native_component,
|
|
139
147
|
register_component,
|
|
140
148
|
)
|
|
149
|
+
from .storage import AsyncStorage, use_persisted_state
|
|
141
150
|
from .style import (
|
|
142
151
|
AlignItems,
|
|
143
152
|
AlignSelf,
|
|
@@ -219,13 +228,20 @@ __all__ = [
|
|
|
219
228
|
"component",
|
|
220
229
|
"create_context",
|
|
221
230
|
"memo",
|
|
231
|
+
"MutationCall",
|
|
232
|
+
"MutationState",
|
|
233
|
+
"QueryResult",
|
|
234
|
+
"use_async_effect",
|
|
222
235
|
"use_callback",
|
|
223
236
|
"use_context",
|
|
224
237
|
"use_effect",
|
|
225
238
|
"use_focus_effect",
|
|
226
239
|
"use_keyboard_height",
|
|
227
240
|
"use_memo",
|
|
241
|
+
"use_mutation",
|
|
228
242
|
"use_navigation",
|
|
243
|
+
"use_persisted_state",
|
|
244
|
+
"use_query",
|
|
229
245
|
"use_reducer",
|
|
230
246
|
"use_ref",
|
|
231
247
|
"use_route",
|
|
@@ -274,6 +290,14 @@ __all__ = [
|
|
|
274
290
|
"FileSystem",
|
|
275
291
|
"Location",
|
|
276
292
|
"Notifications",
|
|
293
|
+
# Networking + persistence
|
|
294
|
+
"AsyncStorage",
|
|
295
|
+
"fetch",
|
|
296
|
+
"HTTPError",
|
|
297
|
+
"Response",
|
|
298
|
+
# Runtime
|
|
299
|
+
"run_async",
|
|
300
|
+
"runtime",
|
|
277
301
|
# Platform
|
|
278
302
|
"Platform",
|
|
279
303
|
# Custom-component SDK
|
pythonnative/alerts.py
CHANGED
|
@@ -1,112 +1,298 @@
|
|
|
1
|
-
"""Imperative system alerts.
|
|
1
|
+
"""Imperative, awaitable system alerts.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Inspired by React Native's ``Alert.alert()`` but designed around
|
|
4
|
+
``async`` / ``await`` instead of per-button callbacks. There are three
|
|
5
|
+
entry points:
|
|
6
|
+
|
|
7
|
+
- [`Alert.show`][pythonnative.alerts.Alert.show]: fire-and-forget
|
|
8
|
+
one-button notice (no return value).
|
|
9
|
+
- [`Alert.confirm`][pythonnative.alerts.Alert.confirm]: awaitable
|
|
10
|
+
two-button yes/no, resolves to a ``bool``.
|
|
11
|
+
- [`Alert.choose`][pythonnative.alerts.Alert.choose]: awaitable
|
|
12
|
+
multi-button picker / action sheet, resolves to the selected
|
|
13
|
+
label (or ``None`` if dismissed).
|
|
6
14
|
|
|
7
15
|
Example:
|
|
8
16
|
```python
|
|
9
17
|
import pythonnative as pn
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
|
|
20
|
+
async def maybe_delete():
|
|
21
|
+
if await pn.Alert.confirm(
|
|
13
22
|
title="Delete item?",
|
|
14
23
|
message="This action cannot be undone.",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"style": "destructive",
|
|
20
|
-
"on_press": delete_item,
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
)
|
|
24
|
+
confirm_label="Delete",
|
|
25
|
+
cancel_label="Keep",
|
|
26
|
+
):
|
|
27
|
+
await delete_item()
|
|
24
28
|
```
|
|
25
29
|
"""
|
|
26
30
|
|
|
27
31
|
from __future__ import annotations
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
import asyncio
|
|
34
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
30
35
|
|
|
31
36
|
from .platform import Platform
|
|
37
|
+
from .runtime import resolve_future
|
|
38
|
+
|
|
39
|
+
# ======================================================================
|
|
40
|
+
# Internal dispatch helpers
|
|
41
|
+
# ======================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _dispatch_alert(
|
|
45
|
+
*,
|
|
46
|
+
title: str,
|
|
47
|
+
message: Optional[str],
|
|
48
|
+
buttons: List[Dict[str, Any]],
|
|
49
|
+
style: str,
|
|
50
|
+
on_result: Any,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Route an alert request to the active platform presenter.
|
|
53
|
+
|
|
54
|
+
``buttons`` is a list of ``{"label": str, "style":
|
|
55
|
+
"default"|"cancel"|"destructive"}`` dicts. The presenter must
|
|
56
|
+
invoke ``on_result(index)`` exactly once when the user picks a
|
|
57
|
+
button, or ``on_result(-1)`` if the dialog is dismissed without a
|
|
58
|
+
selection. ``on_result`` may run on any thread.
|
|
59
|
+
"""
|
|
60
|
+
if Platform.is_ios:
|
|
61
|
+
try:
|
|
62
|
+
from .native_views.ios import _present_alert as _ios_present_alert
|
|
63
|
+
|
|
64
|
+
_ios_present_alert(
|
|
65
|
+
title=title,
|
|
66
|
+
message=message,
|
|
67
|
+
buttons=buttons,
|
|
68
|
+
style=style,
|
|
69
|
+
on_result=on_result,
|
|
70
|
+
)
|
|
71
|
+
return
|
|
72
|
+
except Exception:
|
|
73
|
+
on_result(-1)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
if Platform.is_android:
|
|
77
|
+
try:
|
|
78
|
+
from .native_views.android import _present_alert as _android_present_alert
|
|
79
|
+
|
|
80
|
+
_android_present_alert(
|
|
81
|
+
title=title,
|
|
82
|
+
message=message,
|
|
83
|
+
buttons=buttons,
|
|
84
|
+
style=style,
|
|
85
|
+
on_result=on_result,
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
except Exception:
|
|
89
|
+
on_result(-1)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Test backend: record the call so unit tests can assert on it,
|
|
93
|
+
# then deliver the configured response.
|
|
94
|
+
Alert._test_log.append(
|
|
95
|
+
{
|
|
96
|
+
"title": title,
|
|
97
|
+
"message": message,
|
|
98
|
+
"buttons": list(buttons),
|
|
99
|
+
"style": style,
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
response = Alert._next_test_response()
|
|
103
|
+
on_result(response)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ======================================================================
|
|
107
|
+
# Public Alert API
|
|
108
|
+
# ======================================================================
|
|
32
109
|
|
|
33
110
|
|
|
34
111
|
class Alert:
|
|
35
112
|
"""Imperative alert / action-sheet helper.
|
|
36
113
|
|
|
37
|
-
All methods are static. Use `Alert.show
|
|
38
|
-
|
|
114
|
+
All methods are static. Use [`show`][pythonnative.alerts.Alert.show]
|
|
115
|
+
for a fire-and-forget single-button notice,
|
|
116
|
+
[`confirm`][pythonnative.alerts.Alert.confirm] for an awaitable
|
|
117
|
+
yes/no dialog, and
|
|
118
|
+
[`choose`][pythonnative.alerts.Alert.choose] for a multi-option
|
|
119
|
+
picker.
|
|
39
120
|
"""
|
|
40
121
|
|
|
122
|
+
#: Records every alert call when running off-device. Tests reset
|
|
123
|
+
#: this between cases via ``Alert._test_log.clear()``. Each entry
|
|
124
|
+
#: contains ``title``, ``message``, ``buttons``, and ``style``.
|
|
125
|
+
_test_log: List[Dict[str, Any]] = []
|
|
126
|
+
|
|
127
|
+
#: Queue of indices to deliver to upcoming alerts in tests. Set via
|
|
128
|
+
#: [`Alert.set_test_response`][pythonnative.alerts.Alert.set_test_response].
|
|
129
|
+
#: A negative value (or empty queue) simulates a dismiss.
|
|
130
|
+
_test_responses: List[int] = []
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def set_test_response(*indices: int) -> None:
|
|
134
|
+
"""Queue indices to return from upcoming test alerts.
|
|
135
|
+
|
|
136
|
+
Use in async tests to script the user's choices: each pending
|
|
137
|
+
call to [`confirm`][pythonnative.alerts.Alert.confirm] or
|
|
138
|
+
[`choose`][pythonnative.alerts.Alert.choose] pops the next
|
|
139
|
+
queued index. Pass ``-1`` to simulate a dismiss.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
*indices: Sequence of button indices to deliver, oldest
|
|
143
|
+
first. Calls beyond the queue length resolve to ``-1``.
|
|
144
|
+
"""
|
|
145
|
+
Alert._test_responses[:] = list(indices)
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def _next_test_response() -> int:
|
|
149
|
+
if Alert._test_responses:
|
|
150
|
+
return Alert._test_responses.pop(0)
|
|
151
|
+
return -1
|
|
152
|
+
|
|
41
153
|
@staticmethod
|
|
42
154
|
def show(
|
|
43
|
-
*,
|
|
44
155
|
title: str,
|
|
45
156
|
message: Optional[str] = None,
|
|
46
|
-
|
|
47
|
-
|
|
157
|
+
*,
|
|
158
|
+
button: str = "OK",
|
|
48
159
|
) -> None:
|
|
49
|
-
"""
|
|
160
|
+
"""Display a simple, one-button alert and return immediately.
|
|
50
161
|
|
|
51
162
|
Args:
|
|
52
|
-
title: Dialog title
|
|
163
|
+
title: Dialog title.
|
|
53
164
|
message: Optional body text.
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
Defaults to a single "OK" button.
|
|
57
|
-
style: ``"alert"`` (default) or ``"action_sheet"``.
|
|
58
|
-
|
|
59
|
-
On iOS this uses ``UIAlertController``; on Android it uses
|
|
60
|
-
``AlertDialog.Builder``. On the test backend the call is a
|
|
61
|
-
no-op so unit tests don't need to mock UIKit/AndroidX.
|
|
62
|
-
"""
|
|
63
|
-
if Platform.is_ios:
|
|
64
|
-
try:
|
|
65
|
-
from .native_views.ios import _present_alert as _ios_present_alert
|
|
165
|
+
button: Label for the single dismiss button (default
|
|
166
|
+
``"OK"``).
|
|
66
167
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
# Test environment: record the call so unit tests can assert on it.
|
|
80
|
-
Alert._test_log.append(
|
|
81
|
-
{
|
|
82
|
-
"title": title,
|
|
83
|
-
"message": message,
|
|
84
|
-
"buttons": list(buttons or []),
|
|
85
|
-
"style": style,
|
|
86
|
-
}
|
|
168
|
+
This is fire-and-forget. To know what the user did, use
|
|
169
|
+
[`confirm`][pythonnative.alerts.Alert.confirm] or
|
|
170
|
+
[`choose`][pythonnative.alerts.Alert.choose] and ``await``
|
|
171
|
+
the result.
|
|
172
|
+
"""
|
|
173
|
+
_dispatch_alert(
|
|
174
|
+
title=title,
|
|
175
|
+
message=message,
|
|
176
|
+
buttons=[{"label": button, "style": "default"}],
|
|
177
|
+
style="alert",
|
|
178
|
+
on_result=lambda _idx: None,
|
|
87
179
|
)
|
|
88
180
|
|
|
89
181
|
@staticmethod
|
|
90
|
-
def confirm(
|
|
91
|
-
*,
|
|
182
|
+
async def confirm(
|
|
92
183
|
title: str,
|
|
93
184
|
message: Optional[str] = None,
|
|
185
|
+
*,
|
|
94
186
|
confirm_label: str = "OK",
|
|
95
187
|
cancel_label: str = "Cancel",
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
188
|
+
) -> bool:
|
|
189
|
+
"""Present a two-button yes/no dialog and wait for the choice.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
title: Dialog title.
|
|
193
|
+
message: Optional body text.
|
|
194
|
+
confirm_label: Label for the "yes" button (default
|
|
195
|
+
``"OK"``).
|
|
196
|
+
cancel_label: Label for the "no" button (default
|
|
197
|
+
``"Cancel"``).
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
``True`` if the user pressed the confirm button, ``False``
|
|
201
|
+
for the cancel button or a dismiss.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
```python
|
|
205
|
+
if await pn.Alert.confirm("Save changes?"):
|
|
206
|
+
await save()
|
|
207
|
+
```
|
|
208
|
+
"""
|
|
209
|
+
loop = asyncio.get_running_loop()
|
|
210
|
+
future: asyncio.Future[bool] = loop.create_future()
|
|
211
|
+
|
|
212
|
+
def _on_result(index: int) -> None:
|
|
213
|
+
resolve_future(future, index == 1)
|
|
214
|
+
|
|
215
|
+
_dispatch_alert(
|
|
101
216
|
title=title,
|
|
102
217
|
message=message,
|
|
103
218
|
buttons=[
|
|
104
|
-
{"label": cancel_label, "style": "cancel"
|
|
105
|
-
{"label": confirm_label, "style": "default"
|
|
219
|
+
{"label": cancel_label, "style": "cancel"},
|
|
220
|
+
{"label": confirm_label, "style": "default"},
|
|
106
221
|
],
|
|
222
|
+
style="alert",
|
|
223
|
+
on_result=_on_result,
|
|
107
224
|
)
|
|
225
|
+
return await future
|
|
108
226
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
227
|
+
@staticmethod
|
|
228
|
+
async def choose(
|
|
229
|
+
title: str,
|
|
230
|
+
options: Sequence[str],
|
|
231
|
+
*,
|
|
232
|
+
message: Optional[str] = None,
|
|
233
|
+
cancel_label: Optional[str] = None,
|
|
234
|
+
style: str = "action_sheet",
|
|
235
|
+
destructive_labels: Sequence[str] = (),
|
|
236
|
+
) -> Optional[str]:
|
|
237
|
+
"""Present a multi-option picker and wait for the user's choice.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
title: Dialog title.
|
|
241
|
+
options: Sequence of option labels (in display order).
|
|
242
|
+
message: Optional body text.
|
|
243
|
+
cancel_label: If provided, adds a "cancel" button with
|
|
244
|
+
this label. Selecting it resolves to ``None``.
|
|
245
|
+
style: ``"action_sheet"`` (default) for an iOS-style
|
|
246
|
+
sheet, or ``"alert"`` for a stacked alert dialog.
|
|
247
|
+
destructive_labels: Labels in ``options`` that should be
|
|
248
|
+
styled destructively (red on iOS).
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
The selected label, or ``None`` if the user dismissed or
|
|
252
|
+
tapped the cancel button.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
```python
|
|
256
|
+
choice = await pn.Alert.choose(
|
|
257
|
+
"Photo source",
|
|
258
|
+
options=["Camera", "Gallery"],
|
|
259
|
+
cancel_label="Cancel",
|
|
260
|
+
)
|
|
261
|
+
if choice == "Camera":
|
|
262
|
+
...
|
|
263
|
+
```
|
|
264
|
+
"""
|
|
265
|
+
if not options:
|
|
266
|
+
raise ValueError("Alert.choose requires at least one option")
|
|
267
|
+
|
|
268
|
+
loop = asyncio.get_running_loop()
|
|
269
|
+
future: asyncio.Future[Optional[str]] = loop.create_future()
|
|
270
|
+
|
|
271
|
+
destructive = set(destructive_labels)
|
|
272
|
+
buttons: List[Dict[str, Any]] = [
|
|
273
|
+
{
|
|
274
|
+
"label": opt,
|
|
275
|
+
"style": "destructive" if opt in destructive else "default",
|
|
276
|
+
}
|
|
277
|
+
for opt in options
|
|
278
|
+
]
|
|
279
|
+
if cancel_label is not None:
|
|
280
|
+
buttons.append({"label": cancel_label, "style": "cancel"})
|
|
281
|
+
|
|
282
|
+
def _on_result(index: int) -> None:
|
|
283
|
+
if 0 <= index < len(options):
|
|
284
|
+
resolve_future(future, options[index])
|
|
285
|
+
else:
|
|
286
|
+
resolve_future(future, None)
|
|
287
|
+
|
|
288
|
+
_dispatch_alert(
|
|
289
|
+
title=title,
|
|
290
|
+
message=message,
|
|
291
|
+
buttons=buttons,
|
|
292
|
+
style=style,
|
|
293
|
+
on_result=_on_result,
|
|
294
|
+
)
|
|
295
|
+
return await future
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
__all__ = ["Alert"]
|