Skip to content

Add in-app camera with direct upload to Nextcloud - #4058

Merged
marinofaggiana merged 8 commits into
nextcloud:masterfrom
Rasmussw:feature/direct-upload-camera-clean
Jun 26, 2026
Merged

Add in-app camera with direct upload to Nextcloud#4058
marinofaggiana merged 8 commits into
nextcloud:masterfrom
Rasmussw:feature/direct-upload-camera-clean

Conversation

@Rasmussw

@Rasmussw Rasmussw commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

This PR adds a custom in-app camera to the iOS client that captures photos and videos and uploads them directly to Nextcloud, bypassing the device camera roll by default. Users can optionally enable saving to the camera roll via a per-session toggle, with a global default available in Settings -> Advanced.

In the current flow, media is always saved to the system photo library before being uploaded to Nextcloud. This creates potentially unwanted local copies, introduces privacy concerns, and adds manual cleanup for users. This change ensures media can be captured and stored directly in Nextcloud without ever touching the camera roll. This is especially important in regulated environments with BYOD-policies, i.e. environments where media content captured can identify a person, thus potentially violating GDPR.

The custom camera uses AVFoundation to capture media directly to a temporary directory. Files are reviewed before upload and then passed into Nextcloud's existing upload pipeline. Saving to the camera roll is fully optional and only occurs if explicitly enabled by the user. Existing picker and upload flows remain unchanged.

Additional notes
Default behaviour does not affect existing users
Upload reliability uses existing background upload infrastructure
Camera roll is never accessed unless explicitly enabled

@marinofaggiana

Copy link
Copy Markdown
Member

@Rasmussw can you make a rebase ? thanks

@marinofaggiana marinofaggiana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please make a rebase

@marinofaggiana
marinofaggiana self-requested a review April 17, 2026 09:06
@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from 7ded060 to 5b66fa5 Compare April 20, 2026 09:16
@Rasmussw

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana I've rebased onto the latest master.

@marinofaggiana

Copy link
Copy Markdown
Member

Hi @marinofaggiana I've rebased onto the latest master.

There are some compilation errors (due to the master change). Please rebase and try compiling it. Thanks.

@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from 5b66fa5 to 6efca7e Compare April 27, 2026 06:48
@marinofaggiana

Copy link
Copy Markdown
Member
Screenshot 2026-04-28 alle 09 26 17

@marinofaggiana marinofaggiana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do not build yet

@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch 3 times, most recently from 8330e77 to 9b90f77 Compare May 4, 2026 12:54
@Rasmussw
Rasmussw requested a review from marinofaggiana May 4, 2026 13:07

@marinofaggiana marinofaggiana left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

read first comments

Comment thread iOSClient/Settings/NCPreferences.swift Outdated

var saveCameraMediaToCameraRoll: Bool {
get {
return getBoolPreference(key: "saveCameraMediaToCameraRoll", defaultValue: false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

default, for compatibility with previous version, must be set to true

"_e2e_remove_folder_encrypted_" = "Decrypt";
"_e2e_file_encrypted_" = "File encrypted";
"_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption";
"_e2e_error_" = "An internal end-to-end encryption error occurred.";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

???

let isVideo: Bool
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's used ?

@marinofaggiana

marinofaggiana commented May 5, 2026

Copy link
Copy Markdown
Member

iPad before:

IMG_0016

iPad after:

IMG_0017

In addition to the incorrect view incompatible with iPad, on the second attempt it did not display the image (black)

@marinofaggiana

Copy link
Copy Markdown
Member

Please use Lint, the code should be well formatted and commented in any modified places.

Screenshot 2026-05-05 alle 07 50 02

@marinofaggiana

Copy link
Copy Markdown
Member

Note: dear @Rasmussw It seems like the change is significant.
Why wasn't a fork on TLPhotoPicker considered to make the necessary changes there so as not to have to heavily intervene on the Nextcloud side of the process?

The library is very outdated and one might consider replacing it (also).

@Rasmussw

Rasmussw commented May 5, 2026 via email

Copy link
Copy Markdown
Contributor Author

@marinofaggiana

Copy link
Copy Markdown
Member

Hi @Rasmussw ,

Thanks for the clarification.

Yes, I am interested in this feature, and I think the proposed change is a very good improvement for the app.

From my point of view, keeping most of the implementation on the Nextcloud side is fine and probably easier to maintain. My main concern was the automatic save to the user’s Photo Library when taking a photo or video from TLPhotoPicker.

Since we have now identified the exact point where this happens, I think the cleanest solution would probably be a small and focused change in TLPhotoPicker: add a configuration flag to decide whether captured photos/videos should be saved to the Photo Library or written to a temporary/custom location instead.

On the Nextcloud side, we would then only need to retrieve those captured images/videos from that location and continue with the existing upload flow.

However, I also noticed that the camera capture UI/flow seems to have changed, especially on iPad. I would like to better understand that part before deciding the final direction, because this is not only about where the captured file is saved, but also about how the capture view is presented and handled across devices.

So, I would not require a full fork if this can be handled with a small, focused change. Ideally, this could be proposed upstream. If that is not possible, then a minimal fork limited to this behavior could also be acceptable.

We will of course also need to address the lint and iPad issues before merging.

@Rasmussw

Rasmussw commented May 8, 2026 via email

Copy link
Copy Markdown
Contributor Author

@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from c342ec6 to d15de8c Compare May 12, 2026 07:59
Rasmussw added 3 commits May 12, 2026 11:04
Introduces a custom AVFoundation camera that captures photos and videos
and uploads them directly to Nextcloud without saving to the device
camera roll by default.

- Custom camera UI: shutter, flash, flip, mode selector (photo/video)
- Pinch-to-zoom from 0.5x (ultra-wide) to 10x with live zoom label
- Virtual multi-camera support (triple/dual-wide) for sub-1x zoom
- Recording timer with red dot, screen stays on during video recording
- App backgrounding gracefully stops recording in progress
- Review screen after capture: retake or use, inline video playback
- "Save to camera roll" opt-in toggle per session (off by default)
- Global default for toggle in Settings -> Advanced
- Filenames follow Nextcloud conventions via createFileName(),
  including "Maintain original filename" mode (IMG_XXXX.JPG)
- Videos saved as .mov (QuickTime) matching Apple native camera format
- Restored live photo upload logic: livePhotoFile and nativeFormat
  were inadvertently removed in a previous refactor
- Fix: PHAuthorizationStatus.limited now correctly treated as authorized
- Fix: NCViewerQuickLookView crash when asset is nil after model change

Signed-off-by: Rasmus Wøldike <[email protected]>

Signed-off-by: Rasmus Wøldike <[email protected]>
Signed-off-by: Rasmus Wøldike <[email protected]>

Signed-off-by: Rasmus Wøldike <[email protected]>
Replace custom AVFoundation camera with UIImagePickerController by
intercepting TLPhotoPicker's camera presentation and injecting
customPhotoPickerViewController as delegate. Media is captured to a
temp file and passed directly to the upload flow without saving to
camera roll. Fixes iPad camera support.

Signed-off-by: Rasmus Wøldike <[email protected]>
Signed-off-by: Rasmus Wøldike <[email protected]>

Signed-off-by: Rasmus Wøldike <[email protected]>
@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from d15de8c to 9699781 Compare May 12, 2026 09:04
Signed-off-by: Rasmus Wøldike <[email protected]>

Signed-off-by: Rasmus Wøldike <[email protected]>
@Rasmussw
Rasmussw requested a review from marinofaggiana May 12, 2026 09:50
@marinofaggiana

marinofaggiana commented May 15, 2026

Copy link
Copy Markdown
Member

Definitely better, but I don't understand. As a dependency, you use https://github.com/tilltue/TLPhotoPicker 2.0.0 < 3.0.0, so the feature is already present?

Doing a quick test, I saw that the file name is UUID, which breaks the normal file naming convention. It would be a good idea to use the same mask.

( let destURL = tempDir.appendingPathComponent(UUID().uuidString + ".jpg")

@Rasmussw

Rasmussw commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana ,

Thanks for the review.

To answer your question, didCaptureMediaURL does not exist in the original TLPhotoPicker. I added it in my own fork and submitted a PR upstream (tilltue/TLPhotoPicker#383), which is still awaiting review. The current committed code actually still points to the original tilltue/TLPhotoPicker.

I will fix the UUID filename issue, but regarding the dependency, what would you prefer?

  1. Point to my fork until the upstream PR is merged, then switch back to the original.
  2. Wait for the upstream PR to be merged before moving forward with this PR.

Best regards,
Rasmus

@marinofaggiana

Copy link
Copy Markdown
Member

Hi @marinofaggiana ,

Thanks for the review.

To answer your question, didCaptureMediaURL does not exist in the original TLPhotoPicker. I added it in my own fork and submitted a PR upstream (tilltue/TLPhotoPicker#383), which is still awaiting review. The current committed code actually still points to the original tilltue/TLPhotoPicker.

I will fix the UUID filename issue, but regarding the dependency, what would you prefer?

  1. Point to my fork until the upstream PR is merged, then switch back to the original.
  2. Wait for the upstream PR to be merged before moving forward with this PR.

Best regards, Rasmus

Hi @Rasmussw,
did you ping it to see if it will be added to master?

@Rasmussw

Rasmussw commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana,

Good news, the upstream PR has actually just been merged into master(tilltue/TLPhotoPicker#383), and a new tag(2.1.20) has been released with the changes. I will update the dependency to point to the official repositorys new version and push the UUID filename fix in my next commit.

Best regards, Rasmus

Rasmussw added 3 commits June 17, 2026 09:02
- Use didCaptureMediaURL from TLPhotoPicker 2.1.21 with correct
  filename convention via createFileName
- Remove unused strings (_retake_, _use_photo_, _use_video_)
- Fix autoMkcol version check to use isServerVersion
- Remove duplicate function declarations from merge

Signed-off-by: Rasmus Wøldike <[email protected]>
…ings

Only add _save_to_camera_roll_ and _save_to_camera_roll_desc_ strings.
All other strings from upstream master are preserved.

Signed-off-by: Rasmus Wøldike <[email protected]>
@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from 59eddf2 to a47d8e7 Compare June 17, 2026 08:28
@Rasmussw

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana

Updated the PR with the following changes:

  • Now using didCaptureMediaURL from the official TLPhotoPicker 2.1.21
  • Filenames follow Nextcloud's naming convention via createFileName
  • Rebased onto latest master
  • Lint is clean

Best regards, Rasmus

@marinofaggiana

marinofaggiana commented Jun 23, 2026

Copy link
Copy Markdown
Member

@Rasmussw can you make a git merge origin/master ? Sorry but I've been very busy

@Rasmussw
Rasmussw force-pushed the feature/direct-upload-camera-clean branch from e12df71 to 1e12d52 Compare June 23, 2026 09:19
@Rasmussw

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana

Latest master is merged into the branch.

@marinofaggiana

Copy link
Copy Markdown
Member

@Rasmussw great job ! thank you !

@marinofaggiana
marinofaggiana self-requested a review June 26, 2026 05:55
@marinofaggiana
marinofaggiana merged commit 198a2f0 into nextcloud:master Jun 26, 2026
4 of 5 checks passed
@Rasmussw

Copy link
Copy Markdown
Contributor Author

Hi @marinofaggiana

Thanks again for the review and for merging the PR!

Do you have an idea of when this will be included in a release/tag and available in the App Store?

Best regards Rasmus

@marinofaggiana

Copy link
Copy Markdown
Member

It won't be anytime soon; we've just added a new image and video viewer, as well as an updated Media search, which deserves a thorough test. You can try it out now! It's already in Master and TestFlight.

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