"""Chat cell decoration of deleted messages: translucent, with a red trash icon (from the active icon pack) in the time row like the app's own "edited" pencil. ChatMessageCell (exteraGram 12.10.6, probed on device): setMessageObject (5 and 6 args), setAlpha(float), measureTime(MessageObject), and the private `currentTimeString` (SpannableStringBuilder), `timeTextWidth`, `timeWidth`, `timeLayout`. The pencil is a ColoredImageSpan on a leading "\\u200d " whose width (Layout.getDesiredWidth with Theme.chat_timePaint) the time widths include; the trash icon is added the same way, before the bubble is laid out. """ from base_plugin import MethodHook import diag import store from config import ALPHA_CHOICES, DELETED_ALPHA from jutil import field, hook_methods CELL = "org.telegram.ui.Cells.ChatMessageCell" _ICON = "\u200d " def _dp(value): from org.telegram.messenger import AndroidUtilities return AndroidUtilities.dp(value) def _message(cell): try: return cell.getMessageObject() except Exception: return None def _key(message): """(account, dialog_id, message_id) of a real message, else None.""" if message is None: return None message_id = int(message.getId()) if message_id <= 0: return None return int(message.currentAccount), int(message.getDialogId()), message_id def _deleted(message): key = _key(message) return key is not None and store.is_deleted(*key) def _trash_drawable(res_id): """The trash icon of the active icon pack: the activity's resources are exteraGram's ExteraResources, which apply it (the application context's plain Resources give the stock icon).""" from client_utils import get_last_fragment fragment = get_last_fragment() activity = fragment.getParentActivity() if fragment is not None else None if activity is not None: resources = activity.getResources() else: from org.telegram.messenger import ApplicationLoader resources = ApplicationLoader.applicationContext.getResources() return resources.getDrawable(res_id, None).mutate() def _set_int(cell, name, value): from hook_utils import set_private_field from java import jint set_private_field(cell, name, jint(int(value))) class Render: def __init__(self, plugin): self.plugin = plugin self.handles = [] def _alpha(self): index = int(self.plugin.get_setting(DELETED_ALPHA, 0) or 0) return ALPHA_CHOICES[max(0, min(index, len(ALPHA_CHOICES) - 1))][1] def install(self): plugin = self.plugin render = self class AfterBind(MethodHook): def after_hooked_method(self, param): try: render.apply_alpha(param.thisObject) except Exception as exc: diag.error("render.bind", exc) class BeforeAlpha(MethodHook): def before_hooked_method(self, param): try: render.scale_alpha(param) except Exception as exc: diag.error("render.alpha", exc) class AfterMeasureTime(MethodHook): def after_hooked_method(self, param): try: render.add_trash(param.thisObject, param.args[0]) except Exception as exc: diag.error("render.time", exc) self.handles += hook_methods(plugin, CELL, "setMessageObject", AfterBind()) self.handles += hook_methods(plugin, CELL, "setAlpha", BeforeAlpha()) self.handles += hook_methods(plugin, CELL, "measureTime", AfterMeasureTime()) def uninstall(self): for handle in self.handles: try: self.plugin.unhook_method(handle) except Exception: pass self.handles = [] # opacity def apply_alpha(self, cell): """Re-applies the cell's alpha through setAlpha (scaled below).""" cell.setAlpha(1.0) def scale_alpha(self, param): """Deleted messages stay translucent whatever alpha the list's item animators set: every setAlpha on their cell is scaled.""" if not _deleted(_message(param.thisObject)): return from java import jfloat param.args[0] = jfloat(float(param.args[0]) * self._alpha()) # trash icon def add_trash(self, cell, message): if not _deleted(message): return # measureTime builds the string afresh: the icon goes in once, before # the edited pencil if there is one. text = field(cell, "currentTimeString") if text is None: return from android.text import Layout, Spanned, StaticLayout from org.telegram.messenger import R from org.telegram.ui.ActionBar import Theme from org.telegram.ui.Components import ColoredImageSpan paint = Theme.chat_timePaint before = Layout.getDesiredWidth(text, paint) from java import jint span = ColoredImageSpan(_trash_drawable(R.drawable.msg_delete)) span.setSize(_dp(13)) span.setTranslateX(-3.0) span.setOverrideColor(jint(Theme.getColor(Theme.key_text_RedBold))) text.insert(0, _ICON) text.setSpan(span, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) added = int(Layout.getDesiredWidth(text, paint) - before + 0.999) _set_int(cell, "timeTextWidth", int(field(cell, "timeTextWidth") or 0) + added) _set_int(cell, "timeWidth", int(field(cell, "timeWidth") or 0) + added) if field(cell, "timeLayout") is not None: from hook_utils import set_private_field layout = StaticLayout( text, paint, int(field(cell, "timeTextWidth")) + _dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0, 0.0, False, ) set_private_field(cell, "timeLayout", layout)