"""Opens the client's own "Share / Forward to" sheet and reports the chosen chats. `ShareAlert` is the sheet the app itself uses for forwarding, so reusing it keeps the picker pixel-identical to the stock one. It is constructed with no payload (`sendingMessageObjects`, `sendingText` and `sendingFile` all null), which makes its send path a no-op: it selects chats, fires `ShareAlertDelegate.didShare()` and dismisses. The selection is read back off the (protected) `selectedDialogs` field and the comment off `commentTextView`. """ from android_utils import log, run_on_ui_thread from hook_utils import get_private_field _delegate_class = None # ShareAlert's own constant for "keep the keyboard out of the way". _SOFT_INPUT_ADJUST_NOTHING = 48 # PhotoViewer hands focus over after AdjustPanLayoutHelper.keyboardDuration, # which is 250ms in the app's animation constants. _FOCUS_DELAY_MS = 250 def _make_delegate_class(): """Builds the ShareAlertDelegate proxy lazily: the Java interface can only be resolved once the app classes are loadable.""" from java import dynamic_proxy from org.telegram.ui.Components import ShareAlert class Delegate(dynamic_proxy(ShareAlert.ShareAlertDelegate)): def __init__(self, alert, on_picked): super().__init__() self.alert = alert self.on_picked = on_picked def didCopy(self): return False def didShare(self): self.on_picked(_selected_peers(self.alert), _comment(self.alert)) return Delegate def _selected_peers(alert): dialogs = get_private_field(alert, "selectedDialogs") if dialogs is None: return [] return [dialogs.keyAt(i) for i in range(dialogs.size())] def _comment(alert): field = get_private_field(alert, "commentTextView") if field is None: return "" text = field.getText() return str(text).strip() if text is not None else "" def pick_chats(context, on_picked, resources_provider=None): """Shows the stock share sheet. `on_picked(dialog_ids, comment)` runs on the UI thread once the user confirms.""" global _delegate_class from org.telegram.ui.Components import ShareAlert alert = ShareAlert(context, None, None, False, None, False, resources_provider) if _delegate_class is None: _delegate_class = _make_delegate_class() # Keep a reference on the module so the proxy is not collected early. delegate = _delegate_class(alert, on_picked) alert.setDelegate(delegate) pick_chats._alive = delegate # ShareAlert opens over PhotoViewer's window. Taking focus immediately # fights the viewer's own window animation, so the stock code in # PhotoViewer.showShareAlert() starts unfocused and hands focus over once # the keyboard-transition window has passed; without that second step the # comment field can be tapped but never receives key events. alert.setFocusable(False) alert.getWindow().setSoftInputMode(_SOFT_INPUT_ADJUST_NOTHING) run_on_ui_thread(lambda: _enable_focus(alert), _FOCUS_DELAY_MS) alert.show() return alert def _enable_focus(alert): try: if alert.getWindow() is not None: alert.setFocusable(True) except Exception as exc: log("[extended_media_forwards] focus handoff failed: %s" % exc)