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
|
@@ -21,12 +21,28 @@ applies those frames via per-child `MarginLayoutParams`.
|
|
|
21
21
|
|
|
22
22
|
**Gestures** feed raw ``MotionEvent`` streams into the shared
|
|
23
23
|
pure-Python [`GestureArbiter`][pythonnative.gestures.GestureArbiter],
|
|
24
|
-
so gesture semantics match the desktop preview exactly.
|
|
24
|
+
so gesture semantics match the desktop preview exactly. Serialized
|
|
25
|
+
specs (including ``simultaneous`` / ``wait_for`` composition metadata
|
|
26
|
+
and ``fling`` configs) pass through untouched; this module only
|
|
27
|
+
schedules main-looper polls at the arbiter's ``next_deadline()`` so
|
|
28
|
+
long-press activation and multi-tap windows fire even when no
|
|
29
|
+
further touch events arrive.
|
|
25
30
|
|
|
26
31
|
**Animations**: ``timing`` animations on transform/opacity/color props
|
|
27
32
|
are driven natively by ``ObjectAnimator`` (Choreographer-paced, no
|
|
28
33
|
Python per-frame work); springs and decay fall back to the Python
|
|
29
|
-
ticker
|
|
34
|
+
ticker, whose per-frame values arrive through
|
|
35
|
+
``set_animated_property`` (floats for transforms and opacity,
|
|
36
|
+
interpolated "#AARRGGBB" strings for background and text colors that
|
|
37
|
+
re-bake through the border-drawable machinery instead of clobbering
|
|
38
|
+
it).
|
|
39
|
+
|
|
40
|
+
**Interaction props**: ``pointer_events`` maps onto the template's
|
|
41
|
+
``PNFrameLayout`` touch interception when available (with a
|
|
42
|
+
Python-side fallback for older projects), ``hit_slop`` expands touch
|
|
43
|
+
targets via a parent ``TouchDelegate`` sized after layout,
|
|
44
|
+
``z_index`` maps to ``View.setZ``, and per-corner radii extend the
|
|
45
|
+
``GradientDrawable`` border path.
|
|
30
46
|
|
|
31
47
|
This module is only imported on Android at runtime. Desktop tests
|
|
32
48
|
inject a mock registry via
|
|
@@ -40,6 +56,7 @@ from typing import Any, Callable, Dict, Optional, Tuple
|
|
|
40
56
|
|
|
41
57
|
from java import dynamic_proxy, jclass
|
|
42
58
|
|
|
59
|
+
from .. import diagnostics
|
|
43
60
|
from ..events import dispatch_event, event_names
|
|
44
61
|
from ..gestures import make_arbiter
|
|
45
62
|
from ..utils import get_android_context
|
|
@@ -57,11 +74,20 @@ _SIDE_BORDER_COLOR_KEYS = (
|
|
|
57
74
|
"border_right_color",
|
|
58
75
|
"border_bottom_color",
|
|
59
76
|
)
|
|
77
|
+
# Order matches GradientDrawable.setCornerRadii: top-left, top-right,
|
|
78
|
+
# bottom-right, bottom-left (each corner takes an x and a y radius).
|
|
79
|
+
_CORNER_RADIUS_KEYS = (
|
|
80
|
+
"border_top_left_radius",
|
|
81
|
+
"border_top_right_radius",
|
|
82
|
+
"border_bottom_right_radius",
|
|
83
|
+
"border_bottom_left_radius",
|
|
84
|
+
)
|
|
60
85
|
_DRAWABLE_STYLE_KEYS = (
|
|
61
86
|
"background_color",
|
|
62
87
|
"border_radius",
|
|
63
88
|
"border_width",
|
|
64
89
|
"border_color",
|
|
90
|
+
*_CORNER_RADIUS_KEYS,
|
|
65
91
|
*_SIDE_BORDER_WIDTH_KEYS,
|
|
66
92
|
*_SIDE_BORDER_COLOR_KEYS,
|
|
67
93
|
)
|
|
@@ -97,6 +123,38 @@ def _pn_runtime_class(class_name: str) -> Any:
|
|
|
97
123
|
return jclass(f"{package}.{class_name}")
|
|
98
124
|
|
|
99
125
|
|
|
126
|
+
# Cached lookup for the template's PNFrameLayout container class.
|
|
127
|
+
# ``None`` means "not resolved yet"; ``False`` means "this generated
|
|
128
|
+
# project predates the class", so creation falls back to FrameLayout
|
|
129
|
+
# without retrying the (exception-throwing) lookup per view.
|
|
130
|
+
_pn_container_cls: Any = None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _new_container() -> Any:
|
|
134
|
+
"""Create a container view, preferring the template's ``PNFrameLayout``.
|
|
135
|
+
|
|
136
|
+
``PNFrameLayout`` adds native ``pointer_events`` interception
|
|
137
|
+
(``onInterceptTouchEvent`` / ``dispatchTouchEvent``), which
|
|
138
|
+
Chaquopy can't express from Python. Older generated projects
|
|
139
|
+
without the class get a plain ``FrameLayout``; the
|
|
140
|
+
``pointer_events`` prop then degrades to the Python-side
|
|
141
|
+
approximation in
|
|
142
|
+
[`_apply_pointer_events`][pythonnative.native_views.android._apply_pointer_events].
|
|
143
|
+
"""
|
|
144
|
+
global _pn_container_cls
|
|
145
|
+
if _pn_container_cls is None:
|
|
146
|
+
try:
|
|
147
|
+
_pn_container_cls = _pn_runtime_class("PNFrameLayout")
|
|
148
|
+
except Exception:
|
|
149
|
+
_pn_container_cls = False
|
|
150
|
+
if _pn_container_cls is not False:
|
|
151
|
+
try:
|
|
152
|
+
return _pn_container_cls(_ctx())
|
|
153
|
+
except Exception:
|
|
154
|
+
diagnostics.swallowed("android._new_container")
|
|
155
|
+
return jclass("android.widget.FrameLayout")(_ctx())
|
|
156
|
+
|
|
157
|
+
|
|
100
158
|
def _signed_color(value: int) -> int:
|
|
101
159
|
"""Clamp a Python color int into Java's signed 32-bit range."""
|
|
102
160
|
value &= 0xFFFFFFFF
|
|
@@ -206,7 +264,7 @@ def _apply_side_border(view: Any, props: Dict[str, Any]) -> bool:
|
|
|
206
264
|
try:
|
|
207
265
|
view.invalidate()
|
|
208
266
|
except Exception:
|
|
209
|
-
|
|
267
|
+
diagnostics.swallowed("android._apply_side_border")
|
|
210
268
|
return True
|
|
211
269
|
except Exception:
|
|
212
270
|
return False
|
|
@@ -226,7 +284,7 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
|
|
|
226
284
|
"""
|
|
227
285
|
if _apply_side_border(view, props):
|
|
228
286
|
return
|
|
229
|
-
has_border = any(k in props for k in ("border_radius", "border_width", "border_color"))
|
|
287
|
+
has_border = any(k in props for k in ("border_radius", "border_width", "border_color", *_CORNER_RADIUS_KEYS))
|
|
230
288
|
has_bg = "background_color" in props and props["background_color"] is not None
|
|
231
289
|
if not has_border and not has_bg:
|
|
232
290
|
return
|
|
@@ -237,12 +295,26 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
|
|
|
237
295
|
try:
|
|
238
296
|
drawable.setColor(parse_color_int(props["background_color"]))
|
|
239
297
|
except Exception:
|
|
240
|
-
|
|
241
|
-
if
|
|
298
|
+
diagnostics.swallowed("android._apply_border")
|
|
299
|
+
if any(props.get(k) is not None for k in _CORNER_RADIUS_KEYS):
|
|
300
|
+
# Per-corner radii: unspecified corners inherit the uniform
|
|
301
|
+
# ``border_radius`` (else 0). ``setCornerRadii`` takes eight
|
|
302
|
+
# floats, an (x, y) pair per corner in _CORNER_RADIUS_KEYS
|
|
303
|
+
# order, each in pixels.
|
|
304
|
+
try:
|
|
305
|
+
base_radius = float(props.get("border_radius") or 0.0)
|
|
306
|
+
radii = []
|
|
307
|
+
for key in _CORNER_RADIUS_KEYS:
|
|
308
|
+
r = float(_dp(float(props[key] if props.get(key) is not None else base_radius)))
|
|
309
|
+
radii.extend([r, r])
|
|
310
|
+
drawable.setCornerRadii(radii)
|
|
311
|
+
except Exception:
|
|
312
|
+
diagnostics.swallowed("android._apply_border")
|
|
313
|
+
elif "border_radius" in props and props["border_radius"] is not None:
|
|
242
314
|
try:
|
|
243
315
|
drawable.setCornerRadius(float(_dp(float(props["border_radius"]))))
|
|
244
316
|
except Exception:
|
|
245
|
-
|
|
317
|
+
diagnostics.swallowed("android._apply_border")
|
|
246
318
|
if ("border_width" in props and props["border_width"] is not None) or (
|
|
247
319
|
"border_color" in props and props["border_color"] is not None
|
|
248
320
|
):
|
|
@@ -254,18 +326,56 @@ def _apply_border(view: Any, props: Dict[str, Any]) -> None:
|
|
|
254
326
|
parse_color_int(color or "#000000"),
|
|
255
327
|
)
|
|
256
328
|
except Exception:
|
|
257
|
-
|
|
329
|
+
diagnostics.swallowed("android._apply_border")
|
|
258
330
|
view.setBackground(drawable)
|
|
259
331
|
try:
|
|
260
332
|
drawable.invalidateSelf()
|
|
261
333
|
except Exception:
|
|
262
|
-
|
|
334
|
+
diagnostics.swallowed("android._apply_border")
|
|
263
335
|
try:
|
|
264
336
|
view.invalidate()
|
|
265
337
|
except Exception:
|
|
266
|
-
|
|
338
|
+
diagnostics.swallowed("android._apply_border")
|
|
267
339
|
except Exception:
|
|
268
|
-
|
|
340
|
+
diagnostics.swallowed("android._apply_border")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _set_animated_background(view: Any, value: Any) -> None:
|
|
344
|
+
"""Apply one animated ``background_color`` frame without losing borders.
|
|
345
|
+
|
|
346
|
+
Interpolations hand this module raw color values ("#AARRGGBB" or
|
|
347
|
+
"#RRGGBB" strings, or ints). Views whose background is a
|
|
348
|
+
border-rendering drawable (the uniform ``GradientDrawable`` or the
|
|
349
|
+
per-side ``PNBorderDrawable``) must keep their corner radii and
|
|
350
|
+
strokes while the fill animates, so the color routes through the
|
|
351
|
+
same [`_apply_border`][pythonnative.native_views.android._apply_border]
|
|
352
|
+
machinery ``update()`` uses. When the current background already is
|
|
353
|
+
a ``GradientDrawable``, its fill is mutated in place, so no
|
|
354
|
+
drawable is allocated per animation frame. Plain views take the
|
|
355
|
+
direct ``setBackgroundColor`` path.
|
|
356
|
+
"""
|
|
357
|
+
state = _state_of(view)
|
|
358
|
+
visual = state.get("visual")
|
|
359
|
+
has_border = isinstance(visual, dict) and any(
|
|
360
|
+
visual.get(k) is not None for k in _DRAWABLE_STYLE_KEYS if k != "background_color"
|
|
361
|
+
)
|
|
362
|
+
if has_border:
|
|
363
|
+
visual = dict(visual)
|
|
364
|
+
visual["background_color"] = value
|
|
365
|
+
state["visual"] = visual
|
|
366
|
+
try:
|
|
367
|
+
GradientDrawable = jclass("android.graphics.drawable.GradientDrawable")
|
|
368
|
+
bg = view.getBackground()
|
|
369
|
+
if bg is not None and isinstance(bg, GradientDrawable):
|
|
370
|
+
bg.setColor(parse_color_int(value))
|
|
371
|
+
return
|
|
372
|
+
except Exception:
|
|
373
|
+
diagnostics.swallowed("android._set_animated_background")
|
|
374
|
+
# Per-side ``PNBorderDrawable`` backgrounds (immutable fill) and
|
|
375
|
+
# first frames before any drawable exists re-bake the full set.
|
|
376
|
+
_apply_border(view, visual)
|
|
377
|
+
return
|
|
378
|
+
view.setBackgroundColor(parse_color_int(value))
|
|
269
379
|
|
|
270
380
|
|
|
271
381
|
def _apply_shadow(view: Any, props: Dict[str, Any]) -> None:
|
|
@@ -291,7 +401,7 @@ def _apply_shadow(view: Any, props: Dict[str, Any]) -> None:
|
|
|
291
401
|
try:
|
|
292
402
|
view.setElevation(float(_dp(float(elevation))))
|
|
293
403
|
except Exception:
|
|
294
|
-
|
|
404
|
+
diagnostics.swallowed("android._apply_shadow")
|
|
295
405
|
color = props.get("shadow_color")
|
|
296
406
|
if color is None:
|
|
297
407
|
return
|
|
@@ -308,7 +418,7 @@ def _apply_shadow(view: Any, props: Dict[str, Any]) -> None:
|
|
|
308
418
|
view.setOutlineAmbientShadowColor(signed)
|
|
309
419
|
view.setOutlineSpotShadowColor(signed)
|
|
310
420
|
except Exception:
|
|
311
|
-
|
|
421
|
+
diagnostics.swallowed("android._apply_shadow")
|
|
312
422
|
|
|
313
423
|
|
|
314
424
|
def _apply_transform(view: Any, props: Dict[str, Any]) -> None:
|
|
@@ -324,7 +434,7 @@ def _apply_transform(view: Any, props: Dict[str, Any]) -> None:
|
|
|
324
434
|
view.setTranslationX(0.0)
|
|
325
435
|
view.setTranslationY(0.0)
|
|
326
436
|
except Exception:
|
|
327
|
-
|
|
437
|
+
diagnostics.swallowed("android._apply_transform")
|
|
328
438
|
return
|
|
329
439
|
entries = spec if isinstance(spec, list) else [spec]
|
|
330
440
|
for entry in entries:
|
|
@@ -353,7 +463,7 @@ def _apply_transform(view: Any, props: Dict[str, Any]) -> None:
|
|
|
353
463
|
if "translate_y" in entry:
|
|
354
464
|
view.setTranslationY(float(_dp(float(entry["translate_y"]))))
|
|
355
465
|
except Exception:
|
|
356
|
-
|
|
466
|
+
diagnostics.swallowed("android._apply_transform")
|
|
357
467
|
|
|
358
468
|
|
|
359
469
|
def _apply_accessibility(view: Any, props: Dict[str, Any]) -> None:
|
|
@@ -365,13 +475,13 @@ def _apply_accessibility(view: Any, props: Dict[str, Any]) -> None:
|
|
|
365
475
|
View.IMPORTANT_FOR_ACCESSIBILITY_YES if props["accessible"] else View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
366
476
|
)
|
|
367
477
|
except Exception:
|
|
368
|
-
|
|
478
|
+
diagnostics.swallowed("android._apply_accessibility")
|
|
369
479
|
if "accessibility_label" in props:
|
|
370
480
|
try:
|
|
371
481
|
label = props["accessibility_label"]
|
|
372
482
|
view.setContentDescription(str(label) if label is not None else None)
|
|
373
483
|
except Exception:
|
|
374
|
-
|
|
484
|
+
diagnostics.swallowed("android._apply_accessibility")
|
|
375
485
|
if "accessibility_live_region" in props:
|
|
376
486
|
try:
|
|
377
487
|
mode = {"none": 0, "polite": 1, "assertive": 2}.get(
|
|
@@ -379,14 +489,14 @@ def _apply_accessibility(view: Any, props: Dict[str, Any]) -> None:
|
|
|
379
489
|
)
|
|
380
490
|
view.setAccessibilityLiveRegion(mode)
|
|
381
491
|
except Exception:
|
|
382
|
-
|
|
492
|
+
diagnostics.swallowed("android._apply_accessibility")
|
|
383
493
|
|
|
384
494
|
state = props.get("accessibility_state")
|
|
385
495
|
if isinstance(state, dict) and "selected" in state:
|
|
386
496
|
try:
|
|
387
497
|
view.setSelected(bool(state["selected"]))
|
|
388
498
|
except Exception:
|
|
389
|
-
|
|
499
|
+
diagnostics.swallowed("android._apply_accessibility")
|
|
390
500
|
test_id = props.get("test_id")
|
|
391
501
|
if test_id is None and not isinstance(state, dict):
|
|
392
502
|
return
|
|
@@ -413,7 +523,139 @@ def _apply_accessibility(view: Any, props: Dict[str, Any]) -> None:
|
|
|
413
523
|
delegate.setStateBusy(state.get("busy"))
|
|
414
524
|
delegate.setStateExpanded(state.get("expanded"))
|
|
415
525
|
except Exception:
|
|
526
|
+
diagnostics.swallowed("android._apply_accessibility")
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _pointer_events_blocked(view: Any) -> bool:
|
|
530
|
+
"""Whether ``pointer_events`` disables the view's own touch handling.
|
|
531
|
+
|
|
532
|
+
Consulted at the top of every touch-stream proxy (gesture, press)
|
|
533
|
+
so listeners installed at create time honor the prop without being
|
|
534
|
+
re-wired: ``"none"`` and ``"box_none"`` both mean the view itself
|
|
535
|
+
must not observe or consume the stream.
|
|
536
|
+
"""
|
|
537
|
+
merged = _state_of(view).get("props") or {}
|
|
538
|
+
return merged.get("pointer_events") in ("none", "box_none")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _apply_pointer_events(view: Any, value: Any) -> None:
|
|
542
|
+
"""Apply the ``pointer_events`` prop (``None`` restores ``"auto"``).
|
|
543
|
+
|
|
544
|
+
Two cooperating layers implement the modes:
|
|
545
|
+
|
|
546
|
+
- **Native interception** (preferred): containers built by
|
|
547
|
+
[`_new_container`][pythonnative.native_views.android._new_container]
|
|
548
|
+
are the template's ``PNFrameLayout``, whose
|
|
549
|
+
``setPointerEventsMode(String)`` drives real
|
|
550
|
+
``dispatchTouchEvent`` / ``onInterceptTouchEvent`` overrides.
|
|
551
|
+
``"none"`` declines the whole dispatch so touches pass through
|
|
552
|
+
to views underneath, ``"box_only"`` intercepts events away from
|
|
553
|
+
children, and ``"box_none"`` never intercepts. The call is
|
|
554
|
+
guarded so older generated projects without the class (and
|
|
555
|
+
non-container views) fall back cleanly.
|
|
556
|
+
- **Python-side muting**: ``setClickable`` toggles whether the
|
|
557
|
+
view claims events, and the touch-stream proxies consult
|
|
558
|
+
[`_pointer_events_blocked`][pythonnative.native_views.android._pointer_events_blocked]
|
|
559
|
+
so gesture/press wiring goes quiet without being torn down.
|
|
560
|
+
``"auto"`` / removal restores the original clickability
|
|
561
|
+
captured before the first override.
|
|
562
|
+
|
|
563
|
+
On a plain ``FrameLayout`` (old template) the fallback keeps the
|
|
564
|
+
previous approximation: ``"none"`` silences the view itself but
|
|
565
|
+
interactive children keep receiving touches, and ``"box_only"``
|
|
566
|
+
only claims events no interactive child consumed. ``"box_none"``
|
|
567
|
+
is exact either way, since children need no help.
|
|
568
|
+
"""
|
|
569
|
+
mode = value if value is not None else "auto"
|
|
570
|
+
state = _state_of(view)
|
|
571
|
+
try:
|
|
572
|
+
view.setPointerEventsMode(str(mode))
|
|
573
|
+
except Exception:
|
|
574
|
+
# Plain FrameLayout, a leaf widget, or an older template
|
|
575
|
+
# without PNFrameLayout; the Python-side fallback still runs.
|
|
416
576
|
pass
|
|
577
|
+
try:
|
|
578
|
+
if mode in ("none", "box_none"):
|
|
579
|
+
state.setdefault("pe_was_clickable", bool(view.isClickable()))
|
|
580
|
+
view.setClickable(False)
|
|
581
|
+
elif mode == "box_only":
|
|
582
|
+
state.setdefault("pe_was_clickable", bool(view.isClickable()))
|
|
583
|
+
view.setClickable(True)
|
|
584
|
+
elif "pe_was_clickable" in state:
|
|
585
|
+
view.setClickable(bool(state.pop("pe_was_clickable")))
|
|
586
|
+
except Exception:
|
|
587
|
+
diagnostics.swallowed("android._apply_pointer_events")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _hit_slop_insets(value: Any) -> Optional[Tuple[float, float, float, float]]:
|
|
591
|
+
"""Normalize a ``hit_slop`` prop into ``(top, left, bottom, right)`` points.
|
|
592
|
+
|
|
593
|
+
Accepts a uniform number or a dict with any of ``top`` / ``left``
|
|
594
|
+
/ ``bottom`` / ``right``. Returns ``None`` for absent or
|
|
595
|
+
unparseable values.
|
|
596
|
+
"""
|
|
597
|
+
if value is None:
|
|
598
|
+
return None
|
|
599
|
+
if isinstance(value, dict):
|
|
600
|
+
try:
|
|
601
|
+
return (
|
|
602
|
+
float(value.get("top") or 0.0),
|
|
603
|
+
float(value.get("left") or 0.0),
|
|
604
|
+
float(value.get("bottom") or 0.0),
|
|
605
|
+
float(value.get("right") or 0.0),
|
|
606
|
+
)
|
|
607
|
+
except Exception:
|
|
608
|
+
return None
|
|
609
|
+
try:
|
|
610
|
+
uniform = float(value)
|
|
611
|
+
except Exception:
|
|
612
|
+
return None
|
|
613
|
+
return (uniform, uniform, uniform, uniform)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _update_hit_slop(view: Any) -> None:
|
|
617
|
+
"""Extend the view's touch target via a ``TouchDelegate`` on its parent.
|
|
618
|
+
|
|
619
|
+
The standard Android mechanism: the parent forwards touches
|
|
620
|
+
landing inside an expanded ``Rect`` (the view's frame plus the
|
|
621
|
+
slop insets, in parent-relative pixels) to the view. Parents are
|
|
622
|
+
PythonNative-managed ``FrameLayout``s, so installing the delegate
|
|
623
|
+
is safe; a parent only holds one delegate, so among siblings the
|
|
624
|
+
last ``hit_slop`` applied wins (matching the platform limitation).
|
|
625
|
+
|
|
626
|
+
Requires a known frame, so
|
|
627
|
+
[`set_frame`][pythonnative.native_views.android.AndroidViewHandler.set_frame]
|
|
628
|
+
records the frame and re-invokes this; prop-only updates before
|
|
629
|
+
the first frame defer until layout runs.
|
|
630
|
+
"""
|
|
631
|
+
state = _state_of(view)
|
|
632
|
+
frame = state.get("frame")
|
|
633
|
+
if frame is None:
|
|
634
|
+
return
|
|
635
|
+
merged = state.get("props") or {}
|
|
636
|
+
insets = _hit_slop_insets(merged.get("hit_slop"))
|
|
637
|
+
try:
|
|
638
|
+
parent = view.getParent()
|
|
639
|
+
if parent is None:
|
|
640
|
+
return
|
|
641
|
+
if insets is None:
|
|
642
|
+
if state.pop("hit_slop_active", None):
|
|
643
|
+
parent.setTouchDelegate(None)
|
|
644
|
+
return
|
|
645
|
+
top, left, bottom, right = insets
|
|
646
|
+
x, y, width, height = frame
|
|
647
|
+
Rect = jclass("android.graphics.Rect")
|
|
648
|
+
TouchDelegate = jclass("android.view.TouchDelegate")
|
|
649
|
+
rect = Rect(
|
|
650
|
+
_dp(x - left),
|
|
651
|
+
_dp(y - top),
|
|
652
|
+
_dp(x + width + right),
|
|
653
|
+
_dp(y + height + bottom),
|
|
654
|
+
)
|
|
655
|
+
parent.setTouchDelegate(TouchDelegate(rect, view))
|
|
656
|
+
state["hit_slop_active"] = True
|
|
657
|
+
except Exception:
|
|
658
|
+
diagnostics.swallowed("android._update_hit_slop")
|
|
417
659
|
|
|
418
660
|
|
|
419
661
|
def _apply_common_visual(view: Any, props: Dict[str, Any]) -> None:
|
|
@@ -433,12 +675,24 @@ def _apply_common_visual(view: Any, props: Dict[str, Any]) -> None:
|
|
|
433
675
|
view.setClipChildren(clip)
|
|
434
676
|
view.setClipToPadding(clip)
|
|
435
677
|
except Exception:
|
|
436
|
-
|
|
678
|
+
diagnostics.swallowed("android._apply_common_visual")
|
|
437
679
|
if "opacity" in props and props["opacity"] is not None:
|
|
438
680
|
try:
|
|
439
681
|
view.setAlpha(float(props["opacity"]))
|
|
440
682
|
except Exception:
|
|
441
|
-
|
|
683
|
+
diagnostics.swallowed("android._apply_common_visual")
|
|
684
|
+
if "z_index" in props:
|
|
685
|
+
# ``setZ`` takes pixels, so scale like other coordinates; a
|
|
686
|
+
# removal (``None``) resets the view to the default plane.
|
|
687
|
+
try:
|
|
688
|
+
z = props["z_index"]
|
|
689
|
+
view.setZ(float(_dp(float(z))) if z is not None else 0.0)
|
|
690
|
+
except Exception:
|
|
691
|
+
diagnostics.swallowed("android._apply_common_visual")
|
|
692
|
+
if "pointer_events" in props:
|
|
693
|
+
_apply_pointer_events(view, props["pointer_events"])
|
|
694
|
+
if "hit_slop" in props:
|
|
695
|
+
_update_hit_slop(view)
|
|
442
696
|
_apply_shadow(view, props)
|
|
443
697
|
_apply_transform(view, props)
|
|
444
698
|
_apply_accessibility(view, props)
|
|
@@ -453,10 +707,14 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
453
707
|
"""Feed ``MotionEvent`` streams on ``view`` into a `GestureArbiter`.
|
|
454
708
|
|
|
455
709
|
The arbiter emits ``(gesture_index, payload)`` pairs which are
|
|
456
|
-
forwarded as ``gesture:<i>`` events for this view's tag.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
710
|
+
forwarded as ``gesture:<i>`` events for this view's tag. Specs
|
|
711
|
+
(including ``simultaneous`` / ``wait_for`` metadata and fling
|
|
712
|
+
configs) pass through to the arbiter untouched; all arbitration
|
|
713
|
+
and recognition live in ``pythonnative.gestures``. Time-based
|
|
714
|
+
deadlines (long press, multi-tap windows) are polled with a
|
|
715
|
+
main-looper ``Handler`` scheduled at the arbiter's
|
|
716
|
+
``next_deadline()``. When a pan activates, the parent is asked
|
|
717
|
+
not to intercept so an enclosing ScrollView can't steal the drag.
|
|
460
718
|
"""
|
|
461
719
|
state = _state_of(view)
|
|
462
720
|
if not isinstance(specs, (list, tuple)) or not specs:
|
|
@@ -475,13 +733,27 @@ def _wire_gestures(view: Any, specs: Any) -> None:
|
|
|
475
733
|
|
|
476
734
|
|
|
477
735
|
def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
|
|
478
|
-
"""Schedule a main-looper poll at the arbiter's next deadline
|
|
736
|
+
"""Schedule a main-looper poll at the arbiter's next deadline.
|
|
737
|
+
|
|
738
|
+
Deadlines cover every time-based recognizer decision: long-press
|
|
739
|
+
activation and the multi-tap windows that flush an Exclusive
|
|
740
|
+
double-tap/single-tap buffer. Called after every pointer event
|
|
741
|
+
(not just DOWN) because a deadline can appear on UP, when no
|
|
742
|
+
further touch events arrive to drive the arbiter forward.
|
|
743
|
+
|
|
744
|
+
A pending poll at or before the requested deadline is reused
|
|
745
|
+
rather than stacking duplicate runnables; each poll reschedules
|
|
746
|
+
itself for the following deadline until none remains.
|
|
747
|
+
"""
|
|
479
748
|
arbiter = state.get("arbiter")
|
|
480
749
|
if arbiter is None:
|
|
481
750
|
return
|
|
482
751
|
deadline = arbiter.next_deadline()
|
|
483
752
|
if deadline is None:
|
|
484
753
|
return
|
|
754
|
+
pending = state.get("poll_deadline")
|
|
755
|
+
if pending is not None and pending <= deadline + 0.001:
|
|
756
|
+
return
|
|
485
757
|
delay_ms = max(1, int((deadline - time.monotonic()) * 1000.0))
|
|
486
758
|
try:
|
|
487
759
|
Handler = jclass("android.os.Handler")
|
|
@@ -491,9 +763,14 @@ def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
|
|
|
491
763
|
if handler is None:
|
|
492
764
|
handler = Handler(Looper.getMainLooper())
|
|
493
765
|
state["poll_handler"] = handler
|
|
766
|
+
token = state.get("poll_token", 0) + 1
|
|
767
|
+
state["poll_token"] = token
|
|
768
|
+
state["poll_deadline"] = deadline
|
|
494
769
|
|
|
495
770
|
class _PollRunnable(dynamic_proxy(Runnable)):
|
|
496
771
|
def run(self) -> None:
|
|
772
|
+
if state.get("poll_token") == token:
|
|
773
|
+
state["poll_deadline"] = None
|
|
497
774
|
live = state.get("arbiter")
|
|
498
775
|
if live is not None:
|
|
499
776
|
live.poll(time.monotonic())
|
|
@@ -501,7 +778,8 @@ def _schedule_arbiter_poll(view: Any, state: Dict[str, Any]) -> None:
|
|
|
501
778
|
|
|
502
779
|
handler.postDelayed(_PollRunnable(), delay_ms)
|
|
503
780
|
except Exception:
|
|
504
|
-
|
|
781
|
+
state["poll_deadline"] = None
|
|
782
|
+
diagnostics.swallowed("android._schedule_arbiter_poll")
|
|
505
783
|
|
|
506
784
|
|
|
507
785
|
def _bind_touch_stream(view: Any, state: Dict[str, Any]) -> None:
|
|
@@ -511,11 +789,13 @@ def _bind_touch_stream(view: Any, state: Dict[str, Any]) -> None:
|
|
|
511
789
|
|
|
512
790
|
class _GestureTouchProxy(dynamic_proxy(OnTouchListener)):
|
|
513
791
|
def onTouch(self, v: Any, event: Any) -> bool:
|
|
792
|
+
if _pointer_events_blocked(v):
|
|
793
|
+
return False
|
|
514
794
|
return bool(_feed_motion_event(v, state, event))
|
|
515
795
|
|
|
516
796
|
view.setOnTouchListener(_GestureTouchProxy())
|
|
517
797
|
except Exception:
|
|
518
|
-
|
|
798
|
+
diagnostics.swallowed("android._bind_touch_stream")
|
|
519
799
|
|
|
520
800
|
|
|
521
801
|
def _feed_motion_event(view: Any, state: Dict[str, Any], event: Any) -> bool:
|
|
@@ -535,7 +815,6 @@ def _feed_motion_event(view: Any, state: Dict[str, Any], event: Any) -> bool:
|
|
|
535
815
|
idx = int(event.getActionIndex())
|
|
536
816
|
pid = int(event.getPointerId(idx))
|
|
537
817
|
arbiter.pointer_down(pid, float(event.getX(idx)) / density, float(event.getY(idx)) / density, t)
|
|
538
|
-
_schedule_arbiter_poll(view, state)
|
|
539
818
|
elif action == 2: # MOVE
|
|
540
819
|
for i in range(int(event.getPointerCount())):
|
|
541
820
|
pid = int(event.getPointerId(i))
|
|
@@ -546,13 +825,17 @@ def _feed_motion_event(view: Any, state: Dict[str, Any], event: Any) -> bool:
|
|
|
546
825
|
if parent is not None:
|
|
547
826
|
parent.requestDisallowInterceptTouchEvent(True)
|
|
548
827
|
except Exception:
|
|
549
|
-
|
|
828
|
+
diagnostics.swallowed("android._feed_motion_event")
|
|
550
829
|
elif action in (1, 6): # UP / POINTER_UP
|
|
551
830
|
idx = int(event.getActionIndex())
|
|
552
831
|
pid = int(event.getPointerId(idx))
|
|
553
832
|
arbiter.pointer_up(pid, float(event.getX(idx)) / density, float(event.getY(idx)) / density, t)
|
|
554
833
|
elif action == 3: # CANCEL
|
|
555
834
|
arbiter.cancel(t)
|
|
835
|
+
# Every event can move the next time-based deadline (long-press
|
|
836
|
+
# activation, multi-tap windows opened on UP), so reschedule
|
|
837
|
+
# unconditionally; the scheduler dedups pending polls.
|
|
838
|
+
_schedule_arbiter_poll(view, state)
|
|
556
839
|
return True
|
|
557
840
|
except Exception:
|
|
558
841
|
return True
|
|
@@ -603,7 +886,7 @@ def _read_animated_value(view: Any, prop_name: str) -> Any:
|
|
|
603
886
|
if prop_name == "rotate":
|
|
604
887
|
return float(view.getRotation())
|
|
605
888
|
except Exception:
|
|
606
|
-
|
|
889
|
+
diagnostics.swallowed("android._read_animated_value")
|
|
607
890
|
return None
|
|
608
891
|
|
|
609
892
|
|
|
@@ -639,7 +922,7 @@ def _make_end_listener(anim_id: int) -> Any:
|
|
|
639
922
|
|
|
640
923
|
native_animation_completed(anim_id, not self._cancelled)
|
|
641
924
|
except Exception:
|
|
642
|
-
|
|
925
|
+
diagnostics.swallowed("android._make_end_listener._EndProxy.onAnimationEnd")
|
|
643
926
|
|
|
644
927
|
return _EndProxy()
|
|
645
928
|
|
|
@@ -731,7 +1014,7 @@ class AndroidViewHandler(ViewHandler):
|
|
|
731
1014
|
if parent is not None:
|
|
732
1015
|
parent.removeView(native_view)
|
|
733
1016
|
except Exception:
|
|
734
|
-
|
|
1017
|
+
diagnostics.swallowed("android.AndroidViewHandler.destroy")
|
|
735
1018
|
_forget(native_view)
|
|
736
1019
|
|
|
737
1020
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
@@ -760,17 +1043,24 @@ class AndroidViewHandler(ViewHandler):
|
|
|
760
1043
|
lp.width = px_w
|
|
761
1044
|
lp.height = px_h
|
|
762
1045
|
except Exception:
|
|
763
|
-
|
|
1046
|
+
diagnostics.swallowed("android.AndroidViewHandler.set_frame")
|
|
764
1047
|
try:
|
|
765
1048
|
lp.leftMargin = px_x
|
|
766
1049
|
lp.topMargin = px_y
|
|
767
1050
|
lp.rightMargin = 0
|
|
768
1051
|
lp.bottomMargin = 0
|
|
769
1052
|
except Exception:
|
|
770
|
-
|
|
1053
|
+
diagnostics.swallowed("android.AndroidViewHandler.set_frame")
|
|
771
1054
|
native_view.setLayoutParams(lp)
|
|
1055
|
+
# ``hit_slop`` needs the frame (and an attached parent, which
|
|
1056
|
+
# exists by the time frames are applied) to size its
|
|
1057
|
+
# TouchDelegate rect, so record the frame and recompute here.
|
|
1058
|
+
state = _state_of(native_view)
|
|
1059
|
+
state["frame"] = (x, y, width, height)
|
|
1060
|
+
if (state.get("props") or {}).get("hit_slop") is not None or state.get("hit_slop_active"):
|
|
1061
|
+
_update_hit_slop(native_view)
|
|
772
1062
|
except Exception:
|
|
773
|
-
|
|
1063
|
+
diagnostics.swallowed("android.AndroidViewHandler.set_frame")
|
|
774
1064
|
|
|
775
1065
|
def measure_intrinsic(
|
|
776
1066
|
self,
|
|
@@ -801,7 +1091,13 @@ class AndroidViewHandler(ViewHandler):
|
|
|
801
1091
|
return (0.0, 0.0)
|
|
802
1092
|
|
|
803
1093
|
def set_animated_property(self, native_view: Any, prop_name: str, value: Any) -> None:
|
|
804
|
-
"""Apply one Python-driven animation frame immediately.
|
|
1094
|
+
"""Apply one Python-driven animation frame immediately.
|
|
1095
|
+
|
|
1096
|
+
Handles float transform props (dp-scaled translations, scales,
|
|
1097
|
+
rotation degrees, opacity) and interpolated color frames for
|
|
1098
|
+
``background_color`` / ``color`` ("#AARRGGBB" / "#RRGGBB"
|
|
1099
|
+
strings or ints). Unknown props are silently ignored.
|
|
1100
|
+
"""
|
|
805
1101
|
if native_view is None:
|
|
806
1102
|
return
|
|
807
1103
|
try:
|
|
@@ -821,9 +1117,14 @@ class AndroidViewHandler(ViewHandler):
|
|
|
821
1117
|
elif prop_name == "rotate":
|
|
822
1118
|
native_view.setRotation(float(value))
|
|
823
1119
|
elif prop_name == "background_color":
|
|
824
|
-
native_view
|
|
1120
|
+
_set_animated_background(native_view, value)
|
|
1121
|
+
elif prop_name == "color":
|
|
1122
|
+
# Interpolations emit "#AARRGGBB" strings; parse_color_int
|
|
1123
|
+
# accepts strings and raw ints alike. Views without
|
|
1124
|
+
# setTextColor fall into the except below and skip.
|
|
1125
|
+
native_view.setTextColor(parse_color_int(value))
|
|
825
1126
|
except Exception:
|
|
826
|
-
|
|
1127
|
+
diagnostics.swallowed("android.AndroidViewHandler.set_animated_property")
|
|
827
1128
|
|
|
828
1129
|
def start_animation(
|
|
829
1130
|
self,
|
|
@@ -851,7 +1152,7 @@ class AndroidViewHandler(ViewHandler):
|
|
|
851
1152
|
try:
|
|
852
1153
|
entry["animator"].cancel()
|
|
853
1154
|
except Exception:
|
|
854
|
-
|
|
1155
|
+
diagnostics.swallowed("android.AndroidViewHandler.cancel_animation")
|
|
855
1156
|
return _read_animated_value(entry.get("view"), str(entry.get("prop", "")))
|
|
856
1157
|
|
|
857
1158
|
|
|
@@ -866,11 +1167,13 @@ class FlexContainerHandler(AndroidViewHandler):
|
|
|
866
1167
|
All flex semantics (direction, alignment, distribution, padding)
|
|
867
1168
|
are computed by the layout engine and applied via
|
|
868
1169
|
[`set_frame`][pythonnative.native_views.android.AndroidViewHandler.set_frame].
|
|
869
|
-
The container itself is just a positioning surface
|
|
1170
|
+
The container itself is just a positioning surface (the template's
|
|
1171
|
+
``PNFrameLayout`` when available, so ``pointer_events`` gets real
|
|
1172
|
+
touch interception).
|
|
870
1173
|
"""
|
|
871
1174
|
|
|
872
1175
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
873
|
-
return
|
|
1176
|
+
return _new_container()
|
|
874
1177
|
|
|
875
1178
|
def insert_child(self, parent: Any, child: Any, index: int) -> None:
|
|
876
1179
|
_insert_view(parent, child, index)
|
|
@@ -879,7 +1182,7 @@ class FlexContainerHandler(AndroidViewHandler):
|
|
|
879
1182
|
try:
|
|
880
1183
|
parent.removeView(child)
|
|
881
1184
|
except Exception:
|
|
882
|
-
|
|
1185
|
+
diagnostics.swallowed("android.FlexContainerHandler.remove_child")
|
|
883
1186
|
|
|
884
1187
|
|
|
885
1188
|
def _insert_view(parent: Any, child: Any, index: int) -> None:
|
|
@@ -905,7 +1208,7 @@ def _insert_view(parent: Any, child: Any, index: int) -> None:
|
|
|
905
1208
|
try:
|
|
906
1209
|
parent.addView(child)
|
|
907
1210
|
except Exception:
|
|
908
|
-
|
|
1211
|
+
diagnostics.swallowed("android._insert_view")
|
|
909
1212
|
|
|
910
1213
|
|
|
911
1214
|
# ======================================================================
|
|
@@ -993,7 +1296,7 @@ def _build_spannable(spans: Any) -> Any:
|
|
|
993
1296
|
StrikethroughSpan = jclass("android.text.style.StrikethroughSpan")
|
|
994
1297
|
_set(StrikethroughSpan())
|
|
995
1298
|
except Exception:
|
|
996
|
-
|
|
1299
|
+
diagnostics.swallowed("android._build_spannable")
|
|
997
1300
|
return builder
|
|
998
1301
|
|
|
999
1302
|
|
|
@@ -1035,7 +1338,7 @@ class TextHandler(AndroidViewHandler):
|
|
|
1035
1338
|
else:
|
|
1036
1339
|
tv.setTypeface(tv.getTypeface(), style)
|
|
1037
1340
|
except Exception:
|
|
1038
|
-
|
|
1341
|
+
diagnostics.swallowed("android.TextHandler._apply")
|
|
1039
1342
|
if "max_lines" in props and props["max_lines"] is not None:
|
|
1040
1343
|
tv.setMaxLines(int(props["max_lines"]))
|
|
1041
1344
|
if "text_align" in props:
|
|
@@ -1050,14 +1353,14 @@ class TextHandler(AndroidViewHandler):
|
|
|
1050
1353
|
size = float(merged.get("font_size") or 16.0)
|
|
1051
1354
|
tv.setLetterSpacing(float(props["letter_spacing"]) / max(size, 1.0))
|
|
1052
1355
|
except Exception:
|
|
1053
|
-
|
|
1356
|
+
diagnostics.swallowed("android.TextHandler._apply")
|
|
1054
1357
|
if "line_height" in props and props["line_height"] is not None:
|
|
1055
1358
|
try:
|
|
1056
1359
|
merged = _state_of(tv).get("props") or props
|
|
1057
1360
|
size = float(merged.get("font_size") or 16.0)
|
|
1058
1361
|
tv.setLineSpacing(0.0, float(props["line_height"]) / max(size, 1.0))
|
|
1059
1362
|
except Exception:
|
|
1060
|
-
|
|
1363
|
+
diagnostics.swallowed("android.TextHandler._apply")
|
|
1061
1364
|
if "text_decoration" in props:
|
|
1062
1365
|
try:
|
|
1063
1366
|
Paint = jclass("android.graphics.Paint")
|
|
@@ -1068,7 +1371,7 @@ class TextHandler(AndroidViewHandler):
|
|
|
1068
1371
|
flags |= Paint.STRIKE_THRU_TEXT_FLAG
|
|
1069
1372
|
tv.setPaintFlags(flags)
|
|
1070
1373
|
except Exception:
|
|
1071
|
-
|
|
1374
|
+
diagnostics.swallowed("android.TextHandler._apply")
|
|
1072
1375
|
_apply_common_visual(tv, props)
|
|
1073
1376
|
|
|
1074
1377
|
|
|
@@ -1147,7 +1450,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1147
1450
|
try:
|
|
1148
1451
|
outer.setEnabled(False)
|
|
1149
1452
|
except Exception:
|
|
1150
|
-
|
|
1453
|
+
diagnostics.swallowed("android.ScrollViewHandler.create")
|
|
1151
1454
|
self._bind_scroll_listener(outer, sv)
|
|
1152
1455
|
self._apply(outer, props, initial=True)
|
|
1153
1456
|
if props.get("gestures"):
|
|
@@ -1165,7 +1468,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1165
1468
|
sv.setVerticalScrollBarEnabled(show)
|
|
1166
1469
|
sv.setHorizontalScrollBarEnabled(show)
|
|
1167
1470
|
except Exception:
|
|
1168
|
-
|
|
1471
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply")
|
|
1169
1472
|
if "bounces" in props:
|
|
1170
1473
|
# ``bounces`` is only present when ``False``; map it to the
|
|
1171
1474
|
# closest analogue, the over-scroll (glow) mode.
|
|
@@ -1174,7 +1477,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1174
1477
|
mode = View.OVER_SCROLL_NEVER if props["bounces"] is False else View.OVER_SCROLL_IF_CONTENT_SCROLLS
|
|
1175
1478
|
sv.setOverScrollMode(mode)
|
|
1176
1479
|
except Exception:
|
|
1177
|
-
|
|
1480
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply")
|
|
1178
1481
|
if "refresh_control" in props and state.get("refresh") is not None:
|
|
1179
1482
|
self._apply_refresh(outer, props)
|
|
1180
1483
|
# ``paging_enabled`` and ``keyboard_dismiss_mode`` have no clean
|
|
@@ -1190,7 +1493,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1190
1493
|
try:
|
|
1191
1494
|
sv.removeView(child)
|
|
1192
1495
|
except Exception:
|
|
1193
|
-
|
|
1496
|
+
diagnostics.swallowed("android.ScrollViewHandler.remove_child")
|
|
1194
1497
|
|
|
1195
1498
|
def command(self, native_view: Any, name: str, args: Dict[str, Any]) -> Any:
|
|
1196
1499
|
state = _state_of(native_view)
|
|
@@ -1206,7 +1509,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1206
1509
|
else:
|
|
1207
1510
|
sv.scrollTo(x_px, y_px)
|
|
1208
1511
|
except Exception:
|
|
1209
|
-
|
|
1512
|
+
diagnostics.swallowed("android.ScrollViewHandler.command")
|
|
1210
1513
|
return None
|
|
1211
1514
|
if name == "scroll_to_end":
|
|
1212
1515
|
try:
|
|
@@ -1220,7 +1523,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1220
1523
|
target_y = max(0, int(child.getHeight()) - int(sv.getHeight()))
|
|
1221
1524
|
sv.smoothScrollTo(0, target_y)
|
|
1222
1525
|
except Exception:
|
|
1223
|
-
|
|
1526
|
+
diagnostics.swallowed("android.ScrollViewHandler.command")
|
|
1224
1527
|
return None
|
|
1225
1528
|
if name == "get_scroll_offset":
|
|
1226
1529
|
try:
|
|
@@ -1250,11 +1553,13 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1250
1553
|
density = _density() or 1.0
|
|
1251
1554
|
_fire(outer, "on_scroll", {"x": scroll_x / density, "y": scroll_y / density})
|
|
1252
1555
|
except Exception:
|
|
1253
|
-
|
|
1556
|
+
diagnostics.swallowed(
|
|
1557
|
+
"android.ScrollViewHandler._bind_scroll_listener._ScrollChangeProxy.onScrollChange"
|
|
1558
|
+
)
|
|
1254
1559
|
|
|
1255
1560
|
sv.setOnScrollChangeListener(_ScrollChangeProxy())
|
|
1256
1561
|
except Exception:
|
|
1257
|
-
|
|
1562
|
+
diagnostics.swallowed("android.ScrollViewHandler._bind_scroll_listener")
|
|
1258
1563
|
|
|
1259
1564
|
def _wrap_in_refresh(self, sv: Any) -> Any:
|
|
1260
1565
|
try:
|
|
@@ -1278,7 +1583,7 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1278
1583
|
|
|
1279
1584
|
srl.setOnRefreshListener(_RefreshProxy())
|
|
1280
1585
|
except Exception:
|
|
1281
|
-
|
|
1586
|
+
diagnostics.swallowed("android.ScrollViewHandler._bind_refresh_listener")
|
|
1282
1587
|
|
|
1283
1588
|
def _apply_refresh(self, outer: Any, props: Dict[str, Any]) -> None:
|
|
1284
1589
|
srl = _state_of(outer).get("refresh")
|
|
@@ -1289,21 +1594,21 @@ class ScrollViewHandler(AndroidViewHandler):
|
|
|
1289
1594
|
try:
|
|
1290
1595
|
srl.setEnabled(False)
|
|
1291
1596
|
except Exception:
|
|
1292
|
-
|
|
1597
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply_refresh")
|
|
1293
1598
|
return
|
|
1294
1599
|
try:
|
|
1295
1600
|
srl.setEnabled(True)
|
|
1296
1601
|
except Exception:
|
|
1297
|
-
|
|
1602
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply_refresh")
|
|
1298
1603
|
if spec.get("tint_color"):
|
|
1299
1604
|
try:
|
|
1300
1605
|
srl.setColorSchemeColors([parse_color_int(spec["tint_color"])])
|
|
1301
1606
|
except Exception:
|
|
1302
|
-
|
|
1607
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply_refresh")
|
|
1303
1608
|
try:
|
|
1304
1609
|
srl.setRefreshing(bool(spec.get("refreshing")))
|
|
1305
1610
|
except Exception:
|
|
1306
|
-
|
|
1611
|
+
diagnostics.swallowed("android.ScrollViewHandler._apply_refresh")
|
|
1307
1612
|
|
|
1308
1613
|
|
|
1309
1614
|
class TextInputHandler(AndroidViewHandler):
|
|
@@ -1316,7 +1621,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1316
1621
|
if not props.get("multiline"):
|
|
1317
1622
|
et.setSingleLine(True)
|
|
1318
1623
|
except Exception:
|
|
1319
|
-
|
|
1624
|
+
diagnostics.swallowed("android.TextInputHandler._build")
|
|
1320
1625
|
self._bind_listeners(et, props)
|
|
1321
1626
|
return et
|
|
1322
1627
|
|
|
@@ -1342,7 +1647,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1342
1647
|
|
|
1343
1648
|
et.addTextChangedListener(ChangeProxy())
|
|
1344
1649
|
except Exception:
|
|
1345
|
-
|
|
1650
|
+
diagnostics.swallowed("android.TextInputHandler._bind_listeners")
|
|
1346
1651
|
try:
|
|
1347
1652
|
|
|
1348
1653
|
class _FocusProxy(dynamic_proxy(jclass("android.view.View").OnFocusChangeListener)):
|
|
@@ -1351,7 +1656,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1351
1656
|
|
|
1352
1657
|
et.setOnFocusChangeListener(_FocusProxy())
|
|
1353
1658
|
except Exception:
|
|
1354
|
-
|
|
1659
|
+
diagnostics.swallowed("android.TextInputHandler._bind_listeners")
|
|
1355
1660
|
|
|
1356
1661
|
def _apply(self, et: Any, props: Dict[str, Any], initial: bool) -> None:
|
|
1357
1662
|
state = _state_of(et)
|
|
@@ -1368,7 +1673,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1368
1673
|
selection_start = et.getSelectionStart()
|
|
1369
1674
|
selection_end = et.getSelectionEnd()
|
|
1370
1675
|
except Exception:
|
|
1371
|
-
|
|
1676
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1372
1677
|
state["suppress"] = True
|
|
1373
1678
|
try:
|
|
1374
1679
|
et.setText(incoming)
|
|
@@ -1381,7 +1686,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1381
1686
|
else:
|
|
1382
1687
|
et.setSelection(start, end)
|
|
1383
1688
|
except Exception:
|
|
1384
|
-
|
|
1689
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1385
1690
|
finally:
|
|
1386
1691
|
state["suppress"] = False
|
|
1387
1692
|
if "placeholder" in props:
|
|
@@ -1390,7 +1695,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1390
1695
|
try:
|
|
1391
1696
|
et.setHintTextColor(parse_color_int(props["placeholder_color"]))
|
|
1392
1697
|
except Exception:
|
|
1393
|
-
|
|
1698
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1394
1699
|
if "font_size" in props and props["font_size"] is not None:
|
|
1395
1700
|
et.setTextSize(float(props["font_size"]))
|
|
1396
1701
|
if "color" in props and props["color"] is not None:
|
|
@@ -1428,18 +1733,18 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1428
1733
|
et.setSingleLine(True)
|
|
1429
1734
|
et.setInputType(base)
|
|
1430
1735
|
except Exception:
|
|
1431
|
-
|
|
1736
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1432
1737
|
if "max_length" in props and props["max_length"] is not None:
|
|
1433
1738
|
try:
|
|
1434
1739
|
InputFilter = jclass("android.text.InputFilter$LengthFilter")
|
|
1435
1740
|
et.setFilters([InputFilter(int(props["max_length"]))])
|
|
1436
1741
|
except Exception:
|
|
1437
|
-
|
|
1742
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1438
1743
|
if "auto_focus" in props and props["auto_focus"]:
|
|
1439
1744
|
try:
|
|
1440
1745
|
et.requestFocus()
|
|
1441
1746
|
except Exception:
|
|
1442
|
-
|
|
1747
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1443
1748
|
if "editable" in props:
|
|
1444
1749
|
# ``editable`` is only present when ``False`` (read-only); a
|
|
1445
1750
|
# removal (``None``) restores editing. We keep the field
|
|
@@ -1452,12 +1757,12 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1452
1757
|
et.setCursorVisible(editable)
|
|
1453
1758
|
et.setLongClickable(editable)
|
|
1454
1759
|
except Exception:
|
|
1455
|
-
|
|
1760
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1456
1761
|
if "selection_color" in props and props["selection_color"] is not None:
|
|
1457
1762
|
try:
|
|
1458
1763
|
et.setHighlightColor(parse_color_int(props["selection_color"]))
|
|
1459
1764
|
except Exception:
|
|
1460
|
-
|
|
1765
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1461
1766
|
if "text_content_type" in props and props["text_content_type"] is not None:
|
|
1462
1767
|
self._apply_autofill(et, str(props["text_content_type"]))
|
|
1463
1768
|
if "clear_button" in props:
|
|
@@ -1485,7 +1790,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1485
1790
|
action = rkt_mapping.get(props["return_key_type"], EditorInfo.IME_ACTION_DONE)
|
|
1486
1791
|
et.setImeOptions(action)
|
|
1487
1792
|
except Exception:
|
|
1488
|
-
|
|
1793
|
+
diagnostics.swallowed("android.TextInputHandler._apply")
|
|
1489
1794
|
if initial or "multiline" in props:
|
|
1490
1795
|
self._apply_editor_action(et)
|
|
1491
1796
|
_apply_common_visual(et, props)
|
|
@@ -1514,7 +1819,9 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1514
1819
|
try:
|
|
1515
1820
|
_fire(view, "on_submit", str(view.getText()))
|
|
1516
1821
|
except Exception:
|
|
1517
|
-
|
|
1822
|
+
diagnostics.swallowed(
|
|
1823
|
+
"android.TextInputHandler._apply_editor_action.SubmitProxy.onEditorAction"
|
|
1824
|
+
)
|
|
1518
1825
|
if not multiline:
|
|
1519
1826
|
try:
|
|
1520
1827
|
view.clearFocus()
|
|
@@ -1522,12 +1829,14 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1522
1829
|
imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE)
|
|
1523
1830
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0)
|
|
1524
1831
|
except Exception:
|
|
1525
|
-
|
|
1832
|
+
diagnostics.swallowed(
|
|
1833
|
+
"android.TextInputHandler._apply_editor_action.SubmitProxy.onEditorAction"
|
|
1834
|
+
)
|
|
1526
1835
|
return True
|
|
1527
1836
|
|
|
1528
1837
|
et.setOnEditorActionListener(SubmitProxy())
|
|
1529
1838
|
except Exception:
|
|
1530
|
-
|
|
1839
|
+
diagnostics.swallowed("android.TextInputHandler._apply_editor_action")
|
|
1531
1840
|
|
|
1532
1841
|
@staticmethod
|
|
1533
1842
|
def _autofill_hint(content_type: str) -> Optional[str]:
|
|
@@ -1559,7 +1868,7 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1559
1868
|
if hint:
|
|
1560
1869
|
et.setAutofillHints([hint])
|
|
1561
1870
|
except Exception:
|
|
1562
|
-
|
|
1871
|
+
diagnostics.swallowed("android.TextInputHandler._apply_autofill")
|
|
1563
1872
|
|
|
1564
1873
|
def _apply_clear_button(self, et: Any, enabled: bool) -> None:
|
|
1565
1874
|
# Best-effort drawableEnd "X": shows a system clear icon and wires
|
|
@@ -1588,12 +1897,12 @@ class TextInputHandler(AndroidViewHandler):
|
|
|
1588
1897
|
view.setText("")
|
|
1589
1898
|
return True
|
|
1590
1899
|
except Exception:
|
|
1591
|
-
|
|
1900
|
+
diagnostics.swallowed("android.TextInputHandler._apply_clear_button._ClearTouchProxy.onTouch")
|
|
1592
1901
|
return False
|
|
1593
1902
|
|
|
1594
1903
|
et.setOnTouchListener(_ClearTouchProxy())
|
|
1595
1904
|
except Exception:
|
|
1596
|
-
|
|
1905
|
+
diagnostics.swallowed("android.TextInputHandler._apply_clear_button")
|
|
1597
1906
|
|
|
1598
1907
|
|
|
1599
1908
|
class ImageHandler(AndroidViewHandler):
|
|
@@ -1606,12 +1915,12 @@ class ImageHandler(AndroidViewHandler):
|
|
|
1606
1915
|
ColorStateList = jclass("android.content.res.ColorStateList")
|
|
1607
1916
|
iv.setImageTintList(ColorStateList.valueOf(parse_color_int(props["tint_color"])))
|
|
1608
1917
|
except Exception:
|
|
1609
|
-
|
|
1918
|
+
diagnostics.swallowed("android.ImageHandler._apply")
|
|
1610
1919
|
if "placeholder_color" in props and props["placeholder_color"] is not None:
|
|
1611
1920
|
try:
|
|
1612
1921
|
iv.setBackgroundColor(parse_color_int(props["placeholder_color"]))
|
|
1613
1922
|
except Exception:
|
|
1614
|
-
|
|
1923
|
+
diagnostics.swallowed("android.ImageHandler._apply")
|
|
1615
1924
|
if "source" in props and props["source"]:
|
|
1616
1925
|
self._load_source(iv, str(props["source"]))
|
|
1617
1926
|
if "scale_type" in props and props["scale_type"]:
|
|
@@ -1648,7 +1957,7 @@ class ImageHandler(AndroidViewHandler):
|
|
|
1648
1957
|
iv.setImageBitmap(bitmap)
|
|
1649
1958
|
_fire(iv, "on_load")
|
|
1650
1959
|
except Exception:
|
|
1651
|
-
|
|
1960
|
+
diagnostics.swallowed("android.ImageHandler._load_source._on_ready")
|
|
1652
1961
|
|
|
1653
1962
|
def _on_error(message: str) -> None:
|
|
1654
1963
|
if _state_of(iv).get("pending_uri") == source:
|
|
@@ -1674,7 +1983,7 @@ class ImageHandler(AndroidViewHandler):
|
|
|
1674
1983
|
else:
|
|
1675
1984
|
_fire(iv, "on_error", f"drawable {res_name!r} not found")
|
|
1676
1985
|
except Exception:
|
|
1677
|
-
|
|
1986
|
+
diagnostics.swallowed("android.ImageHandler._load_source")
|
|
1678
1987
|
|
|
1679
1988
|
def _load_data_uri(self, iv: Any, source: str) -> None:
|
|
1680
1989
|
"""Decode an inline ``data:`` URI (base64 payload) into the view."""
|
|
@@ -1719,7 +2028,7 @@ class ImageHandler(AndroidViewHandler):
|
|
|
1719
2028
|
target_w = int(iv.getWidth())
|
|
1720
2029
|
target_h = int(iv.getHeight())
|
|
1721
2030
|
except Exception:
|
|
1722
|
-
|
|
2031
|
+
diagnostics.swallowed("android.ImageHandler._decode_downsampled")
|
|
1723
2032
|
if target_w <= 0:
|
|
1724
2033
|
try:
|
|
1725
2034
|
metrics = _ctx().getResources().getDisplayMetrics()
|
|
@@ -1780,7 +2089,7 @@ class ProgressBarHandler(AndroidViewHandler):
|
|
|
1780
2089
|
ColorStateList = jclass("android.content.res.ColorStateList")
|
|
1781
2090
|
pb.setProgressTintList(ColorStateList.valueOf(parse_color_int(props["color"])))
|
|
1782
2091
|
except Exception:
|
|
1783
|
-
|
|
2092
|
+
diagnostics.swallowed("android.ProgressBarHandler._apply")
|
|
1784
2093
|
if "track_color" in props and props["track_color"] is not None:
|
|
1785
2094
|
try:
|
|
1786
2095
|
ColorStateList = jclass("android.content.res.ColorStateList")
|
|
@@ -1788,12 +2097,12 @@ class ProgressBarHandler(AndroidViewHandler):
|
|
|
1788
2097
|
pb.setProgressBackgroundTintList(track)
|
|
1789
2098
|
pb.setSecondaryProgressTintList(track)
|
|
1790
2099
|
except Exception:
|
|
1791
|
-
|
|
2100
|
+
diagnostics.swallowed("android.ProgressBarHandler._apply")
|
|
1792
2101
|
if "indeterminate" in props:
|
|
1793
2102
|
try:
|
|
1794
2103
|
pb.setIndeterminate(bool(props["indeterminate"]))
|
|
1795
2104
|
except Exception:
|
|
1796
|
-
|
|
2105
|
+
diagnostics.swallowed("android.ProgressBarHandler._apply")
|
|
1797
2106
|
|
|
1798
2107
|
|
|
1799
2108
|
class ActivityIndicatorHandler(AndroidViewHandler):
|
|
@@ -1809,7 +2118,7 @@ class ActivityIndicatorHandler(AndroidViewHandler):
|
|
|
1809
2118
|
ColorStateList = jclass("android.content.res.ColorStateList")
|
|
1810
2119
|
pb.setIndeterminateTintList(ColorStateList.valueOf(parse_color_int(props["color"])))
|
|
1811
2120
|
except Exception:
|
|
1812
|
-
|
|
2121
|
+
diagnostics.swallowed("android.ActivityIndicatorHandler._apply")
|
|
1813
2122
|
if "size" in props and props["size"] is not None:
|
|
1814
2123
|
# The framework ProgressBar has no runtime size switch, so
|
|
1815
2124
|
# approximate "large" by scaling the indeterminate drawable.
|
|
@@ -1818,7 +2127,7 @@ class ActivityIndicatorHandler(AndroidViewHandler):
|
|
|
1818
2127
|
pb.setScaleX(scale)
|
|
1819
2128
|
pb.setScaleY(scale)
|
|
1820
2129
|
except Exception:
|
|
1821
|
-
|
|
2130
|
+
diagnostics.swallowed("android.ActivityIndicatorHandler._apply")
|
|
1822
2131
|
|
|
1823
2132
|
|
|
1824
2133
|
def _make_web_client(wv: Any) -> Any:
|
|
@@ -1854,13 +2163,13 @@ def _make_web_client(wv: Any) -> Any:
|
|
|
1854
2163
|
)
|
|
1855
2164
|
view.evaluateJavascript(shim, None)
|
|
1856
2165
|
except Exception:
|
|
1857
|
-
|
|
2166
|
+
diagnostics.swallowed("android._make_web_client._WebClientProxy.onPageFinished")
|
|
1858
2167
|
inject_js = (_state_of(wv).get("props") or {}).get("inject_javascript")
|
|
1859
2168
|
if inject_js:
|
|
1860
2169
|
try:
|
|
1861
2170
|
view.evaluateJavascript(str(inject_js), None)
|
|
1862
2171
|
except Exception:
|
|
1863
|
-
|
|
2172
|
+
diagnostics.swallowed("android._make_web_client._WebClientProxy.onPageFinished")
|
|
1864
2173
|
|
|
1865
2174
|
def shouldOverrideUrlLoading(self, view: Any, request: Any) -> bool:
|
|
1866
2175
|
try:
|
|
@@ -1873,7 +2182,7 @@ def _make_web_client(wv: Any) -> Any:
|
|
|
1873
2182
|
|
|
1874
2183
|
_fire(wv, "on_message", unquote(url[len(scheme) :]))
|
|
1875
2184
|
except Exception:
|
|
1876
|
-
|
|
2185
|
+
diagnostics.swallowed("android._make_web_client._WebClientProxy.shouldOverrideUrlLoading")
|
|
1877
2186
|
return True
|
|
1878
2187
|
return False
|
|
1879
2188
|
|
|
@@ -1893,7 +2202,7 @@ class WebViewHandler(AndroidViewHandler):
|
|
|
1893
2202
|
try:
|
|
1894
2203
|
wv.getSettings().setJavaScriptEnabled(True)
|
|
1895
2204
|
except Exception:
|
|
1896
|
-
|
|
2205
|
+
diagnostics.swallowed("android.WebViewHandler._apply")
|
|
1897
2206
|
|
|
1898
2207
|
if initial:
|
|
1899
2208
|
client = _make_web_client(wv)
|
|
@@ -1901,19 +2210,19 @@ class WebViewHandler(AndroidViewHandler):
|
|
|
1901
2210
|
try:
|
|
1902
2211
|
wv.setWebViewClient(client)
|
|
1903
2212
|
except Exception:
|
|
1904
|
-
|
|
2213
|
+
diagnostics.swallowed("android.WebViewHandler._apply")
|
|
1905
2214
|
|
|
1906
2215
|
# ``html`` takes precedence over ``url`` when both are present.
|
|
1907
2216
|
if "html" in props and props["html"]:
|
|
1908
2217
|
try:
|
|
1909
2218
|
wv.loadDataWithBaseURL(None, str(props["html"]), "text/html", "utf-8", None)
|
|
1910
2219
|
except Exception:
|
|
1911
|
-
|
|
2220
|
+
diagnostics.swallowed("android.WebViewHandler._apply")
|
|
1912
2221
|
elif "url" in props and props["url"]:
|
|
1913
2222
|
try:
|
|
1914
2223
|
wv.loadUrl(str(props["url"]))
|
|
1915
2224
|
except Exception:
|
|
1916
|
-
|
|
2225
|
+
diagnostics.swallowed("android.WebViewHandler._apply")
|
|
1917
2226
|
|
|
1918
2227
|
if "scroll_enabled" in props:
|
|
1919
2228
|
self._apply_scroll_enabled(wv, props["scroll_enabled"])
|
|
@@ -1930,7 +2239,7 @@ class WebViewHandler(AndroidViewHandler):
|
|
|
1930
2239
|
else:
|
|
1931
2240
|
wv.setOnTouchListener(None)
|
|
1932
2241
|
except Exception:
|
|
1933
|
-
|
|
2242
|
+
diagnostics.swallowed("android.WebViewHandler._apply_scroll_enabled")
|
|
1934
2243
|
|
|
1935
2244
|
|
|
1936
2245
|
class SpacerHandler(AndroidViewHandler):
|
|
@@ -1952,7 +2261,7 @@ class SafeAreaViewHandler(FlexContainerHandler):
|
|
|
1952
2261
|
"""Safe-area container using FrameLayout with ``fitsSystemWindows``."""
|
|
1953
2262
|
|
|
1954
2263
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
1955
|
-
fl =
|
|
2264
|
+
fl = _new_container()
|
|
1956
2265
|
fl.setFitsSystemWindows(True)
|
|
1957
2266
|
return fl
|
|
1958
2267
|
|
|
@@ -1995,7 +2304,7 @@ class ModalHandler(AndroidViewHandler):
|
|
|
1995
2304
|
try:
|
|
1996
2305
|
dialog.setCanceledOnTouchOutside(props["dismiss_on_backdrop"] is not False)
|
|
1997
2306
|
except Exception:
|
|
1998
|
-
|
|
2307
|
+
diagnostics.swallowed("android.ModalHandler._apply")
|
|
1999
2308
|
|
|
2000
2309
|
def insert_child(self, parent: Any, child: Any, index: int) -> None:
|
|
2001
2310
|
state = _state_of(parent)
|
|
@@ -2012,7 +2321,7 @@ class ModalHandler(AndroidViewHandler):
|
|
|
2012
2321
|
try:
|
|
2013
2322
|
content.removeView(child)
|
|
2014
2323
|
except Exception:
|
|
2015
|
-
|
|
2324
|
+
diagnostics.swallowed("android.ModalHandler.remove_child")
|
|
2016
2325
|
else:
|
|
2017
2326
|
buf = state.get("pending")
|
|
2018
2327
|
if buf and child in buf:
|
|
@@ -2057,18 +2366,18 @@ class ModalHandler(AndroidViewHandler):
|
|
|
2057
2366
|
WMLP = jclass("android.view.WindowManager$LayoutParams")
|
|
2058
2367
|
window.setLayout(WMLP.MATCH_PARENT, WMLP.MATCH_PARENT)
|
|
2059
2368
|
except Exception:
|
|
2060
|
-
|
|
2369
|
+
diagnostics.swallowed("android.ModalHandler._present")
|
|
2061
2370
|
try:
|
|
2062
2371
|
dialog.setCanceledOnTouchOutside(props.get("dismiss_on_backdrop") is not False)
|
|
2063
2372
|
except Exception:
|
|
2064
|
-
|
|
2373
|
+
diagnostics.swallowed("android.ModalHandler._present")
|
|
2065
2374
|
state["dialog"] = dialog
|
|
2066
2375
|
state["content_view"] = content
|
|
2067
2376
|
for child in state.pop("pending", []):
|
|
2068
2377
|
try:
|
|
2069
2378
|
content.addView(child)
|
|
2070
2379
|
except Exception:
|
|
2071
|
-
|
|
2380
|
+
diagnostics.swallowed("android.ModalHandler._present")
|
|
2072
2381
|
|
|
2073
2382
|
OnShowListener = jclass("android.content.DialogInterface$OnShowListener")
|
|
2074
2383
|
|
|
@@ -2087,7 +2396,7 @@ class ModalHandler(AndroidViewHandler):
|
|
|
2087
2396
|
dialog.setOnDismissListener(_DismissProxy())
|
|
2088
2397
|
dialog.show()
|
|
2089
2398
|
except Exception:
|
|
2090
|
-
|
|
2399
|
+
diagnostics.swallowed("android.ModalHandler._present")
|
|
2091
2400
|
|
|
2092
2401
|
def _dismiss(self, placeholder: Any, state: Dict[str, Any]) -> None:
|
|
2093
2402
|
dialog = state.pop("dialog", None)
|
|
@@ -2096,7 +2405,7 @@ class ModalHandler(AndroidViewHandler):
|
|
|
2096
2405
|
try:
|
|
2097
2406
|
dialog.dismiss()
|
|
2098
2407
|
except Exception:
|
|
2099
|
-
|
|
2408
|
+
diagnostics.swallowed("android.ModalHandler._dismiss")
|
|
2100
2409
|
|
|
2101
2410
|
|
|
2102
2411
|
# ======================================================================
|
|
@@ -2148,7 +2457,7 @@ class PortalHandler(AndroidViewHandler):
|
|
|
2148
2457
|
lp = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
2149
2458
|
container.addView(overlay, lp)
|
|
2150
2459
|
except Exception:
|
|
2151
|
-
|
|
2460
|
+
diagnostics.swallowed("android.PortalHandler._ensure_attached")
|
|
2152
2461
|
|
|
2153
2462
|
def insert_child(self, parent: Any, child: Any, index: int) -> None:
|
|
2154
2463
|
self._ensure_attached(parent)
|
|
@@ -2158,7 +2467,7 @@ class PortalHandler(AndroidViewHandler):
|
|
|
2158
2467
|
try:
|
|
2159
2468
|
parent.removeView(child)
|
|
2160
2469
|
except Exception:
|
|
2161
|
-
|
|
2470
|
+
diagnostics.swallowed("android.PortalHandler.remove_child")
|
|
2162
2471
|
|
|
2163
2472
|
def set_frame(self, native_view: Any, x: float, y: float, width: float, height: float) -> None:
|
|
2164
2473
|
# The overlay fills its container via MATCH_PARENT layout params;
|
|
@@ -2171,7 +2480,7 @@ class PortalHandler(AndroidViewHandler):
|
|
|
2171
2480
|
if parent is not None:
|
|
2172
2481
|
parent.removeView(overlay)
|
|
2173
2482
|
except Exception:
|
|
2174
|
-
|
|
2483
|
+
diagnostics.swallowed("android.PortalHandler._teardown")
|
|
2175
2484
|
|
|
2176
2485
|
|
|
2177
2486
|
class SliderHandler(AndroidViewHandler):
|
|
@@ -2225,7 +2534,7 @@ class TabBarHandler(AndroidViewHandler):
|
|
|
2225
2534
|
try:
|
|
2226
2535
|
bnv.setLabelVisibilityMode(self._LABEL_VISIBILITY_LABELED)
|
|
2227
2536
|
except Exception:
|
|
2228
|
-
|
|
2537
|
+
diagnostics.swallowed("android.TabBarHandler._build")
|
|
2229
2538
|
return bnv
|
|
2230
2539
|
except Exception:
|
|
2231
2540
|
LinearLayout = jclass("android.widget.LinearLayout")
|
|
@@ -2275,7 +2584,7 @@ class TabBarHandler(AndroidViewHandler):
|
|
|
2275
2584
|
|
|
2276
2585
|
bnv.setOnItemSelectedListener(_TabSelectProxy())
|
|
2277
2586
|
except Exception:
|
|
2278
|
-
|
|
2587
|
+
diagnostics.swallowed("android.TabBarHandler._bind_material_listener")
|
|
2279
2588
|
|
|
2280
2589
|
def _set_menu(self, bnv: Any, items: list) -> None:
|
|
2281
2590
|
try:
|
|
@@ -2289,9 +2598,9 @@ class TabBarHandler(AndroidViewHandler):
|
|
|
2289
2598
|
try:
|
|
2290
2599
|
menu_item.setIcon(res_id)
|
|
2291
2600
|
except Exception:
|
|
2292
|
-
|
|
2601
|
+
diagnostics.swallowed("android.TabBarHandler._set_menu")
|
|
2293
2602
|
except Exception:
|
|
2294
|
-
|
|
2603
|
+
diagnostics.swallowed("android.TabBarHandler._set_menu")
|
|
2295
2604
|
|
|
2296
2605
|
def _resolve_icon(self, icon: Any) -> int:
|
|
2297
2606
|
"""Resolve a tab icon spec to an `android.R.drawable.*` res id.
|
|
@@ -2325,7 +2634,7 @@ class TabBarHandler(AndroidViewHandler):
|
|
|
2325
2634
|
try:
|
|
2326
2635
|
bnv.setSelectedItemId(i)
|
|
2327
2636
|
except Exception:
|
|
2328
|
-
|
|
2637
|
+
diagnostics.swallowed("android.TabBarHandler._set_active")
|
|
2329
2638
|
break
|
|
2330
2639
|
|
|
2331
2640
|
def _apply_fallback(self, ll: Any, props: Dict[str, Any]) -> None:
|
|
@@ -2374,7 +2683,7 @@ class PressableHandler(FlexContainerHandler):
|
|
|
2374
2683
|
_TAP_SLOP_DP = 12.0
|
|
2375
2684
|
|
|
2376
2685
|
def _build(self, props: Dict[str, Any]) -> Any:
|
|
2377
|
-
fl =
|
|
2686
|
+
fl = _new_container()
|
|
2378
2687
|
fl.setClickable(True)
|
|
2379
2688
|
fl.setFocusable(True)
|
|
2380
2689
|
self._bind_press_stream(fl)
|
|
@@ -2403,6 +2712,8 @@ class PressableHandler(FlexContainerHandler):
|
|
|
2403
2712
|
|
|
2404
2713
|
class _PressTouchProxy(dynamic_proxy(OnTouchListener)):
|
|
2405
2714
|
def onTouch(self, view: Any, event: Any) -> bool:
|
|
2715
|
+
if _pointer_events_blocked(view):
|
|
2716
|
+
return False
|
|
2406
2717
|
state = _state_of(view)
|
|
2407
2718
|
_feed_motion_event(view, state, event)
|
|
2408
2719
|
action = int(event.getActionMasked())
|
|
@@ -2425,7 +2736,9 @@ class PressableHandler(FlexContainerHandler):
|
|
|
2425
2736
|
try:
|
|
2426
2737
|
view.animate().alpha(opacity).setDuration(50).start()
|
|
2427
2738
|
except Exception:
|
|
2428
|
-
|
|
2739
|
+
diagnostics.swallowed(
|
|
2740
|
+
"android.PressableHandler._bind_press_stream._PressTouchProxy.onTouch"
|
|
2741
|
+
)
|
|
2429
2742
|
if _has_event(view, "on_long_press"):
|
|
2430
2743
|
seq = press["seq"]
|
|
2431
2744
|
|
|
@@ -2456,7 +2769,9 @@ class PressableHandler(FlexContainerHandler):
|
|
|
2456
2769
|
try:
|
|
2457
2770
|
view.animate().alpha(1.0).setDuration(100).start()
|
|
2458
2771
|
except Exception:
|
|
2459
|
-
|
|
2772
|
+
diagnostics.swallowed(
|
|
2773
|
+
"android.PressableHandler._bind_press_stream._PressTouchProxy.onTouch"
|
|
2774
|
+
)
|
|
2460
2775
|
if action == 1 and not press["moved"] and not press["long_fired"]:
|
|
2461
2776
|
_fire(view, "on_press")
|
|
2462
2777
|
return True
|
|
@@ -2464,7 +2779,7 @@ class PressableHandler(FlexContainerHandler):
|
|
|
2464
2779
|
|
|
2465
2780
|
fl.setOnTouchListener(_PressTouchProxy())
|
|
2466
2781
|
except Exception:
|
|
2467
|
-
|
|
2782
|
+
diagnostics.swallowed("android.PressableHandler._bind_press_stream")
|
|
2468
2783
|
|
|
2469
2784
|
|
|
2470
2785
|
# ======================================================================
|
|
@@ -2491,20 +2806,29 @@ class StatusBarHandler(AndroidViewHandler):
|
|
|
2491
2806
|
return
|
|
2492
2807
|
if "background_color" in props and props["background_color"] is not None:
|
|
2493
2808
|
window.setStatusBarColor(parse_color_int(props["background_color"]))
|
|
2809
|
+
View = jclass("android.view.View")
|
|
2810
|
+
decor = window.getDecorView()
|
|
2494
2811
|
if "bar_style" in props and props["bar_style"] is not None:
|
|
2495
2812
|
# API 23+: setSystemUiVisibility with SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
|
2496
2813
|
# for dark-content (light backgrounds), 0 for light-content.
|
|
2497
|
-
View = jclass("android.view.View")
|
|
2498
2814
|
bar_style = props["bar_style"]
|
|
2499
|
-
decor = window.getDecorView()
|
|
2500
2815
|
flags = decor.getSystemUiVisibility()
|
|
2501
2816
|
if bar_style in ("dark", "default"):
|
|
2502
2817
|
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
|
2503
2818
|
else:
|
|
2504
2819
|
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
|
2505
2820
|
decor.setSystemUiVisibility(flags)
|
|
2506
|
-
|
|
2507
|
-
|
|
2821
|
+
if "hidden" in props and props["hidden"] is not None:
|
|
2822
|
+
flags = decor.getSystemUiVisibility()
|
|
2823
|
+
if props["hidden"]:
|
|
2824
|
+
flags |= View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
2825
|
+
else:
|
|
2826
|
+
flags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
2827
|
+
decor.setSystemUiVisibility(flags)
|
|
2828
|
+
except Exception as exc:
|
|
2829
|
+
from .. import diagnostics
|
|
2830
|
+
|
|
2831
|
+
diagnostics.warn_once(f"StatusBar: could not apply props on Android: {exc!r}")
|
|
2508
2832
|
|
|
2509
2833
|
|
|
2510
2834
|
class KeyboardAvoidingViewHandler(FlexContainerHandler):
|
|
@@ -2552,7 +2876,7 @@ def _present_alert(
|
|
|
2552
2876
|
try:
|
|
2553
2877
|
on_result(index)
|
|
2554
2878
|
except Exception:
|
|
2555
|
-
|
|
2879
|
+
diagnostics.swallowed("android._present_alert._deliver")
|
|
2556
2880
|
|
|
2557
2881
|
def _present_on_main() -> None:
|
|
2558
2882
|
try:
|
|
@@ -2738,7 +3062,7 @@ class CheckboxHandler(AndroidViewHandler):
|
|
|
2738
3062
|
ColorStateList = jclass("android.content.res.ColorStateList")
|
|
2739
3063
|
cb.setButtonTintList(ColorStateList.valueOf(parse_color_int(props["color"])))
|
|
2740
3064
|
except Exception:
|
|
2741
|
-
|
|
3065
|
+
diagnostics.swallowed("android.CheckboxHandler._apply")
|
|
2742
3066
|
_apply_accessibility(cb, props)
|
|
2743
3067
|
|
|
2744
3068
|
|
|
@@ -2798,7 +3122,7 @@ class SegmentedControlHandler(AndroidViewHandler):
|
|
|
2798
3122
|
try:
|
|
2799
3123
|
ll.removeAllViews()
|
|
2800
3124
|
except Exception:
|
|
2801
|
-
|
|
3125
|
+
diagnostics.swallowed("android.SegmentedControlHandler._rebuild")
|
|
2802
3126
|
state["buttons"] = []
|
|
2803
3127
|
LL_LP = jclass("android.widget.LinearLayout$LayoutParams")
|
|
2804
3128
|
restyle = self._restyle
|
|
@@ -2808,7 +3132,7 @@ class SegmentedControlHandler(AndroidViewHandler):
|
|
|
2808
3132
|
try:
|
|
2809
3133
|
btn.setAllCaps(False)
|
|
2810
3134
|
except Exception:
|
|
2811
|
-
|
|
3135
|
+
diagnostics.swallowed("android.SegmentedControlHandler._rebuild")
|
|
2812
3136
|
# Equal-width segments: zero base width + weight 1, full height.
|
|
2813
3137
|
btn.setLayoutParams(LL_LP(0, LL_LP.MATCH_PARENT, 1.0))
|
|
2814
3138
|
enabled = (state.get("props") or {}).get("enabled") is not False
|
|
@@ -2856,7 +3180,7 @@ class SegmentedControlHandler(AndroidViewHandler):
|
|
|
2856
3180
|
btn.setBackground(drawable)
|
|
2857
3181
|
btn.setEnabled(enabled)
|
|
2858
3182
|
except Exception:
|
|
2859
|
-
|
|
3183
|
+
diagnostics.swallowed("android.SegmentedControlHandler._style_segment")
|
|
2860
3184
|
|
|
2861
3185
|
def measure_intrinsic(
|
|
2862
3186
|
self,
|
|
@@ -2911,7 +3235,7 @@ class DatePickerHandler(AndroidViewHandler):
|
|
|
2911
3235
|
try:
|
|
2912
3236
|
btn.setAllCaps(False)
|
|
2913
3237
|
except Exception:
|
|
2914
|
-
|
|
3238
|
+
diagnostics.swallowed("android.DatePickerHandler._build")
|
|
2915
3239
|
open_dialog = self._open_dialog
|
|
2916
3240
|
|
|
2917
3241
|
class _DateTriggerProxy(dynamic_proxy(jclass("android.view.View").OnClickListener)):
|
|
@@ -3021,7 +3345,7 @@ class DatePickerHandler(AndroidViewHandler):
|
|
|
3021
3345
|
if maximum:
|
|
3022
3346
|
picker.setMaxDate(self._parse_to_calendar(maximum, mode).getTimeInMillis())
|
|
3023
3347
|
except Exception:
|
|
3024
|
-
|
|
3348
|
+
diagnostics.swallowed("android.DatePickerHandler._apply_min_max")
|
|
3025
3349
|
|
|
3026
3350
|
def _parse_to_calendar(self, value: Any, mode: str) -> Any:
|
|
3027
3351
|
Calendar = jclass("java.util.Calendar")
|
|
@@ -3033,7 +3357,7 @@ class DatePickerHandler(AndroidViewHandler):
|
|
|
3033
3357
|
fmt = SimpleDateFormat(self._PATTERNS.get(mode, self._PATTERNS["date"]), Locale.US)
|
|
3034
3358
|
cal.setTime(fmt.parse(str(value)))
|
|
3035
3359
|
except Exception:
|
|
3036
|
-
|
|
3360
|
+
diagnostics.swallowed("android.DatePickerHandler._parse_to_calendar")
|
|
3037
3361
|
return cal
|
|
3038
3362
|
|
|
3039
3363
|
def _format_calendar(self, cal: Any, mode: str) -> str:
|
|
@@ -3053,7 +3377,7 @@ class DatePickerHandler(AndroidViewHandler):
|
|
|
3053
3377
|
try:
|
|
3054
3378
|
btn.setText(iso)
|
|
3055
3379
|
except Exception:
|
|
3056
|
-
|
|
3380
|
+
diagnostics.swallowed("android.DatePickerHandler._commit")
|
|
3057
3381
|
_fire(btn, "on_change", iso)
|
|
3058
3382
|
|
|
3059
3383
|
|
|
@@ -3153,7 +3477,7 @@ class VirtualListHandler(AndroidViewHandler):
|
|
|
3153
3477
|
try:
|
|
3154
3478
|
info["pool"].release(_java_id(container))
|
|
3155
3479
|
except Exception:
|
|
3156
|
-
|
|
3480
|
+
diagnostics.swallowed("android.VirtualListHandler._build._Delegate.onRowRecycled")
|
|
3157
3481
|
|
|
3158
3482
|
def onScrolled(self, offset_dp: float, extent_dp: float, range_dp: float) -> None:
|
|
3159
3483
|
view = info.get("view")
|
|
@@ -3202,12 +3526,12 @@ class VirtualListHandler(AndroidViewHandler):
|
|
|
3202
3526
|
try:
|
|
3203
3527
|
view.setVerticalScrollBarEnabled(show)
|
|
3204
3528
|
except Exception:
|
|
3205
|
-
|
|
3529
|
+
diagnostics.swallowed("android.VirtualListHandler._apply")
|
|
3206
3530
|
if data_changed and not initial:
|
|
3207
3531
|
try:
|
|
3208
3532
|
view.notifyDataChanged()
|
|
3209
3533
|
except Exception:
|
|
3210
|
-
|
|
3534
|
+
diagnostics.swallowed("android.VirtualListHandler._apply")
|
|
3211
3535
|
|
|
3212
3536
|
def _teardown(self, native_view: Any) -> None:
|
|
3213
3537
|
info = _pn_vlist_info.pop(_java_id(native_view), None)
|
|
@@ -3238,7 +3562,7 @@ class VirtualListHandler(AndroidViewHandler):
|
|
|
3238
3562
|
args.get("animated", True) is not False,
|
|
3239
3563
|
)
|
|
3240
3564
|
except Exception:
|
|
3241
|
-
|
|
3565
|
+
diagnostics.swallowed("android.VirtualListHandler.command")
|
|
3242
3566
|
return None
|
|
3243
3567
|
if name == "scroll_to_index":
|
|
3244
3568
|
try:
|
|
@@ -3247,7 +3571,7 @@ class VirtualListHandler(AndroidViewHandler):
|
|
|
3247
3571
|
args.get("animated", True) is not False,
|
|
3248
3572
|
)
|
|
3249
3573
|
except Exception:
|
|
3250
|
-
|
|
3574
|
+
diagnostics.swallowed("android.VirtualListHandler.command")
|
|
3251
3575
|
return None
|
|
3252
3576
|
if name == "scroll_to_end":
|
|
3253
3577
|
info = _pn_vlist_info.get(_java_id(native_view))
|
|
@@ -3255,7 +3579,7 @@ class VirtualListHandler(AndroidViewHandler):
|
|
|
3255
3579
|
try:
|
|
3256
3580
|
native_view.scrollToIndex(max(0, count - 1), args.get("animated", True) is not False)
|
|
3257
3581
|
except Exception:
|
|
3258
|
-
|
|
3582
|
+
diagnostics.swallowed("android.VirtualListHandler.command")
|
|
3259
3583
|
return None
|
|
3260
3584
|
if name == "get_scroll_offset":
|
|
3261
3585
|
try:
|