| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d8e8933 commit 235007e
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1516,13 +1516,23 @@ def _present_alert( | |||
| 1516 | 1516 | ) -> None: | |
| 1517 | 1517 | """Present an AlertDialog or BottomSheet (``style='action_sheet'``). | |
| 1518 | 1518 | ||
| 1519 | + Safe to call from any thread — the AlertDialog work is automatically | ||
| 1520 | + marshalled to the main looper via | ||
| 1521 | + [`pythonnative.runtime.call_on_main_thread`][pythonnative.runtime.call_on_main_thread]. | ||
| 1522 | + Returns immediately; the dialog appears on the next main-loop tick. | ||
| 1523 | + | ||
| 1519 | 1524 | ``buttons`` is a list of ``{"label": str, "style": | |
| 1520 | 1525 | "default"|"cancel"|"destructive"}`` dicts (no ``on_press``). | |
| 1521 | 1526 | Exactly one ``on_result(index)`` is invoked when the user picks a | |
| 1522 | - button; a dismiss delivers ``-1``. ``on_result`` runs on the | ||
| 1523 | - Android main thread. | ||
| 1527 | + button; a dismiss delivers ``-1``. ``on_result`` always runs on | ||
| 1528 | + the Android main thread; if it needs to wake an asyncio.Future, | ||
| 1529 | + use | ||
| 1530 | + [`pythonnative.runtime.resolve_future`][pythonnative.runtime.resolve_future] | ||
| 1531 | + to hop back onto the loop thread. | ||
| 1524 | 1532 | """ | |
| 1525 | 1533 | del style # AlertDialog has no distinct action-sheet style on Android. | |
| 1534 | + from ..runtime import call_on_main_thread | ||
| 1535 | + | ||
| 1526 | 1536 | delivered = [False] | |
| 1527 | 1537 | ||
| 1528 | 1538 | def _deliver(index: int) -> None: | |
@@ -1534,69 +1544,71 @@ def _deliver(index: int) -> None: | |||
| 1534 | 1544 | except Exception: | |
| 1535 | 1545 | pass | |
| 1536 | 1546 | ||
| 1537 | - try: | ||
| 1538 | - AlertDialog = jclass("android.app.AlertDialog$Builder") | ||
| 1539 | - builder = AlertDialog(_ctx()) | ||
| 1540 | - builder.setTitle(str(title or "")) | ||
| 1541 | - if message is not None: | ||
| 1542 | - builder.setMessage(str(message)) | ||
| 1543 | - if not buttons: | ||
| 1544 | - buttons = [{"label": "OK", "style": "default"}] | ||
| 1545 | - | ||
| 1546 | - # AlertDialog only has three slots (positive/negative/neutral). | ||
| 1547 | - # Assign the first matching style class to the conventional | ||
| 1548 | - # slot, then spill any leftovers into whichever slot is free. | ||
| 1549 | - slot_for: dict = {} | ||
| 1550 | - free_slots = ["positive", "negative", "neutral"] | ||
| 1551 | - for i, spec in enumerate(buttons): | ||
| 1552 | - kind = spec.get("style", "default") | ||
| 1553 | - preferred = { | ||
| 1554 | - "default": "positive", | ||
| 1555 | - "cancel": "negative", | ||
| 1556 | - "destructive": "neutral", | ||
| 1557 | - }.get(kind, "positive") | ||
| 1558 | - if preferred in free_slots: | ||
| 1559 | - slot_for[i] = preferred | ||
| 1560 | - free_slots.remove(preferred) | ||
| 1561 | - for i, spec in enumerate(buttons): | ||
| 1562 | - if i in slot_for: | ||
| 1563 | - continue | ||
| 1564 | - if not free_slots: | ||
| 1565 | - break | ||
| 1566 | - slot_for[i] = free_slots.pop(0) | ||
| 1567 | - | ||
| 1568 | - OnClickListener = jclass("android.content.DialogInterface$OnClickListener") | ||
| 1569 | - | ||
| 1570 | - def make_listener(button_index: int) -> Any: | ||
| 1571 | - class _Proxy(dynamic_proxy(OnClickListener)): | ||
| 1572 | - def onClick(self, dialog: Any, which: int) -> None: | ||
| 1573 | - _deliver(button_index) | ||
| 1574 | - | ||
| 1575 | - return _Proxy() | ||
| 1547 | + def _present_on_main() -> None: | ||
| 1548 | + try: | ||
| 1549 | + AlertDialog = jclass("android.app.AlertDialog$Builder") | ||
| 1550 | + builder = AlertDialog(_ctx()) | ||
| 1551 | + builder.setTitle(str(title or "")) | ||
| 1552 | + if message is not None: | ||
| 1553 | + builder.setMessage(str(message)) | ||
| 1554 | + button_specs = buttons or [{"label": "OK", "style": "default"}] | ||
| 1555 | + | ||
| 1556 | + # AlertDialog only has three slots (positive/negative/neutral). | ||
| 1557 | + # Assign the first matching style class to the conventional | ||
| 1558 | + # slot, then spill any leftovers into whichever slot is free. | ||
| 1559 | + slot_for: dict = {} | ||
| 1560 | + free_slots = ["positive", "negative", "neutral"] | ||
| 1561 | + for i, spec in enumerate(button_specs): | ||
| 1562 | + kind = spec.get("style", "default") | ||
| 1563 | + preferred = { | ||
| 1564 | + "default": "positive", | ||
| 1565 | + "cancel": "negative", | ||
| 1566 | + "destructive": "neutral", | ||
| 1567 | + }.get(kind, "positive") | ||
| 1568 | + if preferred in free_slots: | ||
| 1569 | + slot_for[i] = preferred | ||
| 1570 | + free_slots.remove(preferred) | ||
| 1571 | + for i, spec in enumerate(button_specs): | ||
| 1572 | + if i in slot_for: | ||
| 1573 | + continue | ||
| 1574 | + if not free_slots: | ||
| 1575 | + break | ||
| 1576 | + slot_for[i] = free_slots.pop(0) | ||
| 1577 | + | ||
| 1578 | + OnClickListener = jclass("android.content.DialogInterface$OnClickListener") | ||
| 1579 | + | ||
| 1580 | + def make_listener(button_index: int) -> Any: | ||
| 1581 | + class _Proxy(dynamic_proxy(OnClickListener)): | ||
| 1582 | + def onClick(self, dialog: Any, which: int) -> None: | ||
| 1583 | + _deliver(button_index) | ||
| 1584 | + | ||
| 1585 | + return _Proxy() | ||
| 1586 | + | ||
| 1587 | + for i, spec in enumerate(button_specs): | ||
| 1588 | + slot = slot_for.get(i) | ||
| 1589 | + if slot is None: | ||
| 1590 | + continue | ||
| 1591 | + label = str(spec.get("label", "")) | ||
| 1592 | + listener = make_listener(i) | ||
| 1593 | + if slot == "positive": | ||
| 1594 | + builder.setPositiveButton(label, listener) | ||
| 1595 | + elif slot == "negative": | ||
| 1596 | + builder.setNegativeButton(label, listener) | ||
| 1597 | + else: | ||
| 1598 | + builder.setNeutralButton(label, listener) | ||
| 1576 | 1599 | ||
| 1577 | - for i, spec in enumerate(buttons): | ||
| 1578 | - slot = slot_for.get(i) | ||
| 1579 | - if slot is None: | ||
| 1580 | - continue | ||
| 1581 | - label = str(spec.get("label", "")) | ||
| 1582 | - listener = make_listener(i) | ||
| 1583 | - if slot == "positive": | ||
| 1584 | - builder.setPositiveButton(label, listener) | ||
| 1585 | - elif slot == "negative": | ||
| 1586 | - builder.setNegativeButton(label, listener) | ||
| 1587 | - else: | ||
| 1588 | - builder.setNeutralButton(label, listener) | ||
| 1600 | + OnCancelListener = jclass("android.content.DialogInterface$OnCancelListener") | ||
| 1589 | 1601 | ||
| 1590 | - OnCancelListener = jclass("android.content.DialogInterface$OnCancelListener") | ||
| 1602 | + class _CancelProxy(dynamic_proxy(OnCancelListener)): | ||
| 1603 | + def onCancel(self, dialog: Any) -> None: | ||
| 1604 | + _deliver(-1) | ||
| 1591 | 1605 | ||
| 1592 | - class _CancelProxy(dynamic_proxy(OnCancelListener)): | ||
| 1593 | - def onCancel(self, dialog: Any) -> None: | ||
| 1594 | - _deliver(-1) | ||
| 1606 | + builder.setOnCancelListener(_CancelProxy()) | ||
| 1607 | + builder.show() | ||
| 1608 | + except Exception: | ||
| 1609 | + _deliver(-1) | ||
| 1595 | 1610 | ||
| 1596 | - builder.setOnCancelListener(_CancelProxy()) | ||
| 1597 | - builder.show() | ||
| 1598 | - except Exception: | ||
| 1599 | - _deliver(-1) | ||
| 1611 | + call_on_main_thread(_present_on_main) | ||
| 1600 | 1612 | ||
| 1601 | 1613 | ||
| 1602 | 1614 | # ====================================================================== | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2467,62 +2467,71 @@ def _present_alert( | |||
| 2467 | 2467 | ) -> None: | |
| 2468 | 2468 | """Present a UIAlertController of the given style. | |
| 2469 | 2469 | ||
| 2470 | + Safe to call from any thread — the UIKit work is automatically | ||
| 2471 | + marshalled to the main thread via | ||
| 2472 | + [`pythonnative.runtime.call_on_main_thread`][pythonnative.runtime.call_on_main_thread]. | ||
| 2473 | + Returns immediately; the alert appears on the next main-loop tick. | ||
| 2474 | + | ||
| 2470 | 2475 | ``buttons`` is a list of ``{"label": str, "style": | |
| 2471 | 2476 | "default"|"cancel"|"destructive"}`` dicts (no ``on_press``). When | |
| 2472 | 2477 | the user picks button ``i`` the helper invokes ``on_result(i)`` | |
| 2473 | 2478 | exactly once. A dismiss (e.g. swipe-to-cancel on iPad) delivers | |
| 2474 | - ``-1``. ``on_result`` runs on the main thread. | ||
| 2479 | + ``-1``. ``on_result`` always runs on the main thread; if it needs | ||
| 2480 | + to wake an asyncio.Future, use | ||
| 2481 | + [`pythonnative.runtime.resolve_future`][pythonnative.runtime.resolve_future] | ||
| 2482 | + to hop back onto the loop thread. | ||
| 2475 | 2483 | """ | |
| 2476 | - try: | ||
| 2477 | - UIAlertController = ObjCClass("UIAlertController") | ||
| 2478 | - UIAlertAction = ObjCClass("UIAlertAction") | ||
| 2479 | - UIApplication = ObjCClass("UIApplication") | ||
| 2480 | - ctrl = UIAlertController.alertControllerWithTitle_message_preferredStyle_( | ||
| 2481 | - str(title or ""), | ||
| 2482 | - str(message) if message is not None else None, | ||
| 2483 | - 0 if style == "alert" else 1, | ||
| 2484 | - ) | ||
| 2485 | - if not buttons: | ||
| 2486 | - buttons = [{"label": "OK", "style": "default"}] | ||
| 2484 | + from ..runtime import call_on_main_thread | ||
| 2487 | 2485 | ||
| 2488 | - delivered = [False] | ||
| 2486 | + delivered = [False] | ||
| 2489 | 2487 | ||
| 2490 | - def _deliver(index: int) -> None: | ||
| 2491 | - if delivered[0]: | ||
| 2492 | - return | ||
| 2493 | - delivered[0] = True | ||
| 2494 | - try: | ||
| 2495 | - on_result(index) | ||
| 2496 | - except Exception: | ||
| 2497 | - pass | ||
| 2488 | + def _deliver(index: int) -> None: | ||
| 2489 | + if delivered[0]: | ||
| 2490 | + return | ||
| 2491 | + delivered[0] = True | ||
| 2492 | + try: | ||
| 2493 | + on_result(index) | ||
| 2494 | + except Exception: | ||
| 2495 | + pass | ||
| 2496 | + | ||
| 2497 | + def _present_on_main() -> None: | ||
| 2498 | + try: | ||
| 2499 | + UIAlertController = ObjCClass("UIAlertController") | ||
| 2500 | + UIAlertAction = ObjCClass("UIAlertAction") | ||
| 2501 | + UIApplication = ObjCClass("UIApplication") | ||
| 2502 | + ctrl = UIAlertController.alertControllerWithTitle_message_preferredStyle_( | ||
| 2503 | + str(title or ""), | ||
| 2504 | + str(message) if message is not None else None, | ||
| 2505 | + 0 if style == "alert" else 1, | ||
| 2506 | + ) | ||
| 2507 | + button_specs = buttons or [{"label": "OK", "style": "default"}] | ||
| 2498 | 2508 | ||
| 2499 | - for i, spec in enumerate(buttons): | ||
| 2500 | - label = str(spec.get("label", "OK")) | ||
| 2501 | - kind = spec.get("style", "default") | ||
| 2502 | - kind_int = {"default": 0, "cancel": 1, "destructive": 2}.get(kind, 0) | ||
| 2509 | + for i, spec in enumerate(button_specs): | ||
| 2510 | + label = str(spec.get("label", "OK")) | ||
| 2511 | + kind = spec.get("style", "default") | ||
| 2512 | + kind_int = {"default": 0, "cancel": 1, "destructive": 2}.get(kind, 0) | ||
| 2503 | 2513 | ||
| 2504 | - def make_handler(button_index: int) -> Any: | ||
| 2505 | - def _on_action(action: _ct.c_void_p) -> None: # noqa: ARG001 | ||
| 2506 | - _deliver(button_index) | ||
| 2514 | + def make_handler(button_index: int) -> Any: | ||
| 2515 | + def _on_action(action: _ct.c_void_p) -> None: # noqa: ARG001 | ||
| 2516 | + _deliver(button_index) | ||
| 2507 | 2517 | ||
| 2508 | - return _on_action | ||
| 2518 | + return _on_action | ||
| 2509 | 2519 | ||
| 2510 | - action = UIAlertAction.actionWithTitle_style_handler_( | ||
| 2511 | - label, | ||
| 2512 | - kind_int, | ||
| 2513 | - make_handler(i), | ||
| 2514 | - ) | ||
| 2515 | - ctrl.addAction_(action) | ||
| 2516 | - top = _top_view_controller_for_alert(UIApplication.sharedApplication) | ||
| 2517 | - if top is not None: | ||
| 2518 | - top.presentViewController_animated_completion_(ctrl, True, None) | ||
| 2519 | - else: | ||
| 2520 | - _deliver(-1) | ||
| 2521 | - except Exception: | ||
| 2522 | - try: | ||
| 2523 | - on_result(-1) | ||
| 2520 | + action = UIAlertAction.actionWithTitle_style_handler_( | ||
| 2521 | + label, | ||
| 2522 | + kind_int, | ||
| 2523 | + make_handler(i), | ||
| 2524 | + ) | ||
| 2525 | + ctrl.addAction_(action) | ||
| 2526 | + top = _top_view_controller_for_alert(UIApplication.sharedApplication) | ||
| 2527 | + if top is not None: | ||
| 2528 | + top.presentViewController_animated_completion_(ctrl, True, None) | ||
| 2529 | + else: | ||
| 2530 | + _deliver(-1) | ||
| 2524 | 2531 | except Exception: | |
| 2525 | - pass | ||
| 2532 | + _deliver(-1) | ||
| 2533 | + | ||
| 2534 | + call_on_main_thread(_present_on_main) | ||
| 2526 | 2535 | ||
| 2527 | 2536 | ||
| 2528 | 2537 | # ====================================================================== | |
| Back | FazBrowse Home | New Git URL |
0 commit comments