Skip to content

feat(mediaviewer): show the position, add forward and draw actions to the media viewer - #6823

Open
ToteMeiSter wants to merge 28 commits into
nextcloud:masterfrom
ToteMeiSter:feat/media-viewer-actions
Open

ToteMeiSter wants to merge 28 commits into
nextcloud:masterfrom
ToteMeiSter:feat/media-viewer-actions

Conversation

@ToteMeiSter

Copy link
Copy Markdown
Contributor

Closes #6822
Fixes #6806

Depends on #6820: "Draw" opens its attachment preview with the brush. The branch is stacked on #6820, so the diff against master also shows #6818–#6820; the change of this PR is the last commit.

🖼️ Screenshots

🏚️ Before 🏡 After
Media viewer with sender and date, overflow menu with Share and Save "N of M" counter, Forward and Draw buttons, overflow menu with all actions

Screenshots from the Huawei DEL-LX9 will follow in a comment.

Feature

Media viewer:

  • a counter "N of M" in the top bar. The newest item is "M of M". While older media can still be loaded, it shows "N of M+";
  • overflow menu: "Save to gallery", "Show all media", "Show in chat", "Reply", "Share", "Delete";
  • buttons "Forward" and "Draw" ("Draw": photos only, not GIF or SVG);
  • video: all actions except "Draw".

Visibility rules are not copied. The viewer uses the same buildMessageActionsState as the message menu, with the message and the conversation from the local database. When the item is not in the database (loaded from the server only), "Reply", "Delete", "Forward" and "Draw" are hidden. The rules are re-evaluated when the online state changes.

The chat runs the actions with its own handlers. The viewer starts ChatActivity with CLEAR_TOP | SINGLE_TOP and the action plus the message id. The chat checks the permissions again before it runs the action. The request survives a configuration change, but it does not run again after process death.

  • "Reply" and "Delete": the same code as the message menu, with the same delete confirmation.
  • "Forward": the existing "Forward to" picker, a confirmation, then ShareOperationWorker.shareFile — the share step that ends an upload. The file is shared into the target conversation again, without a new upload. The caption is sent as metaData. A server error is now shown to the user; before, the error was silent (this also affects "File from cloud").
  • "Draw": the attachment preview opens with the brush on a copy of the file. The original message and the cached file are not changed. The cache path must be inside shared_attachments.

Also fixed (#6806): "Save" from the viewer looked for the file in the cache root, but the viewer stores files in folders per file id, so an empty file was created. The dialog now resolves the source by file id.

Safe passing of the file path when forwarding

This is part of the design of the new "Forward" action, not a fix of an existing vulnerability: there is no file forwarding in master.

ConversationsListActivity is exported, so any app can start it with any extras. The file path is therefore not put into the intent. The chat reads the path from the message in the database, stores it in process memory (PendingFileForward) and passes only a random one-time key. A forged or unknown key sends nothing. This way another app cannot make Talk share a private file.

How to check on a device

  1. Open a chat with several photos and a video. Tap a photo: the counter shows "N of M"; the newest is "M of M".
  2. Swipe to older media: "+" disappears when nothing more can be loaded.
  3. Overflow menu: check each entry. "Show in chat" scrolls to the message; "Delete" asks for confirmation.
  4. "Forward": pick a conversation, confirm. The file appears there without a new upload.
  5. "Draw" on a photo: the preview opens with the brush. Send: the drawing goes to the same chat, the original stays.
  6. Open the viewer from Shared items: "Show all media" closes the viewer.
  7. Offline: "Reply", "Delete", "Forward" are hidden.

Tested by the author on a Huawei DEL-LX9 with a fork build (develop) that has this feature together with other fork changes. Unit tests: MediaViewerActionsTest, MediaViewerChatRequestTest, MessageActionsStateTest.

🚧 TODO

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

The change was written with the help of Claude Code (Anthropic). The author reviewed the code, built it and ran the unit tests, and checked the behavior on a device with the fork build.

The observer of the recording state is registered again when the chat
activity is recreated, for example on a screen rotation. LiveData then
delivers the current value to it and the device vibrated as if a recording
had just started or ended.

Vibrate only when the state differs from the one the activity already knew.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A rotation stops the chat activity. ChatViewModel.onStop then stopped the
MediaRecorder, while the locked and in-progress state of the recording stayed.
The screen still showed a running recording and a truncated file was sent.

The view model now keeps the recorder running when the stopping activity is
changing its configuration. It reads this from the lifecycle owner: ON_STOP
reaches the observer before the body of Activity.onStop runs (API 29+), so a
flag set by the activity in onStop would come too late.
The recorder is still stopped when the user leaves the chat.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A short tap on the record button switches between voice and video mode. The
mode is stored in AppPreferences and shown by the button icon and a snackbar
hint. The result is a regular video attachment.

In video mode holding the button records with CameraX (front camera, 720p
with fallback, about 2.5 Mbit/s, at most 120 s, then it stops and is sent).
Sliding left cancels, sliding up locks. The gestures, the lock state, the
timer and the locked recording view are shared with the voice recording. The
preview is shown above the input and the camera can be switched during the
recording. An unfinished recording is cancelled and the camera released when
the chat is paused. Too short recordings show a hint, camera errors a
message. The finished mp4 goes through the regular file upload.

Refs nextcloud#6812

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
… popup

The record button used to start a recording on the first touch, so a short
tap that should switch the mode started a recording first. Recording now
starts only when the button is held. A tap shows the record hint as a popup
above the button instead of a snackbar, placed in the window of the button
and kept above it after layout changes.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The activity is recreated on rotation while the video recorder belonged to
it: onPause cancelled the recording and deleted its file, and binding the
camera to the next activity made the CameraX recorder configure itself in a
state where that is not allowed.

- The video recorder lives in ChatViewModel like the voice recorder. The
  activity attaches its preview and callback, and the recorder lets go of
  them when the activity is destroyed. A result that arrives without an
  activity is delivered to the next one.
- The camera is bound to a lifecycle of the recorder itself, not to the one
  of the activity, so it stays bound while the activity is recreated.
- A new activity picks up the recording and locks it when it was held, so the
  timer, stop, send and cancel are shown.
- The recording is cancelled when the user leaves the chat, not on a
  configuration change.
- A recording cut off by the camera is not lost but shown in the attachment
  preview.
- The video is recorded in the rotation of the sensor.
- A recording is never stopped while a switched camera settles.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The container of the video recording preview did not consume touches, so
taps on it reached the chat below. Make it consume them and hide it from
TalkBack, where it is only decoration.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Show recent photos and videos from MediaStore in a 3-column grid with a live
camera tile and multi-select, and keep every former menu entry in a bar below
with unchanged visibility rules. Selected media open the existing attachment
preview with caption. Supports Android 14 partial media access.

Removes AttachmentDialog and dialog_attachment.xml.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
READ_MEDIA_VISUAL_USER_SELECTED turns off the Android 14 compatibility mode,
so isFilesPermissionGranted() must accept the partial grant, otherwise voice
and video messages, camera photos, local files and share-to-Talk ask for
permissions again.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Add a full screen CameraX capture screen (shutter, front/back lens, flash
off/auto/on, close) behind the TakePhotoInApp result contract. The photo is
returned right after the shutter, without a confirmation step of its own,
and goes to the attachment preview. The camera tile, the camera action of the
attachment sheet and the photo button of the preview use it. The system
ACTION_IMAGE_CAPTURE path is removed; video capture is unchanged.

The capture screen asks CameraX for the full 4:3 resolution explicitly, ignores
a second shutter tap while a photo is taken and keeps the controls on the
device body when the device is rotated. rotationForDeviceOrientation() is
shared with the video message recorder.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Rework the pre-send attachment preview: the photo fills the screen with the
caption floating over it, a compact bottom panel holds the SD/HD switch and
View-only choice, and a round send button replaces the inline one.

- a tick mark per file excludes it from sending (at least one stays)
- "add more" menu next to the caption: gallery, photo, video
- crop and rotate through the bundled uCrop (setMaxBitmapSize(4096), so uCrop
  does not downscale the picture to the screen size); the result replaces the
  file at the same position and is what gets sent
- brush drawing on a Compose canvas, burned into a full-resolution copy
- the preview result is delivered through the fragment manager, so a send
  after the activity was recreated still uploads the files
- the original image size is shown in display orientation
- selection, uri replacement, drawing geometry and file ownership are pure
  functions covered by unit tests

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A button at the top edge of the capture screen toggles ImageCapture between
CAPTURE_MODE_MINIMIZE_LATENCY (default, fast) and CAPTURE_MODE_MAXIMIZE_QUALITY.
The choice is kept in AppPreferences (camera_capture_mode). Each saved photo
logs "capture mode=... took=... ms" under the PhotoCamera tag to compare modes.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…g start

The start time was taken on ACTION_DOWN, but the recording starts only after
the 400-600 ms hold threshold, so a voice message was judged too short
(or long enough) by a duration which included the hold. Take the time in
beginRecording() after the recording has started and use the monotonic clock.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…alized it

Send or delete of a locked video recording cleared the lock and the
in-progress state at once, while CameraX was still finalizing the file. A new
recording started in that window was silently dropped in beginRecording().
For video the state is now released in onVideoRecordingFinished(), which
CameraX reaches for every outcome (send, cancel, error, too short). Audio, and
a video recorder which is already idle, are still cleared at once.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…rded frame

The locked video recording preview was a fixed 180x240dp box floating over the
message bubbles, squashed to about 2:1 in landscape. Now a scrim dims the chat
pane (it blocks touches and is hidden from TalkBack), and the preview is centered
in it in the aspect of the recorded frame, fitted with margins, at most 75% of
the width and 480dp on the longest side. The placement is a pure function
(videoPreviewPlacement) with unit tests; it is recomputed when the pane changes
size and when the recorder learns the frame aspect from CameraX.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…entation, hide chat from TalkBack

- Center by Gravity.CENTER and change only width and height: the margin
  comparison never settled in RTL and re-laid out the chat every frame.
- The frame aspect is turned into screen coordinates by the difference of the
  recording and the display rotation (screenFrameAspect), so the frame matches
  the picture with auto-rotate off and after a turn during the recording.
- While recording, the siblings of the scrim are hidden from TalkBack and get
  their previous values back afterwards.
- The preview size is recalculated after a camera switch.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
In video mode the locked recording panel is one row: delete, red dot in a
progress ring of the 120 s limit, timer, send. The voice panel is unchanged.
The preview keeps a smaller gap on a short area.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
- Hide the whole recording indicator from TalkBack, so the progress ring
  does not announce its percentage every second.
- Relative padding of the timer, so the gap stays between dot and timer in RTL.
- No inset around the progress ring, so it fills its 24dp.
- The ring scale is set in code only (PROGRESS_MAX).

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…t by the effective state

requestReadFilesPermissions() did not request READ_MEDIA_VISUAL_USER_SELECTED
on Android 14+, although the manifest declares it, and the result handler
looked only at grantResults[0]. With "selected photos" the first permission is
denied, so a partial access showed a false refusal.

The request is now the set of the attachment sheet (mediaPermissionsToRequest)
plus READ_MEDIA_AUDIO from Android 13 on, as before. The result allows the file
picker if any requested permission was granted or the effective access
(permissionUtil.isFilesPermissionGranted()) is given: before Android 10 the
granted READ permission differs from the WRITE one that check looks at.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A video recording result that waited for a new activity was delivered from onCreate, before currentConversation
and spreedCapabilities were set: the send path and the preview path could throw. The pending result is now
delivered once the initial capabilities state has initialized both; a running recording still reattaches at once.

The delete and send buttons of the compact video row announced the voice recording labels to TalkBack.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
… the media viewer

- top bar: centered "N of M" counter counted from the newest item, so it
  stays put when older items are paged in ("M+" while more may load)
- menu: save to gallery, show all media, show in chat, reply, share,
  delete (with confirmation); buttons: forward (file share to another
  conversation) and draw (photos only)
- visibility comes from the chat's buildMessageActionsState (new fields
  showForwardFile, canSendToConversation); hidden when the message is not
  in the local database
- reply/delete/forward/draw run in ChatActivity through its own handlers,
  handed over by intent extras and re-checked there
- the file of a forward is not passed in an intent extra (the conversation
  list is exported): ChatActivity keeps it in PendingFileForward and hands
  over a random key; the entry is taken only after the confirmation
- draw opens the upload preview on the cached file with the drawing editor
  started; edits write copies
- SaveToStorageDialogFragment now finds files in the per-file-id cache
- ShareOperationWorker tells the user when a share is refused

Depends on nextcloud#6820 (drawing editor of the attachment preview).

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eff612d1-8594-490e-b6ad-99856be49ea6
📥 Commits

Reviewing files that changed from the base of the PR and between ce420ca and 8c902c5.

📒 Files selected for processing (1)
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentSheet.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentSheet.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds an attachment sheet for recent media, in-app CameraX photo capture, and crop and drawing tools in the attachment preview. It adds voice and video recording controls and CameraX video recording to chat, including recording restoration across activity recreation. The media viewer gains item counters, chat actions, navigation, and file forwarding. Android media-permission handling, image orientation details, and save-file resolution also change.

Fixed issue severity: Medium

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 8c902

The "Show in chat" navigation gap is fixed. No outstanding merge-blocking risk was found in the reviewed change.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ce420

The reviewed flows retain account and conversation binding, permission checks, and forwarding confirmation. No introduced security defect was established. Recovery behavior and coverage of the wider stacked changes remain incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — Each forwarding job carries one account id, one remote file path, and one target conversation token. Its authenticated sharing authority comes from that account's stored credentials; the new caller does not supply separate credentials or a new service identity.

Trust Boundaries and Controls

  • observed — ChatActivity and MediaViewerActivity remain non-exported activities. The receiving chat reloads the requested message through an account- and room-bound lookup, rechecks action eligibility, and rejects drawing paths outside the canonical shared-attachments directory.
  • observed — The exported conversation picker checks target chat permission and read-only state before showing forwarding confirmation. It rejects unknown handles and handles belonging to another account. Confirmation captures the chosen conversation and atomically removes the handle before enqueueing the share.

Resilience and Maintainability Implications

  • observed — Normal media-request execution removes the action from the intent and clears the pending request before asynchronous loading. Restoration uses only the saved pending request. Forwarding metadata remains process-local, so an old forwarding handle cannot recover its path after process death.

Hardening Proposals

  • proposed — Consider explicit cleanup of abandoned edit outputs and pending forwarding entries when their flow is definitively dismissed, while preserving files needed by queued uploads and state needed for configuration changes.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning [#6822] The viewer adds the counter, action visibility, chat-action requests, forwarding, and drawing support. The change also includes viewer-action tests. [#6806] The save dialog resolves the cached… Remove the CameraX dependencies from this PR. Keep the viewer actions and tests, the drawing support required by Draw, and the file-ID save fix.
Out of Scope Changes check ⚠️ Warning The whole PR also adds in-app photo capture, video-message recording, and a replacement attachment sheet with a recent-media picker. These features do not implement #6822 or #6806. The latest permissi… Move in-app capture, video-message recording, the replacement attachment sheet, the recent-media picker, and their supporting changes to separate PRs. Retain the media-viewer work, drawing support required by #6822, and the file-ID save fix…
Docstring Coverage ⚠️ Warning Docstring coverage is 36.39% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 382 functions across 54 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the media viewer’s counter and its Forward and Draw actions.
Description check ✅ Passed The description covers the feature, behavior, testing, and required template sections. The milestone is not set, and the TODO notes pending maintainer discussion.
Full details: Linked Issues check

Explanation

[#6822] The viewer adds the counter, action visibility, chat-action requests, forwarding, and drawing support. The change also includes viewer-action tests. [#6806] The save dialog resolves the cached source by file ID. However, the whole PR adds CameraX dependencies in app/build.gradle.kts, contrary to #6822’s requirement for no new dependencies.

Full details: Out of Scope Changes check

Explanation

The whole PR also adds in-app photo capture, video-message recording, and a replacement attachment sheet with a recent-media picker. These features do not implement #6822 or #6806. The latest permission-refresh change in AttachmentSheet.kt supports that attachment-sheet feature and is also outside these issues.

Resolution

Move in-app capture, video-message recording, the replacement attachment sheet, the recent-media picker, and their supporting changes to separate PRs. Retain the media-viewer work, drawing support required by #6822, and the file-ID save fix for #6806.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

takePhotoWithInAppCamera() requested the permission and the grant handler only showed a snackbar, so the user had to
open the sheet and tap the camera again. The request is now remembered and the camera opens from the grant branch.
The flag is cleared by the other path which asks for the same permission (system video capture).

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewViewModel.kt (1)

102-112: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Handle the case where the user removes or replaces the file while a drawing save is in progress.

saveDrawing captures description.uri. It then calls finishEdit after the IO work completes. If oldUri is no longer in files, replaceFile returns early. discardIfOwnEdit(oldUri) still runs, and the new edited output is never referenced. The new output file stays in the cache as an orphan. In this case, delete newUri's file instead.

Proposed fix
     private fun finishEdit(oldUri: String, newUri: Uri?) {
         if (newUri == null) {
             editFailed = true
-        } else {
+        } else if (oldUri in files) {
             replaceFile(oldUri, newUri.toString())
             discardIfOwnEdit(oldUri)
+        } else {
+            discardIfOwnEdit(newUri.toString())
         }
         isEditing = false
     }

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e176f2d9-c3fa-4ab6-a85a-d9a593922193
📥 Commits

Reviewing files that changed from the base of the PR and between f3c9097 and 16e3a25.

📒 Files selected for processing (108)
  • app/build.gradle.kts
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/AttachmentEditLogic.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/AttachmentEditing.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/AttachmentToolBar.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/AttachmentToolBarPreviews.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/CameraCaptureActions.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/CaptionInputBar.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/DetailVariants.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/DrawingEditor.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/DrawingSession.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewFragment.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewScreen.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewViewModel.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/FileDescriber.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/FileThumbnailImage.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/ImageDetailLogic.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/LargePreview.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/ThumbnailStrip.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/ThumbnailStripPreviews.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentAction.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentActionResources.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentSheet.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/CameraTile.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/MediaAccess.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/MediaSelection.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/RecentMedia.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/RecentMediaLoader.kt
  • app/src/main/java/com/nextcloud/talk/camera/CaptureActivity.kt
  • app/src/main/java/com/nextcloud/talk/camera/CaptureOrientation.kt
  • app/src/main/java/com/nextcloud/talk/camera/PhotoCamera.kt
  • app/src/main/java/com/nextcloud/talk/camera/PhotoCaptureLogic.kt
  • app/src/main/java/com/nextcloud/talk/camera/PhotoCaptureScreen.kt
  • app/src/main/java/com/nextcloud/talk/camera/TakePhotoInApp.kt
  • app/src/main/java/com/nextcloud/talk/chat/CameraLens.kt
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordButtonGesture.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordHintPopup.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordInputMode.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordingStopCoordinator.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoMessageRecorder.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoPreviewPlacement.kt
  • app/src/main/java/com/nextcloud/talk/chat/ui/MessageActionsBottomSheet.kt
  • app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt
  • app/src/main/java/com/nextcloud/talk/components/StandardAppBar.kt
  • app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
  • app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
  • app/src/main/java/com/nextcloud/talk/jobs/ShareOperationWorker.kt
  • app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerActivity.kt
  • app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerScreen.kt
  • app/src/main/java/com/nextcloud/talk/mediaviewer/model/MediaViewerActions.kt
  • app/src/main/java/com/nextcloud/talk/mediaviewer/model/PendingFileForward.kt
  • app/src/main/java/com/nextcloud/talk/mediaviewer/viewmodels/MediaViewerViewModel.kt
  • app/src/main/java/com/nextcloud/talk/shareditems/activities/SharedItemsActivity.kt
  • app/src/main/java/com/nextcloud/talk/shareditems/adapters/SharedItemsAdapter.kt
  • app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt
  • app/src/main/java/com/nextcloud/talk/ui/dialog/SaveToStorageDialogFragment.kt
  • app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
  • app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt
  • app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
  • app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt
  • app/src/main/res/drawable/bg_record_hint.xml
  • app/src/main/res/drawable/ic_baseline_flash_auto_24.xml
  • app/src/main/res/drawable/ic_baseline_flash_off_24.xml
  • app/src/main/res/drawable/ic_baseline_flash_on_24.xml
  • app/src/main/res/drawable/ic_baseline_hd_24.xml
  • app/src/main/res/drawable/ic_baseline_speed_24.xml
  • app/src/main/res/drawable/ic_record_hint_arrow.xml
  • app/src/main/res/drawable/video_recording_dot.xml
  • app/src/main/res/layout/activity_chat.xml
  • app/src/main/res/layout/dialog_attachment.xml
  • app/src/main/res/layout/fragment_message_input_voice_recording.xml
  • app/src/main/res/layout/view_message_input.xml
  • app/src/main/res/layout/view_record_hint.xml
  • app/src/main/res/values/colors.xml
  • app/src/main/res/values/dimens.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/AttachmentSelectionAndNamingTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/AttachmentToolBarLogicTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/DrawingGeometryTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/EditedFileOwnershipTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewResultTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewViewModelTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/ImageDetailLogicTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/UprightMatrixTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/AttachmentActionTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/MediaAccessTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/MediaSelectionTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/RecentMediaTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/ShareFilePermissionTest.kt
  • app/src/test/java/com/nextcloud/talk/camera/CaptureModeSettingTest.kt
  • app/src/test/java/com/nextcloud/talk/camera/CaptureOrientationTest.kt
  • app/src/test/java/com/nextcloud/talk/camera/PhotoCaptureLogicTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/RecordButtonGestureTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/RecordHintPopupPlacementTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/RecordingStopCoordinatorTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/ShouldVibrateOnRecordingChangeTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoFrameAspectTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoMessageRecordingTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoPreviewPlacementCompactPanelTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoPreviewPlacementTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecorderLifecycleTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecordingRecreationTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/ui/MessageActionsStateTest.kt
  • app/src/test/java/com/nextcloud/talk/mediaviewer/model/MediaViewerActionsTest.kt
  • app/src/test/java/com/nextcloud/talk/mediaviewer/model/MediaViewerChatRequestTest.kt
💤 Files with no reviewable changes (2)
  • app/src/main/res/layout/dialog_attachment.xml
  • app/src/main/java/com/nextcloud/talk/ui/dialog/AttachmentDialog.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentAction.kt Outdated
Comment thread app/src/main/java/com/nextcloud/talk/camera/PhotoCaptureScreen.kt
Comment thread app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
…e loaded

The conversation can be set before the capabilities, and the request reads spreedCapabilities through
actionsStateFor(), which threw UninitializedPropertyAccessException. The request now stays pending and runs from
the initial capabilities state.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The photo entry opens the in-app capture screen, which stays black on a device without camera hardware. It now needs
hasCamera like the video entry.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
setTargetRotation() dropped a rotation which arrived before start() had built the ImageCapture, so a later photo used
the display rotation. The latest rotation is now stored and applied to every ImageCapture that is built.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Open the pending message after the conversation loads. · ChatActivity.kt:1440-1445

app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt:1440-1445
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Open the pending message after the conversation loads.

If a same-room “Show in chat” intent arrives before currentConversation loads, this method leaves its target pending. The initial-load observer runs runPendingMediaRequest() but never calls openPendingTargetMessage(). The chat therefore does not navigate to the requested message. Call openPendingTargetMessage() from the initial-load branch as well.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 33a490a5-c437-4ef3-b985-c929299efa01
📥 Commits

Reviewing files that changed from the base of the PR and between 16e3a25 and c9ad041.

📒 Files selected for processing (4)
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentAction.kt
  • app/src/main/java/com/nextcloud/talk/camera/PhotoCamera.kt
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/AttachmentActionTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentAction.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/AttachmentActionTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

…Android 14

With READ_MEDIA_VISUAL_USER_SELECTED and only one of READ_MEDIA_IMAGES/VIDEO granted, the access resolves to FULL
and the sheet hid the action. canSelectMoreMedia also covers that mix; media loading is unchanged.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Two edits in the same millisecond got the same path, so the second result replaced nothing and the old file was
deleted. The output is now reserved with createNewFile() and gets a numeric suffix when the name is taken.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
… later

For an already open chat whose conversation was not loaded yet, the target message stayed pending and was never
opened. The joined-room branch now calls openPendingTargetMessage() next to runPendingMediaRequest().

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

@coderabbitai Re the outside-diff finding on ChatActivity.kt 1440-1445 of this review (open the pending message after the conversation loads):

Confirmed and fixed in ce420ca: the initial-load branch now calls openPendingTargetMessage() next to runPendingMediaRequest(), and the KDoc of applyIntentTargetsToJoinedRoom() is updated. No double navigation: openPendingTargetMessage() clears the id, and onCreate consumes its own target earlier. The two fixes below this PR (b8eaef7 from #6819, 3d16383 from #6820) came up the stack as 18727d8 and 0eaffee.

This reply and the fix were prepared with the help of an AI assistant (Claude Code) and reviewed before posting.

@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

@coderabbitai Reply to the nitpick on FileAttachmentPreviewViewModel.kt 102-112 of this review (orphan output when the file is removed during a drawing save):

Confirmed, the output file stays in the cache in that case. The race needs the user to remove the page within the short save of a drawing. The file lives in cacheDir/shared_attachments, which the system can reclaim, and nothing references it, so it is not sent or shown. I keep this PR to the reported issues and leave the change out; it can follow if the maintainers prefer it.

This reply was prepared with the help of an AI assistant (Claude Code) and reviewed before posting.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ac66d67d-ad9d-4afb-870b-283de6a1830c
📥 Commits

Reviewing files that changed from the base of the PR and between c9ad041 and ce420ca.

📒 Files selected for processing (7)
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/AttachmentEditing.kt
  • app/src/main/java/com/nextcloud/talk/attachmentpreview/EditNameClaim.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentSheet.kt
  • app/src/main/java/com/nextcloud/talk/attachmentsheet/MediaAccess.kt
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/test/java/com/nextcloud/talk/attachmentpreview/EditNameClaimTest.kt
  • app/src/test/java/com/nextcloud/talk/attachmentsheet/CanSelectMoreMediaTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/attachmentsheet/AttachmentSheet.kt Outdated
@coderabbitai

coderabbitai Bot commented Oct 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd '^ChatActivity\.kt$' .
file="app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt"
ast-grep outline "$file" --match 'onCreate|openPendingTargetMessage|applyIntentTargetsToJoinedRoom|runPendingMediaRequest' --view expanded
rg -n -C 12 'openPendingTargetMessage\(|applyIntentTargetsToJoinedRoom\(|runPendingMediaRequest\(' "$file"

Length of output: 7238


@ToteMeiSter Thanks for the fix and clarification. In ChatActivity.kt, the initial-load branch now calls openPendingTargetMessage() beside runPendingMediaRequest(). The updated KDoc describes the deferred handling.

openPendingTargetMessage() clears the pending target ID after dispatch. The later call therefore does not repeat navigation for a target already consumed by onCreate. This addresses the finding.


✏️ Learnings added
Learnt from: ToteMeiSter
URL: https://github.com/nextcloud/talk-android/pull/6823

Timestamp: 2026-10-05T13:50:17.581Z
Learning: In app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt, applyIntentTargetsToJoinedRoom() defers message-target navigation when currentConversation is null. The initial-load branch calls openPendingTargetMessage() beside runPendingMediaRequest() to handle deferred targets. openPendingTargetMessage() clears the pending target ID after dispatch, so a target already consumed by onCreate does not trigger navigation again.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd '^FileAttachmentPreviewViewModel\.kt$' .
file="app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewViewModel.kt"
ast-grep outline "$file" --match 'saveDrawing|replaceFile|applyCrop' --view expanded
sed -n '1,240p' "$file"

Length of output: 8113


@ToteMeiSter Thanks for the clarification. finishEdit() does not delete the new output if replaceFile() cannot find the source URI. This matches the orphaned-output case you described.

I accept leaving this low-impact cache cleanup out of #6823. The unreferenced output is not sent or shown, and the system can reclaim the cache file.

Would you like me to open a follow-up issue for cleanup if the source disappears during a drawing save?


✏️ Learnings added
Learnt from: ToteMeiSter
URL: https://github.com/nextcloud/talk-android/pull/6823

Timestamp: 2026-10-05T13:54:28.185Z
Learning: In app/src/main/java/com/nextcloud/talk/attachmentpreview/FileAttachmentPreviewViewModel.kt, a drawing output can remain unreferenced in cacheDir/shared_attachments if the source URI disappears before finishEdit() calls replaceFile(). The output is not sent or shown, and Android can reclaim the cache file. The author deliberately deferred this low-impact cleanup from PR #6823 to keep the PR focused on the reported issues; do not insist on including it in this PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

…st returns

When the system grants without a dialog there is no pause/resume, so the sheet kept the old grants and the
placeholder. The request result now bumps a key that the grants and the media list depend on.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

2 participants