diff --git a/app/src/androidTest/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialogIT.java b/app/src/androidTest/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialogIT.java new file mode 100644 index 000000000000..8804a6b0154a --- /dev/null +++ b/app/src/androidTest/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialogIT.java @@ -0,0 +1,204 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * Copyright (C) 2020 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.nmc.android.ui.conflict; + +import android.content.Intent; + +import com.nextcloud.client.account.UserAccountManagerImpl; +import com.owncloud.android.AbstractIT; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.OCUpload; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; +import com.owncloud.android.ui.dialog.ConflictsResolveDialog; +import com.owncloud.android.utils.FileStorageUtils; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; + +import androidx.test.espresso.intent.rule.IntentsTestRule; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; + +public class ConflictsResolveConsentDialogIT extends AbstractIT { + @Rule public IntentsTestRule activityRule = + new IntentsTestRule<>(ConflictsResolveActivity.class, true, false); + private boolean returnCode; + + @Test + public void replaceWithNewFile() { + returnCode = false; + + OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt", + "/newFile.txt", + user.getAccountName()); + + OCFile existingFile = new OCFile("/newFile.txt"); + existingFile.setFileLength(1024000); + existingFile.setModificationTimestamp(1582019340); + existingFile.setRemoteId("00000123abc"); + + OCFile newFile = new OCFile("/newFile.txt"); + newFile.setFileLength(56000); + newFile.setModificationTimestamp(1522019340); + newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt"); + + FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver()); + storageManager.saveNewFile(existingFile); + + Intent intent = new Intent(targetContext, ConflictsResolveActivity.class); + intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId()); + intent.putExtra(ConflictsResolveActivity.EXTRA_LAUNCHED_FROM_TEST, true); + + ConflictsResolveActivity sut = activityRule.launchActivity(intent); + + ConflictsResolveConsentDialog dialog = ConflictsResolveConsentDialog.newInstance(existingFile, + newFile, + UserAccountManagerImpl + .fromContext(targetContext) + .getUser() + ); + dialog.showDialog(sut); + + sut.listener = decision -> { + assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_LOCAL); + returnCode = true; + }; + + getInstrumentation().waitForIdleSync(); + + onView(withId(R.id.replace_btn)).perform(click()); + + assertTrue(returnCode); + } + + @Test + public void keepBothFiles() { + returnCode = false; + + OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt", + "/newFile.txt", + user.getAccountName()); + + OCFile existingFile = new OCFile("/newFile.txt"); + existingFile.setFileLength(1024000); + existingFile.setModificationTimestamp(1582019340); + + OCFile newFile = new OCFile("/newFile.txt"); + newFile.setFileLength(56000); + newFile.setModificationTimestamp(1522019340); + newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt"); + + FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver()); + storageManager.saveNewFile(existingFile); + + Intent intent = new Intent(targetContext, ConflictsResolveActivity.class); + intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId()); + intent.putExtra(ConflictsResolveActivity.EXTRA_LAUNCHED_FROM_TEST, true); + + ConflictsResolveActivity sut = activityRule.launchActivity(intent); + + ConflictsResolveConsentDialog dialog = ConflictsResolveConsentDialog.newInstance(existingFile, + newFile, + UserAccountManagerImpl + .fromContext(targetContext) + .getUser() + ); + dialog.showDialog(sut); + + sut.listener = decision -> { + assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_BOTH); + returnCode = true; + }; + + getInstrumentation().waitForIdleSync(); + + onView(withId(R.id.keep_both_btn)).perform(click()); + + assertTrue(returnCode); + } + + @Test + public void keepExistingFile() { + returnCode = false; + + OCUpload newUpload = new OCUpload(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt", + "/newFile.txt", + user.getAccountName()); + + OCFile existingFile = new OCFile("/newFile.txt"); + existingFile.setFileLength(1024000); + existingFile.setModificationTimestamp(1582019340); + + OCFile newFile = new OCFile("/newFile.txt"); + newFile.setFileLength(56000); + newFile.setModificationTimestamp(1522019340); + newFile.setStoragePath(FileStorageUtils.getSavePath(user.getAccountName()) + "/nonEmpty.txt"); + + FileDataStorageManager storageManager = new FileDataStorageManager(user, targetContext.getContentResolver()); + storageManager.saveNewFile(existingFile); + + Intent intent = new Intent(targetContext, ConflictsResolveActivity.class); + intent.putExtra(ConflictsResolveActivity.EXTRA_FILE, newFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_EXISTING_FILE, existingFile); + intent.putExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, newUpload.getUploadId()); + intent.putExtra(ConflictsResolveActivity.EXTRA_LAUNCHED_FROM_TEST, true); + + ConflictsResolveActivity sut = activityRule.launchActivity(intent); + + ConflictsResolveConsentDialog dialog = ConflictsResolveConsentDialog.newInstance(existingFile, + newFile, + UserAccountManagerImpl + .fromContext(targetContext) + .getUser() + ); + dialog.showDialog(sut); + + sut.listener = decision -> { + assertEquals(decision, ConflictsResolveDialog.Decision.KEEP_SERVER); + returnCode = true; + }; + + getInstrumentation().waitForIdleSync(); + + onView(withId(R.id.cancel_keep_existing_btn)).perform(click()); + + assertTrue(returnCode); + } + + @After + public void after() { + getStorageManager().deleteAllFiles(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialog.java b/app/src/main/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialog.java new file mode 100644 index 000000000000..a192e2416191 --- /dev/null +++ b/app/src/main/java/com/nmc/android/ui/conflict/ConflictsResolveConsentDialog.java @@ -0,0 +1,197 @@ +/* + * ownCloud Android client application + * + * @author Bartek Przybylski + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2015 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.nmc.android.ui.conflict; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.widget.Toast; + +import com.nextcloud.client.account.User; +import com.owncloud.android.R; +import com.owncloud.android.databinding.ConflictResolveConsentDialogBinding; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.dialog.ConflictsResolveDialog; + +import java.io.File; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + + +/** + * Dialog which will be displayed to user upon keep-in-sync file conflict. + */ +public class ConflictsResolveConsentDialog extends DialogFragment { + + private ConflictResolveConsentDialogBinding binding; + + private OCFile existingFile; + private File newFile; + public ConflictsResolveDialog.OnConflictDecisionMadeListener listener; + private User user; + + private static final String KEY_NEW_FILE = "file"; + private static final String KEY_EXISTING_FILE = "ocfile"; + private static final String KEY_USER = "user"; + + public static ConflictsResolveConsentDialog newInstance(OCFile existingFile, OCFile newFile, User user) { + ConflictsResolveConsentDialog dialog = new ConflictsResolveConsentDialog(); + + Bundle args = new Bundle(); + args.putParcelable(KEY_EXISTING_FILE, existingFile); + args.putSerializable(KEY_NEW_FILE, new File(newFile.getStoragePath())); + args.putParcelable(KEY_USER, user); + dialog.setArguments(args); + + return dialog; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + listener = (ConflictsResolveDialog.OnConflictDecisionMadeListener) context; + } catch (ClassCastException e) { + throw new ClassCastException("Activity of this dialog must implement OnConflictDecisionMadeListener"); + } + } + + @Override + public void onStart() { + super.onStart(); + + AlertDialog alertDialog = (AlertDialog) getDialog(); + + if (alertDialog == null) { + Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show(); + return; + } + + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + existingFile = savedInstanceState.getParcelable(KEY_EXISTING_FILE); + newFile = (File) savedInstanceState.getSerializable(KEY_NEW_FILE); + user = savedInstanceState.getParcelable(KEY_USER); + } else if (getArguments() != null) { + existingFile = getArguments().getParcelable(KEY_EXISTING_FILE); + newFile = (File) getArguments().getSerializable(KEY_NEW_FILE); + user = getArguments().getParcelable(KEY_USER); + } else { + Toast.makeText(getContext(), "Failed to create conflict dialog", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(KEY_EXISTING_FILE, existingFile); + outState.putSerializable(KEY_NEW_FILE, newFile); + outState.putParcelable(KEY_USER, user); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Inflate the layout for the dialog + binding = ConflictResolveConsentDialogBinding.inflate(requireActivity().getLayoutInflater()); + + // TODO: 26-05-2021 change replace and keep both button text for multiple files + binding.replaceBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.conflictDecisionMade(ConflictsResolveDialog.Decision.KEEP_LOCAL); + } + } + }); + + binding.keepBothBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.conflictDecisionMade(ConflictsResolveDialog.Decision.KEEP_BOTH); + } + } + }); + + binding.moreDetailsBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + } + }); + + binding.cancelKeepExistingBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.conflictDecisionMade(ConflictsResolveDialog.Decision.KEEP_SERVER); + } + } + }); + + // Build the dialog + // TODO: 26-05-2021 Handle multiple dialog message + String dialogMessage = String.format(getString(R.string.conflict_dialog_message), + existingFile.getDecryptedFileName()); + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); + builder.setView(binding.getRoot()) + // TODO: 26-05-2021 handle multiple dialog title + .setTitle(getString(R.string.conflict_dialog_title)) + .setMessage(dialogMessage); + + + return builder.create(); + } + + public void showDialog(AppCompatActivity activity) { + Fragment prev = activity.getSupportFragmentManager().findFragmentByTag("dialog"); + FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction(); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + this.show(ft, "dialog"); + } + + @Override + public void onCancel(@NonNull DialogInterface dialog) { + if (listener != null) { + listener.conflictDecisionMade(ConflictsResolveDialog.Decision.CANCEL); + } + } + +} diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index d0ee77afc191..8362262c17d7 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -487,13 +487,13 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare return collisionResult; } - mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName()); + mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + mFile.getFileName()); String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile); expectedFile = new File(expectedPath); result = copyFile(originalFile, expectedPath); if (!result.isSuccess()) { - return result; + return returnGracefully(temporalFile, token, parentFile, client, result); } // Get the last modification date of the file from the file system @@ -602,7 +602,7 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare } if (result.isSuccess()) { - mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + originalFile.getName()); + mFile.setDecryptedRemotePath(parentFile.getDecryptedRemotePath() + mFile.getFileName()); mFile.setRemotePath(parentFile.getRemotePath() + encryptedFileName); // update metadata @@ -682,6 +682,20 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare getStorageManager().saveConflict(mFile, mFile.getEtagInConflict()); } + return returnGracefully(temporalFile, token, parentFile, client, result); + } + + private RemoteOperationResult returnGracefully( + File temporalFile, + String token, + OCFile parentFile, + OwnCloudClient client, + RemoteOperationResult result) { + // delete temporal file + if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) { + Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath()); + } + // unlock must be done always if (token != null) { RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(parentFile, @@ -692,12 +706,6 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare return unlockFolderResult; } } - - // delete temporal file - if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) { - Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath()); - } - return result; } @@ -966,6 +974,26 @@ private RemoteOperationResult checkNameCollision(OwnCloudClient client, break; case OVERWRITE: Log_OC.d(TAG, "Overwriting file"); + if (encrypted) { + OCFile file = getStorageManager().getFileByDecryptedRemotePath(mRemotePath); + + if (file != null) { + RemoteOperationResult removeResult = new RemoveFileOperation(file, + false, + user, + true, + mContext, + getStorageManager()) + .execute(client); + + if (!removeResult.isSuccess()) { + throw new IllegalArgumentException("Failed to remove old encrypted file!"); + } + + } else { + throw new IllegalArgumentException("File to remove is null!"); + } + } break; case ASK_USER: Log_OC.d(TAG, "Name collision; asking the user what to do"); @@ -1110,6 +1138,7 @@ private void createNewOCFile(String newRemotePath) { newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); newFile.setStoragePath(mFile.getStoragePath()); newFile.setParentId(mFile.getParentId()); + newFile.setEncrypted(mFile.isEncrypted()); mOldFile = mFile; mFile = newFile; } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 8f41b3aea679..5efd04697c82 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -24,6 +24,7 @@ import com.nextcloud.client.account.User; import com.nextcloud.java.util.Optional; +import com.nmc.android.ui.conflict.ConflictsResolveConsentDialog; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.UploadsStorageManager; @@ -35,7 +36,6 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; -import com.owncloud.android.ui.dialog.ConflictsResolveDialog; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener; import com.owncloud.android.utils.FileStorageUtils; @@ -60,7 +60,10 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict */ public static final String EXTRA_LOCAL_BEHAVIOUR = "LOCAL_BEHAVIOUR"; public static final String EXTRA_EXISTING_FILE = "EXISTING_FILE"; - + /** + * variable to tell activity that it has been launched from test class + */ + public static final String EXTRA_LAUNCHED_FROM_TEST = "LAUNCHED_FROM_TEST"; private static final String TAG = ConflictsResolveActivity.class.getSimpleName(); @Inject UploadsStorageManager uploadsStorageManager; @@ -69,7 +72,7 @@ public class ConflictsResolveActivity extends FileActivity implements OnConflict private OCFile existingFile; private OCFile newFile; private int localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET; - protected OnConflictDecisionMadeListener listener; + public OnConflictDecisionMadeListener listener; public static Intent createIntent(OCFile file, User user, @@ -81,6 +84,7 @@ public static Intent createIntent(OCFile file, intent.setFlags(intent.getFlags() | flag); } intent.putExtra(EXTRA_FILE, file); + intent.putExtra(EXTRA_EXISTING_FILE, file); intent.putExtra(EXTRA_USER, user); intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId); @@ -141,6 +145,10 @@ protected void onCreate(Bundle savedInstanceState) { uploadsStorageManager.removeUpload(upload); break; case KEEP_SERVER: // Download + if (newFile.isEncrypted()) { + // NMC-2361 fix + break; + } if (!shouldDeleteLocal()) { // Overwrite local file Intent intent = new Intent(getBaseContext(), FileDownloader.class); @@ -231,10 +239,12 @@ private void startDialog() { fragmentTransaction.remove(prev); } - if (existingFile != null && getStorageManager().fileExists(newFile.getRemotePath())) { - ConflictsResolveDialog dialog = ConflictsResolveDialog.newInstance(existingFile, - newFile, - userOptional.get()); + // TODO renaming does not work? + // TODO check all three conflict options + if (existingFile != null && getStorageManager().getFileByDecryptedRemotePath(newFile.getRemotePath()) != null) { + ConflictsResolveConsentDialog dialog = ConflictsResolveConsentDialog.newInstance(existingFile, + newFile, + userOptional.get()); dialog.show(fragmentTransaction, "conflictDialog"); } else { // Account was changed to a different one - just finish @@ -244,8 +254,13 @@ private void startDialog() { } private void showErrorAndFinish() { - runOnUiThread(() -> Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show()); - finish(); + //if activity is launched from test case then don't finish the activity as it is required to show the dialog + //but during normal app run activity should finish during error so we have to pass it false or don't pass + // anything + if (!getIntent().getBooleanExtra(EXTRA_LAUNCHED_FROM_TEST, false)) { + runOnUiThread(() -> Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show()); + finish(); + } } /** diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java index 0298ef49bc0e..47dea84f3ba7 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -495,11 +495,10 @@ private boolean checkAndOpenConflictResolutionDialog(User user, OCUpload item, String status) { String remotePath = item.getRemotePath(); - OCFile ocFile = storageManager.getFileByPath(remotePath); + OCFile ocFile = storageManager.getFileByDecryptedRemotePath(remotePath); - if (ocFile == null) { - // Remote file doesn't exist, try to refresh folder - OCFile folder = storageManager.getFileByPath(new File(remotePath).getParent() + "/"); + if (ocFile == null) { // Remote file doesn't exist, try to refresh folder + OCFile folder = storageManager.getFileByDecryptedRemotePath(new File(remotePath).getParent() + "/"); if (folder != null && folder.isFolder()) { this.refreshFolder(itemViewHolder, user, folder, (caller, result) -> { itemViewHolder.binding.uploadStatus.setText(status); diff --git a/app/src/main/res/color/dialog_positive_btn_color.xml b/app/src/main/res/color/dialog_positive_btn_color.xml new file mode 100644 index 000000000000..5913e0da1a1d --- /dev/null +++ b/app/src/main/res/color/dialog_positive_btn_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/conflict_resolve_consent_dialog.xml b/app/src/main/res/layout-land/conflict_resolve_consent_dialog.xml new file mode 100644 index 000000000000..325749554862 --- /dev/null +++ b/app/src/main/res/layout-land/conflict_resolve_consent_dialog.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conflict_resolve_consent_dialog.xml b/app/src/main/res/layout/conflict_resolve_consent_dialog.xml new file mode 100644 index 000000000000..f0ac8de88111 --- /dev/null +++ b/app/src/main/res/layout/conflict_resolve_consent_dialog.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fa7fc73fd5f9..b8b89e13c8a2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1007,4 +1007,16 @@ %d ausgewählt %d ausgewählt + + Ersetzen + Alle ersetzen + Beide behalten + Beide Versionen für alle behalten + Mehr Details + Abbrechen und bestehende Datei behalten + Dateikonflikt + %d Dateikonflikte + %1$s ist im Zielordner bereits vorhanden. Möchten Sie die bestehende Datei behalten oder überschreiben? + Die Dateien sind im Zielordner bereits vorhanden. Möchten Sie die bestehenden Dateien behalten oder überschreiben? + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 08bf64d552ad..88a29668c4cd 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -48,4 +48,70 @@ #1E1E1E @android:color/white + + + #FFFFFF + @color/grey_30 + @color/grey_30 + #CCCCCC + @color/grey_70 + @color/grey_80 + #2D2D2D + @color/grey_70 + @color/grey_70 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_60 + @color/grey_0 + @color/grey_0 + @color/grey_30 + #FFFFFF + @color/grey_30 + @color/grey_80 + #FFFFFF + + + @color/grey_80 + @color/grey_30 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + @color/grey_80 + + + @color/grey_70 + @color/grey_60 + @color/grey_70 + @color/grey_60 + + + @color/grey_70 + @color/grey_70 + + + #FFFFFF + @color/grey_30 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_60 + @color/grey_0 + #FFFFFF + + + #121212 + @color/grey_0 + @color/grey_80 + @color/grey_80 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d72d9d458f83..0ae14280757c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -80,4 +80,95 @@ @android:color/white #666666 #A5A5A5 + + + #191919 + @color/primary + #191919 + #191919 + @color/grey_30 + @android:color/white + #FFFFFF + @color/grey_0 + #CCCCCC + #77c4ff + #B3FFFFFF + @color/grey_10 + + + #101010 + #F2F2F2 + #E5E5E5 + #B2B2B2 + #666666 + #4C4C4C + #333333 + + + @color/design_snackbar_background_color + @color/white + + + #FFFFFF + #191919 + + + @color/grey_0 + #191919 + @color/primary + #191919 + @color/primary + @color/grey_30 + @color/white + #191919 + + + #FFFFFF + #191919 + #191919 + + + #FFFFFF + #191919 + #FFFFFF + + + @color/primary + #F399C7 + @color/grey_0 + @color/grey_0 + #FFFFFF + @color/grey_30 + @color/grey_0 + @color/grey_0 + + + @color/primary + @color/grey_30 + @color/grey_30 + #CCCCCC + + + #191919 + @color/grey_30 + #191919 + #191919 + #191919 + #191919 + @color/grey_30 + #191919 + #000000 + #191919 + #F6E5EB + #C16F81 + #0D39DF + #0099ff + + + @color/grey_0 + #191919 + @color/grey_0 + @color/grey_30 + #77b6bb + #5077b6bb diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..cc9e25255a10 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,31 @@ + + + 4dp + 16dp + 24dp + 6dp + 18sp + 15sp + 15dp + 56dp + 86dp + 80dp + 11sp + 30dp + 55dp + 258dp + 17sp + 20dp + 160dp + 50dp + 150dp + 55dp + 48dp + 48dp + 24dp + 26dp + 20sp + 145dp + 1dp + 13sp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d552f8c69bc1..af48688a1fd8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1080,6 +1080,19 @@ Multiple images Cannot create local file Invalid filename for local file + + Replace + Replace all + Keep both + Keep both for all + More details + Cancel and keep existing + File conflict + %d File conflicts + %1$s already exists in this location. Do you want to replace it with the + file you are moving? + The files already exist in this location. Do you want to replace them with the files you are moving? + Groupfolders +%1$d Unable to open password-protected PDF. Please use an external PDF viewer. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 400b7bb7d326..062055de17e9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -468,6 +468,19 @@ ?android:attr/colorBackground + + + + + + +