Skip to content

feat(chat): Add attachment sheet with recent photos and videos - #2747

Draft
ToteMeiSter wants to merge 7 commits into
nextcloud:mainfrom
ToteMeiSter:feat/attachment-sheet
Draft

ToteMeiSter wants to merge 7 commits into
nextcloud:mainfrom
ToteMeiSter:feat/attachment-sheet

Conversation

@ToteMeiSter

Copy link
Copy Markdown
Contributor

Draft: tested in CI only, not on a device (no Xcode/iPhone available to the author).

Depends on #2746. The branch is stacked on it (and on #2745): until they are merged, the diff also shows their commits. Review only the last commit.

Refs #2743

Feature

Tapping "+" in the chat input opens a sheet instead of the popup menu: a grid of the most recent photos and videos with a live camera tile, and the other ways to share. A photo from the library takes one tap less. I know #1988 was closed with #2032 without a grid, so please treat this as a proposal: it needs the photo library permission, and the sheet can be reduced to the entries and the camera tile if you do not want that.

The same feature for Android: nextcloud/talk-android#6819 (issue nextcloud/talk-android#6814).

Changes

One commit on top of the in-app camera.

  • AttachmentSheetViewController, AttachmentGridViewController, AttachmentSheetCells, AttachmentSheetAction (new, NextcloudTalk/Chat/Attachment sheet): grid of recent photos and videos (PHAsset, video duration shown), up to 10 selected items, and a bar with all nine existing entries (Camera, Photo Library, GIF, Files, Nextcloud Files, Thread, Poll, Location, Contact) with the same conditions as the old menu.
  • First tile: live camera preview (AttachmentCameraPreviewSession), a tap opens the in-app camera. The session stops with the sheet.
  • AttachmentAssetExporter: exports the selection to files, HEIC is converted to JPEG. Selected items go to ShareConfirmationViewController and the existing upload path, with the text of the input field as the caption.
  • Photo library access: limited access shows a banner and "Select more photos"; denied or restricted access shows "Allow access" (opens the settings). PHPhotoLibraryPreventAutomaticLimitedAccessAlert is set, so the system alert does not appear on top of the sheet.
  • BaseChatViewController: "+" presents the sheet, the UIMenu of the button and its handling are removed. Long press still opens the photo picker, the button still cancels while editing. In federated (v1) conversations "+" has no icon already; a tap there opens nothing.
  • Tests: UnitAttachmentSheetActionTest (conditions of the entries), UnitAttachmentSheetExportNameTest (file names).
  • Strings: only the strings of this change (14 new, 2 plural rules).

How to test

Not built or run locally. Build and unit tests: CI only. CI in the fork on the same commit: Build, SwiftLint and all 5 test jobs (main, stable23, stable32, stable33, stable34) passed, run https://github.com/ToteMeiSter/talk-ios/actions/runs/37227747041 (the stable34 job needed one re-run: IntegrationScheduleMeeting lost its host app once, the re-run passed). SwiftLint: no new warnings or errors compared to main. check-localizable-plurals.sh passes.

Manual, on a device (expected results, not verified):

  1. Tap "+": the sheet opens with the grid, newest first; videos show their duration.
  2. Select photos and a video (limit 10, the 11th shows a message), tap "Send (n)": the upload confirmation opens with the items and the caption of the input field.
  3. A HEIC photo is sent as JPEG.
  4. Camera tile: live preview, a tap opens the camera, closing the sheet stops the preview (check the camera indicator).
  5. Photo library permission: allow all, allow some (banner, "Select more photos"), deny (button "Allow access" opens the settings). No second system alert for limited access.
  6. Every other entry (Files, Nextcloud Files, Thread, Poll, Location, Contact, GIF) is shown under the same conditions as in the old menu (e.g. no Poll in a one-to-one chat).
  7. Long press on "+": the photo picker. While a message is edited: the button cancels the edit.
  8. iPad: the sheet in landscape and in split view; VoiceOver reads the items and the selection state.

AI disclosure

This change was prepared with AI assistance (Claude Code, model claude-opus-5-5): code reading, the implementation, the tests and this description. Commits carry Assisted-by: Claude-Code:claude-opus-5-5. The change was reviewed by the author. It was not built or tested on a device (no Xcode or iPhone available to the author).

馃 Generated with Claude Code

Let the user switch the record button of the chat input between voice and
video messages. Tapping the button toggles the mode (the choice is stored in
the user defaults), holding it records, like for voice messages: slide to cancel,
slide up to lock the recording, 60 seconds at most. Too short videos are dropped.
The front and the back camera can be switched while recording.

The video is recorded as H.264/AAC MP4 (1280x720 preset, portrait) with
AVCaptureVideoDataOutput and AVCaptureAudioDataOutput feeding an AVAssetWriter.
AVCaptureMovieFileOutput is not used: removing the video input to switch the
camera tears down its connection and ends the recording, the writer is not
part of the capture graph. The audio session is restored afterwards.

The recording is uploaded as an ordinary file. The upload path of voice
messages is shared (shareRecording), but without a temporary message and
without the voice message type. File names do not clash within one second.

The camera helpers (camera lookup, input replacement, rotation of the output,
live preview, audio session restore) live in CameraCaptureHelpers.swift.

The usage descriptions of camera and microphone mention the video recording.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Replace UIImagePickerController in the chat with an in-app camera, and show
the photos and videos to share in a full screen preview that can be edited.

Camera (NextcloudTalk/Chat/Camera):
- Full screen camera with photo (tap) and video (hold) from one shutter
  button, flash modes (the last one is remembered), front/back camera switch.
- Photos are taken at the full size of the camera and the microphone is
  only used while a video is recorded.
- The controls follow the orientation of the device and the shutter stays at
  the bottom edge of the device, also on an iPad and when the device lies
  flat (the last held orientation is used then).
- The camera is not offered during a call or when none is available.
- The result is uploaded through the same confirmation as before, with the
  text of the input field as the caption. All of UIImagePickerController is
  removed from the chat.

Preview (ShareExtension):
- The confirmation screen shows the items in a full screen preview with a
  caption field, a counter, "Add more" (camera, photo library, files),
  crop and rotate, and drawing (QuickLook markup).
- At most 10 items can be added at once.
- An edited photo is stored as JPEG (PNG stays PNG), its longest side is
  limited to 4096 pixels.

Unit tests cover the flash modes, file names, durations and the orientation
logic of the camera.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Tapping "+" in the chat input opens a sheet instead of the popup menu. It
shows a grid of the most recent photos and videos of the photo library
with a live camera tile, and the other ways to share: files, Nextcloud
files, location, contact, poll, thread and GIF (Giphy) as before.

- Photos and videos are selected in the grid (up to 10) and sent through
  the upload confirmation, with the text of the input field as caption.
  HEIC photos are converted to JPEG.
- The camera tile opens the in-app camera, it stops with the sheet.
- With limited access to the photo library the sheet offers to select more
  photos, without access to open the settings.
- A long press on "+" still opens the photo picker directly, and the button
  still cancels while a message is edited.
- The popup menu of the "+" button and its handling are removed.
- No photo library alert for limited access is shown by the system
  (PHPhotoLibraryPreventAutomaticLimitedAccessAlert), the sheet has its
  own entry for that.

Unit tests cover the actions of the sheet and the exported file names.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The preview of a video message recording is placed by a pure function in the
free space between the safe area and the recording panel: 9:16 in portrait and
16:9 in landscape, 16 pt from the edges, at most 75 % of the width. It is placed
again on rotation, when the panel of a locked recording appears and after the
camera was switched, and its frames follow the orientation of the interface.
The recorded file keeps the orientation the recording started in.

The chat is dimmed under the preview, which keeps the touches away from it, and
VoiceOver does not read the chat while recording.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A locked video recording no longer opens the tall panel of the voice message:
it stays in the row of the inputbar, which shows the indicator and the time,
and gets a cancel button in the row and a send button over the record button.
The preview takes the freed height. Voice messages keep their panel, which
loses the pause switch that only videos needed.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Overlays that appear while recording (pinned message, out of office) are added
below the dimming, VoiceOver gets its focus moved to the preview and does not
read the record button under the send button of a locked recording, the send
button uses the colors of the theme, the orientation of the start is read in
one way, and the test of the centering has room for rounding.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The handler of the record button gets one branch more with the locked video
recording, which raised its cyclomatic complexity. The locking is its own
function now, and the handler is as complex as before.

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

SystemKeeper commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Hey, thanks for the PR. We will discuss your changes internally shortly.

I know #1988 was closed with #2032 without a grid, so please treat this as a proposal: it needs the photo library permission, and the sheet can be reduced to the entries and the camera tile if you do not want that.

That is the main reason why we went with a different approach, as photo library access is not something that we want to have. As for the bottom sheet alternatively without the photos, not really sure it makes sense, in contrast to the native UIMenu we have.

Also it is generally tricky to have PRs that were never actually run by the contributer... May I ask why that is the case?

@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback!

I make the changes in the Android app first and then find and apply the same fixes in the iOS code by reading it. I don't have an iOS device, so I haven't tested these changes on a device yet; so far they were only built and tested in CI on the simulator.

I plan to test everything via TestFlight in the near future.

@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

Hi, thanks for the feedback.

I write and maintain changes for the Android app, and I check and try out everything live on a range of phones and tablets together with my colleagues. Unfortunately I do not have any iOS devices, so I propose the iOS code changes by analogy with Android. I test the build in CI and offer my work to the iOS developer team for consideration. In the future I plan to test the iOS version of the app myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants