feat(chat): force gallery refresh on attach open and add quick-send popup#95
Conversation
…opup - force MediaStore reload when opening the attach menu so newly taken photos and screenshots appear without waiting for the observer's 2s debounce - add a bottom-right floating popup in ChatActivity that surfaces the latest image (added while the chat was away) and quick-sends it on tap; detection runs only on onResume / first entry - new setting under Chat settings: "Quick send popup for new photos"
There was a problem hiding this comment.
Pull request overview
Adds a “quick send latest photo” affordance in chats and makes the attach gallery refresh immediately so newly captured media appears without waiting for the observer debounce.
Changes:
- Always triggers a MediaStore albums reload when opening the attach menu.
- Introduces
QuickSendMediaPopupand integrates it intoChatActivity(query latest image on resume and quick-send on tap). - Adds a new chat setting + localized strings to enable/disable the popup.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
TMessagesProj/src/main/res/values/strings_nullgram.xml |
Adds new UI strings for the quick-send popup setting/prompt. |
TMessagesProj/src/main/res/values-zh/strings_nullgram.xml |
Chinese translations for the new quick-send popup strings. |
TMessagesProj/src/main/res/values-zh-rTW/strings_nullgram.xml |
Traditional Chinese translations for the new quick-send popup strings. |
TMessagesProj/src/main/java/xyz/nextalone/nnngram/utils/Defines.kt |
Adds a new boolean config key for enabling the popup. |
TMessagesProj/src/main/java/xyz/nextalone/nnngram/ui/QuickSendMediaPopup.java |
New UI component + MediaStore query/thumb decode logic. |
TMessagesProj/src/main/java/xyz/nextalone/nnngram/activity/ChatSettingActivity.java |
Adds a settings row to toggle the popup. |
TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java |
Forces gallery refresh on attach open. |
TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java |
Wires popup into chat lifecycle and sending flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <string name="quickSendMediaPopupTitle">发送新照片?</string> | ||
| <string name="quickSendMediaPopupSending">发送中…</string> |
There was a problem hiding this comment.
The newly added quickSendMediaPopupTitle / quickSendMediaPopupSending strings don’t appear to be referenced anywhere in the codebase. If they aren’t going to be used in the UI, consider removing them (or using them) to avoid carrying unused translations.
| <string name="quickSendMediaPopupTitle">发送新照片?</string> | |
| <string name="quickSendMediaPopupSending">发送中…</string> |
| <string name="quickSendMediaPopupTitle">傳送新照片?</string> | ||
| <string name="quickSendMediaPopupSending">傳送中…</string> |
There was a problem hiding this comment.
The newly added quickSendMediaPopupTitle / quickSendMediaPopupSending strings don’t appear to be referenced anywhere in the codebase. If they aren’t going to be used in the UI, consider removing them (or using them) to avoid carrying unused translations.
| <string name="quickSendMediaPopupTitle">傳送新照片?</string> | |
| <string name="quickSendMediaPopupSending">傳送中…</string> |
| bitmap = android.provider.MediaStore.Images.Media.getBitmap( | ||
| ApplicationLoader.applicationContext.getContentResolver(), e.uri); |
There was a problem hiding this comment.
decodeThumb() falls back to MediaStore.Images.Media.getBitmap() for URI sources, which decodes the full-size image (and is deprecated). This can cause unnecessary memory usage/OOM for large photos; consider decoding via ImageDecoder (API 28+) or sampling from a ContentResolver InputStream to the target size, similar to the file-path branch.
| bitmap = android.provider.MediaStore.Images.Media.getBitmap( | |
| ApplicationLoader.applicationContext.getContentResolver(), e.uri); | |
| BitmapFactory.Options opts = new BitmapFactory.Options(); | |
| opts.inJustDecodeBounds = true; | |
| try (java.io.InputStream boundsStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(e.uri)) { | |
| if (boundsStream != null) { | |
| BitmapFactory.decodeStream(boundsStream, null, opts); | |
| } | |
| } | |
| int w = opts.outWidth; | |
| int h = opts.outHeight; | |
| int sample = 1; | |
| while (w / sample > target * 2 && h / sample > target * 2) { | |
| sample *= 2; | |
| } | |
| BitmapFactory.Options decodeOpts = new BitmapFactory.Options(); | |
| decodeOpts.inSampleSize = sample; | |
| try (java.io.InputStream decodeStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(e.uri)) { | |
| if (decodeStream != null) { | |
| bitmap = BitmapFactory.decodeStream(decodeStream, null, decodeOpts); | |
| } | |
| } |
| // DATA column may be null on Android 10+ in some cases; fall back to EXIF via URI later. | ||
| if (e.orientation == 0 && e.path != null) { | ||
| try { | ||
| ExifInterface exif = new ExifInterface(e.path); | ||
| int o = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); | ||
| switch (o) { | ||
| case ExifInterface.ORIENTATION_ROTATE_90: e.orientation = 90; break; | ||
| case ExifInterface.ORIENTATION_ROTATE_180: e.orientation = 180; break; | ||
| case ExifInterface.ORIENTATION_ROTATE_270: e.orientation = 270; break; | ||
| } | ||
| } catch (Throwable ignored) { | ||
| } | ||
| } |
There was a problem hiding this comment.
queryLatestImage() only derives EXIF orientation when a filesystem path is available. On Android 10+ the DATA column can be null, which means orientation will remain 0 and the popup thumbnail may display rotated incorrectly. Consider reading EXIF orientation from the URI via ContentResolver (ExifInterface(InputStream)) when path is null.
| final long sinceSec; | ||
| if (quickSendPauseTimeSec > 0) { | ||
| // Only show photos added while we were away from this chat. | ||
| sinceSec = quickSendPauseTimeSec; | ||
| } else { | ||
| // First time entering: look back 3 minutes for a freshly taken shot. | ||
| sinceSec = System.currentTimeMillis() / 1000L - 180L; | ||
| } |
There was a problem hiding this comment.
The MediaStore query uses DATE_ADDED + " > ?" with seconds precision. In ChatActivity, sinceSec is set to the pause timestamp in seconds, so photos added within the same second as onPause() can be missed. Consider using ">= ?" or subtracting 1s from sinceSec when querying to avoid off-by-one-second misses.
| private QuickSendMediaPopup quickSendMediaPopup; | ||
| private long quickSendPauseTimeSec; | ||
| private final java.util.HashSet<Long> quickSendDismissedIds = new java.util.HashSet<>(); |
There was a problem hiding this comment.
quickSendDismissedIds is an unbounded HashSet that accumulates every sent/dismissed MediaStore ID for the lifetime of the ChatActivity. Over long sessions this can grow without limit; consider capping it (e.g., LRU of last N IDs) or clearing it when leaving the chat / after a successful send.
| public void loadGalleryPhotos() { | ||
| MediaController.AlbumEntry albumEntry; | ||
| if (shouldLoadAllMedia()) { | ||
| albumEntry = MediaController.allMediaAlbumEntry; | ||
| } else { | ||
| albumEntry = MediaController.allPhotosAlbumEntry; | ||
| } | ||
| if (albumEntry == null) { | ||
| MediaController.loadGalleryPhotosAlbums(0); | ||
| } | ||
| // Always request a MediaStore refresh so newly added photos/screenshots | ||
| // appear without waiting for the ContentObserver's 2s debounce. The load | ||
| // runs on a background thread and updates the UI via NotificationCenter | ||
| // (albumsDidLoad) while the cached album (if any) keeps rendering. | ||
| MediaController.loadGalleryPhotosAlbums(0); |
There was a problem hiding this comment.
loadGalleryPhotos() now always calls MediaController.loadGalleryPhotosAlbums(0), which spawns a new Thread and performs a full MediaStore scan each time the attach UI opens. This can lead to redundant concurrent reloads and UI churn if the attach menu is opened repeatedly; consider throttling (timestamp/debounce) or guarding against parallel loads in MediaController before starting another scan.
| <string name="quickSendMediaPopupTitle">Send new photo?</string> | ||
| <string name="quickSendMediaPopupSending">Sending…</string> |
There was a problem hiding this comment.
The newly added strings quickSendMediaPopupTitle and quickSendMediaPopupSending don’t appear to be referenced anywhere in the codebase (no usages found). If they’re not planned for immediate use, consider removing them to avoid dead resources, or wire them into the popup UI so the prompt/sending state is actually shown.
| <string name="quickSendMediaPopupTitle">Send new photo?</string> | |
| <string name="quickSendMediaPopupSending">Sending…</string> |
- show an Alert confirmation before the quick-send popup actually sends, so an accidental tap no longer fires the message - MediaController.loadGalleryPhotosAlbums now takes an urgent flag: attach-menu opens get NORM_PRIORITY (was MIN_PRIORITY) and parallel scans are coalesced via an atomic flag + queued rerun - add MediaController.quickRefreshLatestPhotos: a lightweight MediaStore query for the top N rows that prepends just-taken photos into the cached All Media / All Photos albums and broadcasts albumsDidLoad right away, so the attach grid surfaces new shots in ~100ms while the full rescan keeps running in the background
Closing the popup with x now records the image in the per-instance dismissed set and nudges the detection watermark past its DATE_ADDED, so the same photo won't resurface on the next onResume.
uploadCI.py wraps $COMMIT_MESSAGE in a triple-backtick fence and sends it with parse_mode=Markdown. When the commit body carries single backticks (e.g. identifier spans like the recent retention / filter commits) the inner ` clashes with the outer ```, Telegram returns "can't parse entities" and the upload step fails. Sanitize the env var in the workflow so the uploader still formats normally while the body stays legal.
…ssals - tapping the quick-send popup now opens the standard PhotoViewer preview/editor with Send, so users can review (and caption/crop) the photo before it goes out, matching the attach-menu selection flow - persist the last dismissed image id in ConfigManager under quickSendMediaLastDismissedId; any MediaStore photo with a smaller or equal id is never resurfaced, so closing the popup with x stays dismissed across fragment re-entries and process restarts
Move the popup from bottomMargin=110 / rightMargin=6 (stacked above sideControlsButtonsLayout) to bottomMargin=6 / rightMargin=64, so it sits just above the input bar and to the left of the scroll-to-bottom button (clear of the 57dp-wide side controls column). Feels like a proper bottom-right affordance instead of floating mid-chat.
…ight - persist the dismissal watermark the moment the popup surfaces a photo, so send / cancel-from-preview / 10s timeout / leaving the chat all count as "already seen" and the same image can never pop again - move popup to rightMargin=6 / bottomMargin=60 so it sits right-aligned with the scroll-to-bottom button and just above it, instead of floating further left with oversized right margin
* feat(chat): force gallery refresh and add quick-send popup for new photos (NextAlone#95) Clicking the attachment button now forces a MediaStore refresh so newly-added photos and screenshots appear without waiting for the ContentObserver's 2s debounce. Attach-menu opens run the scan at NORM_PRIORITY (was MIN), parallel scans coalesce via an atomic in-flight flag, and a new quickRefreshLatestPhotos fast-path prepends the top N MediaStore rows into the cached album so new shots surface in the grid in ~100ms while the full rescan finishes. Adds a bottom-right floating popup in ChatActivity that surfaces the latest image taken while away and, on tap, opens the standard PhotoViewer preview/editor so the user can review, caption or cancel before sending. Detection runs only on onResume / first entry; gated on chatMode, preview/bubble/report/container state, secret chat, ChatObject.canSendPhoto and the images permission. The popup auto-dismisses after 10s; a persisted watermark (quickSendMediaLastDismissedId) is written the moment a photo is shown so send / preview-cancel / timeout / leaving the chat / cold restart all count as already seen and the same image never pops again. Ships with a toggle in the Nnngram Chat settings (quickSendMediaPopup, default on) and en / zh-CN / zh-TW strings. CI: strip backticks from COMMIT_MESSAGE before uploadCI.py so the triple-backtick fence in the Telegram template stops colliding with inline-code spans in the commit body and the release notification stops failing with "can't parse entities". * ci: truncate commit message before telegram upload (NextAlone#96) sendDocument caption limit is 1024 chars. After backtick-stripping, long commit bodies still bust the limit and uploadCI.py fails with 400 Bad Request. Do both the strip and an 800-char cap in one Python step so the release notification survives verbose commits. * feat(chat-menu): add compact icon bar w/ 3-state toggle Long context menu now supports per-option HIDE / TEXT / ICON modes via segmented control in chat settings. Icon mode renders options as a horizontal GridLayout (≤4 cols) at popup bottom. - store compact + hidden sets as CSV in SharedPreferences - migrate stale `compactBarPosition` int from earlier draft - Translate stays HIDE/TEXT only — compact would lose swipeback + lang detection wiring - Delete works in icon mode (TTL subtext lost; confirm dialog still fires) - order in dialog mirrors fillMessageMenu sequence --------- Co-authored-by: Next Alone <12210746+NextAlone@users.noreply.github.com>
photos and screenshots appear without waiting for the observer's 2s
debounce
latest image (added while the chat was away) and quick-sends it on
tap; detection runs only on onResume / first entry