From 3b28c7acaf065769002e7cef9259aba8c5fa31c1 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sun, 28 Dec 2025 17:39:46 +0100 Subject: [PATCH 1/8] When uploading a single file, offer input field to change file name This streamlines the upload from files, e.g. from email attachments. Closes #9215. The approach via the listener mFileDisplayNameTransformer was chosen because this allows a potential extension to multiple files (with multiple input fields). Alternatively, ReceiveExternalFilesActivity#uploadFile() could've been used, which requires the storage to a temporary file first. nextFocusForward and nextFocusDown are both needed to support navigation with the Tab key as well as the Enter key Signed-off-by: Philipp Hasper --- .../ReceiveExternalFilesActivity.java | 31 +++++++++++++++++-- .../android/ui/helpers/UriUploader.kt | 10 ++++-- .../res/layout/receive_external_files.xml | 23 ++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index 2e8a6a27cdbf..13d9c16cfc9c 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2025 Philipp Hasper * SPDX-FileCopyrightText: 2023 TSI-mc * SPDX-FileCopyrightText: 2016-2023 Tobias Kaminsky * SPDX-FileCopyrightText: 2019 Chris Narkiewicz @@ -24,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources.NotFoundException; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -96,6 +98,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.List; +import java.util.Objects; import java.util.Stack; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -110,6 +113,7 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.widget.SearchView; +import androidx.core.util.Function; import androidx.core.view.MenuItemCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; @@ -117,6 +121,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import static com.owncloud.android.utils.DisplayUtils.openSortingOrderDialogFragment; +import static com.owncloud.android.utils.UriUtils.getDisplayNameForUri; /** * This can be used to upload things to an Nextcloud instance. @@ -141,10 +146,13 @@ public class ReceiveExternalFilesActivity extends FileActivity private AccountManager mAccountManager; private Stack mParents = new Stack<>(); - private List mStreamsToUpload; + @Nullable private List mStreamsToUpload; private String mUploadPath; private OCFile mFile; + @Nullable + private Function mFileDisplayNameTransformer = null; + private SyncBroadcastReceiver mSyncBroadcastReceiver; private ReceiveExternalFilesAdapter receiveExternalFilesAdapter; @@ -785,6 +793,7 @@ private void populateDirectoryList(OCFile file) { files = sortFileList(files); setupReceiveExternalFilesAdapter(files); } + setupFileNameInputField(); MaterialButton btnChooseFolder = binding.uploaderChooseFolder; viewThemeUtils.material.colorMaterialButtonPrimaryFilled(btnChooseFolder); @@ -838,6 +847,23 @@ public void setMessageForEmptyList(@StringRes final int headline, @StringRes fin }); } + private void setupFileNameInputField() { + binding.userInput.setVisibility(View.GONE); + mFileDisplayNameTransformer = null; + if (mStreamsToUpload == null || mStreamsToUpload.size() != 1) { + return; + } + final String fileName = getDisplayNameForUri((Uri) mStreamsToUpload.get(0), getActivity()); + if (fileName == null) { + return; + } + mFileDisplayNameTransformer = uri -> + Objects.requireNonNullElse(binding.userInput.getText(), fileName).toString(); + + binding.userInput.setVisibility(View.VISIBLE); + binding.userInput.setText(fileName); + } + @Override public void onSavedCertificate() { startSyncFolderOperation(getCurrentDir()); @@ -961,7 +987,8 @@ public void uploadFiles() { getUser().orElseThrow(RuntimeException::new), FileUploadWorker.LOCAL_BEHAVIOUR_DELETE, true, // Show waiting dialog while file is being copied from private storage - this // Copy temp task listener + this, // Listener for copying to temporary files + mFileDisplayNameTransformer ); UriUploader.UriUploaderResultCode resultCode = uploader.uploadUris(); diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt b/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt index 9f08ad99bbce..6b4c63cd71d2 100644 --- a/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt +++ b/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt @@ -13,6 +13,7 @@ package com.owncloud.android.ui.helpers import android.content.ContentResolver import android.net.Uri import android.os.Parcelable +import androidx.core.util.Function import com.nextcloud.client.account.User import com.nextcloud.client.jobs.upload.FileUploadHelper import com.owncloud.android.R @@ -42,14 +43,16 @@ import com.owncloud.android.utils.UriUtils.getDisplayNameForUri "Detekt.SpreadOperator", "Detekt.TooGenericExceptionCaught" ) // legacy code -class UriUploader( +class UriUploader @JvmOverloads constructor( private val mActivity: FileActivity, private val mUrisToUpload: List, private val mUploadPath: String, private val user: User, private val mBehaviour: Int, private val mShowWaitingDialog: Boolean, - private val mCopyTmpTaskListener: OnCopyTmpFilesTaskListener? + private val mCopyTmpTaskListener: OnCopyTmpFilesTaskListener?, + /** If non-null, this function is called to determine the desired display name (i.e. filename) after upload**/ + private val mFileDisplayNameTransformer: Function? = null ) { enum class UriUploaderResultCode { @@ -113,7 +116,8 @@ class UriUploader( } private fun getRemotePathForUri(sourceUri: Uri): String { - val displayName = getDisplayNameForUri(sourceUri, mActivity) + val displayName = mFileDisplayNameTransformer?.apply(sourceUri) + ?: getDisplayNameForUri(sourceUri, mActivity) require(displayName != null) { "Display name cannot be null" } return mUploadPath + displayName } diff --git a/app/src/main/res/layout/receive_external_files.xml b/app/src/main/res/layout/receive_external_files.xml index 1f85eae78a32..29d630f0d922 100644 --- a/app/src/main/res/layout/receive_external_files.xml +++ b/app/src/main/res/layout/receive_external_files.xml @@ -1,6 +1,7 @@