Procházet zdrojové kódy

Merge pull request #10357 from nextcloud/warnOnExistingFile

When during renaming the same name already exists in same folder: show an error, disable confirm button
Tobias Kaminsky před 2 roky
rodič
revize
f6a99ef9ca
20 změnil soubory, kde provedl 265 přidání a 54 odebrání
  1. 2 1
      app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java
  2. 2 1
      app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt
  3. 2 1
      app/src/main/java/com/nextcloud/ui/SetStatusDialogFragment.kt
  4. 8 8
      app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java
  5. 2 2
      app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
  6. 4 3
      app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  7. 2 2
      app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
  8. 6 3
      app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
  9. 47 1
      app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java
  10. 27 2
      app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt
  11. 75 10
      app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
  12. 4 3
      app/src/main/java/com/owncloud/android/ui/dialog/NoteDialogFragment.java
  13. 33 7
      app/src/main/java/com/owncloud/android/ui/dialog/RenameFileDialogFragment.java
  14. 2 1
      app/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
  15. 2 1
      app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
  16. 6 2
      app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  17. 2 2
      app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  18. 2 1
      app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java
  19. 36 3
      app/src/main/java/com/owncloud/android/utils/theme/ThemeTextInputUtils.java
  20. 1 0
      app/src/main/res/values/strings.xml

+ 2 - 1
app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java

@@ -104,7 +104,8 @@ public class DialogFragmentIT extends AbstractIT {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(new OCFile("/Test/"));
+        RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(new OCFile("/Test/"),
+                                                                               new OCFile("/"));
         showDialog(dialog);
     }
 

+ 2 - 1
app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt

@@ -348,7 +348,8 @@ class FileDetailSharingFragmentIT : AbstractIT() {
     }
 
     private fun setupSecondaryFragment() {
-        val secondary = FileDetailFragment.newInstance(file, user)
+        val parentFolder = OCFile("/")
+        val secondary = FileDetailFragment.newInstance(file, parentFolder, user)
         activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES)
         activity.addView(
             FloatingActionButton(activity).apply { // needed for some reason

+ 2 - 1
app/src/main/java/com/nextcloud/ui/SetStatusDialogFragment.kt

@@ -211,7 +211,8 @@ class SetStatusDialogFragment :
         themeTextInputUtils.colorTextInput(
             binding.customStatusInputContainer,
             binding.customStatusInput,
-            themeColorUtils.primaryColor(activity)
+            themeColorUtils.primaryColor(activity),
+            themeColorUtils.primaryAccentColor(activity)
         )
     }
 

+ 8 - 8
app/src/main/java/com/owncloud/android/operations/CreateShareWithShareeOperation.java

@@ -35,9 +35,9 @@ import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.operations.common.SyncOperation;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Creates a new private share for a given file.
@@ -54,12 +54,12 @@ public class CreateShareWithShareeOperation extends SyncOperation {
     private long expirationDateInMillis;
     private String label;
 
-    private static final List<ShareType> supportedShareTypes = new ArrayList<>(Arrays.asList(ShareType.USER,
-                                                                                             ShareType.GROUP,
-                                                                                             ShareType.FEDERATED,
-                                                                                             ShareType.EMAIL,
-                                                                                             ShareType.ROOM,
-                                                                                             ShareType.CIRCLE));
+    private static final Set<ShareType> supportedShareTypes = new HashSet<>(Arrays.asList(ShareType.USER,
+                                                                                          ShareType.GROUP,
+                                                                                          ShareType.FEDERATED,
+                                                                                          ShareType.EMAIL,
+                                                                                          ShareType.ROOM,
+                                                                                          ShareType.CIRCLE));
 
     /**
      * Constructor.

+ 2 - 2
app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -19,11 +19,11 @@
 
 package com.owncloud.android.operations;
 
-import android.accounts.Account;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 
+import com.google.common.collect.Maps;
 import com.google.gson.Gson;
 import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainRemoteOperation;
 import com.nextcloud.client.account.User;
@@ -631,7 +631,7 @@ public class RefreshFolderOperation extends RemoteOperation {
 
     @NonNull
     public static Map<String, OCFile> prefillLocalFilesMap(DecryptedFolderMetadata metadata, List<OCFile> localFiles) {
-        Map<String, OCFile> localFilesMap = new HashMap<>(localFiles.size());
+        Map<String, OCFile> localFilesMap = Maps.newHashMapWithExpectedSize(localFiles.size());
 
         for (OCFile file : localFiles) {
             String remotePath = file.getRemotePath();

+ 4 - 3
app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -2240,11 +2240,12 @@ public class FileDisplayActivity extends FileActivity
      * Requests the download of the received {@link OCFile} , updates the UI to monitor the download progress and
      * prepares the activity to preview or open the file when the download finishes.
      *
-     * @param file {@link OCFile} to download and preview.
+     * @param file         {@link OCFile} to download and preview.
+     * @param parentFolder {@link OCFile} containing above file
      */
-    public void startDownloadForPreview(OCFile file) {
+    public void startDownloadForPreview(OCFile file, OCFile parentFolder) {
         final User currentUser = getUser().orElseThrow(RuntimeException::new);
-        Fragment detailFragment = FileDetailFragment.newInstance(file, currentUser);
+        Fragment detailFragment = FileDetailFragment.newInstance(file, parentFolder, currentUser);
         setLeftFragment(detailFragment);
         mWaitingToPreview = file;
         requestForDownload();

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java

@@ -38,6 +38,7 @@ import android.os.IBinder;
 import android.view.MenuItem;
 import android.view.View;
 
+import com.google.common.collect.Sets;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.jobs.BackgroundJobManager;
@@ -65,7 +66,6 @@ import org.greenrobot.eventbus.ThreadMode;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -217,7 +217,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
     }
 
     private static Set<String> toAccountNames(Collection<User> users) {
-        Set<String> accountNames = new HashSet<>(users.size());
+        Set<String> accountNames = Sets.newHashSetWithExpectedSize(users.size());
         for (User user : users) {
             accountNames.add(user.getAccountName());
         }

+ 6 - 3
app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -412,7 +412,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
                 mText.add(internetShortcutDesktopText(texts[2], texts[0]));
                 mFilenameBase.add(texts[0]);
                 mFilenameSuffix.add(DESKTOP_FILE_SUFFIX);
-                adapter.add(String.format(str,DESKTOP_FILE_SUFFIX));
+                adapter.add(String.format(str, DESKTOP_FILE_SUFFIX));
 
                 selectPos = preferences.getUploadMapFileExtensionUrlSelectedPos();
                 mFileCategory = CATEGORY_MAPS_URL;
@@ -422,7 +422,10 @@ public class ReceiveExternalFilesActivity extends FileActivity
             final TextInputLayout userInputContainer = view.findViewById(R.id.user_input_container);
             setFilename(userInput, selectPos);
             userInput.requestFocus();
-            themeTextInputUtils.colorTextInput(userInputContainer, userInput, themeColorUtils.primaryColor(getContext()));
+            themeTextInputUtils.colorTextInput(userInputContainer,
+                                               userInput,
+                                               themeColorUtils.primaryColor(getContext()),
+                                               themeColorUtils.primaryAccentColor(getContext()));
 
             final Spinner spinner = view.findViewById(R.id.file_type);
             setupSpinner(adapter, selectPos, userInput, spinner);
@@ -432,7 +435,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
             }
             mSpinner = spinner;
 
-            Dialog filenameDialog =  createFilenameDialog(view, userInput, spinner);
+            Dialog filenameDialog = createFilenameDialog(view, userInput, spinner);
             if (filenameDialog.getWindow() != null) {
                 filenameDialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
             }

+ 47 - 1
app/src/main/java/com/owncloud/android/ui/dialog/ChooseRichDocumentsTemplateDialogFragment.java

@@ -27,12 +27,15 @@ import android.app.Dialog;
 import android.content.Intent;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Button;
 
+import com.google.common.collect.Sets;
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.di.Injectable;
@@ -63,6 +66,7 @@ import com.owncloud.android.utils.theme.ThemeTextInputUtils;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -88,6 +92,7 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
     @Inject ThemeColorUtils themeColorUtils;
     @Inject ThemeButtonUtils themeButtonUtils;
     @Inject ThemeTextInputUtils themeTextInputUtils;
+    @Inject FileDataStorageManager fileDataStorageManager;
     private RichDocumentsTemplateAdapter adapter;
     private OCFile parentFolder;
     private OwnCloudClient client;
@@ -147,6 +152,12 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
         }
 
         parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
+        List<OCFile> folderContent = fileDataStorageManager.getFolderContent(parentFolder, false);
+        Set<String> fileNames = Sets.newHashSetWithExpectedSize(folderContent.size());
+
+        for (OCFile file : folderContent) {
+            fileNames.add(file.getFileName());
+        }
 
         // Inflate the layout for the dialog
         LayoutInflater inflater = requireActivity().getLayoutInflater();
@@ -156,7 +167,8 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
         binding.filename.requestFocus();
         themeTextInputUtils.colorTextInput(binding.filenameContainer,
                                            binding.filename,
-                                           themeColorUtils.primaryColor(getContext()));
+                                           themeColorUtils.primaryColor(getContext()),
+                                           themeColorUtils.primaryAccentColor(getContext()));
 
         Type type = Type.valueOf(arguments.getString(ARG_TYPE));
         new FetchTemplateTask(this, client).execute(type);
@@ -171,6 +183,40 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
                                                    themeColorUtils);
         binding.list.setAdapter(adapter);
 
+        binding.filename.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+            }
+
+            /**
+             * When user enters an already taken file name, a message is shown. Otherwise, the
+             * message is ensured to be hidden.
+             */
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                String newFileName = "";
+                if (binding.filename.getText() != null) {
+                    newFileName = binding.filename.getText().toString().trim();
+                }
+
+                if (fileNames.contains(newFileName)) {
+                    binding.filenameContainer.setError(getText(R.string.file_already_exists));
+                    positiveButton.setEnabled(false);
+                } else if (binding.filenameContainer.getError() != null) {
+                    binding.filenameContainer.setError(null);
+                    // Called to remove extra padding
+                    binding.filenameContainer.setErrorEnabled(false);
+                    positiveButton.setEnabled(true);
+                }
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+
+            }
+        });
+
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         builder.setView(view)

+ 27 - 2
app/src/main/java/com/owncloud/android/ui/dialog/ChooseTemplateDialogFragment.kt

@@ -88,6 +88,9 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
     @Inject
     lateinit var themeTextInputUtils: ThemeTextInputUtils
 
+    @Inject
+    lateinit var fileDataStorageManager: FileDataStorageManager
+
     private var adapter: TemplateAdapter? = null
     private var parentFolder: OCFile? = null
     private var title: String? = null
@@ -130,6 +133,8 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
             else -> savedInstanceState.getString(ARG_HEADLINE)
         }
 
+        val fileNames = fileDataStorageManager.getFolderContent(parentFolder, false).map { it.fileName }
+
         // Inflate the layout for the dialog
         val inflater = requireActivity().layoutInflater
         _binding = ChooseTemplateBinding.inflate(inflater, null, false)
@@ -139,7 +144,8 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
         themeTextInputUtils.colorTextInput(
             binding.filenameContainer,
             binding.filename,
-            themeColorUtils.primaryColor(context)
+            themeColorUtils.primaryColor(context),
+            themeColorUtils.primaryAccentColor(context)
         )
         binding.filename.setOnKeyListener { _, _, _ ->
             checkEnablingCreateButton()
@@ -148,7 +154,9 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
         binding.filename.addTextChangedListener(object : TextWatcher {
             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
 
-            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
+            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+                checkExistingFilename(fileNames)
+            }
 
             override fun afterTextChanged(s: Editable) {
                 checkEnablingCreateButton()
@@ -257,6 +265,23 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
         }
     }
 
+    private fun checkExistingFilename(fileNames: List<String>) {
+        var newFileName = ""
+        if (binding.filename.text != null) {
+            newFileName = binding.filename.text.toString().trim()
+        }
+
+        if (fileNames.contains(newFileName)) {
+            binding.filenameContainer.error = getText(R.string.file_already_exists)
+            positiveButton?.isEnabled = false
+        } else if (binding.filenameContainer.error != null) {
+            binding.filenameContainer.error = null
+            // Called to remove extra padding
+            binding.filenameContainer.isErrorEnabled = false
+            positiveButton?.isEnabled = true
+        }
+    }
+
     @Suppress("LongParameterList") // legacy code
     private class CreateFileFromTemplateTask(
         chooseTemplateDialogFragment: ChooseTemplateDialogFragment,

+ 75 - 10
app/src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java

@@ -1,4 +1,4 @@
-/**
+/*
  *   ownCloud Android client application
  *
  *   @author David A. Velasco
@@ -23,16 +23,21 @@ package com.owncloud.android.ui.dialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.text.Editable;
 import android.text.TextUtils;
+import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
 import android.widget.TextView;
 
+import com.google.common.collect.Sets;
 import com.nextcloud.client.di.Injectable;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.EditBoxDialogBinding;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.ui.activity.ComponentsGetter;
@@ -41,6 +46,9 @@ import com.owncloud.android.utils.theme.ThemeButtonUtils;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 import com.owncloud.android.utils.theme.ThemeTextInputUtils;
 
+import java.util.List;
+import java.util.Set;
+
 import javax.inject.Inject;
 
 import androidx.annotation.NonNull;
@@ -62,8 +70,10 @@ public class CreateFolderDialogFragment
     @Inject ThemeColorUtils themeColorUtils;
     @Inject ThemeButtonUtils themeButtonUtils;
     @Inject ThemeTextInputUtils themeTextInputUtils;
+    @Inject FileDataStorageManager fileDataStorageManager;
 
     private OCFile mParentFolder;
+    private Button positiveButton;
 
     /**
      * Public factory method to create new CreateFolderDialogFragment instances.
@@ -86,15 +96,18 @@ public class CreateFolderDialogFragment
 
         AlertDialog alertDialog = (AlertDialog) getDialog();
 
-        themeButtonUtils.themeBorderlessButton(themeColorUtils,
-                                               alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
-                                               alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+        if (alertDialog != null) {
+            positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+            themeButtonUtils.themeBorderlessButton(themeColorUtils,
+                                                   positiveButton,
+                                                   alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
+        }
     }
 
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        int primaryColor = themeColorUtils.primaryColor(getActivity());
         mParentFolder = getArguments().getParcelable(ARG_PARENT_FOLDER);
 
         // Inflate the layout for the dialog
@@ -105,14 +118,66 @@ public class CreateFolderDialogFragment
         // Setup layout
         binding.userInput.setText("");
         binding.userInput.requestFocus();
-        themeTextInputUtils.colorTextInput(binding.userInputContainer, binding.userInput, primaryColor);
+        themeTextInputUtils.colorTextInput(binding.userInputContainer,
+                                           binding.userInput,
+                                           themeColorUtils.primaryColor(getActivity()),
+                                           themeColorUtils.primaryAccentColor(getActivity()));
+
+        OCFile parentFolder = requireArguments().getParcelable(ARG_PARENT_FOLDER);
+        List<OCFile> folderContent = fileDataStorageManager.getFolderContent(parentFolder, false);
+        Set<String> fileNames = Sets.newHashSetWithExpectedSize(folderContent.size());
+
+        for (OCFile file : folderContent) {
+            fileNames.add(file.getFileName());
+        }
+
+        // Add TextChangedListener to handle showing/hiding the input warning message
+        binding.userInput.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            /**
+             * When user enters a hidden file name, the 'hidden file' message is shown. Otherwise,
+             * the message is ensured to be hidden.
+             */
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                String newFileName = "";
+                if (binding.userInput.getText() != null) {
+                    newFileName = binding.userInput.getText().toString().trim();
+                }
+
+                if (!TextUtils.isEmpty(newFileName) && newFileName.charAt(0) == '.') {
+                    binding.userInputContainer.setError(getText(R.string.hidden_file_name_warning));
+                } else if (TextUtils.isEmpty(newFileName)) {
+                    binding.userInputContainer.setError(getString(R.string.filename_empty));
+                    positiveButton.setEnabled(false);
+                } else if (!FileUtils.isValidName(newFileName)) {
+                    binding.userInputContainer.setError(getString(R.string.filename_forbidden_charaters_from_server));
+                    positiveButton.setEnabled(false);
+                } else if (fileNames.contains(newFileName)) {
+                    binding.userInputContainer.setError(getText(R.string.file_already_exists));
+                    positiveButton.setEnabled(false);
+                } else if (binding.userInputContainer.getError() != null) {
+                    binding.userInputContainer.setError(null);
+                    // Called to remove extra padding
+                    binding.userInputContainer.setErrorEnabled(false);
+                    positiveButton.setEnabled(true);
+                }
+            }
+        });
 
         // Build the dialog
-        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
         builder.setView(view)
-                .setPositiveButton(R.string.folder_confirm_create, this)
-                .setNeutralButton(R.string.common_cancel, this)
-                .setTitle(R.string.uploader_info_dirname);
+            .setPositiveButton(R.string.folder_confirm_create, this)
+            .setNeutralButton(R.string.common_cancel, this)
+            .setTitle(R.string.uploader_info_dirname);
         AlertDialog d = builder.create();
 
         Window window = d.getWindow();

+ 4 - 3
app/src/main/java/com/owncloud/android/ui/dialog/NoteDialogFragment.java

@@ -94,8 +94,6 @@ public class NoteDialogFragment extends DialogFragment implements DialogInterfac
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        int primaryColor = themeColorUtils.primaryColor(getContext());
-
         // Inflate the layout for the dialog
         LayoutInflater inflater = requireActivity().getLayoutInflater();
         binding = NoteDialogBinding.inflate(inflater, null, false);
@@ -104,7 +102,10 @@ public class NoteDialogFragment extends DialogFragment implements DialogInterfac
         // Setup layout
         binding.noteText.setText(share.getNote());
         binding.noteText.requestFocus();
-        themeTextInputUtils.colorTextInput(binding.noteContainer, binding.noteText, primaryColor);
+        themeTextInputUtils.colorTextInput(binding.noteContainer,
+                                           binding.noteText,
+                                           themeColorUtils.primaryColor(getContext()),
+                                           themeColorUtils.primaryAccentColor(getContext()));
 
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());

+ 33 - 7
app/src/main/java/com/owncloud/android/ui/dialog/RenameFileDialogFragment.java

@@ -36,10 +36,13 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
 
+import com.google.common.collect.Sets;
 import com.nextcloud.client.di.Injectable;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.EditBoxDialogBinding;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.ui.activity.ComponentsGetter;
@@ -47,6 +50,9 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.theme.ThemeColorUtils;
 import com.owncloud.android.utils.theme.ThemeTextInputUtils;
 
+import java.util.List;
+import java.util.Set;
+
 import javax.inject.Inject;
 
 import androidx.annotation.NonNull;
@@ -63,23 +69,27 @@ public class RenameFileDialogFragment
     extends DialogFragment implements DialogInterface.OnClickListener, Injectable {
 
     private static final String ARG_TARGET_FILE = "TARGET_FILE";
+    private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
 
     @Inject ThemeColorUtils themeColorUtils;
     @Inject ThemeTextInputUtils themeTextInputUtils;
+    @Inject FileDataStorageManager fileDataStorageManager;
 
     private EditBoxDialogBinding binding;
     private OCFile mTargetFile;
+    private Button positiveButton;
 
     /**
      * Public factory method to create new RenameFileDialogFragment instances.
      *
-     * @param file            File to rename.
+     * @param file File to rename.
      * @return Dialog ready to show.
      */
-    public static RenameFileDialogFragment newInstance(OCFile file) {
+    public static RenameFileDialogFragment newInstance(OCFile file, OCFile parentFolder) {
         RenameFileDialogFragment frag = new RenameFileDialogFragment();
         Bundle args = new Bundle();
         args.putParcelable(ARG_TARGET_FILE, file);
+        args.putParcelable(ARG_PARENT_FOLDER, parentFolder);
         frag.setArguments(args);
         return frag;
 
@@ -94,7 +104,8 @@ public class RenameFileDialogFragment
         AlertDialog alertDialog = (AlertDialog) getDialog();
 
         if (alertDialog != null) {
-            alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
+            positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+            positiveButton.setTextColor(color);
             alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(color);
         }
     }
@@ -114,12 +125,21 @@ public class RenameFileDialogFragment
         binding.userInput.setText(currentName);
         themeTextInputUtils.colorTextInput(binding.userInputContainer,
                                            binding.userInput,
-                                           themeColorUtils.primaryColor(getActivity()));
+                                           themeColorUtils.primaryColor(getActivity()),
+                                           themeColorUtils.primaryAccentColor(getActivity()));
         int extensionStart = mTargetFile.isFolder() ? -1 : currentName.lastIndexOf('.');
         int selectionEnd = extensionStart >= 0 ? extensionStart : currentName.length();
         binding.userInput.setSelection(0, selectionEnd);
         binding.userInput.requestFocus();
-        
+
+        OCFile parentFolder = requireArguments().getParcelable(ARG_PARENT_FOLDER);
+        List<OCFile> folderContent = fileDataStorageManager.getFolderContent(parentFolder, false);
+        Set<String> fileNames = Sets.newHashSetWithExpectedSize(folderContent.size());
+
+        for (OCFile file : folderContent) {
+            fileNames.add(file.getFileName());
+        }
+
         // Add TextChangedListener to handle showing/hiding the input warning message
         binding.userInput.addTextChangedListener(new TextWatcher() {
             @Override
@@ -143,11 +163,17 @@ public class RenameFileDialogFragment
 
                 if (!TextUtils.isEmpty(newFileName) && newFileName.charAt(0) == '.') {
                     binding.userInputContainer.setError(getText(R.string.hidden_file_name_warning));
-                }
-                else if(binding.userInputContainer.getError() != null) {
+                } else if (TextUtils.isEmpty(newFileName)) {
+                    binding.userInputContainer.setError(getString(R.string.filename_empty));
+                    positiveButton.setEnabled(false);
+                } else if (fileNames.contains(newFileName)) {
+                    binding.userInputContainer.setError(getText(R.string.file_already_exists));
+                    positiveButton.setEnabled(false);
+                } else if (binding.userInputContainer.getError() != null) {
                     binding.userInputContainer.setError(null);
                     // Called to remove extra padding
                     binding.userInputContainer.setErrorEnabled(false);
+                    positiveButton.setEnabled(true);
                 }
             }
         });

+ 2 - 1
app/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java

@@ -165,7 +165,8 @@ public class SharePasswordDialogFragment extends DialogFragment implements Dialo
         binding.sharePassword.setText("");
         themeTextInputUtils.colorTextInput(binding.sharePasswordContainer,
                                            binding.sharePassword,
-                                           themeColorUtils.primaryColor(getActivity()));
+                                           themeColorUtils.primaryColor(getActivity()),
+                                           themeColorUtils.primaryAccentColor(getActivity()));
         binding.sharePassword.requestFocus();
 
         int negativeButtonCaption;

+ 2 - 1
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java

@@ -185,7 +185,8 @@ public class FileDetailActivitiesFragment extends Fragment implements
 
         themeTextInputUtils.colorTextInput(binding.commentInputFieldContainer,
                                            binding.commentInputField,
-                                           themeColorUtils.primaryColor(getContext()));
+                                           themeColorUtils.primaryColor(getContext()),
+                                           themeColorUtils.primaryAccentColor(getContext()));
 
         DisplayUtils.setAvatar(user,
                                this,

+ 6 - 2
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -98,10 +98,12 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
     static final String FTAG_RENAME_FILE = "RENAME_FILE_FRAGMENT";
 
     private static final String ARG_FILE = "FILE";
+    private static final String ARG_PARENT_FOLDER = "PARENT_FOLDER";
     private static final String ARG_USER = "USER";
     private static final String ARG_ACTIVE_TAB = "TAB";
     private View view;
     private User user;
+    private OCFile parentFolder;
     private boolean previewLoaded;
 
     private FileDetailsFragmentBinding binding;
@@ -128,10 +130,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
      * @param user         Currently active user
      * @return New fragment with arguments set
      */
-    public static FileDetailFragment newInstance(OCFile fileToDetail, User user) {
+    public static FileDetailFragment newInstance(OCFile fileToDetail, OCFile parentFile, User user) {
         FileDetailFragment frag = new FileDetailFragment();
         Bundle args = new Bundle();
         args.putParcelable(ARG_FILE, fileToDetail);
+        args.putParcelable(ARG_PARENT_FOLDER, parentFile);
         args.putParcelable(ARG_USER, user);
         frag.setArguments(args);
         return frag;
@@ -206,6 +209,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         }
 
         setFile(arguments.getParcelable(ARG_FILE));
+        parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER);
         user = arguments.getParcelable(ARG_USER);
         activeTab = arguments.getInt(ARG_ACTIVE_TAB, 0);
 
@@ -402,7 +406,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             dialog.show(getFragmentManager(), FTAG_CONFIRMATION);
             return true;
         } else if (itemId == R.id.action_rename_file) {
-            RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(getFile());
+            RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(getFile(), parentFolder);
             dialog.show(getFragmentManager(), FTAG_RENAME_FILE);
             return true;
         } else if (itemId == R.id.action_cancel_sync) {

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -1075,7 +1075,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
                             mContainerActivity.getFileOperationsHelper().openFileAsRichDocument(file, getContext());
                         } else {
                             // automatic download, preview on finish
-                            ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file);
+                            ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file, mFile);
                         }
                     }
                 }
@@ -1148,7 +1148,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
 
                 return true;
             } else if (itemId == R.id.action_rename_file) {
-                RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile);
+                RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile, mFile);
                 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
                 return true;
             } else if (itemId == R.id.action_see_details) {

+ 2 - 1
app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -26,6 +26,7 @@ import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Pair;
 
+import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import com.nextcloud.client.account.User;
@@ -674,7 +675,7 @@ public final class EncryptionUtils {
 
         SecureRandom random = new SecureRandom();
 
-        List<String> outputLines = new ArrayList<>();
+        List<String> outputLines = Lists.newArrayListWithCapacity(count);
         for (int i = 0; i < count; i++) {
             int randomLine = random.nextInt(lines.size());
             outputLines.add(lines.get(randomLine));

+ 36 - 3
app/src/main/java/com/owncloud/android/utils/theme/ThemeTextInputUtils.java

@@ -46,9 +46,12 @@ public final class ThemeTextInputUtils {
      * @param textInputEditText the TextInputEditText child element
      * @param color             the color to be used for the hint text and box stroke
      */
-    public void colorTextInput(TextInputLayout textInputLayout, TextInputEditText textInputEditText, int color) {
+    public void colorTextInput(TextInputLayout textInputLayout,
+                               TextInputEditText textInputEditText,
+                               int color,
+                               int errorColor) {
         textInputEditText.setHighlightColor(color);
-        colorTextInputLayout(textInputLayout, color);
+        colorTextInputLayout(textInputLayout, color, errorColor);
     }
 
     /**
@@ -57,8 +60,38 @@ public final class ThemeTextInputUtils {
      * @param textInputLayout the TextInputLayout instance
      * @param color           the color to be used for the hint text and box stroke
      */
-    private void colorTextInputLayout(TextInputLayout textInputLayout, int color) {
+    private void colorTextInputLayout(TextInputLayout textInputLayout, int color, int errorColor) {
         textInputLayout.setBoxStrokeColor(color);
+        textInputLayout.setErrorIconTintList(new ColorStateList(
+            new int[][]{
+                new int[]{-android.R.attr.state_focused},
+                new int[]{android.R.attr.state_focused},
+            },
+            new int[]{
+                errorColor,
+                errorColor
+            }
+        ));
+        textInputLayout.setErrorTextColor(new ColorStateList(
+            new int[][]{
+                new int[]{-android.R.attr.state_focused},
+                new int[]{android.R.attr.state_focused},
+            },
+            new int[]{
+                errorColor,
+                errorColor
+            }
+        ));
+        textInputLayout.setBoxStrokeErrorColor(new ColorStateList(
+            new int[][]{
+                new int[]{-android.R.attr.state_focused},
+                new int[]{android.R.attr.state_focused},
+            },
+            new int[]{
+                errorColor,
+                errorColor
+            }
+        ));
         textInputLayout.setDefaultHintTextColor(new ColorStateList(
             new int[][]{
                 new int[]{-android.R.attr.state_focused},

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -1038,6 +1038,7 @@
     <string name="locked_by">Locked by %1$s</string>
     <string name="locked_by_app">Locked by %1$s app</string>
     <string name="lock_expiration_info">Expires: %1$s</string>
+    <string name="file_already_exists">Filename already exists</string>
     <string name="filedetails_export">Export</string>
     <string name="locate_folder">Locate folder</string>
 </resources>