uploads = new ArrayList<>();
@@ -630,17 +476,6 @@ public class UploadsStorageManager extends Observable {
return upload;
}
- public OCUpload[] getCurrentAndPendingUploadsForCurrentAccount() {
- User user = currentAccountProvider.getUser();
-
- return getCurrentAndPendingUploadsForAccount(user.getAccountName());
- }
-
- public OCUpload[] getCurrentAndPendingUploadsForAccount(final @NonNull String accountName) {
- String inProgressUploadsSelection = getInProgressAndDelayedUploadsSelection();
- return getUploads(inProgressUploadsSelection, accountName);
- }
-
public long[] getCurrentUploadIds(final @NonNull String accountName) {
final var result = uploadDao.getAllIds(UploadStatus.UPLOAD_IN_PROGRESS.value, accountName);
return result.stream()
@@ -648,58 +483,10 @@ public class UploadsStorageManager extends Observable {
.toArray();
}
- /**
- * Get all failed uploads.
- */
- public OCUpload[] getFailedUploads() {
- return getUploads("(" + ProviderTableMeta.UPLOADS_STATUS + IS_EQUAL +
- OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
- EQUAL + UploadResult.DELAYED_FOR_WIFI.getValue() +
- OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
- EQUAL + UploadResult.LOCK_FAILED.getValue() +
- OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
- EQUAL + UploadResult.DELAYED_FOR_CHARGING.getValue() +
- OR + ProviderTableMeta.UPLOADS_LAST_RESULT +
- EQUAL + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
- " ) AND " + ProviderTableMeta.UPLOADS_LAST_RESULT +
- "!= " + UploadResult.VIRUS_DETECTED.getValue()
- , String.valueOf(UploadStatus.UPLOAD_FAILED.value));
- }
-
public OCUpload[] getUploadsForAccount(final @NonNull String accountName) {
return getUploads(ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, accountName);
}
- public OCUpload[] getFinishedUploadsForCurrentAccount() {
- User user = currentAccountProvider.getUser();
-
- return getUploads(ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
- ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, user.getAccountName());
- }
-
- public OCUpload[] getCancelledUploadsForCurrentAccount() {
- User user = currentAccountProvider.getUser();
-
- return getUploads(ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_CANCELLED.value + AND +
- ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, user.getAccountName());
- }
-
- public OCUpload[] getFailedButNotDelayedUploadsForCurrentAccount() {
- User user = currentAccountProvider.getUser();
-
- return getUploads(ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_FAILED.value +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- ANGLE_BRACKETS + UploadResult.DELAYED_FOR_WIFI.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- ANGLE_BRACKETS + UploadResult.LOCK_FAILED.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- ANGLE_BRACKETS + UploadResult.DELAYED_FOR_CHARGING.getValue() +
- AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
- ANGLE_BRACKETS + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
- AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL,
- user.getAccountName());
- }
-
private ContentResolver getDB() {
return contentResolver;
}
@@ -797,7 +584,7 @@ public class UploadsStorageManager extends Observable {
upload.getRemotePath(),
localPath
);
- } else {
+ } else if (uploadResult.getCode() != RemoteOperationResult.ResultCode.USER_CANCELLED){
updateUploadStatus(
upload.getOCUploadId(),
UploadStatus.UPLOAD_FAILED,
diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
index 196f42c..46905d6 100644
--- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
+++ b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java
@@ -23,7 +23,7 @@ import java.util.List;
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
- public static final int DB_VERSION = 93;
+ public static final int DB_VERSION = 96;
private ProviderMeta() {
// No instance
@@ -52,6 +52,7 @@ public class ProviderMeta {
public static final String EXTERNAL_LINKS_TABLE_NAME = "external_links";
public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data";
public static final String VIRTUAL_TABLE_NAME = "virtual";
+ public static final String ASSISTANT_TABLE_NAME = "assistant";
public static final String FILESYSTEM_TABLE_NAME = "filesystem";
public static final String EDITORS_TABLE_NAME = "editors";
public static final String CREATORS_TABLE_NAME = "creators";
@@ -285,10 +286,12 @@ public class ProviderMeta {
public static final String CAPABILITIES_FORBIDDEN_FILENAMES = "forbidden_filenames";
public static final String CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS = "forbidden_filename_extensions";
public static final String CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_BASE_NAMES = "forbidden_filename_basenames";
+ public static final String CAPABILITIES_WINDOWS_COMPATIBLE_FILENAMES = "windows_compatible_filenames";
public static final String CAPABILITIES_FILES_DOWNLOAD_LIMIT = "files_download_limit";
public static final String CAPABILITIES_FILES_DOWNLOAD_LIMIT_DEFAULT = "files_download_limit_default";
public static final String CAPABILITIES_NOTES_FOLDER_PATH = "notes_folder_path";
public static final String CAPABILITIES_DEFAULT_PERMISSIONS = "default_permissions";
+ public static final String CAPABILITIES_HAS_VALID_SUBSCRIPTION = "has_valid_subscription";
//Columns of Uploads table
public static final String UPLOADS_LOCAL_PATH = "local_path";
diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
index 3b9199e..6665598 100644
--- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
+++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
@@ -434,7 +434,7 @@ public class FileMenuFilter {
private boolean anyFileUploading() {
for (OCFile file : files) {
- if (FileUploadHelper.Companion.instance().isUploading(user, file)) {
+ if (FileUploadHelper.Companion.instance().isUploading(file.getRemotePath(), user.getAccountName())) {
return true;
}
}
diff --git a/app/src/main/java/com/owncloud/android/files/services/NameCollisionPolicy.java b/app/src/main/java/com/owncloud/android/files/services/NameCollisionPolicy.java
index 41ab166..071a1c0 100644
--- a/app/src/main/java/com/owncloud/android/files/services/NameCollisionPolicy.java
+++ b/app/src/main/java/com/owncloud/android/files/services/NameCollisionPolicy.java
@@ -7,12 +7,24 @@
package com.owncloud.android.files.services;
/**
- * Ordinal of enumerated constants is important for old data compatibility.
+ * Defines how to handle file name collisions during uploads.
+ *
+ * Important: Enum ordinals are stored directly in the database.
+ * Do not change their order or remove constants to avoid breaking
+ * compatibility with old data.
+ *
+ * Database value mapping:
+ *
+ * 0 → {@link #RENAME} (old forceOverwrite = false)
+ * 1 → {@link #OVERWRITE} (old forceOverwrite = true)
+ * 2 → {@link #SKIP}
+ * 3 → {@link #ASK_USER}
+ *
*/
public enum NameCollisionPolicy {
- RENAME, // Ordinal corresponds to old forceOverwrite = false (0 in database)
- OVERWRITE, // Ordinal corresponds to old forceOverwrite = true (1 in database)
- CANCEL,
+ RENAME,
+ OVERWRITE,
+ SKIP,
ASK_USER;
public static final NameCollisionPolicy DEFAULT = RENAME;
diff --git a/app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
index 928f437..d7b50d7 100644
--- a/app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
@@ -38,7 +38,9 @@ import com.owncloud.android.utils.FileExportUtils;
import com.owncloud.android.utils.FileStorageUtils;
import java.io.File;
+import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.nio.file.Files;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@@ -115,7 +117,11 @@ public class DownloadFileOperation extends RemoteOperation {
if (file.getStoragePath() != null) {
File parentFile = new File(file.getStoragePath()).getParentFile();
if (parentFile != null && !parentFile.exists()) {
- parentFile.mkdirs();
+ try {
+ Files.createDirectories(parentFile.toPath());
+ } catch (IOException e) {
+ return FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), file);
+ }
}
File path = new File(file.getStoragePath()); // re-downloads should be done over the original file
if (path.canWrite() || parentFile != null && parentFile.canWrite()) {
diff --git a/app/src/main/java/com/owncloud/android/operations/RenameFileOperation.java b/app/src/main/java/com/owncloud/android/operations/RenameFileOperation.java
index 3f1c3a4..93e60b1 100644
--- a/app/src/main/java/com/owncloud/android/operations/RenameFileOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/RenameFileOperation.java
@@ -25,6 +25,9 @@ import com.owncloud.android.utils.MimeTypeUtil;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
/**
* Remote operation performing the rename of a remote file (or folder?) in the ownCloud server.
@@ -161,26 +164,32 @@ public class RenameFileOperation extends SyncOperation {
}
// create a test file
String tmpFolderName = FileStorageUtils.getTemporalPath("");
- File testFile = new File(tmpFolderName + newName);
- File tmpFolder = testFile.getParentFile();
- if (!tmpFolder.exists() && !tmpFolder.mkdirs()) {
- Log_OC.e(TAG, "Unable to create parent folder " + tmpFolder.getAbsolutePath());
+ Path testFile = Paths.get(tmpFolderName, newName);
+ Path tmpFolder = testFile.getParent();
+ if (tmpFolder != null && !Files.exists(tmpFolder)) {
+ try {
+ Files.createDirectories(tmpFolder);
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Unable to create parent folder " + tmpFolder.toAbsolutePath());
+ }
}
- if (!tmpFolder.isDirectory()) {
+ if (tmpFolder != null && !Files.isDirectory(tmpFolder)) {
throw new IOException("Unexpected error: temporal directory could not be created");
}
try {
- testFile.createNewFile(); // return value is ignored; it could be 'false' because
- // the file already existed, that doesn't invalidate the name
- } catch (IOException e) {
+ Files.createFile(testFile);
+ } catch (Exception e) {
Log_OC.i(TAG, "Test for validity of name " + newName + " in the file system failed");
return false;
}
- boolean result = testFile.exists() && testFile.isFile();
+ boolean result = Files.exists(testFile) && Files.isRegularFile(testFile);
- // cleaning ; result is ignored, since there is not much we could do in case of failure,
- // but repeat and repeat...
- testFile.delete();
+ try {
+ Files.deleteIfExists(testFile);
+ } catch (Exception e) {
+ Log_OC.e("Error deleting file: ", e.getMessage());
+ return true;
+ }
return result;
}
diff --git a/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
index 20a75f5..2b58296 100644
--- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
@@ -28,7 +28,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.ReadFolderRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
-import com.owncloud.android.lib.resources.status.E2EVersion;
import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.utils.FileStorageUtils;
@@ -485,7 +484,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
Log_OC.d(TAG, "Exception caught at startDirectDownloads" + e);
}
} else {
- mFilesForDirectDownload.forEach(file -> fileDownloadHelper.downloadFile(user, file));
+ fileDownloadHelper.downloadFolder(mLocalFolder, user.getAccountName());
}
}
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 29db48f..ddeccdc 100644
--- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
@@ -70,8 +70,6 @@ import com.owncloud.android.utils.theme.CapabilityUtils;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.RequestEntity;
-import org.lukhnos.nnio.file.Files;
-import org.lukhnos.nnio.file.Paths;
import java.io.File;
import java.io.FileInputStream;
@@ -84,6 +82,8 @@ import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -244,7 +244,7 @@ public class UploadFileOperation extends SyncOperation {
"Illegal file in UploadFileOperation; storage path invalid: "
+ upload.getLocalPath());
}
-
+ Log_OC.d(TAG, "creating upload file operation, user: " + user.getAccountName() + " upload account name " + upload.getAccountName());
this.uploadsStorageManager = uploadsStorageManager;
this.connectivityService = connectivityService;
this.powerManagementService = powerManagementService;
@@ -1183,11 +1183,13 @@ public class UploadFileOperation extends SyncOperation {
throws OperationCancelledException {
Log_OC.d(TAG, "Checking name collision in server");
- if (existsFile(client, mRemotePath, fileNames, encrypted)) {
+ boolean isFileExists = existsFile(client, mRemotePath, fileNames, encrypted);
+
+ if (isFileExists) {
switch (mNameCollisionPolicy) {
- case CANCEL:
- Log_OC.d(TAG, "File exists; canceling");
- throw new OperationCancelledException();
+ case SKIP:
+ Log_OC.d(TAG, "user choose to skip upload if same file exists");
+ return new RemoteOperationResult<>(ResultCode.OK);
case RENAME:
mRemotePath = getNewAvailableRemotePath(client, mRemotePath, fileNames, encrypted);
mWasRenamed = true;
@@ -1244,7 +1246,11 @@ public class UploadFileOperation extends SyncOperation {
OwnCloudClient client) {
switch (mLocalBehaviour) {
case FileUploadWorker.LOCAL_BEHAVIOUR_DELETE:
- originalFile.delete();
+ try {
+ Files.delete(originalFile.toPath());
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Could not delete original file: " + originalFile.getAbsolutePath(), e);
+ }
mFile.setStoragePath("");
getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
saveUploadedFile(client);
@@ -1430,21 +1436,37 @@ public class UploadFileOperation extends SyncOperation {
}
/**
- * Allows to cancel the actual upload operation. If actual upload operating is in progress it is cancelled, if
- * upload preparation is being performed upload will not take place.
+ * Cancels the current upload process.
+ *
+ *
+ * Behavior depends on the current state of the upload:
+ *
+ * Upload in preparation: Upload will not start and a cancellation flag is set.
+ * Upload in progress: The ongoing upload operation is cancelled via
+ * {@link UploadFileRemoteOperation#cancel(ResultCode)}.
+ * No upload operation: A cancellation flag is still set, but this situation is unexpected
+ * and logged as an error.
+ *
+ *
+ *
+ * Once cancelled, the database will be updated through
+ * {@link UploadsStorageManager#updateDatabaseUploadResult(RemoteOperationResult, UploadFileOperation)}.
+ *
+ * @param cancellationReason the reason for cancellation
*/
public void cancel(ResultCode cancellationReason) {
- if (mUploadOperation == null) {
- if (mUploadStarted.get()) {
- Log_OC.d(TAG, "Cancelling upload during upload preparations.");
- mCancellationRequested.set(true);
- } else {
- mCancellationRequested.set(true);
- Log_OC.e(TAG, "No upload in progress. This should not happen.");
- }
- } else {
+ if (mUploadOperation != null) {
+ // Cancel an active upload
Log_OC.d(TAG, "Cancelling upload during actual upload operation.");
mUploadOperation.cancel(cancellationReason);
+ } else {
+ // Cancel while preparing or when no upload exists
+ mCancellationRequested.set(true);
+ if (mUploadStarted.get()) {
+ Log_OC.d(TAG, "Cancelling upload during preparation.");
+ } else {
+ Log_OC.e(TAG, "No upload in progress. This should not happen.");
+ }
}
}
@@ -1548,18 +1570,18 @@ public class UploadFileOperation extends SyncOperation {
if (!targetFile.equals(sourceFile)) {
File expectedFolder = targetFile.getParentFile();
- expectedFolder.mkdirs();
+ Files.createDirectories(expectedFolder.toPath());
if (expectedFolder.isDirectory()) {
if (!sourceFile.renameTo(targetFile)) {
// try to copy and then delete
- targetFile.createNewFile();
+ Files.createFile(targetFile.toPath());
try (
FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
FileChannel outChannel = new FileOutputStream(targetFile).getChannel()
) {
inChannel.transferTo(0, inChannel.size(), outChannel);
- sourceFile.delete();
+ Files.delete(sourceFile.toPath());
} catch (Exception e) {
mFile.setStoragePath(""); // forget the local file
// by now, treat this as a success; the file was uploaded
@@ -1652,5 +1674,4 @@ public class UploadFileOperation extends SyncOperation {
void onRenameUpload();
}
-
}
diff --git a/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java b/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
index 246672f..2ec6a71 100644
--- a/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
+++ b/app/src/main/java/com/owncloud/android/providers/DiskLruImageCacheFileProvider.java
@@ -29,6 +29,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.nio.file.Files;
import javax.inject.Inject;
@@ -78,7 +79,7 @@ public class DiskLruImageCacheFileProvider extends ContentProvider {
// create a file to write bitmap data
File f = new File(MainApp.getAppContext().getCacheDir(), ocFile.getFileName());
try {
- f.createNewFile();
+ Files.createFile(f.toPath());
//Convert bitmap to byte array
ByteArrayOutputStream bos = new ByteArrayOutputStream();
diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt
index 91367c7..ab22b2c 100644
--- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt
+++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt
@@ -138,30 +138,34 @@ class AvatarGroupLayout @JvmOverloads constructor(
avatar: ImageView,
viewThemeUtils: ViewThemeUtils
) {
- // maybe federated share
- val split = user.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- val userId: String? = split[0]
- val server = split[1]
+ val split = user.split("@")
+ val userId = split.getOrNull(0) ?: user
+ val server = split.getOrNull(1)
- val url = "https://" + server + "/index.php/avatar/" + userId + "/" +
- resources.getInteger(R.integer.file_avatar_px)
- var placeholder: Drawable?
- try {
- placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius)
+ val url = if (server != null) {
+ "https://$server/index.php/avatar/$userId/${resources.getInteger(R.integer.file_avatar_px)}"
+ } else {
+ // fallback: no federated server, maybe use local avatar
+ null
+ }
+
+ val placeholder: Drawable = try {
+ TextDrawable.createAvatarByUserId(userId, avatarRadius)
} catch (e: Exception) {
Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e)
- placeholder = viewThemeUtils.platform.colorDrawable(
- ResourcesCompat.getDrawable(
- resources,
- R.drawable.account_circle_white,
- null
- )!!,
+ viewThemeUtils.platform.colorDrawable(
+ ResourcesCompat
+ .getDrawable(resources, R.drawable.account_circle_white, null)!!,
ContextCompat.getColor(context, R.color.black)
)
}
avatar.tag = null
- loadCircularBitmapIntoImageView(context, url, avatar, placeholder)
+ if (url != null) {
+ loadCircularBitmapIntoImageView(context, url, avatar, placeholder)
+ } else {
+ avatar.setImageDrawable(placeholder)
+ }
}
override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) {
diff --git a/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java b/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java
index 7fad98a..2795296 100644
--- a/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java
+++ b/app/src/main/java/com/owncloud/android/ui/ThemeableSwitchPreference.java
@@ -7,6 +7,7 @@
*/
package com.owncloud.android.ui;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
@@ -54,7 +55,7 @@ public class ThemeableSwitchPreference extends SwitchPreference {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
- if (child instanceof Switch switchView) {
+ if (child instanceof @SuppressLint("UseSwitchCompatOrMaterialCode") Switch switchView) {
viewThemeUtils.platform.colorSwitch(switchView);
break;
diff --git a/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
index 6c10373..f61121e 100644
--- a/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
@@ -189,7 +189,7 @@ public class ActivitiesActivity extends DrawerActivity implements ActivityListIn
} else {
showEmptyContent(getString(R.string.server_not_reachable),
getString(R.string.server_not_reachable_content));
- binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_cloud_sync_off);
+ binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_sync_off);
}
});
diff --git a/app/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java b/app/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
index d775bad..8ad1408 100644
--- a/app/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
+++ b/app/src/main/java/com/owncloud/android/ui/activities/data/files/FilesServiceApiImpl.java
@@ -7,6 +7,7 @@
*/
package com.owncloud.android.ui.activities.data.files;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
@@ -33,8 +34,8 @@ public class FilesServiceApiImpl implements FilesServiceApi {
private static final String TAG = FilesServiceApiImpl.class.getSimpleName();
- private UserAccountManager accountManager;
- private ClientFactory clientFactory;
+ private final UserAccountManager accountManager;
+ private final ClientFactory clientFactory;
public FilesServiceApiImpl(UserAccountManager accountManager, ClientFactory clientFactory) {
this.accountManager = accountManager;
@@ -58,7 +59,7 @@ public class FilesServiceApiImpl implements FilesServiceApi {
private OCFile remoteOcFile;
private String errorMessage;
// TODO: Figure out a better way to do this than passing a BaseActivity reference.
- private final BaseActivity baseActivity;
+ @SuppressLint("StaticFieldLeak") private final BaseActivity baseActivity;
private final String fileUrl;
private final User user;
private final UserAccountManager accountManager;
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index 29aa804..2b0d1c0 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -31,6 +31,7 @@ import android.graphics.drawable.PictureDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.Menu;
@@ -115,6 +116,7 @@ import java.util.Optional;
import javax.inject.Inject;
+import androidx.activity.OnBackPressedCallback;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -206,6 +208,12 @@ public abstract class DrawerActivity extends ToolbarActivity
@Inject
ClientFactory clientFactory;
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
+ super.onCreate(savedInstanceState, persistentState);
+ addOnBackPressedCallback();
+ }
+
/**
* Initializes the drawer and its content. This method needs to be called after the content view has been set.
*/
@@ -1153,19 +1161,24 @@ public abstract class DrawerActivity extends ToolbarActivity
}
}
- @Override
- public void onBackPressed() {
- if (isDrawerOpen()) {
- closeDrawer();
- return;
- }
- Fragment fileDetailsSharingProcessFragment =
- getSupportFragmentManager().findFragmentByTag(FileDetailsSharingProcessFragment.TAG);
- if (fileDetailsSharingProcessFragment != null) {
- ((FileDetailsSharingProcessFragment) fileDetailsSharingProcessFragment).onBackPressed();
- } else {
- super.onBackPressed();
- }
+ public void addOnBackPressedCallback() {
+ getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ if (isDrawerOpen()) {
+ closeDrawer();
+ return;
+ }
+
+ final var fragment = getSupportFragmentManager().findFragmentByTag(FileDetailsSharingProcessFragment.TAG);
+ if (fragment instanceof FileDetailsSharingProcessFragment fileDetailsSharingProcessFragment) {
+ fileDetailsSharingProcessFragment.onBackPressed();
+ } else {
+ setEnabled(false);
+ getOnBackPressedDispatcher().onBackPressed();
+ }
+ }
+ });
}
@Override
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java
index 8d94e81..ac63db3 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java
@@ -11,6 +11,7 @@
*/
package com.owncloud.android.ui.activity;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
@@ -194,6 +195,7 @@ public class ErrorsWhileCopyingHandlerActivity extends AppCompatActivity implem
/**
* Asynchronous task performing the move of all the local files to the ownCloud folder.
*/
+ @SuppressLint("StaticFieldLeak")
private class MoveFilesTask extends AsyncTask {
/**
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
index 944b15a..1e17272 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
@@ -129,6 +129,7 @@ public abstract class FileActivity extends DrawerActivity
LoadingVersionNumberTask.VersionDevInterface, FileDetailSharingFragment.OnEditShareListener, NetworkChangeListener {
public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE";
+ public static final String EXTRA_FILE_REMOTE_PATH = "com.owncloud.android.ui.activity.FILE_REMOTE_PATH";
public static final String EXTRA_LIVE_PHOTO_FILE = "com.owncloud.android.ui.activity.LIVE.PHOTO.FILE";
public static final String EXTRA_USER = "com.owncloud.android.ui.activity.USER";
public static final String EXTRA_FROM_NOTIFICATION = "com.owncloud.android.ui.activity.FROM_NOTIFICATION";
@@ -333,6 +334,7 @@ public abstract class FileActivity extends DrawerActivity
*
* @return Main {@link OCFile} handled by the activity.
*/
+ @Nullable
public OCFile getFile() {
return mFile;
}
@@ -653,6 +655,7 @@ public abstract class FileActivity extends DrawerActivity
return fileUploadHelper;
}
+ @Nullable
public OCFile getCurrentDir() {
OCFile file = getFile();
if (file != null) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
index 87ef694..82dc1ea 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt
@@ -40,6 +40,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.view.WindowManager.BadTokenException
+import androidx.activity.OnBackPressedCallback
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
@@ -82,6 +83,7 @@ import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.isActive
import com.nextcloud.utils.extensions.lastFragment
import com.nextcloud.utils.extensions.logFileSize
+import com.nextcloud.utils.extensions.navigateToAllFiles
import com.nextcloud.utils.fileNameValidator.FileNameValidator.checkFolderPath
import com.nextcloud.utils.view.FastScrollUtils
import com.owncloud.android.MainApp
@@ -155,7 +157,6 @@ import com.owncloud.android.utils.PermissionUtil.requestNotificationPermission
import com.owncloud.android.utils.PushUtils
import com.owncloud.android.utils.StringUtils
import com.owncloud.android.utils.theme.CapabilityUtils
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -278,6 +279,7 @@ class FileDisplayActivity :
initSyncBroadcastReceiver()
observeWorkerState()
startMetadataSyncForRoot()
+ handleBackPress()
}
private fun loadSavedInstanceState(savedInstanceState: Bundle?) {
@@ -429,7 +431,8 @@ class FileDisplayActivity :
CapabilityUtils.checkOutdatedWarning(
getResources(),
user.get().server.version,
- capabilities.extendedSupport.isTrue
+ capabilities.extendedSupport.isTrue,
+ capabilities.hasValidSubscription.isTrue
)
) {
DisplayUtils.showServerOutdatedSnackbar(this, Snackbar.LENGTH_LONG)
@@ -549,7 +552,7 @@ class FileDisplayActivity :
when {
ACTION_DETAILS.equals(action, ignoreCase = true) -> {
- val file = intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java)
+ val file = getFileFromIntent(intent)
setFile(file)
showDetails(file)
}
@@ -578,12 +581,12 @@ class FileDisplayActivity :
when (intent.action) {
Intent.ACTION_VIEW -> handleOpenFileViaIntent(intent)
OPEN_FILE -> {
- supportFragmentManager.executePendingTransactions()
onOpenFileIntent(intent)
}
}
}
+ @SuppressLint("UnsafeIntentLaunch")
private fun handleRestartIntent(intent: Intent) {
if (intent.action != RESTART) {
return
@@ -645,18 +648,41 @@ class FileDisplayActivity :
// endregion
private fun onOpenFileIntent(intent: Intent) {
- val extra = intent.getStringExtra(EXTRA_FILE)
- val file = storageManager.getFileByDecryptedRemotePath(extra)
- if (file != null) {
- val fileFragment: OCFileListFragment?
- val leftFragment = this.leftFragment
- if (leftFragment is OCFileListFragment) {
- fileFragment = leftFragment
- } else {
- fileFragment = OCFileListFragment()
- this.leftFragment = fileFragment
+ val file = getFileFromIntent(intent)
+ if (file == null) {
+ Log_OC.e(TAG, "Can't open file intent, file is null")
+ return
+ }
+
+ val currentFragment = leftFragment
+
+ if (currentFragment == null) {
+ Log_OC.e(TAG, "Can't open file intent, left fragment is null")
+ return
+ }
+
+ val fileListFragment: OCFileListFragment = when {
+ currentFragment is OCFileListFragment && currentFragment !is GalleryFragment -> {
+ currentFragment
}
- fileFragment.onItemClicked(file)
+
+ else -> {
+ Log_OC.w(
+ TAG,
+ "Left fragment is not a valid OCFileListFragment " +
+ "(was ${currentFragment::class.simpleName}). " +
+ "Replacing with OCFileListFragment."
+ )
+ val newFragment = OCFileListFragment()
+ setLeftFragment(newFragment, false)
+ setupHomeSearchToolbarWithSortAndListButtons()
+ newFragment
+ }
+ }
+
+ // Post to main thread to ensure fragment is fully attached before interacting
+ Handler(Looper.getMainLooper()).post {
+ fileListFragment.onItemClicked(file)
}
}
@@ -935,7 +961,7 @@ class FileDisplayActivity :
) {
openDrawer()
} else {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
} else if (itemId == R.id.action_select_all) {
val fragment = this.listOfFilesFragment
@@ -1046,7 +1072,7 @@ class FileDisplayActivity :
localBasePath = "$localBasePath/"
}
- val remotePathBase = getCurrentDir().remotePath
+ val remotePathBase = getCurrentDir()?.remotePath
val decryptedRemotePaths = getRemotePaths(remotePathBase, filePaths, localBasePath)
val behaviour = when (resultCode) {
@@ -1057,8 +1083,8 @@ class FileDisplayActivity :
connectivityService.isNetworkAndServerAvailable { result: Boolean? ->
if (result == true) {
- val isValidFolderPath = checkFolderPath(remotePathBase, capabilities, this)
- if (!isValidFolderPath) {
+ val isValidFolderPath = remotePathBase?.let { checkFolderPath(it, capabilities, this) }
+ if (isValidFolderPath == false) {
DisplayUtils.showSnackMessage(
this,
R.string.file_name_validator_error_contains_reserved_names_or_invalid_characters
@@ -1136,40 +1162,59 @@ class FileDisplayActivity :
}
}
- private val isRootDirectory: Boolean
- get() {
- val currentDir = getCurrentDir()
- return (currentDir == null || currentDir.parentId == FileDataStorageManager.ROOT_PARENT_ID.toLong())
- }
-
- /*
- * BackPressed priority/hierarchy:
- * 1. close search view if opened
- * 2. close drawer if opened
- * 3. if it is OCFileListFragment and it's in Root -> (finish Activity) or it's not Root -> (browse up)
- * 4. otherwise pop up the fragment and sortGroup view visibility and call super.onBackPressed()
+ /**
+ * Sets up a custom back-press handler for this activity.
+ *
+ * This callback determines how the back button behaves based on the current UI state:
+ * - If the search view is open, it closes it.
+ * - If the navigation drawer is open, it closes it.
+ * - If the left fragment is an [OCFileListFragment]:
+ * - If in the root directory, it either navigates to "All Files" or finishes the activity.
+ * - Otherwise, it navigates one level up.
+ * - Otherwise, it pops the current fragment from the back stack.
+ *
+ * ### About `isEnabled`
+ * `isEnabled` is a property of [OnBackPressedCallback].
+ * When `isEnabled = false`, this callback is **temporarily disabled**,
+ * allowing the system or other callbacks to handle the back press instead.
*/
- @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING") // TODO Apply fail fast principle
- override fun onBackPressed() {
- if (isSearchOpen()) {
- resetSearchAction()
- return
- }
+ private fun handleBackPress() {
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ when {
+ isSearchOpen() -> {
+ isEnabled = false
+ resetSearchAction()
+ }
- if (isDrawerOpen) {
- super.onBackPressed()
- return
- }
+ isDrawerOpen -> {
+ isEnabled = false
+ onBackPressedDispatcher.onBackPressed()
+ }
- if (this.leftFragment is OCFileListFragment) {
- if (isRoot(getCurrentDir())) {
- finish()
- } else {
- browseUp(leftFragment as OCFileListFragment)
+ leftFragment is OCFileListFragment -> {
+ val fragment = leftFragment as OCFileListFragment
+ if (isRoot(getCurrentDir())) {
+ if (fragment.shouldNavigateBackToAllFiles()) {
+ navigateToAllFiles()
+ } else {
+ finish()
+ }
+ } else {
+ browseUp(fragment)
+ }
+ }
+
+ else -> {
+ isEnabled = false
+ popBack()
+ }
+ }
+ }
}
- } else {
- popBack()
- }
+ )
}
private fun browseUp(listOfFiles: OCFileListFragment) {
@@ -1177,8 +1222,12 @@ class FileDisplayActivity :
val currentFile = listOfFiles.currentFile
file = currentFile
- listOfFiles.setFabVisible(currentFile.canCreateFileAndFolder())
- listOfFiles.registerFabListener()
+
+ currentFile?.let {
+ listOfFiles.setFabVisible(currentFile.canCreateFileAndFolder())
+ listOfFiles.registerFabListener()
+ }
+
resetTitleBarAndScrolling()
configureToolbar()
startMetadataSyncForCurrentDir()
@@ -1196,9 +1245,10 @@ class FileDisplayActivity :
if (isRoot(getCurrentDir()) && leftFragment is OCFileListFragment) {
// Remove the list to the original state
-
- val listOfHiddenFiles = leftFragment.adapter.listOfHiddenFiles
- leftFragment.performSearch("", listOfHiddenFiles, true)
+ leftFragment.adapter?.let { adapter ->
+ val listOfHiddenFiles = adapter.listOfHiddenFiles
+ leftFragment.performSearch("", listOfHiddenFiles, true)
+ }
hideSearchView(getCurrentDir())
setDrawerIndicatorEnabled(isDrawerIndicatorAvailable)
@@ -1206,20 +1256,20 @@ class FileDisplayActivity :
if (leftFragment is UnifiedSearchFragment) {
showSortListGroup(false)
- super.onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
}
/**
* Use this method when want to pop the fragment on back press. It resets Scrolling (See
* [with true][.resetScrolling] and pop the visibility for sortListGroup (See
- * [with false][.showSortListGroup]. At last call to super.onBackPressed()
+ * [with false][.showSortListGroup]. At last call to onBackPressedDispatcher.onBackPressed()
*/
private fun popBack() {
binding.fabMain.setImageResource(R.drawable.ic_plus)
resetScrolling(true)
showSortListGroup(false)
- super.onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -1263,13 +1313,11 @@ class FileDisplayActivity :
val ocFileListFragment = leftFragment
syncAndUpdateFolder(ignoreETag = true, ignoreFocus = true)
- var startFile: OCFile? = null
- if (intent != null) {
- val fileArgs = intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java)
- if (fileArgs != null) {
- startFile = fileArgs
- file = startFile
- }
+ // Try to get the OCFile from the intent, if one was provided when launching this activity.
+ // 'file' comes from the FileActivity base class and represents the currently opened file or folder.
+ // We update it only when a valid file is found in the intent.
+ val startFile = intent?.let { getFileFromIntent(it) }?.also {
+ file = it
}
// refresh list of files
@@ -1310,6 +1358,11 @@ class FileDisplayActivity :
}, ON_RESUMED_RESET_DELAY)
}
+ private fun getFileFromIntent(intent: Intent?): OCFile? =
+ intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java)
+ ?: intent?.getStringExtra(EXTRA_FILE_REMOTE_PATH)
+ ?.let { fileDataStorageManager.getFileByDecryptedRemotePath(it) }
+
private fun checkAndSetMenuItemId() {
if (MainApp.isOnlyPersonFiles()) {
menuItemId = R.id.nav_personal_files
@@ -1442,9 +1495,8 @@ class FileDisplayActivity :
return
}
- var currentFile = if (file == null) null else storageManager.getFileByPath(file.remotePath)
- val currentDir =
- if (getCurrentDir() == null) null else storageManager.getFileByPath(getCurrentDir().remotePath)
+ var currentFile = file?.remotePath?.let { storageManager.getFileByPath(it) }
+ val currentDir = getCurrentDir()?.remotePath?.let { storageManager.getFileByPath(it) }
val isSyncFolderRemotePathRoot = OCFile.ROOT_PATH == syncFolderRemotePath
if (currentDir == null && !isSyncFolderRemotePathRoot) {
@@ -1468,7 +1520,7 @@ class FileDisplayActivity :
}
private fun handleRemovedFileFromServer(currentFile: OCFile?, currentDir: OCFile?): OCFile? {
- if (currentFile == null && !file.isFolder) {
+ if (currentFile == null && file?.isFolder == false) {
resetTitleBarAndScrolling()
return currentDir
}
@@ -1617,8 +1669,8 @@ class FileDisplayActivity :
var sameFile = false
if (file != null) {
renamedInUpload =
- file.remotePath == intent.getStringExtra(FileUploadWorker.EXTRA_OLD_REMOTE_PATH)
- sameFile = file.remotePath == uploadedRemotePath || renamedInUpload
+ file?.remotePath == intent.getStringExtra(FileUploadWorker.EXTRA_OLD_REMOTE_PATH)
+ sameFile = file?.remotePath == uploadedRemotePath || renamedInUpload
}
if (sameAccount && sameFile && this@FileDisplayActivity.leftFragment is FileDetailFragment) {
@@ -1638,21 +1690,21 @@ class FileDisplayActivity :
)
}
- if (uploadWasFine || file != null && file.fileExists()) {
+ if (uploadWasFine || file?.fileExists() == true) {
fileDetailFragment.updateFileDetails(false, true)
} else {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
// Force the preview if the file is an image or text file
if (uploadWasFine) {
- val ocFile = file
- if (PreviewImageFragment.canBePreviewed(ocFile)) {
- startImagePreview(file, true)
- } else if (PreviewTextFileFragment.canBePreviewed(ocFile)) {
- startTextPreview(ocFile, true)
+ file?.let {
+ if (PreviewImageFragment.canBePreviewed(it)) {
+ startImagePreview(it, true)
+ } else if (PreviewTextFileFragment.canBePreviewed(it)) {
+ startTextPreview(it, true)
+ }
}
- // TODO what about other kind of previews?
}
}
}
@@ -1910,7 +1962,7 @@ class FileDisplayActivity :
if (getCurrentDir() !=
null
) {
- storageManager.getFileByDecryptedRemotePath(getCurrentDir().remotePath)
+ storageManager.getFileByDecryptedRemotePath(getCurrentDir()?.remotePath)
} else {
null
}
@@ -2080,17 +2132,17 @@ class FileDisplayActivity :
val file = getFile()
// delete old local copy
- if (file.isDown) {
+ if (file?.isDown == true) {
val list: MutableList = ArrayList()
list.add(file)
fileOperationsHelper.removeFiles(list, true, true)
// download new version, only if file was previously download
- showSyncLoadingDialog(file.isFolder)
+ showSyncLoadingDialog(file.isFolder == true)
fileOperationsHelper.syncFile(file)
}
- val parent = storageManager.getFileById(file.parentId)
+ val parent = file?.let { storageManager.getFileById(it.parentId) }
startSyncFolderOperation(parent, ignoreETag = true, ignoreFocus = true)
val leftFragment = this.leftFragment
@@ -2654,11 +2706,7 @@ class FileDisplayActivity :
return
}
- val folder = listOfFiles.currentFile
- if (folder == null) {
- return
- }
-
+ val folder = listOfFiles.currentFile ?: return
startSyncFolderOperation(folder, ignoreETag, ignoreFocus)
}
@@ -2927,7 +2975,18 @@ class FileDisplayActivity :
}
fun performUnifiedSearch(query: String, listOfHiddenFiles: ArrayList?) {
- val unifiedSearchFragment = UnifiedSearchFragment.Companion.newInstance(query, listOfHiddenFiles)
+ val path = currentDir?.decryptedRemotePath
+ ?: run {
+ Log_OC.w(TAG, "currentDir is null, using ROOT_PATH")
+ OCFile.ROOT_PATH
+ }
+
+ val unifiedSearchFragment =
+ UnifiedSearchFragment.Companion.newInstance(
+ query,
+ listOfHiddenFiles,
+ path
+ )
setLeftFragment(unifiedSearchFragment, false)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
index 85ead7b..7599a5b 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt
@@ -21,7 +21,9 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.activity.OnBackPressedCallback
+import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.utils.fileNameValidator.FileNameValidator
import com.owncloud.android.R
@@ -48,6 +50,8 @@ import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.ErrorMessageAdapter
import com.owncloud.android.utils.FileSortOrder
import com.owncloud.android.utils.PathUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject
@@ -60,7 +64,6 @@ open class FolderPickerActivity :
OnSortingOrderListener {
private var mSyncBroadcastReceiver: SyncBroadcastReceiver? = null
- private var mSyncInProgress = false
private var mSearchOnlyFolders = false
var isDoNotEnterEncryptedFolder = false
private set
@@ -105,8 +108,7 @@ open class FolderPickerActivity :
}
updateActionBarTitleAndHomeButtonByString(captionText)
- setBackgroundText()
- handleOnBackPressed()
+ handleBackPress()
}
override fun onDestroy() {
@@ -151,7 +153,7 @@ open class FolderPickerActivity :
}
}
- private fun handleOnBackPressed() {
+ private fun handleBackPress() {
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
@@ -211,25 +213,6 @@ open class FolderPickerActivity :
transaction.commit()
}
- /**
- * Show a text message on screen view for notifying user if content is loading or folder is empty
- */
- private fun setBackgroundText() {
- val listFragment = listOfFilesFragment
-
- if (listFragment == null) {
- Log_OC.e(TAG, "OCFileListFragment is null")
- }
-
- listFragment?.let {
- if (mSyncInProgress) {
- it.setEmptyListMessage(EmptyListState.LOADING)
- } else {
- it.setEmptyListMessage(EmptyListState.ADD_FOLDER)
- }
- }
- }
-
protected val listOfFilesFragment: OCFileListFragment?
get() {
val listOfFiles = supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FOLDERS)
@@ -259,21 +242,33 @@ open class FolderPickerActivity :
}
private fun startSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean) {
- val currentSyncTime = System.currentTimeMillis()
- mSyncInProgress = true
-
- RefreshFolderOperation(
- folder,
- currentSyncTime,
- false,
- ignoreETag,
- storageManager,
- user.orElseThrow { RuntimeException("User not set") },
- applicationContext
- ).also {
- it.execute(account, this, null, null)
+ val optionalUser = user ?: return
+ if (optionalUser.isEmpty) {
+ return
+ }
+ val user: User = optionalUser.get()
+ listOfFilesFragment?.setEmptyListMessage(EmptyListState.LOADING)
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ val currentSyncTime = System.currentTimeMillis()
+ val operation = RefreshFolderOperation(
+ folder,
+ currentSyncTime,
+ false,
+ ignoreETag,
+ storageManager,
+ user,
+ applicationContext
+ )
+ operation.execute(
+ account,
+ this@FolderPickerActivity,
+ { _, _ ->
+ listOfFilesFragment?.setEmptyListMessage(EmptyListState.LOCAL_FILE_LIST_EMPTY_FILE)
+ },
+ null
+ )
}
- setBackgroundText()
}
override fun onResume() {
@@ -327,7 +322,7 @@ open class FolderPickerActivity :
} else if (itemId == android.R.id.home) {
val currentDir = currentFolder
if (currentDir != null && currentDir.parentId != 0L) {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
} else {
retval = super.onOptionsItemSelected(item)
@@ -414,10 +409,10 @@ open class FolderPickerActivity :
file?.isFolder != true -> true
// all of the target files are already in the selected directory
- targetFilePaths?.all { PathUtils.isDirectParent(file.remotePath, it) } == true -> false
+ targetFilePaths?.all { PathUtils.isDirectParent(file?.remotePath ?: "", it) } == true -> false
// some of the target files are parents of the selected folder
- targetFilePaths?.any { PathUtils.isAncestor(it, file.remotePath) } == true -> false
+ targetFilePaths?.any { PathUtils.isAncestor(it, file?.remotePath ?: "") } == true -> false
else -> true
}
@@ -434,7 +429,7 @@ open class FolderPickerActivity :
}
private fun getSelectedFolderPathTitle(): String? {
- val atRoot = (currentDir == null || currentDir.parentId == 0L)
+ val atRoot = (currentDir == null || currentDir?.parentId == 0L)
return if (atRoot) captionText ?: "" else currentDir?.fileName
}
@@ -554,15 +549,13 @@ open class FolderPickerActivity :
return
}
- if (FileSyncAdapter.EVENT_FULL_SYNC_START == event) {
- mSyncInProgress = true
- } else {
+ if (FileSyncAdapter.EVENT_FULL_SYNC_START != event) {
var (currentFile, currentDir) = getCurrentFileAndDirectory()
if (currentDir == null) {
browseRootForRemovedFolder()
} else {
- if (currentFile == null && !file.isFolder) {
+ if (currentFile == null && file?.isFolder == false) {
// currently selected file was removed in the server, and now we know it
currentFile = currentDir
}
@@ -572,28 +565,22 @@ open class FolderPickerActivity :
file = currentFile
}
- mSyncInProgress = (
- FileSyncAdapter.EVENT_FULL_SYNC_END != event &&
- RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED != event
- )
-
checkCredentials(syncResult, event)
}
DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT))
- Log_OC.d(TAG, "Setting progress visibility to $mSyncInProgress")
- setBackgroundText()
} catch (e: RuntimeException) {
Log_OC.e(TAG, "Error on broadcast receiver", e)
// avoid app crashes after changing the serial id of RemoteOperationResult
// in owncloud library with broadcast notifications pending to process
DataHolderUtil.getInstance().delete(intent.getStringExtra(FileSyncAdapter.EXTRA_RESULT))
+ } finally {
+ listOfFilesFragment?.setEmptyListMessage(EmptyListState.LOCAL_FILE_LIST_EMPTY_FILE)
}
}
private fun getCurrentFileAndDirectory(): Pair {
- val currentFile =
- if (file == null) null else storageManager.getFileByEncryptedRemotePath(file.remotePath)
+ val currentFile = file?.let { storageManager.getFileByEncryptedRemotePath(it.remotePath) }
val currentDir = if (currentFolder == null) {
null
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/InternalTwoWaySyncActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/InternalTwoWaySyncActivity.kt
index 8ae77e6..89d37b8 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/InternalTwoWaySyncActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/InternalTwoWaySyncActivity.kt
@@ -201,7 +201,7 @@ class InternalTwoWaySyncActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
R.id.action_dismiss_two_way_sync -> {
disableTwoWaySyncAndWorkers()
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
index 65c4d07..ef3526e 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.kt
@@ -91,7 +91,7 @@ class ManageAccountsActivity :
multipleAccountsSupported = multiAccountSupport(this)
setupUserList()
- handleOnBackPressed()
+ handleBackPress()
}
private fun setupUsers() {
@@ -149,7 +149,7 @@ class ManageAccountsActivity :
performAccountRemoval(user)
}
- private fun handleOnBackPressed() {
+ private fun handleBackPress() {
onBackPressedDispatcher.addCallback(
this,
onBackPressedCallback
@@ -160,13 +160,13 @@ class ManageAccountsActivity :
override fun handleOnBackPressed() {
val resultIntent = Intent()
- if (accountManager.allUsers.size > 0) {
+ if (accountManager.allUsers.isNotEmpty()) {
resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, hasAccountListChanged())
resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, hasCurrentAccountChanged())
setResult(RESULT_OK, resultIntent)
} else {
val intent = Intent(this@ManageAccountsActivity, AuthenticatorActivity::class.java)
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(intent)
}
@@ -229,7 +229,7 @@ class ManageAccountsActivity :
var result = true
if (item.itemId == android.R.id.home) {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
} else {
result = super.onOptionsItemSelected(item)
}
@@ -331,7 +331,7 @@ class ManageAccountsActivity :
)
recyclerView?.adapter = userListAdapter
} else {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
}
@@ -393,8 +393,7 @@ class ManageAccountsActivity :
resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, true)
resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, true)
setResult(RESULT_OK, resultIntent)
-
- super.onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
}
}
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 c9b4be9..be8688e 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
@@ -103,6 +103,7 @@ import java.util.concurrent.Executors;
import javax.inject.Inject;
+import androidx.activity.OnBackPressedCallback;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -147,7 +148,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
private SyncBroadcastReceiver mSyncBroadcastReceiver;
private ReceiveExternalFilesAdapter receiveExternalFilesAdapter;
- private boolean mSyncInProgress;
private final static int REQUEST_CODE__SETUP_ACCOUNT = REQUEST_CODE__LAST_SHARED + 1;
@@ -203,6 +203,8 @@ public class ReceiveExternalFilesActivity extends FileActivity
fm.beginTransaction()
.add(taskRetainerFragment, TaskRetainerFragment.FTAG_TASK_RETAINER_FRAGMENT).commit();
} // else, Fragment already created and retained across configuration change
+
+ handleBackPress();
}
@Override
@@ -656,14 +658,19 @@ public class ReceiveExternalFilesActivity extends FileActivity
}
}
- @Override
- public void onBackPressed() {
- if (mParents.size() <= SINGLE_PARENT) {
- super.onBackPressed();
- } else {
- mParents.pop();
- browseToFolderIfItExists();
- }
+ private void handleBackPress() {
+ getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ if (mParents.size() <= SINGLE_PARENT) {
+ setEnabled(false);
+ getOnBackPressedDispatcher().onBackPressed();
+ } else {
+ mParents.pop();
+ browseToFolderIfItExists();
+ }
+ }
+ });
}
@Override
@@ -848,7 +855,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
executorService.execute(() -> {
long currentSyncTime = System.currentTimeMillis();
- mSyncInProgress = true;
final var optionalUser = getUser();
if (optionalUser.isEmpty()) {
DisplayUtils.showSnackMessage(this, R.string.user_information_retrieval_error);
@@ -874,7 +880,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
private List sortFileList(List files) {
FileSortOrder sortOrder = preferences.getSortOrderByFolder(mFile);
- return sortOrder.sortCloudFiles(files);
+ boolean foldersBeforeFiles = preferences.isSortFoldersBeforeFiles();
+ boolean favoritesFirst = preferences.isSortFavoritesFirst();
+ return sortOrder.sortCloudFiles(files, foldersBeforeFiles, favoritesFirst);
}
private String generatePath(Stack dirs) {
@@ -944,7 +952,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
}
if (mStreamsToUpload.size() > FileUploadHelper.MAX_FILE_COUNT) {
- DisplayUtils.showSnackMessage(this, R.string.max_file_count_warning_message);
+ FileUploadHelper.Companion.instance().showFileUploadLimitMessage(this);
return;
}
@@ -1097,7 +1105,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
dialog.show(getSupportFragmentManager(), CreateFolderDialogFragment.CREATE_FOLDER_FRAGMENT);
} else if (itemId == android.R.id.home) {
if (mParents.size() > SINGLE_PARENT) {
- onBackPressed();
+ getOnBackPressedDispatcher().onBackPressed();
}
} else if (itemId == R.id.action_switch_account) {
showAccountChooserDialog();
@@ -1145,58 +1153,32 @@ public class ReceiveExternalFilesActivity extends FileActivity
boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name)
&& getStorageManager() != null;
- if (sameAccount) {
-
- if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
- mSyncInProgress = true;
+ if (sameAccount && !FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) {
+ OCFile currentFile = (mFile == null) ? null : getStorageManager().getFileByPath(mFile.getRemotePath());
+ OCFile currentDir = (getCurrentFolder() == null) ? null : getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
+ if (currentDir == null) {
+ // current folder was removed from the server
+ DisplayUtils.showSnackMessage(getActivity(), R.string.sync_current_folder_was_removed, getCurrentFolder().getFileName());
+ browseToRoot();
} else {
- OCFile currentFile = (mFile == null) ? null :
- getStorageManager().getFileByPath(mFile.getRemotePath());
- OCFile currentDir = (getCurrentFolder() == null) ? null :
- getStorageManager().getFileByPath(getCurrentFolder().getRemotePath());
-
- if (currentDir == null) {
- // current folder was removed from the server
- DisplayUtils.showSnackMessage(
- getActivity(),
- R.string.sync_current_folder_was_removed,
- getCurrentFolder().getFileName()
- );
- browseToRoot();
-
- } else {
- if (currentFile == null && !mFile.isFolder()) {
- // currently selected file was removed in the server, and now we know it
- currentFile = currentDir;
- }
-
- if (currentDir.getRemotePath().equals(syncFolderRemotePath)) {
- populateDirectoryList(currentFile);
- }
+ if (currentFile == null && !mFile.isFolder()) {
+ // currently selected file was removed in the server, and now we know it
+ currentFile = currentDir;
}
- mSyncInProgress = !FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
- !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event);
-
- if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.equals(event)
- /// TODO refactor and make common
- && syncResult != null && !syncResult.isSuccess()) {
-
- if (syncResult.getCode() == ResultCode.UNAUTHORIZED ||
- (syncResult.isException() && syncResult.getException()
- instanceof AuthenticatorException)) {
-
- requestCredentialsUpdate();
-
- } else if (ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED == syncResult.getCode()) {
-
- showUntrustedCertDialog(syncResult);
- }
+ if (currentDir.getRemotePath().equals(syncFolderRemotePath)) {
+ populateDirectoryList(currentFile);
}
}
- Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress);
+ if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.equals(event) && syncResult != null && !syncResult.isSuccess()) {
+ if (syncResult.getCode() == ResultCode.UNAUTHORIZED || (syncResult.isException() && syncResult.getException() instanceof AuthenticatorException)) {
+ requestCredentialsUpdate();
+ } else if (ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED == syncResult.getCode()) {
+ showUntrustedCertDialog(syncResult);
+ }
+ }
}
} catch (RuntimeException e) {
// avoid app crashes after changing the serial id of RemoteOperationResult
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt
index 6d979f0..c97cca0 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/RichDocumentsEditorWebView.kt
@@ -185,7 +185,7 @@ class RichDocumentsEditorWebView : EditorWebView() {
renameString ?: return
val renameJson = JSONObject(renameString)
val newName = renameJson.getString(NEW_NAME)
- file.fileName = newName
+ file?.fileName = newName
} catch (e: JSONException) {
Log_OC.e(this, "Failed to parse rename json message: $e")
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
index bcbed6b..5cf6399 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
@@ -15,6 +15,7 @@
*/
package com.owncloud.android.ui.activity;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -183,6 +184,9 @@ public class SettingsActivity extends PreferenceActivity
// Synced folders
setupAutoUploadCategory(preferenceScreen);
+ // Files
+ setupFilesCategory();
+
// Details
setupDetailsCategory(preferenceScreen);
@@ -354,6 +358,7 @@ public class SettingsActivity extends PreferenceActivity
}
}
+ @SuppressLint("GestureBackNavigation")
@Override
public void onBackPressed() {
DrawerActivity.menuItemId = R.id.nav_all_files;
@@ -661,25 +666,33 @@ public class SettingsActivity extends PreferenceActivity
boolean fPassCodeEnabled = getResources().getBoolean(R.bool.passcode_enabled);
boolean fDeviceCredentialsEnabled = getResources().getBoolean(R.bool.device_credentials_enabled);
- boolean fShowHiddenFilesEnabled = getResources().getBoolean(R.bool.show_hidden_files_enabled);
boolean fShowEcosystemAppsEnabled = !getResources().getBoolean(R.bool.is_branded_client);
boolean fSyncedFolderLightEnabled = getResources().getBoolean(R.bool.syncedFolder_light);
boolean fShowMediaScanNotifications = preferences.isShowMediaScanNotifications();
setupLockPreference(preferenceCategoryDetails, fPassCodeEnabled, fDeviceCredentialsEnabled);
- setupHiddenFilesPreference(preferenceCategoryDetails, fShowHiddenFilesEnabled);
-
setupShowEcosystemAppsPreference(preferenceCategoryDetails, fShowEcosystemAppsEnabled);
setupShowMediaScanNotifications(preferenceCategoryDetails, fShowMediaScanNotifications);
- if (!fPassCodeEnabled && !fDeviceCredentialsEnabled && !fShowHiddenFilesEnabled && fSyncedFolderLightEnabled
+ if (!fPassCodeEnabled && !fDeviceCredentialsEnabled && fSyncedFolderLightEnabled
&& fShowMediaScanNotifications) {
preferenceScreen.removePreference(preferenceCategoryDetails);
}
}
+ private void setupFilesCategory() {
+ PreferenceCategory preferenceCategoryDetails = (PreferenceCategory) findPreference("files");
+ viewThemeUtils.files.themePreferenceCategory(preferenceCategoryDetails);
+
+ boolean fShowHiddenFilesEnabled = getResources().getBoolean(R.bool.show_hidden_files_enabled);
+
+ setupHiddenFilesPreference(preferenceCategoryDetails, fShowHiddenFilesEnabled);
+ setupFoldersBeforeFilesPreference();
+ setupSortFavoritesFirstPreference();
+ }
+
private void setupShowMediaScanNotifications(PreferenceCategory preferenceCategoryDetails,
boolean fShowMediaScanNotifications) {
ThemeableSwitchPreference mShowMediaScanNotifications =
@@ -703,6 +716,22 @@ public class SettingsActivity extends PreferenceActivity
}
}
+ private void setupFoldersBeforeFilesPreference() {
+ ThemeableSwitchPreference preference = (ThemeableSwitchPreference) findPreference("sort_folders_before_files");
+ preference.setOnPreferenceClickListener(p -> {
+ preferences.setSortFoldersBeforeFiles(preference.isChecked());
+ return true;
+ });
+ }
+
+ private void setupSortFavoritesFirstPreference() {
+ ThemeableSwitchPreference preference = (ThemeableSwitchPreference) findPreference("sort_favorites_first");
+ preference.setOnPreferenceClickListener(p -> {
+ preferences.setSortFavoritesFirst(preference.isChecked());
+ return true;
+ });
+ }
+
private void setupShowEcosystemAppsPreference(PreferenceCategory preferenceCategoryDetails, boolean fShowEcosystemAppsEnabled) {
showEcosystemApps = (ThemeableSwitchPreference) findPreference("show_ecosystem_apps");
if (fShowEcosystemAppsEnabled) {
@@ -1148,7 +1177,7 @@ public class SettingsActivity extends PreferenceActivity
for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.SETTINGS)) {
- // only add if it does not exist, in case activity is re-used
+ // only add if it does not exist, in case activity is reused
if (findPreference(String.valueOf(link.getId())) == null) {
Preference p = new Preference(this);
p.setTitle(link.getName());
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/StorageMigration.java b/app/src/main/java/com/owncloud/android/ui/activity/StorageMigration.java
index 4fd0eee..7992adb 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/StorageMigration.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/StorageMigration.java
@@ -11,11 +11,10 @@ package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.os.AsyncTask;
import android.view.View;
@@ -29,6 +28,8 @@ import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat;
@@ -175,7 +176,7 @@ public class StorageMigration {
private static abstract class FileMigrationTaskBase extends AsyncTask {
protected String mStorageSource;
protected String mStorageTarget;
- protected Context mContext;
+ @SuppressLint("StaticFieldLeak") protected Context mContext;
protected User user;
protected ProgressDialog mProgressDialog;
protected StorageMigrationProgressListener mListener;
@@ -364,10 +365,19 @@ public class StorageMigration {
try {
File dstFile = new File(mStorageTarget + File.separator + MainApp.getDataFolder());
deleteRecursive(dstFile);
- dstFile.delete();
+ try {
+ Files.delete(dstFile.toPath());
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Could not delete destination file: " + dstFile.getAbsolutePath(), e);
+ }
File srcFile = new File(mStorageSource + File.separator + MainApp.getDataFolder());
- srcFile.mkdirs();
+
+ try {
+ Files.createDirectories(srcFile.toPath());
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Could not create directory: " + srcFile.getAbsolutePath(), e);
+ }
publishProgress(R.string.file_migration_checking_destination);
@@ -466,7 +476,11 @@ public class StorageMigration {
if (!deleteRecursive(srcFile)) {
Log_OC.w(TAG, "Migration cleanup step failed");
}
- srcFile.delete();
+ try {
+ Files.delete(srcFile.toPath());
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Could not delete source file: " + srcFile.getAbsolutePath(), e);
+ }
}
private boolean deleteRecursive(File f) {
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
index ca272e9..ad899f6 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
@@ -28,6 +28,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.nextcloud.client.appinfo.AppInfo
import com.nextcloud.client.core.Clock
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.di.Injectable
@@ -146,6 +147,9 @@ class SyncedFoldersActivity :
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
+ @Inject
+ lateinit var appInfo: AppInfo
+
lateinit var binding: SyncedFoldersLayoutBinding
lateinit var adapter: SyncedFolderAdapter
@@ -199,21 +203,6 @@ class SyncedFoldersActivity :
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.activity_synced_folders, menu)
- if (powerManagementService.isPowerSavingExclusionAvailable) {
- val item = menu.findItem(R.id.action_disable_power_save_check)
- item.isVisible = true
- item.isChecked = preferences.isPowerCheckDisabled
- item.setOnMenuItemClickListener { powerCheck -> onDisablePowerSaveCheckClicked(powerCheck) }
- }
- return true
- }
-
- private fun onDisablePowerSaveCheckClicked(powerCheck: MenuItem): Boolean {
- if (!powerCheck.isChecked) {
- showPowerCheckDialog()
- }
- preferences.isPowerCheckDisabled = !powerCheck.isChecked
- powerCheck.isChecked = !powerCheck.isChecked
return true
}
@@ -245,7 +234,9 @@ class SyncedFoldersActivity :
gridWidth,
this,
lightVersion,
- viewThemeUtils
+ viewThemeUtils,
+ powerManagementService,
+ connectivityService
)
binding.emptyList.emptyListIcon.setImageResource(R.drawable.nav_synced_folders)
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.emptyList.emptyListViewAction)
@@ -575,7 +566,7 @@ class SyncedFoldersActivity :
}
}
if (syncedFolderDisplayItem.isEnabled) {
- backgroundJobManager.startImmediateFilesSyncJob(syncedFolderDisplayItem.id, overridePowerSaving = false)
+ backgroundJobManager.startAutoUploadImmediately(syncedFolderDisplayItem, overridePowerSaving = false)
showBatteryOptimizationInfo()
}
}
@@ -738,7 +729,7 @@ class SyncedFoldersActivity :
// existing synced folder setup to be updated
syncedFolderProvider.updateSyncFolder(item)
if (item.isEnabled) {
- backgroundJobManager.startImmediateFilesSyncJob(item.id, overridePowerSaving = false)
+ backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false)
} else {
val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id
val arbitraryDataProvider =
@@ -755,7 +746,7 @@ class SyncedFoldersActivity :
if (storedId != -1L) {
item.id = storedId
if (item.isEnabled) {
- backgroundJobManager.startImmediateFilesSyncJob(item.id, overridePowerSaving = false)
+ backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false)
} else {
val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey)
@@ -835,7 +826,7 @@ class SyncedFoldersActivity :
}
private fun showBatteryOptimizationInfo() {
- if (powerManagementService.isPowerSavingExclusionAvailable || checkIfBatteryOptimizationEnabled()) {
+ if (checkIfBatteryOptimizationEnabled()) {
val alertDialogBuilder = MaterialAlertDialogBuilder(this, R.style.Theme_ownCloud_Dialog)
.setTitle(getString(R.string.battery_optimization_title))
.setMessage(getString(R.string.battery_optimization_message))
@@ -871,7 +862,7 @@ class SyncedFoldersActivity :
val powerManager = getSystemService(POWER_SERVICE) as PowerManager?
return when {
powerManager != null -> !powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)
- else -> true
+ else -> !appInfo.isDebugBuild
}
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt
index 3a60eec..7cf73eb 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt
+++ b/app/src/main/java/com/owncloud/android/ui/activity/TextEditorWebView.kt
@@ -43,7 +43,7 @@ class TextEditorWebView : EditorWebView() {
finish()
}
- val editor = editorUtils.getEditor(user.get(), file.mimeType)
+ val editor = editorUtils.getEditor(user.get(), file?.mimeType)
if (editor != null && editor.id == "onlyoffice") {
webView.settings.userAgentString = generateOnlyOfficeUserAgent()
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
index 73d5148..f494589 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
@@ -157,17 +157,32 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
* Updates title bar and home buttons (state and icon).
*/
protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
- String title;
boolean isRoot = isRoot(chosenFile);
-
- title = isRoot ? themeUtils.getDefaultDisplayNameForRootFolder(this) : fileDataStorageManager.getFilenameConsideringOfflineOperation(chosenFile);
+ String title = getActionBarTitle(chosenFile, isRoot);
updateActionBarTitleAndHomeButtonByString(title);
-
if (mAppBar != null) {
showHomeSearchToolbar(title, isRoot);
}
}
+ private String getActionBarTitle(OCFile chosenFile, boolean isRoot) {
+ if (isRoot) {
+ return themeUtils.getDefaultDisplayNameForRootFolder(this);
+ }
+
+ if (chosenFile.isFolder()) {
+ return fileDataStorageManager.getFilenameConsideringOfflineOperation(chosenFile);
+ }
+
+ long parentId = chosenFile.getParentId();
+ OCFile parentFile = fileDataStorageManager.getFileById(parentId);
+ if (parentFile == null) {
+ return "";
+ }
+
+ return fileDataStorageManager.getFilenameConsideringOfflineOperation(parentFile);
+ }
+
public void showSearchView() {
if (isHomeSearchToolbarShow) {
showHomeSearchToolbar(false);
@@ -194,7 +209,7 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
R.animator.appbar_elevation_off));
mDefaultToolbar.setVisibility(View.GONE);
mHomeSearchToolbar.setVisibility(View.VISIBLE);
- viewThemeUtils.material.themeCardView(mHomeSearchToolbar);
+ viewThemeUtils.material.themeSearchCardView(mHomeSearchToolbar);
viewThemeUtils.material.themeSearchBarText(mSearchText);
} else {
mAppBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(mAppBar.getContext(),
@@ -272,11 +287,21 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
}
public void showSortListGroup(boolean show) {
- findViewById(R.id.sort_list_button_group).setVisibility(show ? View.VISIBLE : View.GONE);
+ final var view = findViewById(R.id.sort_list_button_group);
+ if (view == null) {
+ return;
+ }
+
+ view.setVisibility(show ? View.VISIBLE : View.GONE);
}
public boolean sortListGroupVisibility(){
- return findViewById(R.id.sort_list_button_group).getVisibility() == View.VISIBLE;
+ final var view = findViewById(R.id.sort_list_button_group);
+ if (view == null) {
+ return false;
+ }
+
+ return view.getVisibility() == View.VISIBLE;
}
/**
* Change the bitmap for the toolbar's preview image.
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
index 226bb6d..4cd80f5 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
@@ -238,7 +238,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
public void onItemSelected(AdapterView> parent, View view, int position, long id) {
int i = position;
while (i-- != 0) {
- onBackPressed();
+ getOnBackPressedDispatcher().onBackPressed();
}
// the next operation triggers a new call to this method, but it's necessary to
// ensure that the name exposed in the action bar is the current directory when the
@@ -311,7 +311,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
if (itemId == android.R.id.home) {
if (mCurrentDir != null && mCurrentDir.getParentFile() != null) {
- onBackPressed();
+ getOnBackPressedDispatcher().onBackPressed();
}
} else if (itemId == R.id.action_select_all) {
mSelectAll = !item.isChecked();
@@ -503,7 +503,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
} else {
final var chosenFiles = mFileListFragment.getCheckedFilePaths();
if (chosenFiles.length > FileUploadHelper.MAX_FILE_COUNT) {
- DisplayUtils.showSnackMessage(this, R.string.max_file_count_warning_message);
+ FileUploadHelper.Companion.instance().showFileUploadLimitMessage(this);
return;
}
@@ -670,7 +670,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
} else {
final var chosenFiles = mFileListFragment.getCheckedFilePaths();
if (chosenFiles.length > FileUploadHelper.MAX_FILE_COUNT) {
- DisplayUtils.showSnackMessage(this, R.string.max_file_count_warning_message);
+ FileUploadHelper.Companion.instance().showFileUploadLimitMessage(this);
return;
}
boolean isPositionZero = (binding.uploadFilesSpinnerBehaviour.getSelectedItemPosition() == 0);
@@ -720,7 +720,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag);
final var chosenFiles = mFileListFragment.getCheckedFilePaths();
if (chosenFiles.length > FileUploadHelper.MAX_FILE_COUNT) {
- DisplayUtils.showSnackMessage(this, R.string.max_file_count_warning_message);
+ FileUploadHelper.Companion.instance().showFileUploadLimitMessage(this);
return;
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
index 49ef2bc..8d61cbd 100755
--- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
@@ -41,7 +41,6 @@ import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
import com.owncloud.android.ui.adapter.UploadListAdapter;
import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
-import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.FilesSyncHelper;
import javax.inject.Inject;
@@ -133,15 +132,13 @@ public class UploadListActivity extends FileActivity {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.UploadStarted) {
Log_OC.d(TAG, "Upload worker started");
- handleUploadWorkerState();
+ uploadListAdapter.loadUploadItemsFromDb();
+ } else if (state instanceof WorkerState.UploadFinished) {
+ uploadListAdapter.loadUploadItemsFromDb(() -> swipeListRefreshLayout.setRefreshing(false));
}
});
}
- private void handleUploadWorkerState() {
- uploadListAdapter.loadUploadItemsFromDb();
- }
-
private void setupContent() {
binding.list.setEmptyView(binding.emptyList.getRoot());
binding.emptyList.getRoot().setVisibility(View.GONE);
@@ -182,26 +179,15 @@ public class UploadListActivity extends FileActivity {
}
private void refresh() {
- FilesSyncHelper.startFilesSyncForAllFolders(syncedFolderProvider,
- backgroundJobManager,
- true,
- new String[]{});
+ boolean isUploadStarted = FileUploadHelper.Companion.instance().retryFailedUploads(
+ uploadsStorageManager,
+ connectivityService,
+ accountManager,
+ powerManagementService);
- if (uploadsStorageManager.getFailedUploads().length > 0) {
- new Thread(() -> {
- FileUploadHelper.Companion.instance().retryFailedUploads(
- uploadsStorageManager,
- connectivityService,
- accountManager,
- powerManagementService);
- uploadListAdapter.loadUploadItemsFromDb();
- }).start();
- DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
+ if (!isUploadStarted) {
+ uploadListAdapter.loadUploadItemsFromDb(() -> swipeListRefreshLayout.setRefreshing(false));
}
-
-
- // update UI
- uploadListAdapter.loadUploadItemsFromDb(() -> swipeListRefreshLayout.setRefreshing(false));
}
@Override
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
index 613437a..11adefe 100644
--- a/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
+++ b/app/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
@@ -12,6 +12,7 @@
*/
package com.owncloud.android.ui.activity;
+import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
@@ -158,7 +159,7 @@ public class UserInfoActivity extends DrawerActivity implements Injectable {
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
- onBackPressed();
+ getOnBackPressedDispatcher().onBackPressed();
} else if (itemId == R.id.action_open_account) {
accountClicked(user.hashCode());
} else if (itemId == R.id.action_delete_account) {
@@ -392,6 +393,7 @@ public class UserInfoActivity extends DrawerActivity implements Injectable {
this.viewThemeUtils = viewThemeUtils;
}
+ @SuppressLint("NotifyDataSetChanged")
public void setData(List displayList) {
mDisplayList = displayList == null ? new LinkedList<>() : displayList;
notifyDataSetChanged();
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java
index 00f82c2..51c00db 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityAndVersionListAdapter.java
@@ -8,6 +8,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
@@ -54,6 +55,7 @@ public class ActivityAndVersionListAdapter extends ActivityListAdapter {
this.versionListInterface = versionListInterface;
}
+ @SuppressLint("NotifyDataSetChanged")
public void setActivityAndVersionItems(List items, NextcloudClient newClient, boolean clear) {
if (client == null) {
client = newClient;
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
index 3565302..d824608 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
@@ -10,6 +10,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -98,6 +99,7 @@ public class ActivityListAdapter extends RecyclerView.Adapter activityItems, NextcloudClient client, boolean clear) {
this.client = client;
String sTime = "";
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
index 8b210af..1b8d3cc 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
@@ -15,8 +15,6 @@ package com.owncloud.android.ui.adapter
import android.annotation.SuppressLint
import android.content.Context
-import android.os.Handler
-import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -31,6 +29,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.GalleryItems
import com.owncloud.android.datamodel.GalleryRow
import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.activity.ComponentsGetter
import com.owncloud.android.ui.fragment.GalleryFragment
import com.owncloud.android.ui.fragment.GalleryFragmentBottomSheetDialog
@@ -57,7 +56,28 @@ class GalleryAdapter(
) : SectionedRecyclerViewAdapter(),
CommonOCFileListAdapterInterface,
PopupTextProvider {
- var files: List = mutableListOf()
+
+ companion object {
+ private const val TAG = "GalleryAdapter"
+ }
+
+ // fileId -> (section, row)
+ private val filePositionMap = mutableMapOf>()
+
+ // (section, row) -> unique stable ID for that row
+ private val rowIdMap = mutableMapOf, Long>()
+
+ private var cachedAllFiles: List? = null
+ private var cachedFilesCount: Int = 0
+
+ private var _files: List = mutableListOf()
+ var files: List
+ get() = _files
+ private set(value) {
+ _files = value
+ invalidateCaches()
+ }
+
private val ocFileListDelegate: OCFileListDelegate
private var storageManager: FileDataStorageManager = transferServiceGetter.storageManager
@@ -78,7 +98,50 @@ class GalleryAdapter(
)
}
- override fun getItemId(section: Int, position: Int): Long = files[section].rows[position].calculateHashCode()
+ private fun invalidateCaches() {
+ Log_OC.d(TAG, "invalidating caches")
+ cachedAllFiles = null
+ updateFilesCount()
+ rebuildFilePositionMap()
+ }
+
+ private fun updateFilesCount() {
+ cachedFilesCount = files.fold(0) { acc, item -> acc + item.rows.size }
+ }
+
+ private fun rebuildFilePositionMap() {
+ filePositionMap.clear()
+ rowIdMap.clear()
+
+ files.forEachIndexed { sectionIndex, galleryItem ->
+ galleryItem.rows.forEachIndexed { rowIndex, row ->
+ val position = sectionIndex to rowIndex
+
+ // since row can contain files two to five use first files id as adapter id
+ row.files.firstOrNull()?.fileId?.let { firstFileId ->
+ rowIdMap[position] = firstFileId
+ }
+
+ // map all row files
+ row.files.forEach { file ->
+ filePositionMap[file.fileId] = position
+ }
+ }
+ }
+ }
+
+ override fun getItemId(section: Int, position: Int): Long = rowIdMap[section to position] ?: -1L
+
+ override fun getItemCount(section: Int): Int = files.getOrNull(section)?.rows?.size ?: 0
+
+ override fun getSectionCount(): Int = files.size
+
+ override fun getFilesCount(): Int = cachedFilesCount
+
+ override fun getItemPosition(file: OCFile): Int {
+ val (section, row) = filePositionMap[file.fileId] ?: return -1
+ return getAbsolutePosition(section, row)
+ }
override fun selectAll(value: Boolean) {
if (value) {
@@ -116,16 +179,12 @@ class GalleryAdapter(
relativePosition: Int,
absolutePosition: Int
) {
- if (holder != null) {
- val rowHolder = holder as GalleryRowHolder
- rowHolder.bind(files[section].rows[relativePosition])
+ if (holder is GalleryRowHolder) {
+ val row = files.getOrNull(section)?.rows?.getOrNull(relativePosition)
+ row?.let { holder.bind(it) }
}
}
- override fun getItemCount(section: Int): Int = files[section].rows.size
-
- override fun getSectionCount(): Int = files.size
-
override fun getPopupText(p0: View, position: Int): CharSequence = DisplayUtils.getDateByPattern(
files[getRelativePosition(position).section()].date,
context,
@@ -150,10 +209,6 @@ class GalleryAdapter(
}
}
- override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) {
- TODO("Not yet implemented")
- }
-
@SuppressLint("NotifyDataSetChanged")
fun showAllGalleryItems(
remotePath: String,
@@ -194,36 +249,32 @@ class GalleryAdapter(
photoFragment.setEmptyListMessage(SearchType.GALLERY_SEARCH)
}
- Handler(Looper.getMainLooper()).post {
- files = finalSortedList.toGalleryItems()
- notifyDataSetChanged()
- }
+ files = finalSortedList.toGalleryItems()
+ notifyDataSetChanged()
}
- private fun transformToRows(list: List): List = list
- .sortedBy { it.modificationTimestamp }
- .reversed()
- .chunked(columns)
- .map { entry -> GalleryRow(entry, defaultThumbnailSize, defaultThumbnailSize) }
+ private fun transformToRows(list: List): List {
+ if (list.isEmpty()) return emptyList()
+
+ return list
+ .sortedByDescending { it.modificationTimestamp }
+ .chunked(columns)
+ .map { chunk -> GalleryRow(chunk, defaultThumbnailSize, defaultThumbnailSize) }
+ }
@SuppressLint("NotifyDataSetChanged")
fun clear() {
- Handler(Looper.getMainLooper()).post {
- files = emptyList()
- notifyDataSetChanged()
- }
+ files = emptyList()
+ notifyDataSetChanged()
}
- private fun firstOfMonth(timestamp: Long): Long {
- val cal = Calendar.getInstance()
- cal.time = Date(timestamp)
- cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH))
- cal.set(Calendar.HOUR_OF_DAY, 0)
- cal.set(Calendar.MINUTE, 0)
- cal.set(Calendar.SECOND, 0)
-
- return cal.timeInMillis
- }
+ private fun firstOfMonth(timestamp: Long): Long = Calendar.getInstance().apply {
+ time = Date(timestamp)
+ set(Calendar.DAY_OF_MONTH, getActualMinimum(Calendar.DAY_OF_MONTH))
+ set(Calendar.HOUR_OF_DAY, 0)
+ set(Calendar.MINUTE, 0)
+ set(Calendar.SECOND, 0)
+ }.timeInMillis
fun isEmpty(): Boolean = files.isEmpty()
@@ -244,37 +295,6 @@ class GalleryAdapter(
ocFileListDelegate.cancelAllPendingTasks()
}
- override fun getItemPosition(file: OCFile): Int {
- val findResult = files
- .asSequence()
- .flatMapIndexed { itemIndex, item ->
- item.rows.withIndex().map { row -> Triple(itemIndex, row.index, row.value) }
- }.find {
- it.third.files.contains(file)
- }
-
- val (item, row) = findResult ?: Triple(0, 0, null)
- return getAbsolutePosition(item, row)
- }
-
- override fun swapDirectory(
- user: User,
- directory: OCFile,
- storageManager: FileDataStorageManager,
- onlyOnDevice: Boolean,
- mLimitToMimeType: String
- ) {
- TODO("Not yet implemented")
- }
-
- override fun setHighlightedItem(file: OCFile) {
- TODO("Not yet implemented")
- }
-
- override fun setSortOrder(mFile: OCFile, sortOrder: FileSortOrder) {
- TODO("Not yet implemented")
- }
-
override fun addCheckedFile(file: OCFile) {
ocFileListDelegate.addCheckedFile(file)
}
@@ -288,23 +308,20 @@ class GalleryAdapter(
}
override fun notifyItemChanged(file: OCFile) {
- notifyItemChanged(getItemPosition(file))
- }
-
- override fun getFilesCount(): Int = files.fold(0) { acc, item -> acc + item.rows.size }
-
- @SuppressLint("NotifyDataSetChanged")
- override fun setMultiSelect(boolean: Boolean) {
- ocFileListDelegate.isMultiSelect = boolean
- notifyDataSetChanged()
- }
-
- private fun getAllFiles(): List = files.flatMap { galleryItem ->
- galleryItem.rows.flatMap { row ->
- row.files
+ val position = getItemPosition(file)
+ if (position >= 0) {
+ notifyItemChanged(position)
}
}
+ override fun setMultiSelect(boolean: Boolean) {
+ ocFileListDelegate.isMultiSelect = boolean
+ }
+
+ private fun getAllFiles(): List = cachedAllFiles ?: files.flatMap { galleryItem ->
+ galleryItem.rows.flatMap { row -> row.files }
+ }.also { cachedAllFiles = it }
+
private fun addAllFilesToCheckedFiles() {
val allFiles = getAllFiles()
ocFileListDelegate.addToCheckedFiles(allFiles)
@@ -323,24 +340,36 @@ class GalleryAdapter(
columns = newColumn
}
- @SuppressLint("NotifyDataSetChanged")
fun markAsFavorite(remotePath: String, favorite: Boolean) {
val allFiles = getAllFiles()
- for (file in allFiles) {
- if (file.remotePath == remotePath) {
- file.isFavorite = favorite
- break
- }
- }
-
- Handler(Looper.getMainLooper()).post {
+ allFiles.firstOrNull { it.remotePath == remotePath }?.also { file ->
+ file.isFavorite = favorite
files = allFiles.toGalleryItems()
- notifyDataSetChanged()
+ notifyItemChanged(file)
}
}
- private fun List.toGalleryItems(): List = this
- .groupBy { firstOfMonth(it.modificationTimestamp) }
- .map { GalleryItems(it.key, transformToRows(it.value)) }
- .sortedBy { it.date }.reversed()
+ private fun List.toGalleryItems(): List {
+ if (isEmpty()) return emptyList()
+
+ return groupBy { firstOfMonth(it.modificationTimestamp) }
+ .map { (date, filesList) ->
+ GalleryItems(date, transformToRows(filesList))
+ }
+ .sortedByDescending { it.date }
+ }
+
+ override fun onBindFooterViewHolder(holder: SectionedViewHolder?, section: Int) = Unit
+
+ override fun swapDirectory(
+ user: User,
+ directory: OCFile,
+ storageManager: FileDataStorageManager,
+ onlyOnDevice: Boolean,
+ mLimitToMimeType: String
+ ) = Unit
+
+ override fun setHighlightedItem(file: OCFile) = Unit
+
+ override fun setSortOrder(mFile: OCFile, sortOrder: FileSortOrder) = Unit
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
index 5740936..867c8a3 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
@@ -9,6 +9,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -23,6 +24,7 @@ import android.widget.TextView;
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
import com.nextcloud.client.preferences.AppPreferences;
+import com.nextcloud.utils.FileHelper;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -34,8 +36,6 @@ import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -69,8 +69,10 @@ public class LocalFileListAdapter extends RecyclerView.Adapter();
this.viewThemeUtils = viewThemeUtils;
this.isWithinEncryptedFolder = isWithinEncryptedFolder;
-
- swapDirectory(directory);
+ setHasStableIds(true);
}
- @Override
- public int getItemCount() {
- return mFiles.size() + 1;
- }
-
public int getFilesCount() {
return mFiles.size();
}
@@ -129,30 +125,39 @@ public class LocalFileListAdapter extends RecyclerView.Adapter result = listFilesRecursive(checkedFiles);
+ List result = FileHelper.INSTANCE.listFilesRecursive(checkedFiles);
Log_OC.d(TAG, "Returning " + result.size() + " selected files");
return result.toArray(new String[0]);
}
- public List listFilesRecursive(Collection files) {
- List result = new ArrayList<>();
-
- for (File file : files) {
- if (file.isDirectory()) {
- result.addAll(listFilesRecursive(getFiles(file)));
- } else {
- result.add(file.getAbsolutePath());
- }
- }
-
- return result;
+ @Override
+ public int getItemCount() {
+ return mFiles.size() + 1;
}
@Override
public long getItemId(int position) {
- return mFiles.size() <= position ? position : -1;
+ if (position >= mFiles.size()) {
+ return RecyclerView.NO_ID;
+ }
+
+ File file = mFiles.get(position);
+ return file.getAbsolutePath().hashCode();
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position == mFiles.size()) {
+ return VIEWTYPE_FOOTER;
+ } else {
+ if (MimeTypeUtil.isImageOrVideo(getItem(position))) {
+ return VIEWTYPE_IMAGE;
+ } else {
+ return VIEWTYPE_ITEM;
+ }
+ }
}
@Override
@@ -271,19 +276,6 @@ public class LocalFileListAdapter extends RecyclerView.Adapter {
- List fileList;
- if (directory == null) {
- fileList = new ArrayList<>();
- } else {
- if (mLocalFolderPicker) {
- fileList = getFolders(directory);
- } else {
- fileList = getFiles(directory);
- }
+ // Load first page of folders
+ List firstPage = FileHelper.INSTANCE.listDirectoryEntries(directory, currentOffset, PAGE_SIZE, true);
+
+ if (!firstPage.isEmpty()) {
+ firstPage = sortAndFilterHiddenEntries(firstPage);
}
- if (!fileList.isEmpty()) {
- FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);
- fileList = sortOrder.sortLocalFiles(fileList);
+ currentOffset += PAGE_SIZE;
+ updateUIForFirstPage(firstPage);
- // Fetch preferences for showing hidden files
- boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
- if (!showHiddenFiles) {
- fileList = filterHiddenFiles(fileList);
- }
- }
- final List newFiles = fileList;
+ // Load remaining folders, then all files
+ loadRemainingEntries(directory, true);
- uiHandler.post(() -> {
- mFiles = newFiles;
- mFilesAll = new ArrayList<>();
- mFilesAll.addAll(mFiles);
+ // Reset for files
+ currentOffset = 0;
- notifyDataSetChanged();
- localFileListFragmentInterface.setLoading(false);
- });
+ loadRemainingEntries(directory, false);
});
-
}
+ @SuppressLint("NotifyDataSetChanged")
+ private void updateUIForFirstPage(List firstPage) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ mFiles = new ArrayList<>(firstPage);
+ mFilesAll = new ArrayList<>(firstPage);
+ notifyDataSetChanged();
+ localFileListFragmentInterface.setLoading(false);
+ });
+ }
+
+ private List sortAndFilterHiddenEntries(List nextPage) {
+ boolean showHiddenFiles = preferences.isShowHiddenFilesEnabled();
+ FileSortOrder sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.localFileListView);
+
+ if (!showHiddenFiles) {
+ nextPage = filterHiddenFiles(nextPage);
+ }
+
+ return sortOrder.sortLocalFiles(nextPage);
+ }
+
+ private void loadRemainingEntries(File directory, boolean fetchFolders) {
+ while (true) {
+ List nextPage = FileHelper.INSTANCE.listDirectoryEntries(directory, currentOffset, PAGE_SIZE, fetchFolders);
+ if (nextPage.isEmpty()) {
+ break;
+ }
+
+ nextPage = sortAndFilterHiddenEntries(nextPage);
+
+ currentOffset += PAGE_SIZE;
+ notifyItemRange(nextPage);
+ }
+ }
+
+ private void notifyItemRange(List updatedList) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ int from = mFiles.size();
+ int to = updatedList.size();
+
+ mFiles.addAll(updatedList);
+ mFilesAll.addAll(updatedList);
+
+ Log_OC.d(TAG, "notifyItemRange, item size: " + mFilesAll.size());
+
+ notifyItemRangeInserted(from, to);
+ });
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
public void setSortOrder(FileSortOrder sortOrder) {
localFileListFragmentInterface.setLoading(true);
final Handler uiHandler = new Handler(Looper.getMainLooper());
@@ -366,30 +396,9 @@ public class LocalFileListAdapter extends RecyclerView.Adapter getFolders(final File directory) {
- File[] folders = directory.listFiles(File::isDirectory);
-
- if (folders != null && folders.length > 0) {
- return new ArrayList<>(Arrays.asList(folders));
- } else {
- return new ArrayList<>();
- }
- }
-
- private List getFiles(File directory) {
- File[] files = directory.listFiles();
-
- if (files != null && files.length > 0) {
- return new ArrayList<>(Arrays.asList(files));
- } else {
- return new ArrayList<>();
- }
}
+ @SuppressLint("NotifyDataSetChanged")
public void filter(String text) {
if (text.isEmpty()) {
mFiles = mFilesAll;
@@ -514,11 +523,11 @@ public class LocalFileListAdapter extends RecyclerView.Adapter newFiles) {
mFiles = newFiles;
- mFilesAll = new ArrayList<>();
- mFilesAll.addAll(mFiles);
+ mFilesAll = new ArrayList<>(mFiles);
notifyDataSetChanged();
localFileListFragmentInterface.setLoading(false);
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java
index da5da10..67ca8c6 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java
@@ -10,6 +10,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Typeface;
@@ -75,6 +76,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter notificationItems) {
notificationsList.clear();
notificationsList.addAll(notificationItems);
@@ -340,6 +342,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter Long.compare(o2.getFirstShareTimestamp(), o1.getFirstShareTimestamp()));
} else {
- mFiles = sortOrder.sortCloudFiles(mFiles);
+ boolean foldersBeforeFiles = preferences.isSortFoldersBeforeFiles();
+ boolean favoritesFirst = preferences.isSortFavoritesFirst();
+ mFiles = sortOrder.sortCloudFiles(mFiles, foldersBeforeFiles, favoritesFirst);
}
new Handler(Looper.getMainLooper()).post(() -> {
@@ -505,6 +520,9 @@ public class OCFileListAdapter extends RecyclerView.Adapter() {
private val gridTotal = gridWidth * 2
@@ -59,6 +67,14 @@ class SyncedFolderAdapter(
private var hideItems = true
private val thumbnailThreadPool: Executor = Executors.newCachedThreadPool()
+ private val minimumSizeForTouchableArea
+ by lazy { context.resources.getDimensionPixelSize(R.dimen.minimum_size_for_touchable_area) }
+ private val screenWidth by lazy { context.resources.displayMetrics.widthPixels }
+ private val standardDoubleMargin
+ by lazy { context.resources.getDimensionPixelSize(R.dimen.standard_double_margin) }
+ private val syncedFoldersTitleMargin
+ by lazy { context.resources.getDimensionPixelSize(R.dimen.synced_folders_title_margin) }
+
init {
shouldShowHeadersForEmptySections(true)
shouldShowFooters(true)
@@ -234,51 +250,112 @@ class SyncedFolderAdapter(
return -1
}
+ @Suppress("NestedBlockDepth")
override fun onBindHeaderViewHolder(commonHolder: SectionedViewHolder, section: Int, expanded: Boolean) {
if (section < filteredSyncFolderItems.size) {
val holder = commonHolder as HeaderViewHolder
- holder.binding.headerContainer.visibility = View.VISIBLE
- holder.binding.title.text = filteredSyncFolderItems[section].folderName
+ holder.binding.run {
+ headerContainer.visibility = View.VISIBLE
- if (MediaFolderType.VIDEO == filteredSyncFolderItems[section].type) {
- holder.binding.type.setImageResource(R.drawable.video_32dp)
- } else if (MediaFolderType.IMAGE == filteredSyncFolderItems[section].type) {
- holder.binding.type.setImageResource(R.drawable.image_32dp)
- } else {
- holder.binding.type.setImageResource(R.drawable.folder_star_32dp)
- }
-
- holder.binding.syncStatusButton.visibility = View.VISIBLE
- holder.binding.syncStatusButton.tag = section
- holder.binding.syncStatusButton.setOnClickListener {
- filteredSyncFolderItems[section].setEnabled(
- !filteredSyncFolderItems[section].isEnabled,
- clock.currentTime
- )
- setSyncButtonActiveIcon(
- holder.binding.syncStatusButton,
- filteredSyncFolderItems[section].isEnabled
- )
- clickListener.onSyncStatusToggleClick(section, filteredSyncFolderItems[section])
- }
- setSyncButtonActiveIcon(holder.binding.syncStatusButton, filteredSyncFolderItems[section].isEnabled)
-
- if (light) {
- holder.binding.settingsButton.visibility = View.GONE
- } else {
- holder.binding.settingsButton.visibility = View.VISIBLE
- holder.binding.settingsButton.tag = section
- holder.binding.settingsButton.setOnClickListener { v: View ->
- onOverflowIconClicked(
- section,
- filteredSyncFolderItems[section],
- v
- )
+ if (section == 0) {
+ autoUploadBatterySaverWarningCard.root.run {
+ setVisibleIf(powerManagementService.isPowerSavingEnabled)
+ viewThemeUtils.material.themeCardView(this)
+ }
}
- }
- initSubFolderWarningButton(holder, section)
+ val syncedFolder = filteredSyncFolderItems[section]
+
+ title.text = syncedFolder.folderName
+
+ if (MediaFolderType.VIDEO == syncedFolder.type) {
+ type.setImageResource(R.drawable.video_32dp)
+ } else if (MediaFolderType.IMAGE == syncedFolder.type) {
+ type.setImageResource(R.drawable.image_32dp)
+ } else {
+ type.setImageResource(R.drawable.folder_star_32dp)
+ }
+
+ syncStatusButton.visibility = View.VISIBLE
+ syncStatusButton.tag = section
+ syncStatusButton.setOnClickListener {
+ syncedFolder.setEnabled(
+ !syncedFolder.isEnabled,
+ clock.currentTime
+ )
+ setSyncButtonActiveIcon(
+ syncStatusButton,
+ syncedFolder.isEnabled
+ )
+ clickListener.onSyncStatusToggleClick(section, syncedFolder)
+ }
+ setSyncButtonActiveIcon(syncStatusButton, syncedFolder.isEnabled)
+
+ if (light) {
+ settingsButton.visibility = View.GONE
+ } else {
+ settingsButton.visibility = View.VISIBLE
+ settingsButton.tag = section
+ settingsButton.setOnClickListener { v: View ->
+ onOverflowIconClicked(
+ section,
+ syncedFolder,
+ v
+ )
+ }
+ }
+
+ initSubFolderWarningButton(holder, section)
+ initNextScanIndicator(holder, syncedFolder)
+ }
+ }
+ }
+
+ private fun initNextScanIndicator(holder: HeaderViewHolder, syncedFolder: SyncedFolder) {
+ val scanIndicatorText = getNextScanIndicatorText(syncedFolder)
+
+ holder.binding.scanIndicatorText.setVisibleIf(syncedFolder.isEnabled && (scanIndicatorText != null))
+
+ if (holder.binding.scanIndicatorText.isVisible) {
+ setMaxWidthOfScanIndicatorText(holder)
+ holder.binding.scanIndicatorText.text = scanIndicatorText
+ } else {
+ setBottomMarginOfTitle(holder)
+ }
+ }
+
+ private fun setMaxWidthOfScanIndicatorText(holder: HeaderViewHolder) {
+ var visibleTrailingIconCount = 2
+ if (holder.binding.subFolderWarningButton.isVisible) {
+ visibleTrailingIconCount += 1
+ }
+
+ val takenTrailingSpace = minimumSizeForTouchableArea * visibleTrailingIconCount
+ val maxWidthInPxOfScanIndicatorText = (screenWidth - takenTrailingSpace) - standardDoubleMargin
+
+ holder.binding.scanIndicatorText.maxWidth = maxWidthInPxOfScanIndicatorText
+ }
+
+ private fun setBottomMarginOfTitle(holder: HeaderViewHolder) {
+ val layoutParams = holder.binding.title.layoutParams as ViewGroup.MarginLayoutParams
+ layoutParams.bottomMargin = syncedFoldersTitleMargin
+ holder.binding.title.layoutParams = layoutParams
+ }
+
+ private fun getNextScanIndicatorText(syncedFolder: SyncedFolder): String? {
+ val scanInterval = syncedFolder.calculateScanInterval(connectivityService, powerManagementService)
+ val nextScanInMillis = scanInterval.first - System.currentTimeMillis()
+ val minutesLeft = TimeUnit.MILLISECONDS
+ .toMinutes(nextScanInMillis)
+ .coerceAtLeast(0)
+ .toInt()
+
+ return if (minutesLeft <= 0) {
+ null
+ } else {
+ val scanIntervalMessageId = scanInterval.second ?: return null
+ context.getString(scanIntervalMessageId)
}
}
@@ -289,7 +366,6 @@ class SyncedFolderAdapter(
holder.binding.subFolderWarningButton.run {
setVisibleIf(isGivenLocalPathHasEnabledParent)
if (isVisible) {
- viewThemeUtils.platform.themeImageButton(this)
setOnClickListener {
clickListener.showSubFolderWarningDialog()
}
@@ -448,13 +524,12 @@ class SyncedFolderAdapter(
binding.root
)
- private fun setSyncButtonActiveIcon(syncStatusButton: ImageButton, enabled: Boolean) {
+ private fun setSyncButtonActiveIcon(syncStatusButton: MaterialButton, enabled: Boolean) {
if (enabled) {
- syncStatusButton.setImageDrawable(
+ syncStatusButton.icon =
viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_cloud_sync_on, ColorRole.PRIMARY)
- )
} else {
- syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off)
+ syncStatusButton.icon = ContextCompat.getDrawable(context, R.drawable.ic_cloud_sync_off)
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
index 0393b5a..f382668 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java
@@ -90,6 +90,7 @@ public class TrashbinListAdapter extends RecyclerView.Adapter trashbinFiles, boolean clear) {
if (clear) {
files.clear();
@@ -206,6 +207,7 @@ public class TrashbinListAdapter extends RecyclerView.Adapter
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.adapter
+
+import android.content.Context
+import com.afollestad.sectionedrecyclerview.SectionedViewHolder
+import com.nextcloud.android.common.ui.theme.utils.ColorRole
+import com.nextcloud.client.account.User
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.utils.extensions.setVisibleIf
+import com.owncloud.android.databinding.UnifiedSearchCurrentDirectoryItemBinding
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.theme.ViewThemeUtils
+
+@Suppress("LongParameterList")
+class UnifiedSearchCurrentDirItemViewHolder(
+ val binding: UnifiedSearchCurrentDirectoryItemBinding,
+ val context: Context,
+ private val viewThemeUtils: ViewThemeUtils,
+ private val storageManager: FileDataStorageManager,
+ private val isRTL: Boolean,
+ private val user: User,
+ private val appPreferences: AppPreferences,
+ private val syncedFolderProvider: SyncedFolderProvider,
+ private val action: UnifiedSearchCurrentDirItemAction
+) : SectionedViewHolder(binding.unifiedSearchCurrentDirItemLayout) {
+
+ fun bind(file: OCFile) {
+ val filenameWithExtension = storageManager.getFilenameConsideringOfflineOperation(file)
+ val isFolder = file.isFolder
+ val (filename, extension) = FileStorageUtils.getFilenameAndExtension(filenameWithExtension, isFolder, isRTL)
+ binding.extension.setVisibleIf(!isFolder)
+ binding.extension.text = extension
+ binding.filename.text = filename
+ viewThemeUtils.platform.colorImageView(binding.thumbnail, ColorRole.PRIMARY)
+ DisplayUtils.setThumbnail(
+ file,
+ binding.thumbnail,
+ user,
+ storageManager,
+ listOf(),
+ false,
+ context,
+ binding.thumbnailShimmer,
+ appPreferences,
+ viewThemeUtils,
+ syncedFolderProvider
+ )
+
+ binding.more.setOnClickListener {
+ action.openFile(file.decryptedRemotePath, true)
+ }
+
+ binding.unifiedSearchCurrentDirItemLayout.setOnClickListener {
+ action.openFile(file.decryptedRemotePath, false)
+ }
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
index 69f43d2..fcc8f46 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchItemViewHolder.kt
@@ -8,12 +8,10 @@
package com.owncloud.android.ui.adapter
import android.content.Context
-import android.graphics.drawable.Drawable
import android.view.View
-import androidx.core.content.res.ResourcesCompat
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.nextcloud.android.common.ui.theme.utils.ColorRole
-import com.nextcloud.client.account.User
+import com.nextcloud.common.NextcloudClient
import com.nextcloud.model.SearchResultEntryType
import com.nextcloud.utils.CalendarEventManager
import com.nextcloud.utils.ContactManager
@@ -21,21 +19,19 @@ import com.nextcloud.utils.GlideHelper
import com.nextcloud.utils.extensions.getType
import com.owncloud.android.databinding.UnifiedSearchItemBinding
import com.owncloud.android.datamodel.FileDataStorageManager
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
-import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.theme.ViewThemeUtils
@Suppress("LongParameterList")
class UnifiedSearchItemViewHolder(
private val supportsOpeningCalendarContactsLocally: Boolean,
val binding: UnifiedSearchItemBinding,
- private val user: User,
private val storageManager: FileDataStorageManager,
private val listInterface: UnifiedSearchListInterface,
private val filesAction: FilesAction,
val context: Context,
+ private val nextcloudClient: NextcloudClient,
private val viewThemeUtils: ViewThemeUtils
) : SectionedViewHolder(binding.root) {
@@ -56,12 +52,8 @@ class UnifiedSearchItemViewHolder(
binding.localFileIndicator.visibility = View.GONE
}
- val mimetype = MimeTypeUtil.getBestMimeTypeByFilename(entry.title)
-
val entryType = entry.getType()
- val placeholder = getPlaceholder(entry, entryType, mimetype)
- val nextcloudClient =
- OwnCloudClientManagerFactory.getDefaultSingleton().getNextcloudClientFor(user.toOwnCloudAccount(), context)
+ viewThemeUtils.platform.colorImageView(binding.thumbnail, ColorRole.PRIMARY)
GlideHelper.loadIntoImageView(
context,
nextcloudClient,
@@ -104,18 +96,4 @@ class UnifiedSearchItemViewHolder(
listInterface.onSearchResultClicked(entry)
}
}
-
- private fun getPlaceholder(
- entry: SearchResultEntry,
- entryType: SearchResultEntryType,
- mimetype: String?
- ): Drawable {
- val iconId = entryType.run {
- iconId()
- }
-
- val defaultDrawable = MimeTypeUtil.getFileTypeIcon(mimetype, entry.title, context, viewThemeUtils)
- val drawable: Drawable = ResourcesCompat.getDrawable(context.resources, iconId, null) ?: defaultDrawable
- return viewThemeUtils.platform.tintDrawable(context, drawable, ColorRole.PRIMARY)
- }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
index 6ad1ba0..baf0e9e 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UnifiedSearchListAdapter.kt
@@ -16,15 +16,22 @@ import androidx.core.view.isVisible
import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter
import com.afollestad.sectionedrecyclerview.SectionedViewHolder
import com.nextcloud.client.account.User
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.common.NextcloudClient
import com.owncloud.android.R
+import com.owncloud.android.databinding.UnifiedSearchCurrentDirectoryItemBinding
import com.owncloud.android.databinding.UnifiedSearchEmptyBinding
import com.owncloud.android.databinding.UnifiedSearchFooterBinding
import com.owncloud.android.databinding.UnifiedSearchHeaderBinding
import com.owncloud.android.databinding.UnifiedSearchItemBinding
import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.ThumbnailsCacheManager
+import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchSection
+import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
/**
@@ -38,12 +45,18 @@ class UnifiedSearchListAdapter(
private val filesAction: UnifiedSearchItemViewHolder.FilesAction,
private val user: User,
private val context: Context,
- private val viewThemeUtils: ViewThemeUtils
+ private val viewThemeUtils: ViewThemeUtils,
+ private val appPreferences: AppPreferences,
+ private val syncedFolderProvider: SyncedFolderProvider,
+ private val nextcloudClient: NextcloudClient,
+ private val currentDirItemAction: UnifiedSearchCurrentDirItemAction
) : SectionedRecyclerViewAdapter() {
companion object {
private const val VIEW_TYPE_EMPTY = Int.MAX_VALUE
+ private const val VIEW_TYPE_CURRENT_DIR = 0
}
+ private var currentDirItems: List = listOf()
private var sections: List = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SectionedViewHolder {
@@ -74,14 +87,29 @@ class UnifiedSearchListAdapter(
UnifiedSearchItemViewHolder(
supportsOpeningCalendarContactsLocally,
binding,
- user,
storageManager,
listInterface,
filesAction,
context,
+ nextcloudClient,
viewThemeUtils
)
}
+ VIEW_TYPE_CURRENT_DIR -> {
+ val isRTL = DisplayUtils.isRTL()
+ val binding = UnifiedSearchCurrentDirectoryItemBinding.inflate(layoutInflater, parent, false)
+ UnifiedSearchCurrentDirItemViewHolder(
+ binding,
+ context,
+ viewThemeUtils,
+ storageManager,
+ isRTL,
+ user,
+ appPreferences,
+ syncedFolderProvider,
+ currentDirItemAction
+ )
+ }
VIEW_TYPE_EMPTY -> {
val binding = UnifiedSearchEmptyBinding.inflate(layoutInflater, parent, false)
EmptyViewHolder(binding)
@@ -90,29 +118,67 @@ class UnifiedSearchListAdapter(
}
}
+ private fun isCurrentDirItem(section: Int): Boolean = (currentDirItems.isNotEmpty() && section == 0)
+
+ private fun getSectionIndex(section: Int): Int = if (currentDirItems.isNotEmpty()) section - 1 else section
+
internal class EmptyViewHolder(binding: UnifiedSearchEmptyBinding) : SectionedViewHolder(binding.getRoot())
- override fun getSectionCount(): Int = sections.size
+ override fun getSectionCount(): Int = (if (currentDirItems.isNotEmpty()) 1 else 0) + sections.size
- override fun getItemCount(section: Int): Int = sections[section].entries.size
-
- override fun onBindHeaderViewHolder(holder: SectionedViewHolder, section: Int, expanded: Boolean) {
- (holder as UnifiedSearchHeaderViewHolder).run {
- bind(sections[section])
+ override fun getItemViewType(section: Int, relativePosition: Int, absolutePosition: Int): Int =
+ if (isCurrentDirItem(section)) {
+ VIEW_TYPE_CURRENT_DIR
+ } else {
+ VIEW_TYPE_ITEM
}
+
+ override fun getItemCount(section: Int): Int = if (isCurrentDirItem(section)) {
+ currentDirItems.size
+ } else {
+ val index = if (currentDirItems.isNotEmpty()) section - 1 else section
+ sections.getOrNull(index)?.entries?.size ?: 0
}
- override fun onBindFooterViewHolder(holder: SectionedViewHolder, section: Int) {
- if (sections[section].hasMoreResults) {
- (holder as UnifiedSearchFooterViewHolder).run {
- bind(sections[section])
+ override fun onBindHeaderViewHolder(holder: SectionedViewHolder, section: Int, expanded: Boolean) {
+ if (holder is UnifiedSearchHeaderViewHolder) {
+ if (isCurrentDirItem(section)) {
+ val name = context.getString(R.string.unified_search_fragment_search_in_this_folder)
+ val currentDirUnifiedSearchSection = UnifiedSearchSection("", name, listOf(), false)
+ holder.bind(currentDirUnifiedSearchSection)
+ } else {
+ val index = getSectionIndex(section)
+ val sectionData = sections.getOrNull(index) ?: return
+ holder.bind(sectionData)
}
}
}
- override fun getFooterViewType(section: Int): Int = when {
- sections[section].hasMoreResults -> VIEW_TYPE_FOOTER
- else -> VIEW_TYPE_EMPTY
+ override fun onBindFooterViewHolder(holder: SectionedViewHolder, section: Int) {
+ if (isCurrentDirItem(section)) {
+ return
+ }
+
+ val index = getSectionIndex(section)
+ val sectionData = sections.getOrNull(index) ?: return
+
+ if (sectionData.hasMoreResults && holder is UnifiedSearchFooterViewHolder) {
+ holder.bind(sectionData)
+ }
+ }
+
+ override fun getFooterViewType(section: Int): Int {
+ if (isCurrentDirItem(section)) {
+ return VIEW_TYPE_EMPTY
+ }
+
+ val index = getSectionIndex(section)
+ val sectionData = sections.getOrNull(index)
+
+ return when {
+ sectionData?.hasMoreResults == true -> VIEW_TYPE_FOOTER
+ else -> VIEW_TYPE_EMPTY
+ }
}
override fun onBindViewHolder(
@@ -121,10 +187,13 @@ class UnifiedSearchListAdapter(
relativePosition: Int,
absolutePosition: Int
) {
- // TODO different binding (and also maybe diff UI) for non-file results
- (holder as UnifiedSearchItemViewHolder).run {
- val entry = sections[section].entries[relativePosition]
- bind(entry)
+ if (isCurrentDirItem(section) && holder is UnifiedSearchCurrentDirItemViewHolder) {
+ val entry = currentDirItems.getOrNull(relativePosition) ?: return
+ holder.bind(entry)
+ } else if (holder is UnifiedSearchItemViewHolder) {
+ val index = getSectionIndex(section)
+ val entry = sections.getOrNull(index)?.entries?.getOrNull(relativePosition) ?: return
+ holder.bind(entry)
}
}
@@ -145,6 +214,14 @@ class UnifiedSearchListAdapter(
notifyDataSetChanged()
}
+ @SuppressLint("NotifyDataSetChanged")
+ fun setDataCurrentDirItems(currentDirItems: List) {
+ this.currentDirItems = currentDirItems
+ notifyDataSetChanged()
+ }
+
+ fun isCurrentDirItemsEmpty(): Boolean = currentDirItems.isEmpty()
+
init {
// initialise thumbnails cache on background thread
ThumbnailsCacheManager.initDiskCacheAsync()
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 4dee8b3..5f30c45 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
@@ -8,6 +8,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -30,6 +31,7 @@ import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.utils.extensions.ViewExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.databinding.UploadListHeaderBinding;
@@ -42,6 +44,7 @@ import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.db.OCUploadComparator;
import com.owncloud.android.db.UploadResult;
+import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.RefreshFolderOperation;
@@ -58,6 +61,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
@@ -66,6 +70,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
+import kotlin.Unit;
/**
* This Adapter populates a ListView with following types of uploads: pending, active, completed. Filtering possible.
@@ -73,6 +78,30 @@ import androidx.annotation.NonNull;
public class UploadListAdapter extends SectionedRecyclerViewAdapter {
private static final String TAG = UploadListAdapter.class.getSimpleName();
+ private static class GroupConfig {
+ final Type type;
+ final int titleRes;
+ final UploadStatus status;
+ final NameCollisionPolicy nameCollisionPolicy;
+
+ GroupConfig(Type type, int titleRes, UploadStatus status, NameCollisionPolicy nameCollisionPolicy) {
+ this.type = type;
+ this.titleRes = titleRes;
+ this.status = status;
+ this.nameCollisionPolicy = nameCollisionPolicy;
+ }
+
+ public static List getConfigs() {
+ return List.of(
+ new GroupConfig(Type.CURRENT, R.string.uploads_view_group_current_uploads, UploadStatus.UPLOAD_IN_PROGRESS, null),
+ new GroupConfig(Type.FAILED, R.string.uploads_view_group_failed_uploads, UploadStatus.UPLOAD_FAILED, null),
+ new GroupConfig(Type.CANCELLED, R.string.uploads_view_group_manually_cancelled_uploads, UploadStatus.UPLOAD_CANCELLED, null),
+ new GroupConfig(Type.FINISHED, R.string.uploads_view_group_finished_uploads, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.ASK_USER), // ASK_USER default value
+ new GroupConfig(Type.SKIPPED, R.string.uploads_view_upload_status_skip, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.SKIP)
+ );
+ }
+ }
+
private UploadProgressListener uploadProgressListener;
private final FileActivity parentActivity;
private final UploadsStorageManager uploadsStorageManager;
@@ -90,8 +119,63 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter(1),
new UploadGroupLoadPolicy());
+ private final List uploadGroupConfigs = GroupConfig.getConfigs();
+
private final FileUploadHelper uploadHelper = FileUploadHelper.Companion.instance();
+ public UploadListAdapter(final FileActivity fileActivity,
+ final UploadsStorageManager uploadsStorageManager,
+ final FileDataStorageManager storageManager,
+ final UserAccountManager accountManager,
+ final ConnectivityService connectivityService,
+ final PowerManagementService powerManagementService,
+ final Clock clock,
+ final ViewThemeUtils viewThemeUtils) {
+ Log_OC.d(TAG, "UploadListAdapter");
+
+ this.parentActivity = fileActivity;
+ this.uploadsStorageManager = uploadsStorageManager;
+ this.storageManager = storageManager;
+ this.accountManager = accountManager;
+ this.connectivityService = connectivityService;
+ this.powerManagementService = powerManagementService;
+ this.clock = clock;
+ this.viewThemeUtils = viewThemeUtils;
+
+ uploadGroups = new UploadGroup[uploadGroupConfigs.size()];
+
+ shouldShowHeadersForEmptySections(false);
+ initUploadGroups();
+ showUser = accountManager.getAccounts().length > 1;
+ }
+
+ private void initUploadGroups() {
+ final var optionalUser = parentActivity.getUser();
+ if (optionalUser.isEmpty()) {
+ return;
+ }
+
+ final var accountName = optionalUser.get().getAccountName();
+
+ for (int i = 0; i < uploadGroupConfigs.size(); i++) {
+ final var config = uploadGroupConfigs.get(i);
+ uploadGroups[i] = createUploadGroup(config, accountName);
+ }
+ }
+
+ private UploadGroup createUploadGroup(GroupConfig config, String accountName) {
+ return new UploadGroup(config.type, parentActivity.getString(config.titleRes)) {
+ @Override
+ public void refresh(LoadCompleteListener listener) {
+ uploadHelper.getUploadsByStatus(accountName, config.status, config.nameCollisionPolicy,ocUploads -> {
+ fixAndSortItems(ocUploads);
+ listener.onComplete();
+ return Unit.INSTANCE;
+ });
+ }
+ };
+ }
+
@Override
public int getSectionCount() {
return uploadGroups.length;
@@ -120,6 +204,13 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {{
+ toggleSectionExpanded(section);
+ headerViewHolder.binding.uploadListState.setImageResource(isSectionExpanded(section) ?
+ R.drawable.ic_expand_less :
+ R.drawable.ic_expand_more);
+ }});
+
switch (group.type) {
case CURRENT, FINISHED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
case CANCELLED, FAILED ->
@@ -127,9 +218,12 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {
switch (group.type) {
- case CURRENT -> new Thread(() -> {
+ case CURRENT -> {
OCUpload ocUpload = group.getItem(0);
if (ocUpload == null) {
return;
@@ -140,18 +234,20 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter Unit.INSTANCE);
+ }
loadUploadItemsFromDb();
- }).start();
+ }
case FINISHED -> {
uploadsStorageManager.clearSuccessfulUploads();
loadUploadItemsFromDb();
}
- case FAILED -> {
- showFailedPopupMenu(headerViewHolder);
- }
- case CANCELLED -> {
- showCancelledPopupMenu(headerViewHolder);
+ case FAILED -> showFailedPopupMenu(headerViewHolder);
+ case CANCELLED -> showCancelledPopupMenu(headerViewHolder);
+ default -> {
+
}
}
});
@@ -168,16 +264,12 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {
- uploadHelper.retryFailedUploads(
- uploadsStorageManager,
- connectivityService,
- accountManager,
- powerManagementService);
- loadUploadItemsFromDb();
- }).start();
+ uploadHelper.retryFailedUploads(
+ uploadsStorageManager,
+ connectivityService,
+ accountManager,
+ powerManagementService);
+ loadUploadItemsFromDb();
}
return true;
@@ -238,66 +330,6 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter 1;
- }
-
-
@Override
public void onBindViewHolder(SectionedViewHolder holder, int section, int relativePosition, int absolutePosition) {
if (uploadGroups.length == 0 || section < 0 || section >= uploadGroups.length) {
@@ -423,7 +455,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {
- uploadHelper.cancelFileUpload(item.getRemotePath(), item.getAccountName());
+ uploadHelper.updateUploadStatus(item.getRemotePath(), item.getAccountName(), UploadStatus.UPLOAD_CANCELLED);
+ FileUploadWorker.Companion.cancelCurrentUpload(item.getRemotePath(), item.getAccountName(), () -> Unit.INSTANCE);
loadUploadItemsFromDb();
});
@@ -727,40 +760,48 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {
- status = parentActivity.getString(R.string.uploads_view_later_waiting_to_upload);
- if (uploadHelper.isUploadingNow(upload)) {
- // really uploading, bind the progress bar to listen for progress updates
- status = parentActivity.getString(R.string.uploader_upload_in_progress_ticker);
- }
- if (parentActivity.getAppPreferences().isGlobalUploadPaused()) {
- status = parentActivity.getString(R.string.upload_global_pause_title);
- }
- }
- case UPLOAD_SUCCEEDED -> {
- if (upload.getLastResult() == UploadResult.SAME_FILE_CONFLICT) {
- status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded_same_file);
- } else if (upload.getLastResult() == UploadResult.FILE_NOT_FOUND) {
- status = getUploadFailedStatusText(upload.getLastResult());
+ if (prefs.isGlobalUploadPaused()) {
+ status = statusRes.getString(R.string.upload_global_pause_title);
+ } else if (uploadHelper.isUploadingNow(upload)) {
+ status = statusRes.getString(R.string.uploader_upload_in_progress_ticker);
} else {
- status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded);
+ status = statusRes.getString(R.string.uploads_view_later_waiting_to_upload);
}
}
- case UPLOAD_FAILED -> {
- status = getUploadFailedStatusText(upload.getLastResult());
- }
- case UPLOAD_CANCELLED -> {
- status = parentActivity.getString(R.string.upload_manually_cancelled);
- }
- default -> {
- status = "Uncontrolled status: " + upload.getUploadStatus();
+ case UPLOAD_SUCCEEDED -> {
+ UploadResult result = upload.getLastResult();
+ if (result == UploadResult.SAME_FILE_CONFLICT) {
+ status = statusRes.getString(R.string.uploads_view_upload_status_succeeded_same_file);
+ } else if (result == UploadResult.FILE_NOT_FOUND) {
+ status = getUploadFailedStatusText(result);
+ } else if (upload.getNameCollisionPolicy() == NameCollisionPolicy.SKIP) {
+ status = statusRes.getString(R.string.uploads_view_upload_status_skip_reason);
+ } else {
+ status = statusRes.getString(R.string.uploads_view_upload_status_succeeded);
+ }
}
+
+ case UPLOAD_FAILED ->
+ status = getUploadFailedStatusText(upload.getLastResult());
+
+ case UPLOAD_CANCELLED ->
+ status = statusRes.getString(R.string.upload_manually_cancelled);
+
+ default ->
+ status = "Uncontrolled status: " + uploadStatus;
}
+
return status;
}
+
@NonNull
private String getUploadFailedStatusText(UploadResult result) {
return switch (result) {
@@ -910,7 +951,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter {
+ synchronized (completedCount) {
+ group.apply();
+ completedCount[0]++;
+ if (completedCount[0] == groupCount) {
+
+ // All groups finished, update UI once
+ parentActivity.runOnUiThread(() -> {
+ notifyDataSetChanged();
+ for (LoadCompleteListener loadCompleteListener : loadCompleteListenerSet) {
+ loadCompleteListener.onComplete();
+ }
+ });
+ }
+ }
+ });
}
- parentActivity.runOnUiThread(() -> {
- for (UploadGroup uploadGroup : uploadGroups) {
- uploadGroup.apply();
- }
- notifyDataSetChanged();
- for (LoadCompleteListener loadCompleteListener : loadCompleteListenerSet) {
- loadCompleteListener.onComplete();
- }
- });
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
index 1ad6097..6c61b21 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
@@ -12,6 +12,7 @@
*/
package com.owncloud.android.ui.adapter;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
@@ -162,6 +163,7 @@ public class UserListAdapter extends RecyclerView.Adapter items){
if(values == null){
values = new ArrayList<>();
diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java b/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java
index 79b2f3a..a26bb6b 100644
--- a/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java
+++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java
@@ -8,6 +8,7 @@
*/
package com.owncloud.android.ui.asynctasks;
+import android.annotation.SuppressLint;
import android.os.AsyncTask;
import com.nextcloud.client.account.User;
@@ -30,7 +31,7 @@ public class FetchRemoteFileTask extends AsyncTask {
private final User user;
private final String fileId;
private final FileDataStorageManager storageManager;
- private final FileDisplayActivity fileDisplayActivity;
+ @SuppressLint("StaticFieldLeak") private final FileDisplayActivity fileDisplayActivity;
private OCFile ocFile;
public FetchRemoteFileTask(User user,
diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/NotificationExecuteActionTask.java b/app/src/main/java/com/owncloud/android/ui/asynctasks/NotificationExecuteActionTask.java
index 1e3d15d..d8e92df 100644
--- a/app/src/main/java/com/owncloud/android/ui/asynctasks/NotificationExecuteActionTask.java
+++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/NotificationExecuteActionTask.java
@@ -6,6 +6,7 @@
*/
package com.owncloud.android.ui.asynctasks;
+import android.annotation.SuppressLint;
import android.os.AsyncTask;
import com.nextcloud.common.NextcloudClient;
@@ -32,7 +33,7 @@ public class NotificationExecuteActionTask extends AsyncTask {
@Override
protected Boolean doInBackground(Void... voids) {
+ if (file == null) return Boolean.FALSE;
+
HttpClient client = new HttpClient();
GetMethod getMethod = null;
- FileOutputStream fos = null;
try {
getMethod = new GetMethod(url);
int status = client.executeMethod(getMethod);
- if (status == HttpStatus.SC_OK) {
- if (file.exists() && !file.delete()) {
- return Boolean.FALSE;
- }
+ if (status != HttpStatus.SC_OK) return Boolean.FALSE;
- file.getParentFile().mkdirs();
+ if (file.exists() && !file.delete()) return Boolean.FALSE;
- if (!file.getParentFile().exists()) {
- Log_OC.d(TAG, file.getParentFile().getAbsolutePath() + " does not exist");
- return Boolean.FALSE;
- }
+ File parentFile = file.getParentFile();
+ if (parentFile == null) return Boolean.FALSE;
- if (!file.createNewFile()) {
- Log_OC.d(TAG, file.getAbsolutePath() + " could not be created");
- return Boolean.FALSE;
- }
+ Files.createDirectories(parentFile.toPath());
+ if (!parentFile.exists()) {
+ Log_OC.d(TAG, parentFile.getAbsolutePath() + " does not exist");
+ return Boolean.FALSE;
+ }
+ if (!file.createNewFile()) {
+ Log_OC.d(TAG, file.getAbsolutePath() + " could not be created");
+ return Boolean.FALSE;
+ }
+
+ Header contentLengthHeader = getMethod.getResponseHeader("Content-Length");
+ long totalToTransfer = 0;
+ if (contentLengthHeader != null && !contentLengthHeader.getValue().isEmpty()) {
+ totalToTransfer = Long.parseLong(contentLengthHeader.getValue());
+ }
+
+ try (
BufferedInputStream bis = new BufferedInputStream(getMethod.getResponseBodyAsStream());
- fos = new FileOutputStream(file);
+ FileOutputStream fos = new FileOutputStream(file)
+ ) {
+ byte[] buffer = new byte[4096];
long transferred = 0;
+ int read;
- Header contentLength = getMethod.getResponseHeader("Content-Length");
- long totalToTransfer = contentLength != null && contentLength.getValue().length() > 0 ?
- Long.parseLong(contentLength.getValue()) : 0;
-
- byte[] bytes = new byte[4096];
- int readResult;
- while ((readResult = bis.read(bytes)) != -1) {
- fos.write(bytes, 0, readResult);
- transferred += readResult;
+ while ((read = bis.read(buffer)) != -1) {
+ fos.write(buffer, 0, read);
+ transferred += read;
}
- // Check if the file is completed
- if (transferred != totalToTransfer) {
+
+ if (totalToTransfer > 0 && transferred != totalToTransfer) {
+ Log_OC.d(TAG, "Transferred bytes (" + transferred +
+ ") != expected (" + totalToTransfer + ")");
return Boolean.FALSE;
}
-
- if (getMethod.getResponseBodyAsStream() != null) {
- getMethod.getResponseBodyAsStream().close();
- }
}
- } catch (IOException e) {
- Log_OC.e(TAG, "Error reading file", e);
+
+ return Boolean.TRUE;
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Error downloading file", e);
+ return Boolean.FALSE;
} finally {
if (getMethod != null) {
getMethod.releaseConnection();
}
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- Log_OC.e(TAG, "Error closing file output stream", e);
- }
- }
}
-
- return Boolean.TRUE;
}
@Override
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt
index cde23d6..9c300b5 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/RemoveFilesDialogFragment.kt
@@ -24,7 +24,6 @@ import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
-import com.owncloud.android.ui.preview.PreviewImageActivity
import javax.inject.Inject
/**
@@ -125,14 +124,11 @@ class RemoveFilesDialogFragment :
}
finishActionMode()
- finishPreviewImageActivity()
}
}
override fun onNeutral(callerTag: String?) = Unit
- private fun finishPreviewImageActivity() = getTypedActivity(PreviewImageActivity::class.java)?.finish()
-
private fun setActionMode(actionMode: ActionMode?) {
this.actionMode = actionMode
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt b/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
index d3372b7..6656d0b 100644
--- a/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.kt
@@ -542,29 +542,59 @@ class SyncedFolderPreferencesDialogFragment :
}
/**
- * Get index for name collision selection dialog.
+ * Converts a [NameCollisionPolicy] enum value into the corresponding
+ * **UI dialog selection index** used in the dialog.
*
- * @return 0 if ASK_USER, 1 if OVERWRITE, 2 if RENAME, 3 if SKIP, Otherwise: 0
+ * ⚠️ **Important:**
+ * These dialog indices are **not** the same as the integer values stored
+ * in the database or defined in [NameCollisionPolicy].
+ * This mapping is purely for UI selection purposes.
+ *
+ * | Policy | Dialog Index |
+ * |---------------------|--------------|
+ * | ASK_USER (default) | 0 |
+ * | OVERWRITE | 1 |
+ * | RENAME | 2 |
+ * | SKIP | 3 |
+ *
+ * @param nameCollisionPolicy The collision handling policy.
+ * @return The index to preselect in the UI dialog for the given policy.
*/
@Suppress("MagicNumber")
private fun getSelectionIndexForNameCollisionPolicy(nameCollisionPolicy: NameCollisionPolicy): Int =
when (nameCollisionPolicy) {
NameCollisionPolicy.OVERWRITE -> 1
NameCollisionPolicy.RENAME -> 2
- NameCollisionPolicy.CANCEL -> 3
+ NameCollisionPolicy.SKIP -> 3
NameCollisionPolicy.ASK_USER -> 0
}
/**
- * Get index for name collision selection dialog. Inverse of getSelectionIndexForNameCollisionPolicy.
+ * Converts a **UI dialog selection index** from the dialog
+ * back into a [NameCollisionPolicy] enum value.
*
- * @return ASK_USER if 0, OVERWRITE if 1, RENAME if 2, SKIP if 3. Otherwise: ASK_USER
+ * ⚠️ **Important:**
+ * These indices are defined only for the dialog and are **not** the same as
+ * the values used in the database or internal enum ordinals.
+ * Always use this function to translate the dialog result safely.
+ *
+ * | Dialog Index | Policy |
+ * |---------------|--------------------|
+ * | 0 | ASK_USER (default) |
+ * | 1 | OVERWRITE |
+ * | 2 | RENAME |
+ * | 3 | SKIP |
+ *
+ * Any unexpected index value will default to [NameCollisionPolicy.ASK_USER].
+ *
+ * @param index The selected index from the dialog.
+ * @return The corresponding [NameCollisionPolicy] value.
*/
@Suppress("MagicNumber")
private fun getNameCollisionPolicyForSelectionIndex(index: Int): NameCollisionPolicy = when (index) {
1 -> NameCollisionPolicy.OVERWRITE
2 -> NameCollisionPolicy.RENAME
- 3 -> NameCollisionPolicy.CANCEL
+ 3 -> NameCollisionPolicy.SKIP
0 -> NameCollisionPolicy.ASK_USER
else -> NameCollisionPolicy.ASK_USER
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
index a88da89..54bbad8 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt
@@ -25,7 +25,6 @@ import android.os.Handler
import android.os.Looper
import android.os.Parcelable
import android.util.DisplayMetrics
-import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -57,7 +56,7 @@ import com.nextcloud.client.di.Injectable
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.client.preferences.AppPreferencesImpl
import com.nextcloud.utils.extensions.getTypedActivity
-import com.nextcloud.utils.extensions.handleBackButtonEvent
+import com.nextcloud.utils.extensions.mainThread
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.ListFragmentBinding
@@ -65,7 +64,6 @@ import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.files.SearchRemoteOperation
import com.owncloud.android.lib.resources.status.OwnCloudVersion
import com.owncloud.android.ui.EmptyRecyclerView
-import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.activity.FolderPickerActivity
import com.owncloud.android.ui.activity.OnEnforceableRefreshListener
@@ -707,6 +705,14 @@ open class ExtendedListFragment :
true
)
}
+ EmptyListState.ERROR -> {
+ setMessageForEmptyList(
+ R.string.file_list_error_headline,
+ R.string.file_list_error_description,
+ R.drawable.ic_no_internet,
+ false
+ )
+ }
else -> {
setMessageForEmptyList(
R.string.file_list_empty_headline,
@@ -715,6 +721,10 @@ open class ExtendedListFragment :
true
)
}
+ }.also {
+ mainThread {
+ mRefreshListLayout?.isRefreshing = false
+ }
}
}
@@ -767,18 +777,7 @@ open class ExtendedListFragment :
}
}
- protected fun setupBackButtonRedirectToAllFiles() {
- view?.isFocusableInTouchMode = true
- view?.requestFocus()
- view?.setOnKeyListener { _: View, keyCode: Int, event: KeyEvent ->
- if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- val fda = getTypedActivity(FileActivity::class.java)
- val currentDir = fda?.currentDir ?: return@setOnKeyListener false
- return@setOnKeyListener fda.handleBackButtonEvent(currentDir)
- }
- false
- }
- }
+ protected fun isAccountManagerInitialized(): Boolean = ::accountManager.isInitialized
private data class EmptyListData(val headline: Int, val message: Int, val icon: Int?, val tintIcon: Boolean)
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
index 858c02e..2aaa0b4 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
@@ -197,7 +197,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
}
public void goBackToOCFileListFragment() {
- requireActivity().onBackPressed();
+ requireActivity().getOnBackPressedDispatcher().onBackPressed();
}
@Override
@@ -574,7 +574,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
// configure UI for depending upon local state of the file
if (transferring
|| (FileDownloadHelper.Companion.instance().isDownloading(user, file))
- || (FileUploadHelper.Companion.instance().isUploading(user, file))) {
+ || (FileUploadHelper.Companion.instance().isUploading(file.getRemotePath(), user.getAccountName()))) {
setButtonsForTransferring();
} else if (file.isDown()) {
@@ -736,7 +736,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
if (FileDownloadHelper.Companion.instance().isDownloading(user, getFile())) {
binding.progressText.setText(R.string.downloader_download_in_progress_ticker);
} else {
- if (FileUploadHelper.Companion.instance().isUploading(user, getFile())) {
+ if (FileUploadHelper.Companion.instance().isUploading(getFile().getRemotePath(), user.getAccountName())) {
binding.progressText.setText(R.string.uploader_upload_in_progress_ticker);
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
index 672ca0f..094c03d 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragment.java
@@ -234,12 +234,6 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
}
}
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- setupBackButtonRedirectToAllFiles();
- }
-
@Override
public void onMessageEvent(ChangeMenuEvent changeMenuEvent) {
super.onMessageEvent(changeMenuEvent);
@@ -301,10 +295,8 @@ public class GalleryFragment extends OCFileListFragment implements GalleryFragme
@Override
public boolean onOptionsItemSelected(MenuItem item) {
-
// Handle item selection
- if (item.getItemId() == R.id.action_three_dot_icon && !this.isPhotoSearchQueryRunning()
- && galleryFragmentBottomSheetDialog != null) {
+ if (item.getItemId() == R.id.action_three_dot_icon && galleryFragmentBottomSheetDialog != null) {
showBottomSheet();
return true;
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
index 05118c0..4b382de 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/GalleryFragmentBottomSheetDialog.kt
@@ -13,6 +13,8 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.di.Injectable
@@ -32,6 +34,11 @@ class GalleryFragmentBottomSheetDialog(private val actions: GalleryFragmentBotto
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentGalleryBottomSheetBinding.inflate(layoutInflater, container, false)
+
+ val bottomSheetDialog = dialog as BottomSheetDialog
+ bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ bottomSheetDialog.behavior.skipCollapsed = true
+
return binding.root
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
index 25fa784..f5757c8 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
@@ -112,7 +112,6 @@ public class LocalFileListFragment extends ExtendedListFragment implements
super.onActivityCreated(savedInstanceState);
mAdapter = new LocalFileListAdapter(mContainerActivity.isFolderPickerMode(),
- mContainerActivity.getInitialDirectory(),
this,
preferences,
getActivity(),
@@ -256,32 +255,30 @@ public class LocalFileListFragment extends ExtendedListFragment implements
* @param directory Directory to be listed
*/
public void listDirectory(File directory) {
-
- // Check input parameters for null
if (directory == null) {
- if (mDirectory != null) {
- directory = mDirectory;
- } else {
- directory = Environment.getExternalStorageDirectory();
- // TODO be careful with the state of the storage; could not be available
- if (directory == null) {
- return; // no files to show
- }
- }
+ directory = (mDirectory != null) ? mDirectory : Environment.getExternalStorageDirectory();
+ if (directory == null) return;
}
-
- // if that's not a directory -> List its parent
+ // If input is not a directory, list its parent
if (!directory.isDirectory()) {
Log_OC.w(TAG, "You see, that is not a directory -> " + directory);
directory = directory.getParentFile();
+ if (directory == null) {
+ Log_OC.w(TAG, "parent directory is null, cannot swap directory");
+ return;
+ }
}
- // by now, only files in the same directory will be kept as selected
mAdapter.removeAllFilesFromCheckedFiles();
mAdapter.swapDirectory(directory);
mDirectory = directory;
+
+ final var recyclerView = getRecyclerView();
+ if (recyclerView != null) {
+ recyclerView.scrollToPosition(0);
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
index 1d79902..d676897 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt
@@ -7,6 +7,7 @@
*/
package com.owncloud.android.ui.fragment
+import android.os.Build
import android.os.Bundle
import android.view.View
import com.google.android.material.bottomsheet.BottomSheetDialog
@@ -16,7 +17,9 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.device.DeviceInfo
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.documentscan.AppScanOptionalFeature
+import com.nextcloud.utils.BuildHelper.isFlavourGPlay
import com.nextcloud.utils.EditorUtils
+import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.FileListActionsBottomSheetCreatorBinding
import com.owncloud.android.databinding.FileListActionsBottomSheetFragmentBinding
@@ -26,6 +29,7 @@ import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.DirectEditing
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.utils.MimeTypeUtil
+import com.owncloud.android.utils.PermissionUtil
import com.owncloud.android.utils.theme.ThemeUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
@@ -66,6 +70,20 @@ class OCFileListBottomSheetDialog(
createRichWorkspace()
setupClickListener()
filterActionsForOfflineOperations()
+
+ if (MainApp.isClientBranded() && isFlavourGPlay()) {
+ // this way we can have branded clients with that permission
+ val hasPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ PermissionUtil.manifestHasAllFilesPermission(context)
+ } else {
+ true
+ }
+
+ if (!hasPermission) {
+ binding.menuUploadFiles.visibility = View.GONE
+ binding.uploadContentFromOtherApps.text = context.getString(R.string.upload_files)
+ }
+ }
}
private fun applyBranding() {
@@ -77,6 +95,8 @@ class OCFileListBottomSheetDialog(
colorImageView(menuIconScanDocUpload, ColorRole.PRIMARY)
colorImageView(menuIconMkdir, ColorRole.PRIMARY)
colorImageView(menuIconAddFolderInfo, ColorRole.PRIMARY)
+
+ colorViewBackground(binding.bottomSheet, ColorRole.SURFACE)
}
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
index ace98a6..2489b4d 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
@@ -225,7 +225,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
protected SearchType currentSearchType;
protected boolean searchFragment;
protected SearchEvent searchEvent;
- protected AsyncTask remoteOperationAsyncTask;
+ private OCFileListSearchTask searchTask;
protected String mLimitToMimeType;
private FloatingActionButton mFabMain;
public static boolean isMultipleFileSelectedForCopyOrMove = false;
@@ -277,12 +277,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
super.onResume();
}
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- setupBackButtonRedirectToAllFiles();
- }
-
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -356,8 +350,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
setOnRefreshListener(null);
mContainerActivity = null;
- if (remoteOperationAsyncTask != null) {
- remoteOperationAsyncTask.cancel(true);
+ if (searchTask != null) {
+ searchTask.cancel();
}
super.onDetach();
}
@@ -622,7 +616,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
return;
}
- boolean isWithinEncryptedFolder = getCurrentFile().isEncrypted();
+ boolean isWithinEncryptedFolder = file.isEncrypted();
UploadFilesActivity.startUploadActivityForResult(fileActivity, user.get(), FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM, isWithinEncryptedFolder);
}
@@ -1051,7 +1045,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
private Future> getPreviousFile() {
CompletableFuture> completableFuture = new CompletableFuture<>();
- Executors.newCachedThreadPool().submit(() -> {
+ Executors.newCachedThreadPool().execute(() -> {
var result = new Pair(null, null);
FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
@@ -1067,7 +1061,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
completableFuture.complete(result);
- return null;
});
return completableFuture;
@@ -1248,6 +1241,11 @@ public class OCFileListFragment extends ExtendedListFragment implements
}
private void handlePendingDownloadFile(OCFile file) {
+ if (!isAccountManagerInitialized()) {
+ Log_OC.e(TAG, "AccountManager not yet initialized");
+ return;
+ }
+
User account = accountManager.getUser();
OCCapability capability = mContainerActivity.getStorageManager().getCapability(account.getAccountName());
@@ -1509,6 +1507,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
*
* @return The currently viewed OCFile
*/
+ @Nullable
public OCFile getCurrentFile() {
return mFile;
}
@@ -1908,10 +1907,10 @@ public class OCFileListFragment extends ExtendedListFragment implements
return;
}
- // avoid calling api multiple times if async task is already executing
- if (remoteOperationAsyncTask != null && remoteOperationAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
+ // avoid calling api multiple times if task is already executing
+ if (searchTask != null && !searchTask.isFinished()) {
if (searchEvent != null) {
- Log_OC.d(TAG, "OCFileListSearchAsyncTask already running skipping new api call for search event: " + searchEvent.getSearchType());
+ Log_OC.d(TAG, "OCFileListSearchTask already running skipping new api call for search event: " + searchEvent.getSearchType());
}
return;
@@ -1942,11 +1941,10 @@ public class OCFileListFragment extends ExtendedListFragment implements
final User currentUser = accountManager.getUser();
- final RemoteOperation remoteOperation = getSearchRemoteOperation(currentUser, event);
+ final var remoteOperation = getSearchRemoteOperation(currentUser, event);
- remoteOperationAsyncTask = new OCFileListSearchAsyncTask(mContainerActivity, this, remoteOperation, currentUser, event);
-
- remoteOperationAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ searchTask = new OCFileListSearchTask(mContainerActivity, this, remoteOperation, currentUser, event, SharedListFragment.TASK_TIMEOUT);
+ searchTask.execute();
}
@@ -2325,4 +2323,15 @@ public class OCFileListFragment extends ExtendedListFragment implements
public SearchEvent getSearchEvent() {
return searchEvent;
}
+
+ public boolean isSearchEventFavorite() {
+ if (searchEvent == null) {
+ return false;
+ }
+ return searchEvent.getSearchType() == SearchRemoteOperation.SearchType.FAVORITE_SEARCH;
+ }
+
+ public boolean shouldNavigateBackToAllFiles() {
+ return ((this instanceof GalleryFragment) || isSearchEventFavorite() || DrawerActivity.menuItemId == R.id.nav_favorites);
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt
new file mode 100644
index 0000000..20d545a
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt
@@ -0,0 +1,102 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-FileCopyrightText: 2022 Álvaro Brey
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+package com.owncloud.android.ui.fragment
+
+import android.annotation.SuppressLint
+import androidx.lifecycle.lifecycleScope
+import com.nextcloud.client.account.User
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.lib.common.operations.RemoteOperation
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.ui.events.SearchEvent
+import com.owncloud.android.utils.DisplayUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+import java.lang.ref.WeakReference
+
+@Suppress("LongParameterList")
+@SuppressLint("NotifyDataSetChanged")
+class OCFileListSearchTask(
+ containerActivity: FileFragment.ContainerActivity,
+ fragment: OCFileListFragment,
+ private val remoteOperation: RemoteOperation>,
+ private val currentUser: User,
+ private val event: SearchEvent,
+ private val taskTimeout: Long
+) {
+ companion object {
+ private const val TAG = "OCFileListSearchTask"
+ }
+
+ private val activityReference: WeakReference = WeakReference(containerActivity)
+ private val fragmentReference: WeakReference = WeakReference(fragment)
+
+ private val fileDataStorageManager: FileDataStorageManager?
+ get() = activityReference.get()?.storageManager
+
+ private var job: Job? = null
+
+ @Suppress("TooGenericExceptionCaught", "DEPRECATION", "ReturnCount")
+ fun execute() {
+ Log_OC.d(TAG, "search task running, query: ${event.searchType}")
+ val fragment = fragmentReference.get() ?: return
+ val context = fragment.context ?: return
+
+ job = fragment.lifecycleScope.launch {
+ val result: RemoteOperationResult>? = withContext(Dispatchers.IO) {
+ try {
+ withTimeoutOrNull(taskTimeout) {
+ remoteOperation.execute(currentUser, context)
+ } ?: remoteOperation.executeNextcloudClient(currentUser, context)
+ } catch (e: Exception) {
+ Log_OC.e(TAG, "exception execute: ", e)
+ null
+ }
+ }
+
+ withContext(Dispatchers.Main) {
+ if (!fragment.isAdded || !fragment.searchFragment) {
+ Log_OC.e(TAG, "cannot fetch sharees fragment is not ready")
+ return@withContext
+ }
+
+ if (result?.isSuccess == true) {
+ if (result.resultData.isEmpty()) {
+ fragment.setEmptyListMessage(SearchType.NO_SEARCH)
+ return@withContext
+ }
+
+ fragment.searchEvent = event
+ fragment.adapter.setData(
+ result.resultData,
+ fragment.currentSearchType,
+ fileDataStorageManager,
+ fragment.mFile,
+ true
+ )
+
+ return@withContext
+ }
+
+ fragment.activity?.let {
+ DisplayUtils.showSnackMessage(it, R.string.error_fetching_sharees)
+ }
+ }
+ }
+ }
+
+ fun cancel() = job?.cancel(null)
+
+ fun isFinished(): Boolean = job?.isCompleted == true
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
index 36c1e6f..4e27e02 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt
@@ -32,5 +32,6 @@ enum class EmptyListState : Parcelable {
ADD_FOLDER,
ONLY_ON_DEVICE,
LOCAL_FILE_LIST_EMPTY_FILE,
- LOCAL_FILE_LIST_EMPTY_FOLDER
+ LOCAL_FILE_LIST_EMPTY_FOLDER,
+ ERROR
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt
index 6c86921..c146a57 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt
@@ -14,6 +14,7 @@ import androidx.lifecycle.lifecycleScope
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.logger.Logger
+import com.nextcloud.common.SessionTimeOut
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.operations.RemoteOperation
@@ -68,7 +69,7 @@ class SharedListFragment :
}
override fun getSearchRemoteOperation(currentUser: User?, event: SearchEvent?): RemoteOperation<*> =
- GetSharesRemoteOperation()
+ GetSharesRemoteOperation(false, SessionTimeOut(TASK_TIMEOUT, TASK_TIMEOUT))
@Suppress("DEPRECATION")
private suspend fun fetchFileData(partialFile: OCFile): OCFile? = withContext(Dispatchers.IO) {
@@ -185,5 +186,6 @@ class SharedListFragment :
companion object {
private val SHARED_TAG = SharedListFragment::class.java.simpleName
+ const val TASK_TIMEOUT = 120_000
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
index 6089e78..1aa3e15 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/UnifiedSearchFragment.kt
@@ -6,7 +6,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.owncloud.android.ui.fragment
-
import android.Manifest
import android.content.Context
import android.content.Intent
@@ -27,25 +26,33 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.nextcloud.client.account.CurrentAccountProvider
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.client.core.AsyncRunner
+import com.nextcloud.client.core.Clock
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.di.ViewModelFactory
import com.nextcloud.client.network.ClientFactory
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.utils.extensions.getTypedActivity
+import com.nextcloud.utils.extensions.searchFilesByName
import com.nextcloud.utils.extensions.typedActivity
import com.owncloud.android.R
import com.owncloud.android.databinding.ListFragmentBinding
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.status.NextcloudVersion
+import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.adapter.UnifiedSearchItemViewHolder
import com.owncloud.android.ui.adapter.UnifiedSearchListAdapter
import com.owncloud.android.ui.fragment.util.PairMediatorLiveData
+import com.owncloud.android.ui.interfaces.UnifiedSearchCurrentDirItemAction
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.ui.unifiedsearch.IUnifiedSearchViewModel
import com.owncloud.android.ui.unifiedsearch.ProviderID
@@ -55,6 +62,9 @@ import com.owncloud.android.ui.unifiedsearch.filterOutHiddenFiles
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.PermissionUtil
import com.owncloud.android.utils.theme.ViewThemeUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
@@ -67,7 +77,8 @@ class UnifiedSearchFragment :
Injectable,
UnifiedSearchListInterface,
SearchView.OnQueryTextListener,
- UnifiedSearchItemViewHolder.FilesAction {
+ UnifiedSearchItemViewHolder.FilesAction,
+ UnifiedSearchCurrentDirItemAction {
private lateinit var adapter: UnifiedSearchListAdapter
private var _binding: ListFragmentBinding? = null
val binding get() = _binding!!
@@ -77,16 +88,20 @@ class UnifiedSearchFragment :
companion object {
private const val TAG = "UnifiedSearchFragment"
- const val ARG_QUERY = "ARG_QUERY"
- const val ARG_HIDDEN_FILES = "ARG_HIDDEN_FILES"
+ private const val ARG_QUERY = "ARG_QUERY"
+ private const val ARG_HIDDEN_FILES = "ARG_HIDDEN_FILES"
+ private const val CURRENT_DIR_PATH = "CURRENT_DIR"
- fun newInstance(query: String?, listOfHiddenFiles: ArrayList?): UnifiedSearchFragment {
- val fragment = UnifiedSearchFragment()
- val args = Bundle()
- args.putString(ARG_QUERY, query)
- args.putStringArrayList(ARG_HIDDEN_FILES, listOfHiddenFiles)
- fragment.arguments = args
- return fragment
+ fun newInstance(
+ query: String?,
+ listOfHiddenFiles: ArrayList?,
+ currentDirPath: String
+ ): UnifiedSearchFragment = UnifiedSearchFragment().apply {
+ arguments = Bundle().apply {
+ putString(ARG_QUERY, query)
+ putString(CURRENT_DIR_PATH, currentDirPath)
+ putStringArrayList(ARG_HIDDEN_FILES, listOfHiddenFiles)
+ }
}
}
@@ -111,23 +126,26 @@ class UnifiedSearchFragment :
@Inject
lateinit var accountManager: UserAccountManager
+ @Inject
+ lateinit var appPreferences: AppPreferences
+
+ @Inject
+ lateinit var clock: Clock
+
private var listOfHiddenFiles = ArrayList()
private var showMoreActions = false
+ private var currentDir: OCFile? = null
+ private var initialQuery: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
vm = ViewModelProvider(this, vmFactory)[UnifiedSearchViewModel::class.java]
- setUpViewModel()
-
- val query = savedInstanceState?.getString(ARG_QUERY) ?: arguments?.getString(ARG_QUERY)
+ initialQuery = savedInstanceState?.getString(ARG_QUERY) ?: arguments?.getString(ARG_QUERY)
+ val currentDirPath = savedInstanceState?.getString(CURRENT_DIR_PATH) ?: arguments?.getString(CURRENT_DIR_PATH)
+ currentDir = storageManager.getFileByDecryptedRemotePath(currentDirPath)
listOfHiddenFiles =
savedInstanceState?.getStringArrayList(ARG_HIDDEN_FILES) ?: arguments?.getStringArrayList(ARG_HIDDEN_FILES)
?: ArrayList()
-
- if (!query.isNullOrEmpty()) {
- vm.setQuery(query)
- vm.initialQuery()
- }
}
@Suppress("DEPRECATION")
@@ -149,6 +167,15 @@ class UnifiedSearchFragment :
}
}
+ override fun onResume() {
+ super.onResume()
+ typedActivity()?.run {
+ setupToolbar()
+ setMainFabVisible(false)
+ updateActionBarTitleAndHomeButtonByString(null)
+ }
+ }
+
private fun supportsOpeningCalendarContactsLocally(): Boolean = storageManager
.getCapability(accountManager.user)
.version
@@ -181,7 +208,7 @@ class UnifiedSearchFragment :
// Because this fragment is opened with TextView onClick on the previous screen
maxWidth = Integer.MAX_VALUE
viewThemeUtils.androidx.themeToolbarSearchView(this)
- setQuery(vm.query.value, false)
+ setQuery(vm.query.value ?: initialQuery, false)
setOnQueryTextListener(this@UnifiedSearchFragment)
isIconified = false
clearFocus()
@@ -200,6 +227,7 @@ class UnifiedSearchFragment :
vm.setQuery("")
adapter.setData(emptyList())
+ adapter.setDataCurrentDirItems(listOf())
showStartYourSearch()
showKeyboard(searchView)
@@ -282,13 +310,14 @@ class UnifiedSearchFragment :
}
}
+ @Suppress("ComplexCondition")
private fun setUpViewModel() {
- vm.searchResults.observe(this, this::onSearchResultChanged)
- vm.isLoading.observe(this) { loading ->
+ vm.searchResults.observe(viewLifecycleOwner, this::onSearchResultChanged)
+ vm.isLoading.observe(viewLifecycleOwner) { loading ->
binding.swipeContainingList.isRefreshing = loading
}
- PairMediatorLiveData(vm.searchResults, vm.isLoading).observe(this) { pair ->
+ PairMediatorLiveData(vm.searchResults, vm.isLoading).observe(viewLifecycleOwner) { pair ->
if (pair.second == false) {
var count = 0
@@ -296,22 +325,26 @@ class UnifiedSearchFragment :
count += it.entries.size
}
- if (count == 0 && pair.first?.isNotEmpty() == true && context != null) {
+ if (count == 0 &&
+ pair.first?.isNotEmpty() == true &&
+ context != null &&
+ !adapter.isCurrentDirItemsEmpty()
+ ) {
showNoResult()
}
}
}
- vm.error.observe(this) { error ->
+ vm.error.observe(viewLifecycleOwner) { error ->
if (!error.isNullOrEmpty()) {
DisplayUtils.showSnackMessage(binding.root, error)
}
}
- vm.browserUri.observe(this) { uri ->
+ vm.browserUri.observe(viewLifecycleOwner) { uri ->
val browserIntent = Intent(Intent.ACTION_VIEW, uri)
startActivity(browserIntent)
}
- vm.file.observe(this) {
+ vm.file.observe(viewLifecycleOwner) {
showFile(it, showMoreActions)
}
}
@@ -337,30 +370,42 @@ class UnifiedSearchFragment :
}
}
- override fun onResume() {
- super.onResume()
- typedActivity()?.run {
- setupToolbar()
- setMainFabVisible(false)
- updateActionBarTitleAndHomeButtonByString(null)
- }
- }
-
private fun setupAdapter() {
+ val syncedFolderProvider = SyncedFolderProvider(requireContext().contentResolver, appPreferences, clock)
val gridLayoutManager = GridLayoutManager(requireContext(), 1)
- adapter = UnifiedSearchListAdapter(
- supportsOpeningCalendarContactsLocally(),
- storageManager,
- this,
- this,
- currentAccountProvider.user,
- requireContext(),
- viewThemeUtils
- )
- adapter.shouldShowFooters(true)
- adapter.setLayoutManager(gridLayoutManager)
- binding.listRoot.layoutManager = gridLayoutManager
- binding.listRoot.adapter = adapter
+
+ lifecycleScope.launch(Dispatchers.IO) {
+ val client =
+ getTypedActivity(FileActivity::class.java)?.clientRepository?.getNextcloudClient() ?: return@launch
+
+ withContext(Dispatchers.Main) {
+ adapter = UnifiedSearchListAdapter(
+ supportsOpeningCalendarContactsLocally(),
+ storageManager,
+ this@UnifiedSearchFragment,
+ this@UnifiedSearchFragment,
+ currentAccountProvider.user,
+ requireContext(),
+ viewThemeUtils,
+ appPreferences,
+ syncedFolderProvider,
+ client,
+ this@UnifiedSearchFragment
+ )
+
+ adapter.shouldShowFooters(true)
+ adapter.setLayoutManager(gridLayoutManager)
+ binding.listRoot.layoutManager = gridLayoutManager
+ binding.listRoot.adapter = adapter
+ searchInCurrentDirectory(initialQuery ?: "")
+
+ setUpViewModel()
+ if (!initialQuery.isNullOrEmpty()) {
+ vm.setQuery(initialQuery!!)
+ vm.initialQuery()
+ }
+ }
+ }
}
override fun onSearchResultClicked(searchResultEntry: SearchResultEntry) {
@@ -394,9 +439,19 @@ class UnifiedSearchFragment :
override fun onQueryTextChange(newText: String?): Boolean {
val closeButton = searchView?.findViewById(androidx.appcompat.R.id.search_close_btn)
closeButton?.visibility = if (newText?.isEmpty() == true) View.INVISIBLE else View.VISIBLE
+ searchInCurrentDirectory(newText ?: "")
return true
}
+ private fun searchInCurrentDirectory(query: String) {
+ currentDir?.run {
+ val files = storageManager
+ .searchFilesByName(this, accountManager.user.accountName, query)
+ .filter { !it.isEncrypted }
+ adapter.setDataCurrentDirItems(files)
+ }
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
@@ -406,4 +461,9 @@ class UnifiedSearchFragment :
showMoreActions = true
vm.openResult(searchResultEntry)
}
+
+ override fun openFile(remotePath: String, showMoreActions: Boolean) {
+ this.showMoreActions = showMoreActions
+ vm.getRemoteFile(remotePath)
+ }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java
index 03c8f0e..99ff53f 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java
@@ -404,7 +404,7 @@ public class BackupListFragment extends FileFragment implements Injectable {
private void closeFragment() {
ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity != null) {
- contactsPreferenceActivity.onBackPressed();
+ contactsPreferenceActivity.getOnBackPressedDispatcher().onBackPressed();
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
index 283e191..b4d0869 100755
--- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
+++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
@@ -34,6 +34,7 @@ import com.nextcloud.client.account.User;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.jobs.download.FileDownloadHelper;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.utils.EditorUtils;
import com.owncloud.android.MainApp;
@@ -42,6 +43,7 @@ import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.files.StreamMediaFileOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -86,7 +88,6 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
@@ -97,6 +98,7 @@ import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
+import kotlin.Unit;
/**
* Helper implementation for file operations locally and remote.
@@ -1006,17 +1008,22 @@ public class FileOperationsHelper {
}
}
- if (FileDownloadHelper.Companion.instance().isDownloading(currentUser, file)) {
+ final var fileDownloadHelper = FileDownloadHelper.Companion.instance();
+ if (fileDownloadHelper.isDownloading(currentUser, file)) {
List files = fileActivity.getStorageManager().getAllFilesRecursivelyInsideFolder(file);
- FileDownloadHelper.Companion.instance().cancelPendingOrCurrentDownloads(currentUser, files);
+ fileDownloadHelper.cancelPendingOrCurrentDownloads(currentUser, files);
}
- if (FileUploadHelper.Companion.instance().isUploading(currentUser, file)) {
- try {
- FileUploadHelper.Companion.instance().cancelFileUpload(file.getRemotePath(), currentUser.getAccountName());
- } catch (NoSuchElementException e) {
- Log_OC.e(TAG, "Error cancelling current upload because user does not exist!");
- }
+ if (file.isFolder()) {
+ fileDownloadHelper.cancelFolderDownload();
+ }
+
+ final var fileUploadHelper = FileUploadHelper.Companion.instance();
+ if (fileUploadHelper.isUploading(file.getRemotePath(), currentUser.getAccountName())) {
+ FileUploadWorker.Companion.cancelCurrentUpload(file.getRemotePath(), currentUser.getAccountName(), () -> {
+ fileUploadHelper.updateUploadStatus(file.getRemotePath(), currentUser.getAccountName(), UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED);
+ return Unit.INSTANCE;
+ });
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/UnifiedSearchCurrentDirItemAction.kt b/app/src/main/java/com/owncloud/android/ui/interfaces/UnifiedSearchCurrentDirItemAction.kt
new file mode 100644
index 0000000..e328546
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/interfaces/UnifiedSearchCurrentDirItemAction.kt
@@ -0,0 +1,12 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.interfaces
+
+interface UnifiedSearchCurrentDirItemAction {
+ fun openFile(remotePath: String, showMoreActions: Boolean)
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.kt b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.kt
index c125d80..61fbc9d 100644
--- a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.kt
+++ b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.kt
@@ -23,6 +23,7 @@ object NotificationUtils {
const val NOTIFICATION_CHANNEL_PUSH: String = "NOTIFICATION_CHANNEL_PUSH"
const val NOTIFICATION_CHANNEL_BACKGROUND_OPERATIONS: String = "NOTIFICATION_CHANNEL_BACKGROUND_OPERATIONS"
const val NOTIFICATION_CHANNEL_OFFLINE_OPERATIONS: String = "NOTIFICATION_CHANNEL_OFFLINE_OPERATIONS"
+ const val NOTIFICATION_CHANNEL_CONTENT_OBSERVER: String = "NOTIFICATION_CHANNEL_CONTENT_OBSERVER"
fun createUploadNotificationTag(file: OCFile): String =
createUploadNotificationTag(file.remotePath, file.storagePath)
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewBitmapActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewBitmapActivity.kt
index c526501..b6c9e86 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewBitmapActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewBitmapActivity.kt
@@ -54,7 +54,7 @@ class PreviewBitmapActivity :
}
override fun onSupportNavigateUp(): Boolean {
- onBackPressed()
+ onBackPressedDispatcher.onBackPressed()
return true
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
index c71c86b..e45c42c 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.kt
@@ -14,6 +14,7 @@ import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.view.View
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBar
import androidx.core.content.ContextCompat
import androidx.drawerlayout.widget.DrawerLayout
@@ -55,6 +56,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import java.io.Serializable
import javax.inject.Inject
import kotlin.math.max
+import kotlin.math.min
/**
* Holds a swiping gallery where image files contained in an Nextcloud directory are shown.
@@ -123,6 +125,7 @@ class PreviewImageActivity :
observeWorkerState()
applyDisplayCutOutTopPadding()
+ handleBackPress()
}
private fun applyDisplayCutOutTopPadding() {
@@ -174,7 +177,7 @@ class PreviewImageActivity :
)
} else {
// get parent from path
- var parentFolder = storageManager.getFileById(file.parentId)
+ var parentFolder = file?.let { storageManager.getFileById(it.parentId) }
if (parentFolder == null) {
// should not be necessary
@@ -194,7 +197,13 @@ class PreviewImageActivity :
viewPager = findViewById(R.id.fragmentPager)
- var position = if (savedPosition != null) savedPosition else previewImagePagerAdapter?.getFilePosition(file)
+ var position = if (savedPosition !=
+ null
+ ) {
+ savedPosition
+ } else {
+ file?.let { previewImagePagerAdapter?.getFilePosition(it) }
+ }
position = position?.toDouble()?.let { max(it, 0.0).toInt() }
viewPager?.adapter = previewImagePagerAdapter
@@ -207,16 +216,32 @@ class PreviewImageActivity :
viewPager?.setCurrentItem(position, false)
}
- if (position == 0 && !file.isDown) {
+ if (position == 0 && file?.isDown == false) {
// this is necessary because mViewPager.setCurrentItem(0) just after setting the
// adapter does not result in a call to #onPageSelected(0)
screenState = PreviewImageActivityState.WaitingForBinder
}
}
- override fun onBackPressed() {
- sendRefreshSearchEventBroadcast()
- super.onBackPressed()
+ private fun updateViewPagerAfterDeletionAndAdvanceForward() {
+ val deletePosition = viewPager?.currentItem ?: return
+ previewImagePagerAdapter?.let { adapter ->
+ val nextPosition = min(deletePosition, adapter.itemCount - 1)
+ viewPager?.setCurrentItem(nextPosition, true)
+ adapter.delete(deletePosition)
+ // Page needs to be reselected after the adapter has been updated. Otherwise, wrong title is shown
+ selectPage(nextPosition)
+ }
+ }
+
+ private fun handleBackPress() {
+ onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ sendRefreshSearchEventBroadcast()
+ isEnabled = false
+ onBackPressedDispatcher.onBackPressed()
+ }
+ })
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -256,7 +281,7 @@ class PreviewImageActivity :
if (file != null) {
// / Refresh the activity according to the Account and OCFile set
setFile(file) // reset after getting it fresh from storageManager
- updateActionBarTitle(getFile().fileName)
+ updateActionBarTitle(getFile()?.fileName)
// if (!stateWasRecovered) {
initViewPager(optionalUser.get())
@@ -278,9 +303,6 @@ class PreviewImageActivity :
super.onRemoteOperationFinish(operation, result)
if (operation is RemoveFileOperation) {
- val deletePosition = viewPager?.currentItem ?: return
- val nextPosition = if (deletePosition > 0) deletePosition - 1 else 0
-
previewImagePagerAdapter?.let {
if (it.itemCount <= 1) {
backToDisplayActivity()
@@ -288,12 +310,9 @@ class PreviewImageActivity :
}
}
- if (user.isPresent) {
- initViewPager(user.get())
+ if (result.isSuccess) {
+ updateViewPagerAfterDeletionAndAdvanceForward()
}
-
- viewPager?.setCurrentItem(nextPosition, true)
- previewImagePagerAdapter?.delete(deletePosition)
} else if (operation is SynchronizeFileOperation) {
onSynchronizeFileOperationFinish(result)
}
@@ -340,8 +359,10 @@ class PreviewImageActivity :
savedPosition?.let { position ->
previewImagePagerAdapter?.run {
- updateFile(position, file)
- notifyItemChanged(position)
+ file?.let {
+ updateFile(position, it)
+ notifyItemChanged(position)
+ }
}
if (user.isPresent) {
@@ -365,7 +386,9 @@ class PreviewImageActivity :
dismissLoadingDialog()
screenState = PreviewImageActivityState.Idle
file = downloadedFile
- startEditImageActivity(file)
+ file?.let {
+ startEditImageActivity(it)
+ }
}
override fun onResume() {
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt
index 430e330..9f75a69 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.kt
@@ -63,7 +63,9 @@ class PreviewImagePagerAdapter : FragmentStateAdapter {
imageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice)
val sortOrder = preferences.getSortOrderByFolder(parentFolder)
- imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList()).toMutableList()
+ val foldersBeforeFiles = preferences.isSortFoldersBeforeFiles()
+ val favoritesFirst = preferences.isSortFavoritesFirst()
+ imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList(), foldersBeforeFiles, favoritesFirst)
mObsoleteFragments = HashSet()
mObsoletePositions = HashSet()
@@ -101,7 +103,9 @@ class PreviewImagePagerAdapter : FragmentStateAdapter {
if (type == VirtualFolderType.FAVORITE) {
val sortOrder = preferences.getSortOrderByType(FileSortOrder.Type.favoritesListView)
- imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList()).toMutableList()
+ val foldersBeforeFiles = preferences.isSortFoldersBeforeFiles()
+ val favoritesFirst = preferences.isSortFavoritesFirst()
+ imageFiles = sortOrder.sortCloudFiles(imageFiles.toMutableList(), foldersBeforeFiles, favoritesFirst)
}
mObsoleteFragments = HashSet()
@@ -201,4 +205,11 @@ class PreviewImagePagerAdapter : FragmentStateAdapter {
override fun createFragment(position: Int): Fragment = getItem(position)
override fun getItemCount(): Int = imageFiles.size
+
+ override fun getItemId(position: Int): Long {
+ // The item ID function is needed to detect whether the deletion of the current item needs a UI update
+ return imageFiles.getOrNull(position)?.fileId ?: position.toLong()
+ }
+
+ override fun containsItem(itemId: Long): Boolean = imageFiles.any { it.fileId == itemId }
}
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
index c15405d..30266b0 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
@@ -384,18 +384,19 @@ class PreviewMediaActivity :
@Suppress("TooGenericExceptionCaught")
private fun playAudio() {
- if (file.isDown) {
- prepareAudioPlayer(file.storageUri)
+ if (file?.isDown == true) {
+ prepareAudioPlayer(file?.storageUri)
} else {
try {
- LoadStreamUrl(this, user, clientFactory).execute(file.localId)
+ LoadStreamUrl(this, user, clientFactory).execute(file?.localId)
} catch (e: Exception) {
Log_OC.e(TAG, "Loading stream url for Audio not possible: $e")
}
}
}
- private fun prepareAudioPlayer(uri: Uri) {
+ private fun prepareAudioPlayer(uri: Uri?) {
+ uri ?: return
audioMediaController?.let { audioPlayer ->
audioPlayer.addListener(object : Player.Listener {
@@ -434,7 +435,7 @@ class PreviewMediaActivity :
})
val mediaItem = MediaItem.Builder()
.setUri(uri)
- .setMediaMetadata(MediaMetadata.Builder().setTitle(file.fileName).build())
+ .setMediaMetadata(MediaMetadata.Builder().setTitle(file?.fileName).build())
.build()
audioPlayer.setMediaItem(mediaItem)
audioPlayer.playWhenReady = autoplay
@@ -563,8 +564,8 @@ class PreviewMediaActivity :
R.id.action_remove_file -> {
videoPlayer?.pause()
- val dialog = RemoveFilesDialogFragment.newInstance(file)
- dialog.show(supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
+ val dialog = file?.let { RemoveFilesDialogFragment.newInstance(it) }
+ dialog?.show(supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
}
R.id.action_see_details -> {
@@ -572,7 +573,7 @@ class PreviewMediaActivity :
}
R.id.action_sync_file -> {
- showSyncLoadingDialog(file.isFolder)
+ showSyncLoadingDialog(file?.isFolder == true)
fileOperationsHelper.syncFile(file)
}
@@ -586,7 +587,7 @@ class PreviewMediaActivity :
R.id.action_export_file -> {
val list = ArrayList()
- list.add(file)
+ file?.let { list.add(it) }
fileOperationsHelper.exportFiles(
list,
this,
@@ -673,18 +674,19 @@ class PreviewMediaActivity :
private fun playVideo() {
setupVideoView()
- if (file.isDown) {
- prepareVideoPlayer(file.storageUri)
+ if (file?.isDown == true) {
+ prepareVideoPlayer(file?.storageUri)
} else {
try {
- LoadStreamUrl(this, user, clientFactory).execute(file.localId)
+ LoadStreamUrl(this, user, clientFactory).execute(file?.localId)
} catch (e: Exception) {
Log_OC.e(TAG, "Loading stream url for Video not possible: $e")
}
}
}
- private fun prepareVideoPlayer(uri: Uri) {
+ private fun prepareVideoPlayer(uri: Uri?) {
+ uri ?: return
binding.progress.visibility = View.GONE
val videoMediaItem = MediaItem.fromUri(uri)
videoPlayer?.run {
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java
index d788e38..9be0339 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java
@@ -7,6 +7,7 @@
*/
package com.owncloud.android.ui.preview;
+import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -146,6 +147,7 @@ public class PreviewTextFileFragment extends PreviewTextFragment {
/**
* Reads the file to preview and shows its contents. Too critical to be anonymous.
*/
+ @SuppressLint("StaticFieldLeak")
private class TextLoadAsyncTask extends AsyncTask {
private static final int PARAMS_LENGTH = 1;
private final WeakReference textViewReference;
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.kt
index a3aec17..99b0bff 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.kt
@@ -165,7 +165,7 @@ abstract class PreviewTextFragment :
* Finishes the preview
*/
protected fun finish() {
- requireActivity().runOnUiThread { requireActivity().onBackPressed() }
+ requireActivity().runOnUiThread { requireActivity().onBackPressedDispatcher.onBackPressed() }
}
companion object {
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt
index 30b9da6..dd1e681 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt
+++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFullscreenDialog.kt
@@ -7,15 +7,16 @@
*/
package com.owncloud.android.ui.preview
-import android.app.Activity
import android.app.Dialog
import android.os.Build
import android.view.ViewGroup
import android.view.Window
+import androidx.activity.addCallback
import androidx.annotation.OptIn
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
+import androidx.fragment.app.FragmentActivity
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@@ -35,7 +36,7 @@ import com.owncloud.android.lib.common.utils.Log_OC
*/
@OptIn(UnstableApi::class)
class PreviewVideoFullscreenDialog(
- private val activity: Activity,
+ private val activity: FragmentActivity,
nextcloudClient: NextcloudClient,
private val sourceExoPlayer: ExoPlayer,
private val sourceView: PlayerView
@@ -69,6 +70,7 @@ class PreviewVideoFullscreenDialog(
binding.videoPlayer.player = mExoPlayer
mExoPlayer.prepare()
}
+ handleOnBackPressed()
}
private fun isRotatedVideo(): Boolean {
@@ -95,7 +97,7 @@ class PreviewVideoFullscreenDialog(
setOnShowListener {
enableImmersiveMode()
switchTargetViewFromSource()
- binding.videoPlayer.setFullscreenButtonClickListener { onBackPressed() }
+ binding.videoPlayer.setFullscreenButtonClickListener { activity.onBackPressedDispatcher.onBackPressed() }
if (isPlaying) {
mExoPlayer.play()
}
@@ -111,23 +113,25 @@ class PreviewVideoFullscreenDialog(
}
}
- override fun onBackPressed() {
- val isPlaying = mExoPlayer.isPlaying
- if (isPlaying) {
- mExoPlayer.pause()
- }
- setOnDismissListener {
- disableImmersiveMode()
- playingStateListener?.let {
- mExoPlayer.removeListener(it)
- }
- switchTargetViewToSource()
+ private fun handleOnBackPressed() {
+ activity.onBackPressedDispatcher.addCallback(activity) {
+ val isPlaying = mExoPlayer.isPlaying
if (isPlaying) {
- sourceExoPlayer.play()
+ mExoPlayer.pause()
}
- sourceView.showController()
+ setOnDismissListener {
+ disableImmersiveMode()
+ playingStateListener?.let {
+ mExoPlayer.removeListener(it)
+ }
+ switchTargetViewToSource()
+ if (isPlaying) {
+ sourceExoPlayer.play()
+ }
+ sourceView.showController()
+ }
+ dismiss()
}
- dismiss()
}
private fun switchTargetViewToSource() {
diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
index 64a103b..2ceef02 100644
--- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
+++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt
@@ -128,6 +128,7 @@ class TrashbinActivity :
updateActionBarTitleAndHomeButtonByString(getString(R.string.trashbin_activity_title))
setupDrawer()
+ handleBackPress()
}
override fun onStart() {
@@ -179,8 +180,6 @@ class TrashbinActivity :
loadFolder()
- handleOnBackPressed()
-
mMultiChoiceModeListener = MultiChoiceModeListener(
this,
trashbinListAdapter,
@@ -189,7 +188,7 @@ class TrashbinActivity :
addDrawerListener(mMultiChoiceModeListener)
}
- private fun handleOnBackPressed() {
+ private fun handleBackPress() {
onBackPressedDispatcher.addCallback(
this,
onBackPressedCallback
@@ -390,7 +389,7 @@ class TrashbinActivity :
binding.emptyList.emptyListViewHeadline.visibility = View.VISIBLE
binding.emptyList.emptyListViewText.visibility = View.VISIBLE
binding.emptyList.emptyListView.visibility = View.VISIBLE
- binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_cloud_sync_off)
+ binding.emptyList.emptyListIcon.setImageResource(R.drawable.ic_sync_off)
}
private fun openActionsMenu(filesCount: Int, checkedFiles: Set) {
diff --git a/app/src/main/java/com/owncloud/android/ui/unifiedsearch/IUnifiedSearchViewModel.kt b/app/src/main/java/com/owncloud/android/ui/unifiedsearch/IUnifiedSearchViewModel.kt
index 449fdf5..04c7d0e 100644
--- a/app/src/main/java/com/owncloud/android/ui/unifiedsearch/IUnifiedSearchViewModel.kt
+++ b/app/src/main/java/com/owncloud/android/ui/unifiedsearch/IUnifiedSearchViewModel.kt
@@ -23,4 +23,6 @@ interface IUnifiedSearchViewModel {
fun loadMore(provider: ProviderID)
fun openResult(result: SearchResultEntry)
fun setQuery(query: String)
+ fun openFile(remotePath: String)
+ fun getRemoteFile(remotePath: String)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/unifiedsearch/UnifiedSearchViewModel.kt b/app/src/main/java/com/owncloud/android/ui/unifiedsearch/UnifiedSearchViewModel.kt
index aaa82a6..97ba8e4 100644
--- a/app/src/main/java/com/owncloud/android/ui/unifiedsearch/UnifiedSearchViewModel.kt
+++ b/app/src/main/java/com/owncloud/android/ui/unifiedsearch/UnifiedSearchViewModel.kt
@@ -156,21 +156,25 @@ class UnifiedSearchViewModel(application: Application) :
}
}
- fun openFile(fileUrl: String) {
+ override fun openFile(remotePath: String) {
if (isLoading.value == false) {
isLoading.value = true
- val user = currentAccountProvider.user
- val task = GetRemoteFileTask(
- context,
- fileUrl,
- clientFactory.create(currentAccountProvider.user),
- FileDataStorageManager(user, context.contentResolver),
- user
- )
- runner.postQuickTask(task, onResult = this::onFileRequestResult)
+ getRemoteFile(remotePath)
}
}
+ override fun getRemoteFile(remotePath: String) {
+ val user = currentAccountProvider.user
+ val task = GetRemoteFileTask(
+ context,
+ remotePath,
+ clientFactory.create(user),
+ FileDataStorageManager(user, context.contentResolver),
+ user
+ )
+ runner.postQuickTask(task, onResult = this::onFileRequestResult)
+ }
+
fun onError(error: Throwable) {
Log_OC.e(TAG, "Error: " + error.stackTrace)
}
diff --git a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.java b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.java
index 881661c..064fd73 100644
--- a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.java
@@ -15,8 +15,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
-import android.graphics.ImageDecoder;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -25,10 +23,9 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.text.TextUtils;
import android.widget.ImageView;
+import com.nextcloud.utils.BitmapExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -36,8 +33,6 @@ import com.owncloud.android.lib.resources.users.Status;
import com.owncloud.android.lib.resources.users.StatusType;
import com.owncloud.android.ui.StatusDrawable;
-import java.io.File;
-import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -45,12 +40,13 @@ import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.exifinterface.media.ExifInterface;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import static com.nextcloud.utils.extensions.ThumbnailsCacheManagerExtensionsKt.getExifOrientation;
+
/**
* Utility class with methods for decoding Bitmaps.
*/
@@ -77,23 +73,6 @@ public final class BitmapUtils {
return resultBitmap;
}
- @Nullable
- @RequiresApi(Build.VERSION_CODES.P)
- private static Bitmap decodeSampledBitmapViaImageDecoder(@NonNull File file, int reqWidth, int reqHeight) {
- try {
- Log_OC.i(TAG, "Decoding Bitmap via ImageDecoder");
-
- final var imageDecoderSource = ImageDecoder.createSource(file);
-
- return ImageDecoder.decodeBitmap(imageDecoderSource, (decoder, info, source1) -> decoder.setTargetSize(reqWidth, reqHeight)
- );
-
- } catch (IOException exception) {
- Log_OC.w(TAG, "Decoding Bitmap via ImageDecoder failed, BitmapFactory.decodeFile will be used");
- return null;
- }
- }
-
/**
* Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap will be drawn in a
* surface of reqWidth x reqHeight
@@ -105,41 +84,7 @@ public final class BitmapUtils {
*/
@Nullable
public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {
- if (TextUtils.isEmpty(srcPath)) {
- Log_OC.e(TAG, "srcPath is null or empty");
- return null;
- }
-
- final var file = new File(srcPath);
- if (!file.exists()) {
- Log_OC.e(TAG, "File does not exists, returning null");
- return null;
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- final var result = decodeSampledBitmapViaImageDecoder(file, reqWidth, reqHeight);
- if (result != null) {
- return result;
- }
- }
-
- Log_OC.i(TAG, "Decoding Bitmap via BitmapFactory.decodeFile");
-
- // set desired options that will affect the size of the bitmap
- final Options options = new Options();
-
- // make a false load of the bitmap to get its dimensions
- options.inJustDecodeBounds = true;
-
- // FIXME after auto-rename can't generate thumbnail from localPath
- BitmapFactory.decodeFile(srcPath, options);
-
- // calculate factor to subsample the bitmap
- options.inSampleSize = calculateSampleFactor(options, reqWidth, reqHeight);
-
- // decode bitmap with inSampleSize set
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeFile(srcPath, options);
+ return BitmapExtensionsKt.decodeSampledBitmapFromFile(srcPath, reqWidth, reqHeight);
}
/**
@@ -153,15 +98,6 @@ public final class BitmapUtils {
var originalWidth = bitmapResolution[0];
var originalHeight = bitmapResolution[1];
- // Detect Orientation and swap height/width if the image is to be rotated
- var shouldRotate = detectRotateImage(storagePath);
- if (shouldRotate) {
- // Swap the width and height
- var tempWidth = originalWidth;
- originalWidth = originalHeight;
- originalHeight = tempWidth;
- }
-
// Calculate the scaling factors based on screen dimensions
var widthScaleFactor = (float) minWidth/ originalWidth;
var heightScaleFactor = (float) minHeight / originalHeight;
@@ -173,7 +109,14 @@ public final class BitmapUtils {
var scaledWidth = (int) (originalWidth * scaleFactor);
var scaledHeight = (int) (originalHeight * scaleFactor);
- return decodeSampledBitmapFromFile(storagePath, scaledWidth, scaledHeight);
+ var shouldRotate = detectRotateImage(storagePath);
+ var result = decodeSampledBitmapFromFile(storagePath, scaledWidth, scaledHeight);
+ if (shouldRotate) {
+ int orientation = getExifOrientation(storagePath);
+ return BitmapExtensionsKt.rotateBitmapViaExif(result, orientation);
+ } else {
+ return result;
+ }
}
/**
* Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing the memory overload and
@@ -222,74 +165,6 @@ public final class BitmapUtils {
return Bitmap.createScaledBitmap(bitmap, w, h, true);
}
- /**
- * Rotate bitmap according to EXIF orientation. Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
- *
- * @param bitmap Bitmap to be rotated
- * @param storagePath Path to source file of bitmap. Needed for EXIF information.
- * @return correctly EXIF-rotated bitmap
- */
- public static Bitmap rotateImage(Bitmap bitmap, String storagePath) {
- Bitmap resultBitmap = bitmap;
-
- try {
- ExifInterface exifInterface = new ExifInterface(storagePath);
- int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
-
- if (orientation != ExifInterface.ORIENTATION_NORMAL) {
- Matrix matrix = new Matrix();
- switch (orientation) {
- // 2
- case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: {
- matrix.postScale(-1.0f, 1.0f);
- break;
- }
- // 3
- case ExifInterface.ORIENTATION_ROTATE_180: {
- matrix.postRotate(180);
- break;
- }
- // 4
- case ExifInterface.ORIENTATION_FLIP_VERTICAL: {
- matrix.postScale(1.0f, -1.0f);
- break;
- }
- // 5
- case ExifInterface.ORIENTATION_TRANSPOSE: {
- matrix.postRotate(-90);
- matrix.postScale(1.0f, -1.0f);
- break;
- }
- // 6
- case ExifInterface.ORIENTATION_ROTATE_90: {
- matrix.postRotate(90);
- break;
- }
- // 7
- case ExifInterface.ORIENTATION_TRANSVERSE: {
- matrix.postRotate(90);
- matrix.postScale(1.0f, -1.0f);
- break;
- }
- // 8
- case ExifInterface.ORIENTATION_ROTATE_270: {
- matrix.postRotate(270);
- break;
- }
- }
-
- // Rotate the bitmap
- resultBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
- if (!resultBitmap.equals(bitmap)) {
- bitmap.recycle();
- }
- }
- } catch (Exception exception) {
- Log_OC.e("BitmapUtil", "Could not rotate the image: " + storagePath);
- }
- return resultBitmap;
- }
-
/**
* Detect if Image will be rotated according to EXIF orientation. Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
*
diff --git a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
index 795bd43..dfedc9f 100644
--- a/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/DisplayUtils.java
@@ -79,6 +79,7 @@ import java.net.IDN;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -828,7 +829,7 @@ public final class DisplayUtils {
private static void setThumbnailFirstTimeForFile(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, List asyncTasks, boolean gridView, LoaderImageView shimmerThumbnail, User user, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) {
if (file.getRemoteId() != null) {
- generateNewThumbnail(file, thumbnailView, user, storageManager, asyncTasks, gridView, context, shimmerThumbnail, preferences, viewThemeUtils);
+ generateNewThumbnail(file, thumbnailView, user, storageManager, new ArrayList<>(asyncTasks), gridView, context, shimmerThumbnail, preferences, viewThemeUtils);
return;
}
@@ -873,7 +874,7 @@ public final class DisplayUtils {
private static void setThumbnailFromCache(OCFile file, ImageView thumbnailView, FileDataStorageManager storageManager, List asyncTasks, boolean gridView, LoaderImageView shimmerThumbnail, User user, AppPreferences preferences, Context context, ViewThemeUtils viewThemeUtils) {
final var thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
if (thumbnail == null || file.isUpdateThumbnailNeeded()) {
- generateNewThumbnail(file, thumbnailView, user, storageManager, asyncTasks, gridView, context, shimmerThumbnail, preferences, viewThemeUtils);
+ generateNewThumbnail(file, thumbnailView, user, storageManager, new ArrayList<>(asyncTasks), gridView, context, shimmerThumbnail, preferences, viewThemeUtils);
setThumbnailBackgroundForPNGFileIfNeeded(file, context, thumbnailView);
return;
}
@@ -901,7 +902,7 @@ public final class DisplayUtils {
ImageView thumbnailView,
User user,
FileDataStorageManager storageManager,
- List asyncTasks,
+ ArrayList asyncTasks,
boolean gridView,
Context context,
LoaderImageView shimmerThumbnail,
@@ -986,7 +987,7 @@ public final class DisplayUtils {
new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file,
file.getRemoteId()));
thumbnailView.invalidate();
- } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage());
}
}
diff --git a/app/src/main/java/com/owncloud/android/utils/FileSortOrder.kt b/app/src/main/java/com/owncloud/android/utils/FileSortOrder.kt
index 0767754..b2b8c3d 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileSortOrder.kt
+++ b/app/src/main/java/com/owncloud/android/utils/FileSortOrder.kt
@@ -80,7 +80,7 @@ open class FileSortOrder(@JvmField var name: String, var isAscending: Boolean) {
* @param files files to sort
*/
@JvmStatic
- fun sortCloudFilesByFavourite(files: MutableList): List {
+ fun sortCloudFilesByFavourite(files: MutableList): MutableList {
files.sortWith { o1: OCFile, o2: OCFile ->
when {
o1.isFavorite && o2.isFavorite -> 0
@@ -91,9 +91,46 @@ open class FileSortOrder(@JvmField var name: String, var isAscending: Boolean) {
}
return files
}
+
+ /**
+ * Sorts list by Folders.
+ *
+ * @param files files to sort
+ */
+ @JvmStatic
+ fun sortCloudFilesByFolderFirst(files: MutableList): MutableList {
+ files.sortWith { o1: OCFile, o2: OCFile ->
+ when {
+ o1.isFolder && o2.isFolder -> 0
+ o1.isFolder -> -1
+ o2.isFolder -> 1
+ else -> 0
+ }
+ }
+ return files
+ }
}
- open fun sortCloudFiles(files: MutableList): List = sortCloudFilesByFavourite(files)
+ @Suppress("ReturnCount")
+ open fun sortCloudFiles(
+ files: MutableList,
+ foldersBeforeFiles: Boolean,
+ favoritesFirst: Boolean
+ ): MutableList {
+ if (foldersBeforeFiles && favoritesFirst) {
+ return sortCloudFilesByFavourite(sortCloudFilesByFolderFirst(files))
+ }
+
+ if (foldersBeforeFiles) {
+ return sortCloudFilesByFolderFirst(files)
+ }
+
+ if (favoritesFirst) {
+ return sortCloudFilesByFavourite(files)
+ }
+
+ return files
+ }
open fun sortLocalFiles(files: MutableList): List = files
diff --git a/app/src/main/java/com/owncloud/android/utils/FileSortOrderByDate.kt b/app/src/main/java/com/owncloud/android/utils/FileSortOrderByDate.kt
index d62f076..6d59836 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileSortOrderByDate.kt
+++ b/app/src/main/java/com/owncloud/android/utils/FileSortOrderByDate.kt
@@ -20,12 +20,16 @@ class FileSortOrderByDate(name: String, ascending: Boolean) : FileSortOrder(name
*
* @param files list of files to sort
*/
- override fun sortCloudFiles(files: MutableList): List {
+ override fun sortCloudFiles(
+ files: MutableList,
+ foldersBeforeFiles: Boolean,
+ favoritesFirst: Boolean
+ ): MutableList {
val multiplier = if (isAscending) 1 else -1
files.sortWith { o1: OCFile, o2: OCFile ->
multiplier * o1.modificationTimestamp.compareTo(o2.modificationTimestamp)
}
- return super.sortCloudFiles(files)
+ return super.sortCloudFiles(files, foldersBeforeFiles, favoritesFirst)
}
/**
diff --git a/app/src/main/java/com/owncloud/android/utils/FileSortOrderByName.kt b/app/src/main/java/com/owncloud/android/utils/FileSortOrderByName.kt
index 5b27b63..cb39a2a 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileSortOrderByName.kt
+++ b/app/src/main/java/com/owncloud/android/utils/FileSortOrderByName.kt
@@ -24,9 +24,13 @@ class FileSortOrderByName internal constructor(name: String?, ascending: Boolean
* @param files files to sort
*/
@SuppressFBWarnings("Bx")
- override fun sortCloudFiles(files: MutableList): List {
- val sortedByName = sortServerFiles(files)
- return super.sortCloudFiles(sortedByName)
+ override fun sortCloudFiles(
+ files: MutableList,
+ foldersBeforeFiles: Boolean,
+ favoritesFirst: Boolean
+ ): MutableList {
+ val sortedByName = sortOnlyByName(files)
+ return super.sortCloudFiles(sortedByName, foldersBeforeFiles, favoritesFirst)
}
/**
@@ -51,6 +55,11 @@ class FileSortOrderByName internal constructor(name: String?, ascending: Boolean
return files
}
+ private fun sortOnlyByName(files: MutableList): MutableList {
+ files.sortWith { o1: OCFile, o2: OCFile -> sortMultiplier * AlphanumComparator.compare(o1, o2) }
+ return files
+ }
+
/**
* Sorts list by Name.
*
diff --git a/app/src/main/java/com/owncloud/android/utils/FileSortOrderBySize.kt b/app/src/main/java/com/owncloud/android/utils/FileSortOrderBySize.kt
index fbe999a..5b188f1 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileSortOrderBySize.kt
+++ b/app/src/main/java/com/owncloud/android/utils/FileSortOrderBySize.kt
@@ -17,9 +17,13 @@ import java.io.File
*/
class FileSortOrderBySize internal constructor(name: String?, ascending: Boolean) : FileSortOrder(name!!, ascending) {
- override fun sortCloudFiles(files: MutableList): List {
+ override fun sortCloudFiles(
+ files: MutableList,
+ foldersBeforeFiles: Boolean,
+ favoritesFirst: Boolean
+ ): MutableList {
val sortedBySize = sortServerFiles(files)
- return super.sortCloudFiles(sortedBySize)
+ return super.sortCloudFiles(sortedBySize, foldersBeforeFiles, favoritesFirst)
}
override fun sortTrashbinFiles(files: MutableList): List {
@@ -40,10 +44,14 @@ class FileSortOrderBySize internal constructor(name: String?, ascending: Boolean
}
override fun sortLocalFiles(files: MutableList): List {
+ val folderSizes =
+ files.associateWith { file -> FileStorageUtils.getFolderSize(file) }
+
files.sortWith { o1: File, o2: File ->
when {
- o1.isDirectory && o2.isDirectory -> sortMultiplier * FileStorageUtils.getFolderSize(o1)
- .compareTo(FileStorageUtils.getFolderSize(o2))
+ o1.isDirectory && o2.isDirectory -> sortMultiplier * (folderSizes[o1] ?: 0L).compareTo(
+ folderSizes[o2] ?: 0L
+ )
o1.isDirectory -> -1
o2.isDirectory -> 1
else -> sortMultiplier * o1.length().compareTo(o2.length())
diff --git a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
index 550b0e8..b9988cb 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
@@ -11,6 +11,7 @@
package com.owncloud.android.utils;
import android.Manifest;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -37,9 +38,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -80,8 +81,8 @@ public final class FileStorageUtils {
String decoded;
try {
decoded = URLDecoder.decode(filename, StandardCharsets.UTF_8.toString());
- } catch (UnsupportedEncodingException e) {
- return false;
+ } catch (Exception e) {
+ decoded = filename;
}
int[] bidiControlCharacters = {
@@ -231,6 +232,7 @@ public final class FileStorageUtils {
*
* @return Optimistic number of available bytes (can be less)
*/
+ @SuppressLint("UsableSpace")
public static long getUsableSpace() {
File savePath = new File(MainApp.getStoragePath());
return savePath.getUsableSpace();
@@ -404,24 +406,28 @@ public final class FileStorageUtils {
* @return Size in bytes
*/
public static long getFolderSize(File dir) {
- if (dir.exists() && dir.isDirectory()) {
- File[] files = dir.listFiles();
-
- if (files != null) {
- long result = 0;
- for (File f : files) {
- if (f.isDirectory()) {
- result += getFolderSize(f);
- } else {
- result += f.length();
- }
- }
- return result;
- }
+ if (dir == null || !dir.exists() || !dir.isDirectory()) {
+ return 0;
}
- return 0;
+
+ File[] files = dir.listFiles();
+ if (files == null) {
+ return 0;
+ }
+
+ long result = 0;
+ for (File f : files) {
+ if (f.isDirectory()) {
+ result += getFolderSize(f);
+ continue;
+ }
+ result += f.length();
+ }
+
+ return result;
}
+
/**
* Mimetype String of a file.
*
@@ -531,7 +537,11 @@ public final class FileStorageUtils {
}
storageManager.deleteFileInMediaScan(file.getAbsolutePath());
- file.delete();
+ try {
+ Files.deleteIfExists(file.toPath());
+ } catch (Exception e) {
+ Log_OC.e("Error deleting file: ", e.getMessage());
+ }
}
public static boolean deleteRecursive(File file) {
diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
index 9dfa3fa..94461e0 100644
--- a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
+++ b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
@@ -18,9 +18,11 @@ import android.provider.MediaStore;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.device.PowerManagementService;
import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
+import com.nextcloud.client.jobs.ContentObserverWork;
+import com.nextcloud.client.jobs.autoUpload.AutoUploadHelper;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.utils.extensions.UriExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.datamodel.FilesystemDataProvider;
import com.owncloud.android.datamodel.MediaFolderType;
@@ -29,20 +31,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.lib.common.utils.Log_OC;
-import org.lukhnos.nnio.file.AccessDeniedException;
-import org.lukhnos.nnio.file.FileVisitResult;
-import org.lukhnos.nnio.file.FileVisitor;
-import org.lukhnos.nnio.file.Path;
-import org.lukhnos.nnio.file.Paths;
-import org.lukhnos.nnio.file.SimpleFileVisitor;
-import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
-import org.lukhnos.nnio.file.Files;
-import org.lukhnos.nnio.file.impl.FileBasedPathImpl;
-
import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
@@ -58,110 +47,7 @@ public final class FilesSyncHelper {
// utility class -> private constructor
}
- /**
- * Copy of {@link Files#walkFileTree(Path, FileVisitor)} that walks the file tree in random order.
- *
- * @see org.lukhnos.nnio.file.Files#walkFileTree(Path, FileVisitor)
- */
- public static void walkFileTreeRandomly(Path start, FileVisitor super Path> visitor) throws IOException {
- File file = start.toFile();
- if (!file.canRead()) {
- Log_OC.d(TAG, "walkFileTreeRandomly, cant read the file: " + file.getAbsolutePath());
- visitor.visitFileFailed(start, new AccessDeniedException(file.toString()));
- } else {
- Log_OC.d(TAG, "walkFileTreeRandomly, reading file: " + file.getAbsolutePath());
-
- if (Files.isDirectory(start)) {
- Log_OC.d(TAG, "walkFileTreeRandomly, file is directory: " + file.getAbsolutePath());
-
- FileVisitResult preVisitDirectoryResult = visitor.preVisitDirectory(start, null);
- if (preVisitDirectoryResult == FileVisitResult.CONTINUE) {
- Log_OC.d(TAG, "walkFileTreeRandomly, preVisitDirectoryResult == FileVisitResult.CONTINUE");
- File[] children = start.toFile().listFiles();
- if (children != null) {
- Log_OC.d(TAG, "walkFileTreeRandomly, children exists");
-
- Collections.shuffle(Arrays.asList(children));
- File[] var5 = children;
- int var6 = children.length;
-
- for(int var7 = 0; var7 < var6; ++var7) {
- Log_OC.d(TAG, "walkFileTreeRandomly -- recursive call");
- File child = var5[var7];
- walkFileTreeRandomly(FileBasedPathImpl.get(child), visitor);
- }
-
- visitor.postVisitDirectory(start, null);
- } else {
- Log_OC.w(TAG, "walkFileTreeRandomly, children is null");
- }
- } else {
- Log_OC.w(TAG, "walkFileTreeRandomly, preVisitDirectoryResult != FileVisitResult.CONTINUE");
- }
- } else {
- Log_OC.d(TAG, "walkFileTreeRandomly, file is not directory");
- visitor.visitFile(start, new BasicFileAttributes(file));
- }
- }
- }
-
- private static void insertCustomFolderIntoDB(Path path,
- SyncedFolder syncedFolder,
- FilesystemDataProvider filesystemDataProvider,
- long lastCheck) {
- Log_OC.d(TAG, "insertCustomFolderIntoDB called");
- final long enabledTimestampMs = syncedFolder.getEnabledTimestampMs();
-
- try {
- walkFileTreeRandomly(path, new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
- File file = path.toFile();
- if (syncedFolder.isExcludeHidden() && file.isHidden()) {
- Log_OC.w(TAG, "skipping files, exclude hidden file/folder: " + path);
- // exclude hidden file or folder
- return FileVisitResult.CONTINUE;
- }
-
- if (attrs.lastModifiedTime().toMillis() < lastCheck) {
- Log_OC.w(TAG, "skipping files that already checked: " + path);
- // skip files that were already checked
- return FileVisitResult.CONTINUE;
- }
-
- if (syncedFolder.isExisting() || attrs.lastModifiedTime().toMillis() >= enabledTimestampMs) {
- // storeOrUpdateFileValue takes a few ms
- // -> Rest of this file check takes not even 1 ms.
- filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
- attrs.lastModifiedTime().toMillis(),
- file.isDirectory(), syncedFolder);
- } else {
- Log_OC.w(TAG, "skipping files. SynchedFolder not exists or enabledTimestampMs not meeting condition" + path);
- }
-
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
- if (syncedFolder.isExcludeHidden() && dir.compareTo(Paths.get(syncedFolder.getLocalPath())) != 0 && dir.toFile().isHidden()) {
- Log_OC.d(TAG, "skipping hidden path: " + dir.getFileName());
- return FileVisitResult.SKIP_SUBTREE;
- }
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult visitFileFailed(Path file, IOException exc) {
- return FileVisitResult.CONTINUE;
- }
- });
- } catch (IOException e) {
- Log_OC.e(TAG, "Something went wrong while indexing files for auto upload: " + e.getLocalizedMessage());
- }
- }
-
- public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
+ public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder, AutoUploadHelper helper) {
Log_OC.d(TAG, "insertAllDBEntriesForSyncedFolder, called. ID: " + syncedFolder.getId());
final Context context = MainApp.getAppContext();
@@ -195,8 +81,7 @@ public final class FilesSyncHelper {
} else {
Log_OC.d(TAG, "inserting other media types: " + mediaType.toString());
FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
- Path path = Paths.get(syncedFolder.getLocalPath());
- FilesSyncHelper.insertCustomFolderIntoDB(path, syncedFolder, filesystemDataProvider, lastCheckTimestampMs);
+ helper.insertCustomFolderIntoDB(syncedFolder, filesystemDataProvider);
}
Log_OC.d(TAG,"File-sync finished full check for custom folder "+syncedFolder.getLocalPath()+" within "+(System.nanoTime() - startTime)+ "ns");
@@ -213,47 +98,58 @@ public final class FilesSyncHelper {
}
}
- public static void insertChangedEntries(SyncedFolder syncedFolder,
- String[] changedFiles) {
- Log_OC.d(TAG, "insertChangedEntries, called. ID: " + syncedFolder.getId());
- final ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
- final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
- for (String changedFileURI : changedFiles){
- String changedFile = getFileFromURI(changedFileURI);
- if (syncedFolder.containsTypedFile(changedFile)){
- File file = new File(changedFile);
- if (!file.exists()) {
- Log_OC.w(TAG, "syncedFolder contains not existing changed file: " + changedFile);
- }
- filesystemDataProvider.storeOrUpdateFileValue(changedFile,
- file.lastModified(),file.isDirectory(),
- syncedFolder);
- } else {
- Log_OC.w(TAG, "syncedFolder not contains typed file, changedFile: " + changedFile);
- }
- }
- }
-
- private static String getFileFromURI(String uri){
- Log_OC.d(TAG, "getFileFromURI, URI: " + uri);
+ /**
+ * Attempts to get the file path from a content URI string (e.g., content://media/external/images/media/2281)
+ * and checks its type. If the conditions are met, the file is stored for auto-upload.
+ *
+ * If any attempt fails, the method returns {@code false}.
+ *
+ * @param syncedFolder The folder marked for auto-upload.
+ * @param contentUris An array of content URI strings collected from {@link ContentObserverWork##checkAndTriggerAutoUpload()}.
+ * @return {@code true} if all changed content URIs were successfully stored; {@code false} otherwise.
+ */
+ public static boolean insertChangedEntries(SyncedFolder syncedFolder, String[] contentUris) {
+ Log_OC.d(TAG, "insertChangedEntries, syncedFolderID: " + syncedFolder.getId());
final Context context = MainApp.getAppContext();
+ final ContentResolver contentResolver = context.getContentResolver();
+ final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
+ for (String contentUriString : contentUris) {
+ if (contentUriString == null) {
+ Log_OC.w(TAG, "null content uri string");
+ return false;
+ }
- Cursor cursor;
- int column_index_data;
- String filePath = null;
+ Uri contentUri;
+ try {
+ contentUri = Uri.parse(contentUriString);
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Invalid URI: " + contentUriString, e);
+ return false;
+ }
- String[] projection = {MediaStore.MediaColumns.DATA};
+ String filePath = UriExtensionsKt.toFilePath(contentUri, context);
+ if (filePath == null) {
+ Log_OC.w(TAG, "File path is null");
+ return false;
+ }
- cursor = context.getContentResolver().query(Uri.parse(uri), projection, null, null, null, null);
+ File file = new File(filePath);
+ if (!file.exists()) {
+ Log_OC.w(TAG, "syncedFolder contains not existing changed file: " + filePath);
+ return false;
+ }
- if (cursor != null && cursor.moveToFirst()) {
- column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
- filePath = cursor.getString(column_index_data);
- cursor.close();
- } else {
- Log_OC.e(TAG, "cant get file from URI");
+ if (!syncedFolder.containsTypedFile(file, filePath)) {
+ Log_OC.w(TAG, "syncedFolder not contains typed file, changedFile: " + filePath);
+ return false;
+ }
+
+ filesystemDataProvider.storeOrUpdateFileValue(filePath, file.lastModified(), file.isDirectory(), syncedFolder);
}
- return filePath;
+
+ Log_OC.d(TAG, "changed content uris successfully stored");
+
+ return true;
}
private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder,
@@ -325,18 +221,18 @@ public final class FilesSyncHelper {
final ConnectivityService connectivityService,
final PowerManagementService powerManagementService) {
Log_OC.d(TAG, "restartUploadsIfNeeded, called");
- new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads(
+ FileUploadHelper.Companion.instance().retryFailedUploads(
uploadsStorageManager,
connectivityService,
accountManager,
- powerManagementService)).start();
+ powerManagementService);
}
public static void scheduleFilesSyncForAllFoldersIfNeeded(Context context, SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager) {
Log_OC.d(TAG, "scheduleFilesSyncForAllFoldersIfNeeded, called");
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled()) {
- jobManager.schedulePeriodicFilesSyncJob(syncedFolder.getId());
+ jobManager.schedulePeriodicFilesSyncJob(syncedFolder);
}
}
if (context != null) {
@@ -346,42 +242,21 @@ public final class FilesSyncHelper {
}
}
- public static void startFilesSyncForAllFolders(SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager, boolean overridePowerSaving, String[] changedFiles) {
- Log_OC.d(TAG, "startFilesSyncForAllFolders, called");
+ public static void startAutoUploadImmediatelyWithContentUris(SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager, boolean overridePowerSaving, String[] contentUris) {
+ Log_OC.d(TAG, "startAutoUploadImmediatelyWithContentUris");
for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
if (syncedFolder.isEnabled()) {
- jobManager.startImmediateFilesSyncJob(syncedFolder.getId(),overridePowerSaving,changedFiles);
+ jobManager.startAutoUploadImmediately(syncedFolder, overridePowerSaving, contentUris);
}
}
}
- public static long calculateScanInterval(
- SyncedFolder syncedFolder,
- ConnectivityService connectivityService,
- PowerManagementService powerManagementService
- ) {
- long defaultInterval = BackgroundJobManagerImpl.DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES * 1000 * 60;
- if (!connectivityService.isConnected() || connectivityService.isInternetWalled()) {
- return defaultInterval * 2;
+ public static void startAutoUploadImmediately(SyncedFolderProvider syncedFolderProvider, BackgroundJobManager jobManager, boolean overridePowerSaving) {
+ Log_OC.d(TAG, "startAutoUploadImmediately");
+ for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
+ if (syncedFolder.isEnabled()) {
+ jobManager.startAutoUploadImmediately(syncedFolder, overridePowerSaving, new String[]{});
+ }
}
-
- if ((syncedFolder.isWifiOnly() && !connectivityService.getConnectivity().isWifi())) {
- return defaultInterval * 4;
- }
-
- if (powerManagementService.getBattery().getLevel() < 80){
- return defaultInterval * 2;
- }
-
- if (powerManagementService.getBattery().getLevel() < 50){
- return defaultInterval * 4;
- }
-
- if (powerManagementService.getBattery().getLevel() < 20){
- return defaultInterval * 8;
- }
-
- return defaultInterval;
}
}
-
diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
index 5e9493f..5c31855 100644
--- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
+++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
@@ -461,6 +461,7 @@ public final class MimeTypeUtil {
MIMETYPE_TO_ICON_MAPPING.put("application/rss+xml", R.drawable.file_code);
MIMETYPE_TO_ICON_MAPPING.put("application/rtf", R.drawable.file);
MIMETYPE_TO_ICON_MAPPING.put("application/vnd.android.package-archive", R.drawable.file_zip);
+ MIMETYPE_TO_ICON_MAPPING.put("application/vnd.excalidraw+json", R.drawable.file_whiteboard);
MIMETYPE_TO_ICON_MAPPING.put("application/vnd.garmin.tcx+xml", R.drawable.file_location);
MIMETYPE_TO_ICON_MAPPING.put("application/vnd.google-earth.kml+xml", R.drawable.file_location);
MIMETYPE_TO_ICON_MAPPING.put("application/vnd.google-earth.kmz", R.drawable.file_location);
diff --git a/app/src/main/java/com/owncloud/android/utils/PermissionUtil.kt b/app/src/main/java/com/owncloud/android/utils/PermissionUtil.kt
index cca5cd8..5b1767a 100644
--- a/app/src/main/java/com/owncloud/android/utils/PermissionUtil.kt
+++ b/app/src/main/java/com/owncloud/android/utils/PermissionUtil.kt
@@ -29,6 +29,8 @@ import com.nextcloud.client.preferences.AppPreferencesImpl
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment
+import com.owncloud.android.utils.PermissionUtil.PERMISSIONS_EXTERNAL_STORAGE
+import com.owncloud.android.utils.PermissionUtil.REQUEST_CODE_MANAGE_ALL_FILES
import com.owncloud.android.utils.theme.ViewThemeUtils
object PermissionUtil {
@@ -211,7 +213,7 @@ object PermissionUtil {
}
@RequiresApi(Build.VERSION_CODES.R)
- private fun manifestHasAllFilesPermission(context: Context): Boolean {
+ fun manifestHasAllFilesPermission(context: Context): Boolean {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
return packageInfo?.requestedPermissions?.contains(Manifest.permission.MANAGE_EXTERNAL_STORAGE) ?: false
}
diff --git a/app/src/main/java/com/owncloud/android/utils/theme/CapabilityUtils.java b/app/src/main/java/com/owncloud/android/utils/theme/CapabilityUtils.java
index e158af9..90a4930 100644
--- a/app/src/main/java/com/owncloud/android/utils/theme/CapabilityUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/theme/CapabilityUtils.java
@@ -81,10 +81,11 @@ public final class CapabilityUtils {
public static boolean checkOutdatedWarning(Resources resources,
OwnCloudVersion version,
- boolean hasExtendedSupport) {
+ boolean hasExtendedSupport,
+ boolean hasValidSubscription) {
return resources.getBoolean(R.bool.show_outdated_server_warning) &&
(MainApp.OUTDATED_SERVER_VERSION.isSameMajorVersion(version) ||
version.isOlderThan(MainApp.OUTDATED_SERVER_VERSION))
- && !hasExtendedSupport;
+ && !hasExtendedSupport && !hasValidSubscription;
}
}
diff --git a/app/src/main/res/drawable/file_whiteboard.xml b/app/src/main/res/drawable/file_whiteboard.xml
new file mode 100644
index 0000000..56ebbef
--- /dev/null
+++ b/app/src/main/res/drawable/file_whiteboard.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_no_internet.xml b/app/src/main/res/drawable/ic_no_internet.xml
new file mode 100644
index 0000000..623ec33
--- /dev/null
+++ b/app/src/main/res/drawable/ic_no_internet.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_sync_off.xml b/app/src/main/res/drawable/ic_sync_off.xml
new file mode 100644
index 0000000..2f3d628
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sync_off.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/account_item.xml b/app/src/main/res/layout/account_item.xml
index 5bfb546..3bd3adc 100644
--- a/app/src/main/res/layout/account_item.xml
+++ b/app/src/main/res/layout/account_item.xml
@@ -78,7 +78,7 @@
android:maxLines="1"
android:text="@string/placeholder_filename"
android:textAppearance="?android:attr/textAppearanceListItem"
- tools:text="Firstname Lastname" />
+ tools:text="@string/placeholder_first_name_last_name" />
+ tools:text="@string/placeholder_status" />
+ tools:text="@string/placeholder_random_link" />
diff --git a/app/src/main/res/layout/account_removal_dialog.xml b/app/src/main/res/layout/account_removal_dialog.xml
index 5051881..4fcf4c5 100644
--- a/app/src/main/res/layout/account_removal_dialog.xml
+++ b/app/src/main/res/layout/account_removal_dialog.xml
@@ -40,14 +40,14 @@
android:layout_height="wrap_content"
android:ellipsize="middle"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
- tools:text="Alice Muster" />
+ tools:text="@string/placeholder_first_name_last_name" />
+ tools:text="@string/placeholder_random_account" />
diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml
index 26b892a..f5cda35 100644
--- a/app/src/main/res/layout/activity_compose.xml
+++ b/app/src/main/res/layout/activity_compose.xml
@@ -34,7 +34,7 @@
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_navigation_menu"
- app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
+ app:layout_behavior="com.nextcloud.ui.behavior.OnScrollBehavior" />
diff --git a/app/src/main/res/layout/activity_document_scan.xml b/app/src/main/res/layout/activity_document_scan.xml
index ad881a5..11e8d9e 100644
--- a/app/src/main/res/layout/activity_document_scan.xml
+++ b/app/src/main/res/layout/activity_document_scan.xml
@@ -33,7 +33,7 @@
android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin"
android:contentDescription="@string/scan_page"
- app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
+ app:layout_behavior="com.nextcloud.ui.behavior.OnScrollBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_plus" />
diff --git a/app/src/main/res/layout/activity_edit_image.xml b/app/src/main/res/layout/activity_edit_image.xml
index 4b11869..097431a 100644
--- a/app/src/main/res/layout/activity_edit_image.xml
+++ b/app/src/main/res/layout/activity_edit_image.xml
@@ -10,7 +10,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/black">
+ xmlns:tools="http://schemas.android.com/tools"
+ android:background="@color/black"
+ tools:ignore="Overdraw">
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/auto_upload_battery_saver_warning_card.xml b/app/src/main/res/layout/auto_upload_battery_saver_warning_card.xml
new file mode 100644
index 0000000..c08ca97
--- /dev/null
+++ b/app/src/main/res/layout/auto_upload_battery_saver_warning_card.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/conflict_resolve_dialog.xml b/app/src/main/res/layout/conflict_resolve_dialog.xml
index 0313b7b..a82faaa 100644
--- a/app/src/main/res/layout/conflict_resolve_dialog.xml
+++ b/app/src/main/res/layout/conflict_resolve_dialog.xml
@@ -63,13 +63,13 @@
android:id="@+id/left_timestamp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:text="12. Dec 2020 - 23:10:20" />
+ tools:text="@string/placeholder_date" />
+ tools:text="@string/placeholder_fileSize_2" />
+ tools:text="@string/placeholder_date_2" />
+ tools:text="@string/placeholder_fileSize_3" />
diff --git a/app/src/main/res/layout/dialog_sso_grant_permission.xml b/app/src/main/res/layout/dialog_sso_grant_permission.xml
index 12ca622..5b66f75 100644
--- a/app/src/main/res/layout/dialog_sso_grant_permission.xml
+++ b/app/src/main/res/layout/dialog_sso_grant_permission.xml
@@ -43,6 +43,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:text="Grant Nextcloud News access to your Nextcloud account incrediblyLong_username_with_123456789_number@Nextcloud_dummy.com?" />
+ tools:text="@string/placeholder_sso" />
diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml
index 4c99847..88f7ade 100644
--- a/app/src/main/res/layout/drawer_header.xml
+++ b/app/src/main/res/layout/drawer_header.xml
@@ -46,8 +46,7 @@
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
- tools:ignore="RtlHardcoded"
- tools:text="Nextcloud" />
+ tools:ignore="RtlHardcoded" />
diff --git a/app/src/main/res/layout/empty_list.xml b/app/src/main/res/layout/empty_list.xml
index 5f1aea4..4bb213e 100644
--- a/app/src/main/res/layout/empty_list.xml
+++ b/app/src/main/res/layout/empty_list.xml
@@ -43,6 +43,7 @@
android:paddingTop="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:text="@string/file_list_loading"
+ android:textColor="@color/text_color"
android:textSize="26sp" />
diff --git a/app/src/main/res/layout/etm_background_job_list_item.xml b/app/src/main/res/layout/etm_background_job_list_item.xml
index b67386e..113b865 100644
--- a/app/src/main/res/layout/etm_background_job_list_item.xml
+++ b/app/src/main/res/layout/etm_background_job_list_item.xml
@@ -27,7 +27,7 @@
android:id="@+id/etm_background_job_uuid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:text="d7edb387-0b61-4e4e-a728-ffab3055d700" />
+ tools:text="@string/placeholder_uuid" />
+ tools:text="@string/placeholder_job_name" />
@@ -64,7 +64,7 @@
android:id="@+id/etm_background_job_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="user@nextcloud.com" />
+ tools:text="@string/placeholder_random_account" />
@@ -82,7 +82,7 @@
android:id="@+id/etm_background_job_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="ENQUEUED" />
+ tools:text="@string/placeholder_job_state" />
@@ -100,7 +100,7 @@
android:id="@+id/etm_background_job_started"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="2020-02-15T20:53:15Z" />
+ tools:text="@string/placeholder_date_3" />
@@ -119,7 +119,7 @@
android:id="@+id/etm_background_job_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="50%" />
+ tools:text="@string/placeholder_progress" />
@@ -139,7 +139,7 @@
android:id="@+id/etm_background_execution_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="0" />
+ tools:text="@string/placeholder_random_number" />
diff --git a/app/src/main/res/layout/etm_transfer_list_item.xml b/app/src/main/res/layout/etm_transfer_list_item.xml
index 22dd93b..f708b1d 100644
--- a/app/src/main/res/layout/etm_transfer_list_item.xml
+++ b/app/src/main/res/layout/etm_transfer_list_item.xml
@@ -46,7 +46,7 @@
app:layout_constraintStart_toEndOf="@+id/etm_transfer_type_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- tools:text="@string/etm_transfer_type_download" />
+ tools:text="@string/placeholder_download" />
@@ -65,7 +65,7 @@
android:id="@+id/etm_transfer_uuid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:text="d7edb387-0b61-4e4e-a728-ffab3055d700" />
+ tools:text="@string/placeholder_uuid" />
+ tools:text="@string/placeholder_file_path" />
@@ -100,7 +100,7 @@
android:id="@+id/etm_transfer_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="user@nextcloud.com" />
+ tools:text="@string/placeholder_random_account" />
@@ -118,7 +118,7 @@
android:id="@+id/etm_transfer_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="PENDING" />
+ tools:text="@string/placeholder_job_state" />
@@ -137,7 +137,7 @@
android:id="@+id/etm_transfer_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- tools:text="50%" />
+ tools:text="@string/placeholder_progress" />
diff --git a/app/src/main/res/layout/file_actions_bottom_sheet_item.xml b/app/src/main/res/layout/file_actions_bottom_sheet_item.xml
index 5767260..77b245f 100644
--- a/app/src/main/res/layout/file_actions_bottom_sheet_item.xml
+++ b/app/src/main/res/layout/file_actions_bottom_sheet_item.xml
@@ -46,7 +46,7 @@
android:layout_marginStart="@dimen/bottom_sheet_text_start_margin"
android:textColor="@color/text_color"
android:textSize="@dimen/bottom_sheet_text_size"
- tools:text="Delete file" />
+ tools:text="@string/placeholder_delete_file" />
diff --git a/app/src/main/res/layout/file_details_share_group.xml b/app/src/main/res/layout/file_details_share_group.xml
index 1831d49..075e5ba 100644
--- a/app/src/main/res/layout/file_details_share_group.xml
+++ b/app/src/main/res/layout/file_details_share_group.xml
@@ -49,7 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/two_line_primary_text_size"
- tools:text="@string/share_internal_link_to_folder_text" />
+ tools:text="@string/placeholder_share_internal_link_text" />
diff --git a/app/src/main/res/layout/file_details_share_internal_share_link.xml b/app/src/main/res/layout/file_details_share_internal_share_link.xml
index 1831d49..8bc5eb8 100644
--- a/app/src/main/res/layout/file_details_share_internal_share_link.xml
+++ b/app/src/main/res/layout/file_details_share_internal_share_link.xml
@@ -49,7 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/two_line_primary_text_size"
- tools:text="@string/share_internal_link_to_folder_text" />
+ tools:text="@string/placeholder_share_internal_folder_text" />
diff --git a/app/src/main/res/layout/file_details_share_link_share_item.xml b/app/src/main/res/layout/file_details_share_link_share_item.xml
index ea32fd5..e7cf425 100644
--- a/app/src/main/res/layout/file_details_share_link_share_item.xml
+++ b/app/src/main/res/layout/file_details_share_link_share_item.xml
@@ -56,7 +56,7 @@
android:gravity="center_vertical"
android:visibility="gone"
android:singleLine="true"
- tools:text="5 downloads remaining"
+ tools:text="@string/placeholder_remaining_downloads"
tools:visibility="visible"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size" />
@@ -67,7 +67,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:singleLine="true"
- tools:text="View only"
+ tools:text="@string/placeholder_view_only"
app:drawableEndCompat="@drawable/ic_baseline_arrow_drop_down_24"
app:drawableTint="@color/primary"
app:drawableRightCompat="@drawable/ic_baseline_arrow_drop_down_24"
diff --git a/app/src/main/res/layout/file_details_share_share_item.xml b/app/src/main/res/layout/file_details_share_share_item.xml
index 054c6e4..facdb0c 100644
--- a/app/src/main/res/layout/file_details_share_share_item.xml
+++ b/app/src/main/res/layout/file_details_share_share_item.xml
@@ -60,7 +60,7 @@
app:drawableEndCompat="@drawable/ic_baseline_arrow_drop_down_24"
app:drawableRightCompat="@drawable/ic_baseline_arrow_drop_down_24"
app:drawableTint="@color/primary"
- tools:text="View only" />
+ tools:text="@string/placeholder_view_only" />
+ app:layout_behavior="com.nextcloud.ui.behavior.OnScrollBehavior" />
diff --git a/app/src/main/res/layout/first_run_activity.xml b/app/src/main/res/layout/first_run_activity.xml
index a9a44b6..369bb3f 100644
--- a/app/src/main/res/layout/first_run_activity.xml
+++ b/app/src/main/res/layout/first_run_activity.xml
@@ -8,6 +8,7 @@
-->
@@ -24,7 +25,8 @@
android:id="@+id/contentPanel"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ tools:ignore="NestedWeights" />
+ tools:text="@string/placeholder_month" />
+ tools:text="@string/placeholder_year" />
diff --git a/app/src/main/res/layout/info_box.xml b/app/src/main/res/layout/info_box.xml
index d7ed508..8541f5f 100644
--- a/app/src/main/res/layout/info_box.xml
+++ b/app/src/main/res/layout/info_box.xml
@@ -29,6 +29,6 @@
android:paddingRight="@dimen/standard_half_padding"
android:paddingStart="@dimen/standard_half_margin"
android:textColor="@color/standard_grey"
- tools:text="@string/offline_mode" />
+ tools:text="@string/placeholder_share_no_internet_connection" />
diff --git a/app/src/main/res/layout/internal_two_way_sync_view_holder.xml b/app/src/main/res/layout/internal_two_way_sync_view_holder.xml
index 680b921..ba949f3 100644
--- a/app/src/main/res/layout/internal_two_way_sync_view_holder.xml
+++ b/app/src/main/res/layout/internal_two_way_sync_view_holder.xml
@@ -38,7 +38,7 @@
android:singleLine="true"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size"
- tools:text="Folder abc" />
+ tools:text="@string/placeholder_filename" />
+ tools:text="@string/placeholder_fileSize_2" />
+ tools:text="@string/placeholder_time_ago" />
+ tools:text="@string/placeholder_success" />
diff --git a/app/src/main/res/layout/list_header_open_in.xml b/app/src/main/res/layout/list_header_open_in.xml
index 74b907b..10401f3 100644
--- a/app/src/main/res/layout/list_header_open_in.xml
+++ b/app/src/main/res/layout/list_header_open_in.xml
@@ -39,7 +39,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_quarter_margin"
- tools:text="This folder is best viewed in the Notes app" />
+ tools:text="@string/placeholder_open_in_notes_info" />
@@ -49,7 +49,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_quarter_margin"
- tools:text="Open in Notes" />
+ tools:text="@string/placeholder_open_in_notes" />
+ app:ensureMinTouchTargetSize="false" />
+ app:ensureMinTouchTargetSize="false" />
+ app:ensureMinTouchTargetSize="false" />
diff --git a/app/src/main/res/layout/material_list_item_single_line.xml b/app/src/main/res/layout/material_list_item_single_line.xml
index 535362d..a0da69f 100644
--- a/app/src/main/res/layout/material_list_item_single_line.xml
+++ b/app/src/main/res/layout/material_list_item_single_line.xml
@@ -25,7 +25,7 @@
android:lines="1"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
- tools:text="Single line of text"/>
+ tools:text="@string/placeholder_filename"/>
@@ -63,8 +63,7 @@
+ android:importantForAutofill="no">
+ tools:text="@string/placeholder_predefined_status_icon" />
+ tools:text="@string/placeholder_predefined_status_name" />
+ tools:text="@string/placeholder_predefined_status_clear_at" />
diff --git a/app/src/main/res/layout/preview_image_details_fragment.xml b/app/src/main/res/layout/preview_image_details_fragment.xml
index f9b3ad6..72f0017 100644
--- a/app/src/main/res/layout/preview_image_details_fragment.xml
+++ b/app/src/main/res/layout/preview_image_details_fragment.xml
@@ -54,14 +54,14 @@
android:layout_height="wrap_content"
android:padding="2dp"
android:textStyle="bold"
- tools:text="Wednesday • 26 Jul 2023 • 12:27" />
+ tools:text="@string/placeholder_image_detail_date" />
+ tools:text="@string/placeholder_image_details" />
@@ -95,14 +95,14 @@
android:layout_height="wrap_content"
android:padding="2dp"
android:textStyle="bold"
- tools:text="Camera Phone (4th generation)" />
+ tools:text="@string/placeholder_image_detail_model" />
+ tools:text="@string/placeholder_image_detail_condition" />
@@ -133,7 +133,7 @@
android:textAlignment="center"
android:textStyle="bold"
android:visibility="gone"
- tools:text="Mitte, Berlin, Germany"
+ tools:text="@string/placeholder_image_detail_location"
tools:visibility="visible" />
+ tools:text="@string/placeholder_image_detail_example_copyright" />
diff --git a/app/src/main/res/layout/profile_bottom_sheet_action.xml b/app/src/main/res/layout/profile_bottom_sheet_action.xml
index 097bd1c..17078fd 100644
--- a/app/src/main/res/layout/profile_bottom_sheet_action.xml
+++ b/app/src/main/res/layout/profile_bottom_sheet_action.xml
@@ -31,7 +31,7 @@
android:layout_gravity="center_vertical"
android:paddingStart="24dp"
android:paddingEnd="0dp"
- tools:text="Compose email"
+ tools:text="@string/placeholder_compose_mail"
android:textColor="@color/text_color"
android:textSize="@dimen/bottom_sheet_text_size" />
diff --git a/app/src/main/res/layout/profile_bottom_sheet_fragment.xml b/app/src/main/res/layout/profile_bottom_sheet_fragment.xml
index 5f0a6ef..06c8927 100644
--- a/app/src/main/res/layout/profile_bottom_sheet_fragment.xml
+++ b/app/src/main/res/layout/profile_bottom_sheet_fragment.xml
@@ -38,7 +38,7 @@
android:layout_gravity="center_vertical"
android:paddingTop="@dimen/standard_padding"
android:paddingBottom="@dimen/standard_padding"
- tools:text="Christine Scott"
+ tools:text="@string/placeholder_first_name_last_name"
android:textColor="@color/text_color"
android:textSize="@dimen/bottom_sheet_text_size" />
diff --git a/app/src/main/res/layout/send_button.xml b/app/src/main/res/layout/send_button.xml
index 25f2ef2..539fe52 100644
--- a/app/src/main/res/layout/send_button.xml
+++ b/app/src/main/res/layout/send_button.xml
@@ -32,5 +32,5 @@
android:gravity="center_horizontal"
android:paddingTop="@dimen/standard_half_padding"
android:textColor="@color/text_color"
- tools:text="@string/app_name" />
+ tools:text="@string/placeholder_send_button" />
diff --git a/app/src/main/res/layout/setup_encryption_dialog.xml b/app/src/main/res/layout/setup_encryption_dialog.xml
index 66a8403..61702a8 100644
--- a/app/src/main/res/layout/setup_encryption_dialog.xml
+++ b/app/src/main/res/layout/setup_encryption_dialog.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/standard_margin"
- tools:text="@string/end_to_end_encryption_keywords_description" />
+ tools:text="@string/placeholder_share_internal_e2ee_keyword_description" />
+ tools:text="@string/placeholder_file_path" />
diff --git a/app/src/main/res/layout/synced_folders_footer.xml b/app/src/main/res/layout/synced_folders_footer.xml
index 8ef6727..ec40a61 100644
--- a/app/src/main/res/layout/synced_folders_footer.xml
+++ b/app/src/main/res/layout/synced_folders_footer.xml
@@ -21,7 +21,7 @@
android:layout_marginBottom="@dimen/min_list_item_size"
android:gravity="center"
android:padding="@dimen/standard_padding"
- tools:text="Show 3 hidden folders"
+ tools:text="@string/placeholder_show_hidden_folders"
android:textColor="@color/secondary_text_color" />
diff --git a/app/src/main/res/layout/synced_folders_item_header.xml b/app/src/main/res/layout/synced_folders_item_header.xml
index 642276c..d4b9a81 100644
--- a/app/src/main/res/layout/synced_folders_item_header.xml
+++ b/app/src/main/res/layout/synced_folders_item_header.xml
@@ -2,91 +2,120 @@
-
-
+
+
+
+
+ android:ellipsize="middle"
+ android:gravity="center|start"
+ android:textColor="?android:textColorPrimary"
+ android:textStyle="bold"
+ tools:text="@string/placeholder_filename"
+ android:maxLines="1"
+ app:layout_constraintBottom_toTopOf="@+id/scanIndicatorText"
+ app:layout_constraintStart_toEndOf="@+id/type" />
-
-
-
-
-
-
-
+ android:layout_marginBottom="@dimen/standard_quarter_margin"
+ android:ellipsize="end"
+ android:gravity="center|start"
+ android:maxLines="1"
+ android:textColor="@color/log_level_warning"
+ android:textStyle="normal"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/type"
+ tools:text="@string/placeholder_scan_indicator"
+ tools:visibility="visible" />
-
+
-
+
-
-
-
+
+
diff --git a/app/src/main/res/layout/synced_folders_layout.xml b/app/src/main/res/layout/synced_folders_layout.xml
index ea30c5c..48427af 100644
--- a/app/src/main/res/layout/synced_folders_layout.xml
+++ b/app/src/main/res/layout/synced_folders_layout.xml
@@ -39,10 +39,6 @@
android:scrollbars="vertical"
android:visibility="visible" />
-
-
+
+
+
diff --git a/app/src/main/res/layout/synced_folders_settings_layout.xml b/app/src/main/res/layout/synced_folders_settings_layout.xml
index d0e0e42..c8850c4 100644
--- a/app/src/main/res/layout/synced_folders_settings_layout.xml
+++ b/app/src/main/res/layout/synced_folders_settings_layout.xml
@@ -41,7 +41,7 @@
android:ellipsize="middle"
android:maxLines="2"
android:textColor="?android:attr/textColorSecondary"
- tools:text="For /storage/emulated/0/DCIM/Camera" />
+ tools:text="@string/placeholder_auto_upload_overview_path" />
+ tools:text="@string/placeholder_template" />
diff --git a/app/src/main/res/layout/toolbar_standard.xml b/app/src/main/res/layout/toolbar_standard.xml
index 5b8e23e..b449700 100644
--- a/app/src/main/res/layout/toolbar_standard.xml
+++ b/app/src/main/res/layout/toolbar_standard.xml
@@ -200,7 +200,7 @@
app:layout_constraintLeft_toRightOf="@id/menu_button"
app:layout_constraintRight_toLeftOf="@id/notification_button"
app:layout_constraintTop_toTopOf="parent"
- tools:text="Search in Nextcloud" />
+ tools:text="@string/placeholder_search_in_nextcloud" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/unified_search_footer.xml b/app/src/main/res/layout/unified_search_footer.xml
index d9535df..f4a9602 100755
--- a/app/src/main/res/layout/unified_search_footer.xml
+++ b/app/src/main/res/layout/unified_search_footer.xml
@@ -25,8 +25,7 @@
android:paddingStart="@dimen/standard_quarter_padding"
android:paddingEnd="0dp"
android:text="@string/load_more_results"
- android:textColor="@color/secondary_text_color"
- tools:text="Load more results">
+ android:textColor="@color/secondary_text_color">
diff --git a/app/src/main/res/layout/unified_search_header.xml b/app/src/main/res/layout/unified_search_header.xml
index fdd8363..f37cd7c 100755
--- a/app/src/main/res/layout/unified_search_header.xml
+++ b/app/src/main/res/layout/unified_search_header.xml
@@ -21,7 +21,7 @@
android:layout_height="wrap_content"
android:ellipsize="middle"
android:paddingHorizontal="@dimen/standard_padding"
- android:paddingVertical="@dimen/standard_half_padding"
+ android:paddingVertical="@dimen/standard_padding"
android:textColor="@color/color_accent"
- tools:text="Files" />
+ tools:text="@string/placeholder_files" />
diff --git a/app/src/main/res/layout/unified_search_item.xml b/app/src/main/res/layout/unified_search_item.xml
index 396cc11..9f65a55 100755
--- a/app/src/main/res/layout/unified_search_item.xml
+++ b/app/src/main/res/layout/unified_search_item.xml
@@ -80,7 +80,7 @@
android:text=""
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size"
- tools:text="Test 123" />
+ tools:text="@string/placeholder_filename" />
+ tools:text="@string/placeholder_in_folder" />
diff --git a/app/src/main/res/layout/upload_file_dialog.xml b/app/src/main/res/layout/upload_file_dialog.xml
index ca1e0ba..aeeb380 100755
--- a/app/src/main/res/layout/upload_file_dialog.xml
+++ b/app/src/main/res/layout/upload_file_dialog.xml
@@ -20,7 +20,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/upload_file_dialog_filename"
- tools:text="@string/upload_file_dialog_filename"/>
+ tools:text="@string/placeholder_filename"/>
+ tools:text="@string/placeholder_file_type"/>
-
+ tools:text="@string/placeholder_current_2" />
+ android:src="@drawable/ic_delete"
+ app:tint="#757575" />
+
+
diff --git a/app/src/main/res/layout/user_info_details_table_item.xml b/app/src/main/res/layout/user_info_details_table_item.xml
index 60257d6..c53b416 100644
--- a/app/src/main/res/layout/user_info_details_table_item.xml
+++ b/app/src/main/res/layout/user_info_details_table_item.xml
@@ -37,6 +37,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
- tools:text="+49 123 456 789 12" />
+ tools:text="@string/placeholder_example_phone_number" />
diff --git a/app/src/main/res/layout/user_info_layout.xml b/app/src/main/res/layout/user_info_layout.xml
index 5a7d442..fb29fe8 100644
--- a/app/src/main/res/layout/user_info_layout.xml
+++ b/app/src/main/res/layout/user_info_layout.xml
@@ -62,7 +62,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/userinfo_icon"
app:layout_constraintTop_toTopOf="@id/userinfo_icon"
- tools:text="John Doe" />
+ tools:text="@string/placeholder_first_name_last_name" />
+ tools:text="@string/placeholder_example_mail" />
diff --git a/app/src/main/res/layout/version_list_item.xml b/app/src/main/res/layout/version_list_item.xml
index 3dd1541..dad1ef3 100644
--- a/app/src/main/res/layout/version_list_item.xml
+++ b/app/src/main/res/layout/version_list_item.xml
@@ -45,7 +45,7 @@
android:layout_height="0dp"
android:layout_weight="1"
android:ellipsize="end"
- tools:text="256 KB"
+ tools:text="@string/placeholder_fileSize"
android:textColor="?android:attr/textColorSecondary"/>
@@ -67,7 +67,7 @@
android:layout_weight="1"
android:ellipsize="end"
android:textAlignment="textEnd"
- tools:text="13:24"
+ tools:text="@string/placeholder_media_time"
android:textColor="?android:attr/textColorSecondary"/>
diff --git a/app/src/main/res/layout/whats_new_activity.xml b/app/src/main/res/layout/whats_new_activity.xml
index e30e408..b85385e 100644
--- a/app/src/main/res/layout/whats_new_activity.xml
+++ b/app/src/main/res/layout/whats_new_activity.xml
@@ -7,7 +7,8 @@
~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
-->
@@ -51,7 +52,8 @@
android:gravity="center"
android:paddingLeft="0dp"
android:paddingRight="0dp"
- android:text="@string/whats_new_skip"/>
+ android:text="@string/whats_new_skip"
+ tools:ignore="NestedWeights" />
+ tools:text="@string/placeholder_widget_first_line" />
+ tools:text="@string/placeholder_widget_second_line" />
diff --git a/app/src/main/res/layout/widget_list_item.xml b/app/src/main/res/layout/widget_list_item.xml
index f2bc008..0b6b66d 100644
--- a/app/src/main/res/layout/widget_list_item.xml
+++ b/app/src/main/res/layout/widget_list_item.xml
@@ -29,5 +29,5 @@
android:layout_height="32dp"
android:layout_weight="1"
android:gravity="center_vertical"
- tools:text="Widget name" />
+ tools:text="@string/placeholder_first_name_last_name" />
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 8bdf6de..3e09e31 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -544,7 +544,6 @@
قد تستغرق هذه العملية بعض الوقت.
إدارة المساحة
- تجاوزت الحد الأقصى المسموح به لزمن رفع الملفات. رجاءً، لا تقم برفع عدد يزيد عن 500 ملف في كل مرة.
ملف الوسائط لا يمكن بثه
تعذرت قراءة ملف الوسائط
يحتوي ملف الوسائط على ترميز غير صحيح
@@ -642,12 +641,6 @@
لم يتم العثور على تطبيق لتعيين صورة به
ثبِّت في الشاشة الرئيسية
فتح %1$s
- .txt
- 389 ك.ب
- 12:23:45
- تغيرت مؤخرا
- هذه مساحة محجوزة
- 2012/05/18 12:23 مساء
إيقاف
تبديل
من فضلك، قم بتحديد الخادم...
@@ -668,6 +661,7 @@
حول
تفاصيل
التطوير
+ الملفّات
عام
المزيد
زامن
@@ -852,6 +846,8 @@
التسجيل عبر مزوّد خدمة
هل تسمح لـ%1$s بالوصول إلى حسابك %2$s في Nextcloud؟
ترتيب
+ فرز المفضلة أولا
+ فرز المجلدات قبل الملفات
إخفاء
تفاصيل
لا يمكن التحقق من هوية الخادم
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index b8df4e2..bce3f1d 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -382,12 +382,6 @@
Negar
Ríquense permisos adicionales pa xubir y baxar ficheros.
Abrir «%1$s»
- .txt
- 389 KB
- 12:23:45
- Editóse apocayá
- Esto ye un marcador de posición
- 2012/05/18 12:23 PM
desanicióse
guardáu en carpeta orixinal
movíu a la carpeta d\'aplicaciones
@@ -399,6 +393,7 @@
Amestar cuenta
Tocante a
Detalles
+ Ficheros
Xeneral
Más
Sincronizar
@@ -492,6 +487,8 @@
Rexistrase con un fornidor
¿Quies permitr que %1$s acceda a la cuenta de Nextcloud %2$s?
Ordenar per
+ Ordenar los favoritos primero
+ Ordenar les carpetes enantes que los ficheros
Esconder
Detalles
Nun se pudo verificar la identidá del sirvidor
diff --git a/app/src/main/res/values-b+en+001/strings.xml b/app/src/main/res/values-b+en+001/strings.xml
index e88e7af..71cb8a8 100644
--- a/app/src/main/res/values-b+en+001/strings.xml
+++ b/app/src/main/res/values-b+en+001/strings.xml
@@ -44,6 +44,7 @@
Shows one widget from dashboard
Search in %s
Appear offline
+ This content was generated by AI and can make mistakes.
Add new task
Create a new task from bottom right
Type some text
@@ -55,6 +56,7 @@
An error occurred while deleting the task
Task successfully deleted
Task list is empty.
+ Task list is empty. Check assistant app configuration.
Unable to fetch task list, please check your internet connection.
Delete Task
The task output is not ready yet.
@@ -93,12 +95,19 @@
%1$s does not support multiple accounts
Could not establish connection
Cancel Login
+ Please enter a valid server address.
+ Unable to fetch login details. Please try again.
There was an issue processing your login request. Please try again later.
+ No browser is available to open this link.
Please complete login process in your browser
+ Auto-upload is paused because Battery Saver is on.
kept in original folder, as it is readonly
+ Low battery, upload might take longer
Only upload on unmetered Wi-Fi
/AutoUpload
This folder is already included in the parent folder’s sync, which may cause duplicate uploads
+ Waiting for Wi-Fi to start uploading
+ Uploading files from %s to %s
Configure
Create new custom folder setup
Set up a custom folder
@@ -202,6 +211,7 @@
Import failed to start. Please try again
No file found
Could not find your last backup!
+ Detecting content changes
Copied to clipboard
An error occurred while trying to copy this file or folder
It is not possible to copy a folder into one of its own underlying folders
@@ -407,6 +417,8 @@
No results found for your query
Start your search
Type in the search bar above to find files, contacts, calendar events, and more across your account.
+ Check your Internet connection or try again later
+ Poor connection
folder
LIVE
Loading…
@@ -471,6 +483,11 @@
Folder already exists
This folder is best viewed in %1$s.
Create
+ %1$d of %2$d · %3$s
+ An error occurred during synchronisation of the %s folder
+ Insufficient disk space, synchronisation cancelled
+ %s folder successfully synchronised
+ Syncing…
No folders here
Folder name cannot be empty
Choose
@@ -558,7 +575,6 @@
Clear data
Settings, database and server certificates from %1$s\'s data will be deleted permanently. \n\nDownloaded files will be kept untouched.\n\nThis process can take a while.
Manage space
- You have reached the maximum file upload limit. Please upload fewer than 500 files at a time.
The media file cannot be streamed
Could not read the media file
The media file has incorrect encoding
@@ -607,6 +623,8 @@
Failed to execute action.
Show notifications to interact result of background operations
Background operations
+ Detects local file changes
+ Content observer
Shows download progress
Downloads
Shows file sync progress and results
@@ -647,6 +665,7 @@
Enter your passcode
The passcode will be requested every time the app is started
Please enter your passcode
+ The passcode will be requested every time the app is opened or reopened after 5 seconds.
The passcodes are not the same
Please reenter your passcode
Delete your passcode
@@ -662,13 +681,6 @@
No app found to set a picture with
Pin to home screen
Open %1$s
- .txt
- 389 KB
- placeholder
- 12:23:45
- Recently edited
- This is a placeholder
- 2012/05/18 12:23 PM
stop
toggle
Please select a server…
@@ -690,6 +702,7 @@
About
Details
Dev
+ Files
General
More
Sync
@@ -881,6 +894,8 @@
Sign up with provider
Allow %1$s to access your Nextcloud account %2$s?
Sort by
+ Sort favourites first
+ Sort folders before files
Hide
Details
The identity of the server could not be verified
@@ -991,6 +1006,7 @@
Event not found, you can always sync to update. Redirecting to web…
Contact not found, you can always sync to update. Redirecting to web…
Permissions are required to open search result otherwise it will redirected to web…
+ In this folder
Unknown
Unlock file
Unread comments exist
@@ -1098,6 +1114,8 @@
Untrusted server certificate
Fetching server version…
App terminated
+ Skipped
+ A file with the same name already exists.
Completed
Same file found on remote, skipping upload
Unknown error
@@ -1191,6 +1209,10 @@
- %d file will be exported. See notification for details.
- %d files will be exported. See notification for details.
+
+ - You can upload only %d file at once.
+ - You can upload up to %d files at once.
+
- %1$d folder
- %1$d folders
diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml
index 6c84e0a..7403658 100644
--- a/app/src/main/res/values-bg-rBG/strings.xml
+++ b/app/src/main/res/values-bg-rBG/strings.xml
@@ -43,6 +43,7 @@
Търсене в %s
Изтрива задача
Задачата е успешно изтрита
+ Асистент
Свързания профил не е намерен!
Достъп неуспешен: %1$s
Профилът все още не съществува на устройството
@@ -88,6 +89,7 @@
Изключване
Възможно е устройството ви да има активирана оптимизация на батерията. AutoUpload работи правилно само, ако изключите това приложение от него.
Оптимизация на батерията
+ Асистент
Любими
Всички файлове
Зает
@@ -119,6 +121,7 @@
Грешка
Няма достатъчно памет
Неизвестна грешка
+ Напускане на споделянето
Зареждане…
Следващ
Не
@@ -234,6 +237,7 @@
Фоново изображение на заглавката на чекмедже/панел/
Активност
Всички файлове
+ Асистент
Любими
Медия
Начало
@@ -250,6 +254,7 @@
%1$s използвани
Автоматично качване
E2E все още няма настройки
+ Асистент
Повече
Включване на криптиране
Настройки за криптиране
@@ -524,12 +529,6 @@
Изискват се допълнителни права за изтегляне и сваляне на файлове.
Няма намерени приложения за задаване на снимка
Отваряне на %1$s
- .txt
- 389 KB
- 12:23:45
- Наскоро редактирани
- Това е за запазено място
- 2012/05/18 12:23 PM
стоп
превключване
Деактивирането на проверката за пестене на енергия може да доведе до качване на файлове, в състояние с ниска батерия!
@@ -547,6 +546,7 @@
Относно
Подробности
Разработка
+ Файлове
Общи
Още
Синхронизиране
@@ -694,6 +694,8 @@
Абониране при доставчик
Разрешавате ли на %1$s да достъпва вашия Nextcloud профил %2$s?
Сортиране по
+ Първо сортирайте любимите
+ Сортиране на папки преди файлове
Скриване
Подробности
Идентичността на сървъра не може да бъде потвърдена
@@ -824,6 +826,7 @@
Локалното място за съхранение е пълно
Файлът не може да бъде копиран в локалното хранилище
Неуспешно заключване на папката
+ Качването е отменено от потребителя
Шифроването е възможно само с > = Android 5.0
В папка %1$s няма достатъчно място за копиране на избраните файлове. Желаете ли да бъдат преместени?
Сканиране на документ от камера
diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml
index ffe0b47..7e14f88 100644
--- a/app/src/main/res/values-br/strings.xml
+++ b/app/src/main/res/values-br/strings.xml
@@ -510,11 +510,6 @@
Difennet
Aotreoù ouzhpenn ez eus ezhomp evit pellkas ha pellgargañ restroù.
Meziant ebet kavet evit lakaat ur skeudenn gant
- 389 KB
- 12:23:45
- Cheñchet n\'eus ket pell zo
- Ur lec\'h miret eo
- 2012/05/18 12:23 PM
Disaotreañ gwiriañ ar saver pod-tredañ a c\'hel lezel an ardivink pellkargañ restroù pa ne vez ket kalz a dredañ kenn !
dilamet
gwarn ar restr orin
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 411b1de..9c75124 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -141,7 +141,7 @@
Error
Memòria insuficient
Error desconegut
- Abandona aquest element compartit
+ Abandona aquest compartició
S\'està carregant…
Següent
No
@@ -215,7 +215,7 @@
Carpeta nova
Nova presentació
Nou full de càlcul
- Afegiu una descripció per a la carpeta
+ Afegeix una descripció per a la carpeta
Afegeix la descripció de la carpeta
Credencials inhabilitades
Còpia de seguretat diària
@@ -233,7 +233,7 @@
No s\'han verificat els duplicats.
Aquest algorisme de resum no és disponible al vostre telèfon.
l\'Inici de sessió via enllaç directe ha fallat!
- Inicia la sessió amb %1$s a %2$s
+ Inici de sessió amb %1$s a %2$s
Inhabilita
Descarta
Descarta la notificació
@@ -443,7 +443,7 @@
La carpeta ja existeix
Crea
No hi ha carpetes
- El nom del fitxer no pot estar buit
+ El nom de la carpeta no pot estar buit
Tria
Trieu la carpeta de destinació
Còpia
@@ -463,7 +463,7 @@
Endavant
4 hores
Google ha restringit la baixada de fitxers APK/AAB!
- Aquesta icona indica la disponibilitat de la foto en directe
+ Aquesta icona indica la disponibilitat de la foto en viu
El nom donarà com a resultat un fitxer ocult
Nom
Nota
@@ -528,7 +528,6 @@
Neteja dades
Els paràmetres, la base de dades i els certificats del servidor %1$s se suprimiran de forma permanent. \n\nEls fitxers descarregats romandran intactes.\n\n Aquest procés pot trigar una estona.
Gestió d\'espai
- Has assolit el límit màxim de pujada de fitxers. Puja menys de 500 fitxers alhora.
No es pot reproduir el fitxer multimèdia
No s\'ha pogut llegir el fitxer multimèdia
El fitxer multimèdia té una codificació incorrecta
@@ -621,12 +620,6 @@
No s\'ha trobat cap aplicació per establir-hi la foto
Fixa-ho a la pantalla d\'inici
Obre %1$s
- .txt
- 389 KB
- 12:23:45
- Editat recentment
- Això és un marcador de posició
- 2012/05/18 12:23 PM
atura
commuta
Seleccioneu un servidor…
@@ -647,13 +640,14 @@
Quant a
Detalls
Dev
+ Fitxers
General
Més
Sincronitza
Còpia de seguretat diària del calendari i els contactes
Còpia de seguretat diària dels vostres contactes
Ubicació de l\'emmagatzematge de dades
- Gestioneu la ubicació de l\'emmagatzematge de dades
+ Administrar la ubicació de l\'emmagatzematge de dades
S\'ha configurat el xifratge d\'extrem a extrem
E2E mnemònic
Per mostrar els mnemònics, si us plau habiliteu les credencials del dispositiu.
@@ -809,6 +803,8 @@
Registreu-vos amb el proveïdor
Voleu permetre que %1$s accedeixi al vostre compte de Nextcloud %2$s?
Ordena per
+ Ordena primer els preferits
+ Ordena les carpetes abans dels fitxers
Amaga
Detalls
No s\'ha pogut verificar la identitat del servidor
@@ -858,7 +854,7 @@
\"%1$s\" us ha estat compartit
%1$s us ha compartit %2$s
Només fotografies
- Fotos i vídeos
+ Fotografies i vídeos
Només vídeos
Suggereix
Sincronitza
@@ -999,7 +995,7 @@
Completat
Error desconegut
S\'ha detectat un virus. No es pot completar la pujada!
- S\'està esperant al mode d\'apagada segur
+ S\'està esperant a sortir del mode d\'estalvi d\'energia
S\'està esperant càrrega de bateria
S\'està esperant una wifi sense mètrica de consum de dades
Usuari
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index f844709..ff1cd6a 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -44,6 +44,7 @@
Zobrazuje jeden ovládací prvek z nástěnky
Hledat v %s
Jevit se offline
+ Tento obsah byl vytvořen pomocí AI a může obsahovat chyby.
Přidat nový úkol
Z pravého dolního rohu vytvořte nový úkol
Zadejte nějaký text
@@ -55,6 +56,7 @@
Při mazání úlohy se vyskytla chyba
Úloha úspěšně smazána
Seznam úkolů je prázdný.
+ Seznam úkolů je prázdný. Zkontrolujte nastavení aplikace Asistent.
Nedaří se získat seznam úloh – zkontrolujte připojení k Internetu.
Smazat úlohu
Výstup z úkolu ještě není připraven.
@@ -93,12 +95,19 @@
%1$s nepodporuje vícero účtů
Nedaří se navázat spojení
Zrušit přihlášení
+ Zadejte platnou adresu serveru.
+ Nebylo možné získat podrobnosti o přihlášení. Zkuste to znovu.
Došlo k problému při zpracovávání vašeho požadavku na přihlášení se. Zkuste to prosím později.
+ Není k dispozici žádný webový prohlížeč pro otevření tohoto odkazu.
Dokončete proces přihlášení prostřednictvím webového prohlížeče
+ Automatické nahrávání je pozastaveno, protože je zapnuté šetření energií z akumulátoru.
ponechán v původní složce, protože je pouze pro čtení
+ Téměř vybitý akumulátor – nahrávání může trvat déle
Nahrávat pouze přes neúčtované Wi-Fi připojení
/AutoUpload
Tato složka už je obsažena v synchronizaci nadřazené složky, což může způsobovat duplicitní nahrávání
+ Čeká se na Wi-Fi, aby bylo možné spustit nahrávání
+ Jsou nahrávány soubory z %s do %s
Nastavit
Vytvořit nové uživatelsky určené nastavení složky
Nastavit uživatelsky určenou složku
@@ -202,6 +211,7 @@
Import se nepodařilo spustit. Zkuste to znovu
Nenalezen žádný soubor
Nepodařilo se najít vaši nejaktuálnější zálohu!
+ Zjišťování změn obsahu
Zkopírováno do schránky
Při pokusu o zkopírování tohoto souboru či složky došlo k chybě
Není možné zkopírovat složku do některé z jejích vlastních podsložek
@@ -407,6 +417,8 @@
Na váš dotaz nic nenalezeno
Zahájit vaše vyhledávání
Pište do pruhu vyhledávání výše a hledejte v souborech, kontaktech, událostech v kalendáři a dalším napříč vaším účtem.
+ Zkontrolujte své připojení k Internetu nebo to zkuste znovu
+ Nekvalitní připojení
složka
ŽIVĚ
Načítání…
@@ -471,6 +483,11 @@
Složka už existuje
Tuto složku je nejlépe prohlížet si v %1$s.
Vytvořit
+ %1$d z %2$d · %3$s
+ Při synchronizování složky %s došlo k chybě
+ Nedostatek prostoru na disku – synchronizace zrušena
+ složka %s úspěšně synchronizována
+ Synchronizování…
Nejsou zde žádné složky
Název složky je třeba vyplnit
Vybrat
@@ -558,7 +575,6 @@
Vyčistit data
Nastavení, databáze a certifikáty serverů z dat %1$s budou natrvalo smazány. \n\nStažené soubory zůstanou beze změny.\n\nTento proces může chvíli trvat.
Spravovat úložný prostor
- Dosáhli jste maximálního limitu počtu nahrávaných souborů. Nahrávejte méně než 500 souborů naráz.
Soubor s médii se nedaří proudově vysílat (stream)
Nepodařilo se číst soubor média
Soubor médií nemá platné kódování
@@ -607,6 +623,8 @@
Akci se nepodařilo provést.
Zobrazovat upozornění pro interakci s výsledkem operací na pozadí
Operace na pozadí
+ Zjišťovat změny v lokálních souborech
+ Pozorovatel obsahu
Zobrazuje ukazatel postupu stahování
Stažené
Zobrazuje průběh a výsledek synchronizace souborů
@@ -647,6 +665,7 @@
Zadejte svůj bezpečnostní kód
Bezpečnostní kód bude vyžadován při každém spuštění aplikace
Zadejte svůj bezpečnostní kód
+ Bezpečnostní kód bude vyžadován při každém spuštění aplikace nebo jejím znovuotevření za déle než 5 sekund.
Zadání bezpečnostního kódu se neshodují
Zopakujte zadání svého bezpečnostního kódu
Smazat váš bezpečnostní kód
@@ -662,13 +681,6 @@
Nenalezena žádná aplikace pro nastavení obrázku
Připnout na domovskou obrazovku
Otevřít %1$s
- .txt
- 389 KB
- výplň
- 12:23:45
- Nedávno upravováno
- Toto je zástupný text
- 2012/05/18 12:23
vypnout
přepnout
Vyberte server…
@@ -690,6 +702,7 @@
O aplikaci
Podrobnosti
Vývojové
+ Soubory
Obecné
Více
Synchronizovat
@@ -881,6 +894,8 @@
Zaregistrovat se u poskytovatele
Povolit %1$s přístup k vašemu Nextcloud účtu %2$s?
Řadit podle
+ Seřadit od oblíbených
+ Při řazení zobrazovat složky a pak až soubory
Skrýt
Podrobnosti
Totožnost serveru se nepodařilo ověřit
@@ -990,6 +1005,7 @@
Událost nenalezena, ale ještě pořád je možné aktualizovat synchronizací. Přesměrovává se na web…
Kontakt nenalezen, ale ještě pořád je možné aktualizovat synchronizací. Přesměrovává se na web…
Pro otevření výsledků vyhledávání je zapotřebí oprávnění – pokud chybí, přesměruje na web…
+ V této složce
Neznámé
Odemknout soubor
Existuje nepřečtený komentář
@@ -1097,6 +1113,8 @@
Nedůvěryhodný certifikát serveru
Zjišťování verze serveru…
Aplikace ukončena
+ Přeskočeno
+ Takto nazvaný soubor už existuje.
Dokončeno
Stejný soubor nalezen na vzdáleném – nahrání bude přeskočeno
Neznámá chyba
@@ -1220,6 +1238,12 @@
- Bude exportováno %d souborů. Podrobnosti viz upozornění.
- Budou exportovány %d soubory. Podrobnosti viz upozornění.
+
+ - Je možné nahrávat pouze %d soubor naráz.
+ - Je možné nahrávat pouze %d soubory naráz.
+ - Je možné nahrávat pouze %d souborů naráz.
+ - Je možné nahrávat pouze %d soubory naráz.
+
- %1$d složka
- %1$d složky
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index d6647e3..2a49ec6 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -15,7 +15,7 @@
List visning
Gendan kontakter og kalender
Ny mappe
- Flyt eller kopier
+ Flyt eller kopiér
Åbn med
Søg
Detaljer
@@ -93,12 +93,19 @@
%1$s understøtter ikke flere konti
Kunne ikke etablere forbindelse
Annuller log på
+ Angiv venligst en gyldig serveradresse.
+ Kan ikke hent log på detaljer. Prøv venligst igen.
Der opstod et problem i forbindelse med din log på forespørgsel. Prøv venligst igen senere.
+ Der er ingen browser tilgængelig til at åbne dette link.
Gennemfør venligst log på processen i din browser
+ Autoupload er pauseret for batterisparer er aktiveret.
Forblevet i oprindelig folder, da den er skrivebeskyttet
+ Lav batteri, upload kan tage længere tid
Upload kun på ubegrænset wifi
/AutoUpload
Denne mappe er allerede inkluderet i den overordnede mappes synkronisering, hvilket kan medføre dobbelt uploads
+ Venter på Wi-Fi for at starte upload
+ Uploader filer fra %s til %s
Konfigurer
Opret en ny brugerdefineret mappeopsætning
Opsæt en brugerdefineret mappe
@@ -207,7 +214,7 @@
Det er ikke muligt at kopiere en mappe til en af dens egne undermapper
Filen findes allerede i destinationsmappen
Ikke muligt at kopiere. Undersøg venligst om filen eksisterer.
- Kopier link
+ Kopiér link
Kopier/flyt til krypteret mappe endnu ikke understøttet.
Kunne ikke hente hele billedet
Kunne ikke hente delte drev
@@ -456,9 +463,9 @@
Succes ved genskabning af fil version!
Detaljer
Download
- Eksporter
+ Eksportér
File omdøbt %1$sunder overførelse
- Synkroniser
+ Synkronisér
Ingen fil valgt
Filnavnet kan ikke stå tomt.
Ugyldige tegn: / \\ < > : \" | ? *
@@ -471,11 +478,13 @@
Mappe findes allerede
Denne mappe vises bedst i %1$s.
Opret
+ %1$d ud af %2$d · %3$s
+ Synkroniserer...
Ingen mappe her
Mappenavnet må ikke være tomt
Vælg
Vælg destinationsmappe
- Kopier
+ Kopiér
Flyt
Du har ikke tilladelse %s
til at kopiere denne fil
@@ -558,7 +567,6 @@
Fjern data
Indstillinger, database og server certifikater fra %1$s\'s data vil blive permanent slettede.\n\nHentede filer vil forblive uberørte.\n\n Denne proces kan tage lidt tid.
Håndter plads
- Du har nået din maksimale uploadgrænse. Upload venligst mindre end 500 filer på samme tid.
Medie filen kan ikke streames.
Kunne ikke læse mediefil
Mediefilen har en ugyldig indkodning.
@@ -597,7 +605,7 @@
Ingen kalender eksisterer
Ingen app tilgængelig til at håndtere mailadresser
Ingen elementer
- Ingen App tilgængelig til håndtering af Kort
+ Ingen App tilgængelig til håndtering af kort
kun en konto tilladt
Ingen App tilgængelig til håndtering af PDF
Der er ingen tilgængelig app til at sende de valgte filer
@@ -662,13 +670,6 @@
Ingen apps fundet til at vælge et billede med
Fastgør til hjemmeskærm
Åbn %1$s
- .txt
- 389 KB
- pladsholder
- 12:23:45
- Nyligt redigeret
- Dette er en pladsholder
- 2012/05/18 12:23 PM
stop
skift
Vælg venligst en server...
@@ -690,6 +691,7 @@
Om
Detaljer
Udvikling
+ Filer
Generel
Mere
Synkronisér
@@ -822,7 +824,7 @@
Del
Tillad download og synkronisering
Vi kunne ikke opdatere delingen. Tilføj en note og prøv igen.
- Del og kopier link
+ Del og kopiér link
Opret
Brugerdefinerede rettigheder
Slet
@@ -882,6 +884,8 @@
Indskrivning hos udbyder
Tillad %1$s adgang til din Nextcloud konto %2$s?
Sorter efter
+ Vis favoritter først
+ Sorter mapper før filer
Gem
Detaljer
Serverens identitet kunne ikke verificeres
@@ -992,6 +996,7 @@
Event ikke fundet. Du kan altid synkronisere for at opdatere. Omdirigerer til web…
Kontakt ikke fundet. Du kan altid synkronisere for at opdatere. Omdirigerer til web…
Rettigheder er krævet for at åbne søgeresultat, ellers vil det omdirigere til web...
+ I denne mappe
Ukendt
Lås op filen
Der er ulæste kommentarer
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 316d90a..47eec8a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -44,6 +44,7 @@
Zeigt ein Widget aus dem Dashboard an
Suche in %s
Offline erscheinen
+ Dieser Inhalt wurde von KI generiert und kann Fehler enthalten.
Neue Aufgabe hinzufügen
Unten rechts eine neue Aufgabe erstellen
Bitte einen Text eingeben
@@ -55,6 +56,7 @@
Es ist ein Fehler beim Löschen der Aufgabe aufgetreten
Aufgabe gelöscht
Aufgabenliste ist leer.
+ Die Aufgabenliste ist leer. Bitte die Konfiguration der Assistenten-App überprüfen.
Die Aufgabenliste kann nicht abgerufen werden. Bitte überprüfen Sie Ihre Internetverbindung.
Aufgabe löschen
Die Aufgabenausgabe ist noch nicht fertig.
@@ -98,10 +100,14 @@
Fehler bei der Verarbeitung Ihrer Anmeldeanforderung. Bitte versuchen Sie es später erneut.
Es ist kein Browser verfügbar, um diesen Link zu öffnen.
Bitte schließen Sie den Anmeldevorgang in Ihrem Browser ab
+ Der automatische Upload wurde angehalten, da Battery-Saver eingeschaltet ist.
im Original-Verzeichnis belassen, da nur lesbar
+ Niedriger Akkustand, das Hochladen kann länger dauern
Nur über gebührenfreies WLAN hochladen
/AutoUpload
Dieser Ordner ist bereits in der Synchronisierung des übergeordneten Ordners enthalten, was zu doppelten Uploads führen kann
+ Warte auf WLAN für den Beginn des Hochladens
+ Lade Dateien von %s nach %s hoch
Einrichten
Erstellen Sie ein Setup für den eigenen Ordner
Erstellen Sie einen eigenen Ordner
@@ -205,6 +211,7 @@
Der Import konnte nicht gestartet werden. Bitte erneut versuchen
Keine Datei gefunden
Wir können Ihr letztes Backup nicht finden!
+ Erkennen von Inhaltsänderungen
In die Zwischenablage kopiert
Es ist ein Fehler beim Kopieren der Datei oder des Ordners aufgetreten.
Es ist nicht möglich, einen Ordner in einen seiner Unterordner zu kopieren
@@ -410,6 +417,8 @@
Keine Ergebnisse für Ihre Suche gefunden
Beginnen Sie Ihre Suche
Geben Sie in die Suchleiste oben einen Begriff ein, um Dateien, Kontakte, Kalendertermine und mehr in Ihrem Konto zu finden.
+ Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie es später erneut.
+ Schlechte Verbindung
Ordner
LIVE
Lade…
@@ -474,6 +483,11 @@
Ordner existiert bereits
Dieser Ordner lässt sich am besten in %1$s anzeigen.
Erstellen
+ %1$d von %2$d · %3$s
+ Es ist ein Fehler beim Synchronisieren des Ordners %s aufgetreten.
+ Unzureichender Speicherplatz, Synchronisierung abgebrochen
+ %s Ordner erfolgreich synchronisiert
+ Synchronisiere…
Keine Ordner vorhanden
Der Ordnername darf nicht leer sein
Auswählen
@@ -561,7 +575,6 @@
Daten löschen
Einstellungen, Datenbank und Serverzertifikate von %1$s\'s Daten werden dauerhaft gelöscht.\n\nHerunter geladene Dateien bleiben unangetastet.\n\nDieser Vorgang kann eine Zeit dauern.
Verwalte Speicherplatz
- Sie haben die maximale Anzahl an Datei-Uploads erreicht. Bitte laden Sie weniger als 500 Dateien gleichzeitig hoch.
Die Mediendatei kann nicht gestreamt werden
Die Mediendatei konnte nicht gelesen werden
Mediendatei ist nicht korrekt encodiert
@@ -610,6 +623,8 @@
Aktion konnte nicht ausgeführt werden
Benachrichtigungen anzeigen, um auf die Ergebnisse von Hintergrundoperationen zu reagieren
Hintergrundvorgänge
+ Erkennt lokale Dateiänderungen
+ Inhaltsmonitor
Zeigt den Herunterlade-Fortschritt an
Downloads
Zeigt den Fortschritt der Dateisynchronisierung und die Ergebnisse an
@@ -650,6 +665,7 @@
PIN eingeben
Die PIN wird jedes mal beim Start der App abgefragt
Bitte geben Sie Ihre PIN ein
+ Die PIN wird jedes mal beim Start der App oder beim erneuten Öffnen nach 5 Sekunden abgefragt
Die PINs stimmen nicht überein
Bitte Ihre PIN nochmals eingeben
Ihre PIN löschen
@@ -665,13 +681,6 @@
Keine App gefunden, mit der ein Bid gesetzt werden könnte
An Startbildschirm anheften
%1$s öffnen
- .txt
- 389 KB
- Platzhalter
- 12:23:45
- Kürzlich bearbeitet
- Dies ist ein Platzhalter
- 18.5.2012 12:23
Stopp
Umschalten
Bitte einen Server auswählen…
@@ -693,6 +702,7 @@
Über
Details
Dev
+ Dateien
Allgemein
Mehr
Synchronisieren
@@ -884,6 +894,8 @@
Mit Provider anmelden
Zulassen, dass %1$s auf Ihr Nextcloud Konto %2$s zugreifen darf?
Sortieren nach
+ Favoriten zuerst sortieren
+ Ordner vor Dateien sortieren
Ausblenden
Details
Die Identität des Servers konnte nicht verifiziert werden
@@ -959,7 +971,7 @@
Nicht genügend Speicherplatz
Status-synchronisieren-Button
Dateien
- Schaltfläche „Synchronisationswarnung“
+ Schaltfläche \"Synchronisationswarnung\"
Einstellungs-Button
Ordner konfigurieren
Die Sofort-Uploads wurden vollständig überarbeitet. Konfiguriere Sie Ihren automatischen Uploader im Hauptmenü.\n\nGenießen Sie den verbesserten Auto-Upload.
@@ -994,6 +1006,7 @@
Termin nicht gefunden, Sie können jederzeit die Synchronisierung wiederholen. Weiterleiten zum Web…
Kontakt nicht gefunden, Sie können jederzeit synchronisieren für eine Aktualisierung. Weiterleiten zum Web…
Berechtigungen für das Öffnen der Suchergebnisse notwendig, ansonsten werden Sie zum Web weitergeleitet
+ In diesem Ordner
Unbekannt
Datei entsperren
Es gibt ungelesene Kommentare
@@ -1101,6 +1114,8 @@
Serverzertifikat ist nicht vertrauenswürdig
Serverversion wird abgerufen …
App beendet
+ Überspringen
+ Eine Datei mit demselben Namen ist bereits vorhanden.
Fertiggestellt
Gleiche Datei auf dem Remote-Server gefunden, überspringe das Hochladen
Unbekannter Fehler
@@ -1194,6 +1209,10 @@
- %d Datei wird exportiert. Einzelheiten finden Sie in der Benachrichtigung.
- %d Dateien werden exportiert. Einzelheiten finden Sie in der Benachrichtigung.
+
+ - Es können bis zu %d Datei gleichzeitig hochgeladen werden.
+ - Es können bis zu %d Dateien gleichzeitig hochgeladen werden.
+
- %1$d Ordner
- %1$d Ordner
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 55cba02..b3399e7 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -287,6 +287,7 @@
Σφάλμα κατά την εκκίνηση της κάμερας
Σφάλμα σάρωσης εγγράφων
Λογαριασμοί
+ Δημιουργήθηκε
Όνομα εργασίας
Πρόοδος
Κατάσταση
@@ -531,12 +532,6 @@
Επιπλέον διακαιώματα απαιτούνται για μεταφόρτωση και λήψη αρχείων.
Δεν βρέθηκε εφαρμογή για τον ορισμό φωτογραφίας
Άνοιγμα %1$s
- .txt
- 389 KB
- 12:23:45
- Επεξεργάστηκε πρόσφατα
- Αυτό είναι ένα σημείο placeholder
- 2012/05/18 12:23 PM
Παύση
Εναλλαγή
Η απενεργοποίηση του έλεγχου εξοικονόμησης ενέργειας ίσως έχει ως αποτέλεσμα το ανέβασμα αρχείων ενώ βρίσκεστε σε κατάσταση χαμηλής μπαταρίας
@@ -554,6 +549,7 @@
Περί
Λεπτομέρειες
Dev
+ Αρχεία
Γενικά
Περισσότερα
Συγχρονισμός
@@ -701,6 +697,8 @@
Σύνδεση με πάροχο
Να επιτραπεί%1$sη πρόσβαση στον λογαριασμό σας Nextcloud %2$s ?
Ταξινόμηση κατά
+ Ταξινόμηση των αγαπημένων πρώτα
+ Ταξινόμηση φακέλων πριν από τα αρχεία
Απόκρυψη
Λεπτομέρειες
Δεν ήταν δυνατή η επαλήθευση της ταυτότητας του διακομιστή
@@ -816,6 +814,7 @@
Μεταφόρτωση περιεχομένου από άλλες εφαρμογές
Φωτογραφία
Μεταφόρτωση από την φωτογραφική μηχανή
+ Βίντεο
Όνομα αρχείου
Τύπος αρχείου
Αρχείο συντόμευσης Google Maps(%s)
@@ -913,6 +912,10 @@
Αποστολή email
Ο φάκελος αποθήκευσης δεδομένων δεν υπάρχει!
Αυτό μπορεί να οφείλεται σε μια επαναφορά αντιγράφων ασφαλείας σε άλλη συσκευή. Επιστροφή στην προεπιλογή. Παρακαλούμε ελέγξτε τις ρυθμίσεις για να προσαρμόσετε τον φάκελο αποθήκευσης δεδομένων.
+
+ - %d ώρα
+ - %d ώρες
+
- Αδύνατος συγχρονισμός %1$d αρχείου (διενέξεις: %2$d)
- Αδύνατος συγχρονισμός %1$d αρχείων (διενέξεις: %2$d)
@@ -961,6 +964,10 @@
- %1$d αρχείο
- %1$d αρχεία
+
+ - %1$d στοιχείο
+ - %1$d στοιχεία
+
- Εμφάνιση %1$dκρυφού φακέλου
- Εμφάνιση %1$dκρυφών φακέλων
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index 6dd01f3..de8cac3 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -372,11 +372,6 @@
Rifuzi
Pliaj permesoj bezonataj por el- kaj alŝuti dosierojn.
Neniu aplikaĵo trovita por uzi tiun bildon
- .txt
- 389 KB
- 12:23:45
- Ĉi tio estas lokokupilo
- 2012/05/18 12:23
forigita
konservita en origina dosierujo
movita al aplikaĵa dosierujo
diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml
index c82d6dd..2e7b333 100644
--- a/app/src/main/res/values-es-rAR/strings.xml
+++ b/app/src/main/res/values-es-rAR/strings.xml
@@ -484,7 +484,6 @@
Borrar datos
Las configuraciones , base de datos y certificados del servidor de los datos de %1$s serán borrados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
Administrar espacio
- Ha alcanzado el límite de carga máxima de archivos. Por favor, cargue menos de 500 archivos a la vez.
No se puede transmitir el archivo multimedia
No fue posible leer el archivo de medios
El archivo de medios tiene una codificacion incorrecta
@@ -566,11 +565,6 @@
Se requieren permisos adicionales para cargar y descargar archivos.
No se encontró ninguna aplicación para establecer una imagen con
Abrir %1$s
- 389 KB
- 12:23:45
- Editado recientemente
- Este es un marcador de posición
- 2012/05/18 12:23 PM
detener
cambiar
¡La desactivación de la verificación de ahorro de energía puede provocar la carga de archivos cuando la batería está baja!
@@ -590,6 +584,7 @@
Acerca de
Detalles
Dev
+ Archivo
General
Más
Sincronizar
diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml
index 1f099bb..15249d5 100644
--- a/app/src/main/res/values-es-rCL/strings.xml
+++ b/app/src/main/res/values-es-rCL/strings.xml
@@ -369,10 +369,6 @@
Se requieren permisos adicionales para cargar y descargar archivos.
No se encontró una aplicación con la cual establecer la imagen
Abrir %1$s
- 389 KB
- 12:23:45
- Este es un marcador de posición
- 2012/05/18 12:23 PM
Borrado
mantenido en la carpeta original
movido a la carpeta de la aplicación
@@ -381,6 +377,7 @@
Acerca de
Detalles
Desarrollo
+ Archivo
General
Más
Sincronizar
diff --git a/app/src/main/res/values-es-rCO/strings.xml b/app/src/main/res/values-es-rCO/strings.xml
index b1ce06e..9b75011 100644
--- a/app/src/main/res/values-es-rCO/strings.xml
+++ b/app/src/main/res/values-es-rCO/strings.xml
@@ -514,10 +514,6 @@
Código de seguridad incorrecto
Se requieren permisos adicionales para cargar y descargar archivos.
No se encontró una aplicación con la cual establecer la imagen
- 389 KB
- 12:23:45
- Este es un marcador de posición
- 2012/05/18 12:23 PM
Borrado
mantenido en la carpeta original
movido a la carpeta de la aplicación
@@ -526,6 +522,7 @@
Acerca de
Detalles
Desarrollo
+ Archivo
General
Más
Sincronizar
diff --git a/app/src/main/res/values-es-rEC/strings.xml b/app/src/main/res/values-es-rEC/strings.xml
index 7c85226..bd405da 100644
--- a/app/src/main/res/values-es-rEC/strings.xml
+++ b/app/src/main/res/values-es-rEC/strings.xml
@@ -525,12 +525,6 @@
Se requieren permisos adicionales para cargar y descargar archivos.
No se encontró una aplicación con la cual establecer la imagen
Abrir %1$s
- .txt
- 389 KB
- 12:23:45
- Recientemente editados
- Este es un marcador de posición
- 2012/05/18 12:23 PM
detener
alternar
¡Desactivar la comprobación de ahorro de energía podría resultar en la carga de archivos cuando la batería esté baja!
@@ -548,6 +542,7 @@
Acerca de
Detalles
Desarrollo
+ Archivo
General
Más
Sincronizar
@@ -693,6 +688,7 @@
Registrarse con el proveedor
¿Permitir que %1$s acceda a tu cuenta de Nextcloud %2$s?
Ordenar por
+ Ordenar primero los favoritos.
Ocultar
Detalles
La identidad del servidor no pudo ser verificada
diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml
index 710716b..6fa6dcc 100644
--- a/app/src/main/res/values-es-rMX/strings.xml
+++ b/app/src/main/res/values-es-rMX/strings.xml
@@ -489,7 +489,6 @@
Borrar datos
Las configuraciones, base de datos y certificados del servidor de los datos de %1$s serán eliminados permanentemente.\n\nLos archivos descargados se mantendrán sin cambios.\n\nEste proceso puede tomar algo de tiempo.
Administrar espacio
- Ha alcanzado el límite de carga máxima de archivos. Por favor, cargue menos de 500 archivos a la vez.
No se puede transmitir el archivo multimedia
No fue posible leer el archivo de medios
El archivo de medios tiene una codificación incorrecta
@@ -572,11 +571,6 @@
Se requieren permisos adicionales para cargar y descargar archivos.
No se encontró una aplicación con la cual establecer la imagen
Abrir %1$s
- 389 KB
- 12:23:45
- Editado recientemente
- Este es un marcador de posición
- 2012/05/18 12:23 PM
detener
alternar
¡Desactivar la comprobación de ahorro de energía podría resultar en la carga de archivos cuando la batería esté baja!
@@ -596,6 +590,7 @@
Acerca de
Detalles
Desarrollo
+ Archivo
General
Más
Sincronizar
@@ -763,6 +758,8 @@
Registrarse con el proveedor
¿Permitir que %1$s acceda a su cuenta de Nextcloud %2$s?
Ordenar por
+ Ordenar los favoritos primero
+ Ordenar carpetas antes que archivos
Ocultar
Detalles
La identidad del servidor no pudo ser verificada
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 89d5304..d15ec90 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -102,6 +102,7 @@
Subir sólo con conexión Wi-Fi sin límite de datos
/CargaAutomática
Esta carpeta ya está incluida en la sincronización de la carpeta principal, lo que puede provocar subidas duplicadas.
+ Subiendo archivos desde %s a %s
Configurar
Crear nueva configuración de una carpeta especifica
Configure una carpeta especifica
@@ -561,7 +562,6 @@
Limpiar datos
Las opciones, certificados y bases de datos de %1$s serán borrados permanentemente.\n\nLos archivos bajados no se tocarán.\n\nEste proceso tardará algún tiempo.
Gestionar espacio
- Has alcanzado el límite máximo de subida de archivos. Por favor, suba menos de 500 archivos cada vez.
El archivo multimedia no puede ser transmitido
El archivo de medios no se ha podido leer
El archivo de medios tiene una codificación incorrecta
@@ -665,13 +665,6 @@
No se ha encontrado una app para establecer con ella la imagen
Anclar a la pantalla de inicio
Abrir %1$s
- .txt
- 389 KB
- marcador de posición
- 12:23:45
- Editado recientemente
- Esto es un marcador de posición
- 18/05/2012 12:23 PM
parar
cambiar
Por favor, seleccione un servidor…
@@ -693,6 +686,7 @@
Acerca de
Detalles
Dev
+ Archivos
General
Más
Sincronizar
@@ -884,6 +878,8 @@
Registrarse con un proveedor
¿Permitir a %1$s acceder a tu cuenta de Nextcloud %2$s?
Ordenar por
+ Ordenar los favoritos primero
+ Ordenar carpetas antes que archivos
Ocultar
Detalles
No se ha podido verificar la identidad del servidor
diff --git a/app/src/main/res/values-et-rEE/strings.xml b/app/src/main/res/values-et-rEE/strings.xml
index c589e29..797d8b1 100644
--- a/app/src/main/res/values-et-rEE/strings.xml
+++ b/app/src/main/res/values-et-rEE/strings.xml
@@ -44,6 +44,7 @@
Näitab ühte vidinat juhtpaneelilt/töölaualt
Otsi siin: %s
Sellega paistad olema võrgust väljas
+ See sisu on tehisaru koostatud ja võib sisaldada vigu.
Lisa uus ülesanne
Loo uus ülesanne all paremal asuvast valikust
Kirjuta midagi vahvat
@@ -55,6 +56,7 @@
Ülesande kustutamisel tekkis viga
Ülesande kustutamine õnnestus
Ülesannete loend on tühi.
+ Ülesannete loend on tühi. Kontrolli Abilise rakenduse seadistusi.
Ülesannete loendi laadimine ei õnnestunud. Palun kontrolli oma nutiseadme internetiühenduse toimivust.
Kustuta ülesanne
Ülesande väljund pole veel valmis.
@@ -98,10 +100,14 @@
Sinu sisselogimispäringu töötlemisel tekkis viga. Palun proovi hiljem uuesti.
Selle lingi avamiseks ei leidu ühtegi brauserit.
Palun lõpeta sisselogimisprotsess oma veebibrauseris
+ Kuna akukasutuse optimeerimine on lülitatud sisse, siis automaatne üleslaadimine pole töös.
säilitatud lugemisõigustega algkaustas
+ Aku on üsna tühi, üleslaadimine võib kesta kauem
Laadi üles ainult mahupiiranguta WiFi võrgus
/AutoUpload
Kuna ülalpool asuv kaust kuulub sünkroonimisele, siis see kaust on juba kaasatud ning nii võivad tekkida topelt üleslaadimised
+ Ootan sünkroonimiseks WiFi ühenduse loomist
+ Laadin faile üles: %s kuni %s
Seadista
Loo uus kohandatud kausta seadistus
Seadista kohandatud kaust
@@ -205,6 +211,7 @@
Ei õnnestunud käivitada importimist. Palun proovi uuesti
Faili ei leitud
Sinu viimast varukoopiat ei leidu!
+ Tuvastan sisu muudatusi
Kopeeritud lõikepuhvrisse
Selle faili või kausta kopeerimisel tekkis tõrge
Kausta ei saa kopeerida tema enda alamkausta
@@ -410,6 +417,8 @@
Sinu päringul pole tulemusi
Alusta otsingut
Otsimaks faile, kontakte, kalendrisündmusi ja muud sinu kasutajakontoga seotud teavet, sisesta otsitu ülaltoodud otsinguribale.
+ Palun kontrolli oma nutiseadme internetiühenduse toimimist või proovi hiljem uuesti
+ Kehv ühendus
kaust
OTSE
Laadimine…
@@ -474,6 +483,11 @@
Kaust on juba olemas
Kausta on kõige parem vaadata siin: %1$s.
Loo
+ %1$d / %2$d · %3$s
+ „%s“ kausta sünkroonimisel tekkis viga
+ Andmekandjal pole piisavalt ruumi, sünkroonimine on katkestatud
+ %s kausta sünkroonimine õnnestus
+ Sünkroonin…
Siin ei ole kaustu
Kausta nimi ei saa olla tühi.
Vali
@@ -561,7 +575,6 @@
Kustuta andmed
Järgnevaga kustutatakse jäädavalt kõik „%1$s“ andmete seadistused, andmebaasikirjed ja sertifikaadid.\n\nAllalaaditud failid jäävad alles.\n\nKogu toiming võib märgatavalt aega võtta.
Halda andmeruumi
- Sa üritad laadida üles enam faile kui võimalik on. Palun laadi korraga üles vähem, kui 500 faili.
Seda meediafaili ei saa voogedastada
Meediafaili lugemine ebaõnnestus
Sellel meediafailil on vigane kodeering
@@ -610,6 +623,8 @@
Ei õnnestunud käivitada tegevust.
Näita teavitusi, mis võimaldavad suhelda taustal tehtud toimingutega
Toimingud taustal
+ Tuvastab kohalike failide muudatused
+ Sisuvaatleja
Näitab allalaadimise edenemist
Allalaadimised
Näitab failide sünkroniseerimise edenemist ja tulemusi
@@ -650,10 +665,11 @@
Sisesta oma täiendav salasõna
Täiendavat salasõna küsitakse iga kord, kui sa selle rakenduse käivitad
Palun sisesta oma täiendav salasõna
+ Täiendavat salasõna küsitakse iga kord, kui rakendust avatakse või avatakse uuesti 5 sekundi möödumisel.
Täiendavad salasõnad pole samad
Palun sisesta täiendav salasõna uuesti
Kustuta oma täiendav salasõna
- Täiendav salasõna kustutatud
+ Täiendav salasõna on kustutatud
Täiendav salasõna on salvestatud
Vale täiendav salasõna
Ei õnnestu avada salasõnaga kaitstud pdf-faili. Palun kasuta välist pdf-failide vaatamise rakendust.
@@ -665,13 +681,6 @@
Ei leidu rakendust pildi määramiseks
Kinnita avalehele
Ava %1$s
- .txt
- 389 KB
- kohatäitja
- 12:23:45
- Hiljuti muudetud
- See on kohahoidja
- 2012/05/18 12:23 PM
peata
lülita sisse/välja
Palun vali server…
@@ -693,6 +702,7 @@
Info
Üksikasjad
Arendusversioon
+ Failid
Üldine
Rohkem
Sünkrooni
@@ -724,7 +734,7 @@
Pole kasutusel
Kaitse rakendust kasutades
Nutiseadmepõhist autentimist
- Täiendavat salasõna
+ Täiendav salasõna
Halda kontosid
Soovita sõbrale
Eemalda krüptimine kohalikus seadmes
@@ -884,6 +894,8 @@
Või liitu teenusepakkujaga
Kas lubad „%1$s“ teenusel ligipääsu sinu Nextcloudi kasutajakontole „%2$s“?
Sorteeri
+ Järjesta lemmikud esimesena
+ Järjesta kaustad enne faile
Peida
Üksikasjad
Serveri identiteeti polnud võimalik kontrollida
@@ -994,6 +1006,7 @@
Sündmust ei leidu, sa võid andmete uuendamiseks alati sünkroniseerida. Suunan edasi veebi…
Kontakti ei leidu, sa võid andmete uuendamiseks alati sünkroniseerida. Suunan edasi veebi…
Otsingutulemuste avamiseks on vajalikud vastavad õigused, vastasel juhul suunan päringu ümber veebi…
+ Selles kaustas
Teadmata
Eemalda faili lukustus
Leidub lugemata kommentaare
@@ -1101,6 +1114,8 @@
Serveri sertifikaat pole usaldusväärne
Laadin serveris leiduvat versiooni…
Rakenduse töö on lõpetatud
+ Vahelejäetud
+ Sellise nimega fail on juba olemas.
Lõpetatud
Sama fail leidub kaugseadmes, jätan üleslaadimise vahele
Tundmatu viga
@@ -1194,6 +1209,10 @@
- Eksportimisele kuulub %d fail. Üksikasjalik info leidub teavituses.
- Eksportimisele kuulub %d faili. Üksikasjalik info leidub teavituses.
+
+ - Ühe korraga saad üles laadida kuni %d faili.
+ - Ühe korraga saad üles laadida kuni %d faili.
+
- %1$d kaust
- %1$d kausta
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 877893f..4d05aab 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -92,6 +92,7 @@
%1$s(e)k ez ditu hainbat kontu onartzen
Ezin izan da konexioa ezarri
Utzi saioa hasiera
+ Mesedez, sartu baliozko zerbitzari baten helbidea
Arazo bat egon da zure saioa hasteko eskaera prozesatzean. Mesedez saiatu geroxeago.
Mesedez, osatu saioa hasteko prozesua zure nabigatzailean
Jatorrizko karpetan mantendu da, soilik irakurtzeko delako
@@ -392,6 +393,7 @@
Partekatzen dituzun fitxategiak eta karpetak hemen agertuko dira.
Oraindik ez dago ezer partekatuta
Ez da emaitzarik aurkitu zure bilaketarentzat
+ Hasi zure bilaketa
karpeta
ZUZENEAN
Kargatzen…
@@ -540,7 +542,6 @@
Garbitu datuak
%1$s(r)en ezarpenak, datu-basea eta zerbitzariaren ziurtagiriak betirako ezabatuko dira.\n\n Deskargatutako fitxategiak ez dira ukituko.\n\n Prozesu honek denbora behar du.
Kudeatu lekua
- Fitxategiak igotzeko gehienezko mugara iritsi zara. Mesedez, ez igo 500 fitxategi baino gutxiago aldi berean.
Ezin da multimedia fitxategia transmititu
Ezin izan da multimedia fitxategia irakurri
Multimedia fitxategiak kodeketa desegokia du
@@ -563,6 +564,7 @@
Ezin da karpeta bat azpikarpeta batera mugitu
Fitxategi hau existitzen da jadanik helburuko karpetan
Ezin izan da fitxategia mugitu. Mesedez egiaztatu fitxategia existitzen dela.
+ Isilarazi jakinarazpen guztiak
Errore bat gertatu da zerbitzariari itxarotean. Ezin izan da eragiketa burutu.
Errore bat gertatu da zerbitzariarekin konektatzean.
Errore bat gertatu da zerbitzariari itxarotean. Ezin izan da eragiketa burutu.
@@ -637,12 +639,6 @@
Ez da aplikaziorik aurkitu irudia ezartzeko
Finkatu pantaila nagusian
Ireki %1$s
- .txt
- 389 KB
- 12:23:45
- Berriki editatua
- Hau leku-marka bat da
- 2012/05/18 12:23 PM
gelditu
txandakatu
Hautatu zerbitzari bat...
@@ -664,6 +660,7 @@
Honi buruz
Xehetasunak
Garapena
+ Fitxategiak
Orokorra
Gehiago
Sinkronizatu
@@ -848,6 +845,8 @@
Erregistratu hornitzaile batekin
%1$s zure %2$s Nextcloud kontuan sartzea baimendu nahi duzu?
Ordenatu honen arabera
+ Ordenatu gogokoak lehenengo
+ Ordenatu karpetak fitxategien aurretik
Ezkutatu
Xehetasunak
Ezin izan da zerbitzariaren identitatea egiaztatu
@@ -1006,6 +1005,7 @@
Ezin izan da fitxategia kopiatu biltegiratze lokalera
Karpeta blokeatzeak huts egin du
Igoera bertan behera utzi du erabiltzaileak
+ Aplikazioaren baimenak
%1$d/%2$d-%3$s
Zifratzea >= Android 5.0 bertsioarekin soilik da posible
Hautatutako fitxategiak ezin dira %1$sra kopiatu ez dagoelako toki nahikorik. Kopiatu ordez bertara mugitu nahi dituzu?
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index f2fd12b..65afda3 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -552,12 +552,6 @@
نیاز به مجوزهای اضافی برای بارگذاری و دریافت پروندهها می باشد.
هیچ برنامه ای برای تنظیم عکس یافت نشد
باز کنید %1$s
- .txt
- ۳۸۹ کیلوبایت
- 12:23:45
- اخیراً ویرایش شده
- این یک حفره است.
- 2012/05/18 12:23 بعد از ظهر
توقف
تغییر وضعیت
غیرفعال کردن چک مارک صرفه جویی در مصرف برق ممکن است باعث شود بارگذاری پرونده ها در حالت باتری کم باشد!
@@ -577,6 +571,7 @@
درباره
جزئیات
توسعه
+ پروندهها
عمومی
بیشتر
همگامسازی
@@ -738,6 +733,8 @@
ثبتنام با ارائه دهنده
اجازه به %1$s برای دسترسی به حساب نکستکلود %2$s؟
مرتب سازی بر اساس
+ ابتدا موارد دلخواه را مرتب کنید
+ Sort folders before files
پنهان کردن
جزییات
هویت کارساز نتوانست تأیید شود
diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml
index 6c1f6a5..4e90aac 100644
--- a/app/src/main/res/values-fi-rFI/strings.xml
+++ b/app/src/main/res/values-fi-rFI/strings.xml
@@ -578,12 +578,6 @@
Tiedostojen lähetys ja lataaminen vaatii lisäoikeuksia.
Kuvan liittämiseksi ei löytynyt sovellusta
Avaa %1$s
- .txt
- 389 kt
- 12:23:45
- Äskettäin muokattu
- Tämä on paikkamerkki
- 18.05.2012 12:23
pysäytä
vaihda
Valitse palvelin...
@@ -604,6 +598,7 @@
Tietoja
Tiedot
Kehittäjä
+ Tiedostot
Yleiset
Enemmän
Synkronoi
@@ -765,6 +760,8 @@ GNU yleinen lisenssi, versio 2
Kirjaudu sisään palvelutarjoajan kautta
Salli käyttäjälle %1$s pääsy Nextcloud -tilillesi %2$s?
Lajittelujärjestys
+ Järjestä suosikit ensiksi
+ Järjestä kansiot ennen tiedostoja
Piilota
Tiedot
Palvelimen identiteettiä ei voitu vahvistaa
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 108578d..0b99e08 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -44,6 +44,7 @@
Affiche un widget du tableau de bord
Recherche dans %s
Apparaître hors-ligne
+ Ce contenu a été généré par IA et peut faire des erreurs.
Ajouter une nouvelle tâche
Créer une nouvelle tâche en bas à droite
Tapez du texte
@@ -55,6 +56,7 @@
Une erreur est survenue lors de la suppression de la tâche
Tâche supprimée avec succès
La liste des tâches est vide.
+ La liste des tâches est vide. Vérifiez la configuration de l’application de l’assistant.
Impossible de récupérer la liste des tâches, veuillez vérifier votre connexion Internet.
Supprimer la tâche
Le résultat de la tâche n\'est pas encore prêt.
@@ -93,12 +95,19 @@
%1$s ne prend pas en charge les comptes multiples
Impossible d\'établir la connexion
Annuler la connexion
+ Veuillez saisir une adresse valide de serveur.
+ Impossible de récupérer les informations de connexion. Veuillez réessayer.
Un problème est survenu lors du traitement de votre demande de connexion. Veuillez réessayer plus tard.
+ Aucun navigateur n\'est disponible pour ouvrir ce lien.
Veuillez finaliser la connexion dans votre navigateur
+ Le téléversement automatique est suspendu, car l\'économiseur de batterie est activé.
conservé dans le dossier original (puisqu\'il est en lecture seule)
+ Batterie faible, le téléversement peut prendre plus de temps
Téléverser par Wi-Fi uniquement
/TéléversementAuto
Ce dossier fait partie de la synchronisation du dossier parent, ce qui peut créer des doublons.
+ En attente du Wi-Fi pour commencer le téléversement
+ Téléversement des fichiers de %s vers %s
Configurer
Créer une nouvelle configuration de dossier personnalisé
Définir un dossier personnalisé
@@ -202,6 +211,7 @@
L\'importation n\'a pas pu démarrer. Veuillez réessayer.
Aucun fichier trouvé
Impossible de trouver la dernière sauvegarde !
+ Détection des modifications de contenu
Copié dans le presse-papier
Une erreur est survenue lors de la copie de ce fichier ou dossier
Il n\'est pas possible de copier un dossier vers un de ses descendants
@@ -407,6 +417,8 @@
Aucun résultat trouvé pour votre requête
Démarrer votre recherche
Tapez dans la barre de recherche ci-dessus pour trouver des fichiers, des contacts, des événements de calendrier et plus encore dans votre compte.
+ Vérifiez votre connexion internet ou ré-essayez plus tard
+ Connexion limitée
dossier
EN DIRECT
Chargement…
@@ -471,6 +483,11 @@
Le dossier existe déjà
Ce dossier est mieux visualisé dans %1$s.
Créer
+ %1$d de %2$d · %3$s
+ Une erreur s\'est produite lors de la synchronisation du dossier %s
+ Espace disque insuffisant, synchronisation annulée
+ Dossier %s synchronisé avec succès
+ Synchronisation en cours…
Aucun dossier
Le nom du dossier ne peut être vide
Choisir
@@ -558,7 +575,6 @@
Effacer les données
Les paramètres, la base de données et les certificats du serveur provenant de %1$s seront définitivement effacés. \n\nLes fichiers téléchargés ne seront pas impactés.\n\nCette opération peut prendre du temps.
Gestion de l\'espace
- Vous avez atteint la limite maximum de téléversement. Veuillez téléverser moins de 500 fichiers à la fois.
Le fichier ne peut être streamer
Impossible de lire le fichier média
Le fichier média n\'est pas correctement encodé
@@ -607,6 +623,8 @@
Échec lors de l\'exécution de l\'action.
Afficher des notifications pour interagir avec le résultat des opérations en arrière-plan
Opérations en arrière-plan
+ Détecte les modifications apportées aux fichiers locaux
+ Observateur de contenu
Afficher la progression de téléchargement
Téléchargements
Afficher les résultats et la progression de synchronisation du fichier
@@ -647,6 +665,7 @@
Saisissez votre code de sécurité
Le code de sécurité sera demandé à chaque ouverture de l\'application
Veuillez saisir votre code de sécurité
+ Le code de sécurité sera demandé chaque fois que l’application est ouverte ou rouverte après 5 secondes.
Les codes de sécurité ne sont pas identiques
Veuillez saisir de nouveau votre code de sécurité
Supprimer votre code de sécurité
@@ -662,13 +681,6 @@
Aucune application trouvée pour utiliser cette image
Épingler sur l\'écran d\'accueil
Ouvrir %1$s
- .txt
- 389 Ko
- exemple
- 12:23:45
- Modifié récemment
- Ceci est un espace réservé
- 18/05/2012 12:23 PM
arrêter
inverser
Veuillez sélectionner un serveur…
@@ -690,6 +702,7 @@
À propos
Préférences
Dév
+ Fichiers
Général
Plus
Synchroniser
@@ -815,7 +828,7 @@
Impossible de définir la limite de téléchargement. Veuillez vérifier les autorisations.
Définir le message
Définir la note
- Statut de connexion
+ Statuts de connexion
Utiliser l\'image comme
Pendant la configuration du chiffrement de bout en bout, vous recevrez une phrase secrète aléatoire de 12 mots dont vous aurez besoin pour ouvrir vos fichiers sur d\'autres appareils. Cette phrase secrète ne sera stockée que sur cet appareil, et pourra être affichée à nouveau sur cet écran. Veuillez la noter en lieu sûr !
Partager
@@ -881,6 +894,8 @@
Se connecter avec un fournisseur
Autoriser %1$s à accéder à votre compte Nextcloud %2$s ?
Trier par
+ Trier les favoris en premier
+ Trier les dossiers avant les fichiers
Masquer
Détails
L\'identité du serveur n\'a pas pu être vérifiée
@@ -991,6 +1006,7 @@
Événement introuvable, vous pouvez toujours synchroniser pour mettre à jour. Redirection au web…
Contact introuvable, vous pouvez toujours synchroniser pour mettre à jour. Redirection au web...
Des permissions sont requises pour ouvrir le résultat de recherche, autrement ceci redirigera au web…
+ Dans ce dossier
Inconnu
Déverrouiller le fichier
Il y a des commentaire non lus
@@ -1098,6 +1114,8 @@
Certificat du serveur non approuvé
Récupération de la version serveur …
L\'application s\'est arrêtée
+ Ignoré
+ Un fichier portant le même nom existe déjà.
Terminé
Le même fichier a été trouvé sur le serveur distant, le téléversement est ignoré
Erreur inconnue
@@ -1206,6 +1224,11 @@
- %d fichiers vont être exportés. Voir la notification pour plus de détails.
- %d fichiers vont être exportés. Voir la notification pour plus de détails.
+
+ - Vous pouvez téléverser uniquement 1%d fichier à la fois.
+ - Vous pouvez téléverser jusqu\'à %d fichiers à la fois.
+ - Vous pouvez téléverser jusqu\'à %d fichiers à la fois.
+
- %1$d dossier
- %1$d dossiers
diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml
index c7642e5..6a1a18a 100644
--- a/app/src/main/res/values-ga/strings.xml
+++ b/app/src/main/res/values-ga/strings.xml
@@ -44,6 +44,7 @@
Taispeáin giuirléid amháin ón deais
Cuardaigh i %s
Le feiceáil as líne
+ Gineadh an t-ábhar seo le hintleacht shaorga agus is féidir botúin a dhéanamh ann.
Cuir tasc nua leis
Cruthaigh tasc nua ón mbun ar dheis
Clóscríobh roinnt téacs
@@ -55,6 +56,7 @@
Tharla earráid agus an tasc á scriosadh
D\'éirigh leis an tasc a scriosadh
Tá liosta tascanna folamh.
+ Tá an liosta tascanna folamh. Seiceáil cumraíocht aip an chúntóra.
Ní féidir liosta tascanna a fháil, seiceáil do nasc idirlín le do thoil.
Scrios Tasc
Níl an t-aschur tasc réidh fós.
@@ -93,12 +95,19 @@
Ní thacaíonn %1$s le cuntais iolracha
Níorbh fhéidir ceangal a bhunú
Cealaigh Logáil Isteach
+ Cuir isteach seoladh freastalaí bailí le do thoil.
+ Ní féidir sonraí logála isteach a fháil. Déan iarracht arís.
Bhí fadhb ann d\'iarratas logáil isteach a phróiseáil. Bain triail eile as ar ball le do thoil.
+ Níl aon bhrabhsálaí ar fáil chun an nasc seo a oscailt.
Críochnaigh an próiseas logáil isteach i do bhrabhsálaí le do thoil
+ Tá an uaslódáil uathoibríoch ar sos mar go bhfuil Coigilteoir Ceallraí ar siúl.
coinnithe sa bhunfhillteán, mar atá sé inléite amháin
+ Ceallraí íseal, d\'fhéadfadh sé go dtógfadh an uaslódáil níos faide
Uaslódáil ar Wi-Fi neamh-mhéadraithe amháin
/Uaslódáil Uathoibríoch
Tá an fillteán seo san áireamh cheana féin i gcomhshioncronú an fhillteáin tuismitheora, rud a d’fhéadfadh uaslódálacha dúblacha a chruthú
+ Ag fanacht go dtosóidh Wi-Fi ag uaslódáil
+ Ag uaslódáil comhad ó %s go %s
Cumraigh
Cruthaigh socrú fillteán saincheaptha nua
Socraigh fillteán saincheaptha
@@ -202,6 +211,7 @@
Theip ar an iompórtáil. Bain triail eile as
Níor aimsíodh aon chomhad
Níorbh fhéidir do chúltaca deiridh a aimsiú!
+ Athruithe ábhair á mbrath
Cóipeáladh chuig an ngearrthaisce
Tharla earráid agus iarracht á déanamh an comhad nó an fillteán seo a chóipeáil
Ní féidir fillteán a chóipeáil isteach i gceann dá bhunfhillteáin féin
@@ -407,6 +417,8 @@
Níor aimsíodh aon torthaí do do cheist
Tosaigh do chuardach
Clóscríobh sa bharra cuardaigh thuas chun comhaid, teagmhálacha, imeachtaí féilire agus tuilleadh a aimsiú ar fud do chuntais.
+ Seiceáil do nasc idirlín nó déan iarracht arís ar ball
+ Droch-nasc
fillteán
BEO
Á lódáil…
@@ -471,6 +483,11 @@
Tá fillteán ann cheana féin
Is fearr féachaint ar an bhfillteán seo i %1$s.
Cruthaigh
+ %1$d de %2$d · %3$s
+ Tharla earráid le linn sioncrónú an fhillteáin %s
+ Gan dóthain spáis diosca, sioncrónú curtha ar ceal
+ Sioncrónaíodh an fillteán %s go rathúil
+ Ag sioncrónú…
Níl fillteáin anseo
Ní féidir ainm fillteáin a bheith folamh
Roghnaigh
@@ -558,7 +575,6 @@
Sonraí soiléire
Scriosfar socruithe, bunachar sonraí agus teastais an fhreastalaí ó shonraí %1$s go buan. \n\nCoimeádfar comhaid íosluchtaithe gan teagmháil.\n\nTógfaidh an próiseas seo tamall.
Bainistigh spás
- Tá an uasteorainn uaslódáil comhad sroichte agat. Uaslódáil níos lú ná 500 comhad ag an am céanna.
Ní féidir an comhad meán a shruthú
Níorbh fhéidir an comhad meán a léamh
Tá ionchódú mícheart ar an gcomhad meán
@@ -607,6 +623,8 @@
Theip ar an ngníomh a chur i gcrích.
Taispeáin fógraí chun idirghníomhú a dhéanamh ar thoradh oibríochtaí cúlra
Oibríochtaí cúlra
+ Braitheann athruithe ar chomhaid áitiúla
+ Breathnóir ábhair
Léiríonn dul chun cinn íoslódáil
Íoslódálacha
Taispeánann sé dul chun cinn agus torthaí sioncronaithe comhad
@@ -647,6 +665,7 @@
Cuir isteach do phaschód
Iarrfar an paschód gach uair a thosófar an app
Cuir isteach do phaschód le do thoil
+ Iarrfar an pasfhocal gach uair a osclófar nó a athosclófar an aip tar éis 5 soicind.
Níl na paschóid mar an gcéanna
Cuir isteach do phaschód arís le do thoil
Scrios do phaschód
@@ -662,13 +681,6 @@
Níor aimsíodh aon aip chun pictiúr a shocrú leis
Pinn chuig an scáileán baile
Oscail %1$s
- .txt
- 389 KB
- áitchoinneálaí
- 12:23:45
- Curtha in eagar le déanaí
- Is sealbhóir áit é seo
- 2012/05/18 12:23 PM
stad
scoránaigh
Roghnaigh freastalaí le do thoil…
@@ -690,6 +702,7 @@
Faoi
Sonraí
Dev
+ Comhaid
Ginearálta
Tuilleadh
Sioncrónaigh
@@ -881,6 +894,8 @@
Cláraigh leis an soláthraí
Ceadaigh do %1$s rochtain a fháil ar do chuntas Nextcloud %2$s?
Sórtáil de réir
+ Sórtáil na cinn is ansa leat ar dtús
+ Sórtáil fillteáin roimh chomhaid
Folaigh
Sonraí
Níorbh fhéidir aitheantas an fhreastalaí a fhíorú
@@ -991,6 +1006,7 @@
Níor aimsíodh an t-imeacht, is féidir leat sioncronú a dhéanamh i gcónaí chun nuashonrú a dhéanamh. Á atreorú chuig an ngréasán…
Níor aimsíodh an teagmhálaí, is féidir leat sioncronú a dhéanamh i gcónaí chun nuashonrú a dhéanamh. Á atreorú chuig an ngréasán…
Teastaíonn ceadanna chun toradh cuardaigh a oscailt nó déanfar é a atreorú chuig an ngréasán…
+ Sa bhfillteán seo
Anaithnid
Díghlasáil an comhad
Tá tuairimí neamhléite ann
@@ -1098,6 +1114,8 @@
Teastas freastalaí neamhiontaofa
Leagan an fhreastalaí á fháil…
Cuireadh deireadh leis an aip
+ Scipeáilte
+ Tá comhad leis an ainm céanna ann cheana féin.
Críochnaithe
Fuarthas an comhad céanna ar chianchlár, gan bacadh le huaslódáil
Earráid anaithnid
@@ -1236,6 +1254,13 @@
- easpórtálfar %d comhad. Féach an fógra le haghaidh sonraí.
- easpórtálfar %d comhad. Féach an fógra le haghaidh sonraí.
+
+ - Ní féidir leat ach %d comhad a uaslódáil ag an am céanna.
+ - Is féidir leat suas le %d comhad a uaslódáil ag an am céanna.
+ - Is féidir leat suas le %d comhad a uaslódáil ag an am céanna.
+ - Is féidir leat suas le %d comhad a uaslódáil ag an am céanna.
+ - Is féidir leat suas le %d comhad a uaslódáil ag an am céanna.
+
- %1$d fillteán
- %1$d fillteán
diff --git a/app/src/main/res/values-gd/strings.xml b/app/src/main/res/values-gd/strings.xml
index 823d9f1..87198a3 100644
--- a/app/src/main/res/values-gd/strings.xml
+++ b/app/src/main/res/values-gd/strings.xml
@@ -433,10 +433,6 @@
Diùlt
Tha feum air barrachd cheadan mus gabh faidhlichean a luchdadh suas is a-nuas.
Cha deach aplacaid a lorg airson dealbhan a chur leatha
- 389 KB
- 12:23:45
- Seo glèidheadair-àite
- 2012/05/18 12:23f
cuir stad air
toglaich
Ma chuireas tu à comas dearbhadh caomhnadh cumhachd, dh’fhaoidte gun dèid faidhlichean a luchdadh suas nuair a bhios am bataraidh fann!
@@ -454,6 +450,7 @@
Mu dhèidhinn
Mion-fhiosrachadh
Leasachadh
+ Faidhlichean
Coitcheann
Barrachd
Sioncronaich
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 0919afa..e05087b 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -44,6 +44,7 @@
Amosa un trebello do taboleiro
Buscar en %s
Aparecer como sen conexión
+ Este contido foi xerado por IA e pode conter erros.
Engadir unha nova tarefa
Crear unha nova tarefa a desde a parte inferior dereita
Escriba algún texto
@@ -55,6 +56,7 @@
Produciuse un erro ao eliminar a tarefa
A tarefa foi eliminada satisfactoriamente
A lista de tarefas está baleira
+ A lista de tarefas está baleira. Comprobe a configuración da aplicación do asistente.
Non é posíbel recuperar a lista de tarefas. Comprobe a conexión a Internet.
Eliminar tarefa
A saída da tarefa aínda non está preparada.
@@ -94,14 +96,18 @@
Non foi posíbel estabelecer a conexión
Cancelar o acceso
Introduza un enderezo de servidor válido.
- Non foi posíbel obter os datos do acceso. Ténteo de novo.
+ Non é posíbel obter os datos do acceso. Ténteo de novo.
Houbo un problema ao procesar a súa solicitude de acceso. Ténteo de novo máis tarde.
Non hai ningún navegador dispoñíbel para abrir esta ligazón.
Complete o proceso de acceso no seu navegador
+ O envío automático está en pausa porque o aforro de batería está activado.
mantense no cartafol orixinal, xa que é de só lectura
+ Batería baixa, o envío pode levar máis tempo
Enviar só con wifi sen límite de datos
/EnvíoAutomático
Este cartafol xa está incluído na sincronización do cartafol principal, iso pode provocar envíos duplicados
+ Agardando pola wifi para iniciar o envío
+ Enviar ficheiros de %s a %s
Configurar
Crear un cartafol personalizado novo
Configurar un cartafol personalizado
@@ -182,7 +188,7 @@
Axúdenos facendo probas
Informe dun incidente no GitHub
Configurar
- Retirar o cifrado local
+ Retirar a cifraxe local
Confirma que quere eliminar %1$s?
Confirma que quere eliminar os elementos seleccionados?
Confirma que quere eliminar %1$s e todo o seu contido?
@@ -205,6 +211,7 @@
Importación fallou ao iniciar. Ténteo de novo
Non se atopou ningún ficheiro
Non foi posíbel atopar a súa última copia de seguranza!
+ Detección de cambios no contido
Copiado no portapapeis
Produciuse un erro ao tentar copiar este ficheiro ou cartafol.
Non é posíbel copiar un cartafol nun dos seus propios subcartafoles
@@ -312,21 +319,21 @@
Definir como cifrado
Non é posíbel recuperar o certificado de servidor
Produciuse un fallo ao verificar a chave pública
- Configurar o cifrado
+ Configurar a cifraxe
Descifrando…
Pechar
Introduza a súa frase de contrasinal para acceder aos seus ficheiros
Este cartafol non está baleiro
Xerando chave novas…
As 12 palabras xuntas forman un contrasinal moi forte, permitíndolle só a Vde. ver e facer uso dos seus ficheiros cifrados. Escríbaas e mantéñaas nun lugar seguro.
- Cifrado de extremo a extremo desactivado no servidor.
- Tome nota do seu contrasinal de cifrado de 12 palabras
+ Cifraxe de extremo a extremo desactivado no servidor.
+ Tome nota do seu contrasinal de cifraxe de 12 palabras
Contrasinal…
Recuperando chaves…
Non é posíbel recuperar a chave privada
Non é posíbel recuperar a chave pública
Almacenando as chaves
- Configurar o cifrado
+ Configurar a cifraxe
Produciuse un erro non agardado ao descargar as chaves
Non foi posíbel gardar as súas chaves. Ténteo de novo
Produciuse un erro ao descifrar. Contrasinal incorrecto?
@@ -346,7 +353,7 @@
Produciuse un erro ao recuperar o ficheiro
Produciuse un erro ao recuperar modelos
Produciuse un erro ao axustar a mensaxe de estado!
- Produciuse un erro ao amosar o diálogo de configuración do cifrado!
+ Produciuse un erro ao amosar o diálogo de configuración da cifraxe!
Produciuse un erro ao iniciar a cámara
Produciuse un erro ao iniciar o escaneamento do documento
Produciuse un fallo ao enviar os ficheiros multimedia capturados.
@@ -410,6 +417,8 @@
Non se atopou ningún resultado para a súa consulta
Comezar a súa busca
Escribir na barra de busca enriba para atopar ficheiros, contactos, eventos de calendario, e máis na súa conta.
+ Comprobe a súa conexión a Internet ou ténteo de novo máis tarde.
+ Conexión deficiente
cartafol
DIRECTO
Cargando…
@@ -450,7 +459,7 @@
%s é un nome prohibido.
%s. Cambie o nome do ficheiro antes de mover ou copiar
Non se atopou o ficheiro
- Non se atopou o ficheiro. Non foi posíbel crear unha compartición.
+ Non se atopou o ficheiro. Non é posíbel crear unha compartición.
Non foi posíbel sincronizar o ficheiro. Amosase a última versión dispoñíbel.
Cambiar o nome
Produciuse un fallo no envío. Non hai conexión a Internet
@@ -474,6 +483,11 @@
Xa existe o cartafol
Este cartafol vese mellor en %1$s.
Crear
+ %1$d de %2$d · %3$s
+ Produciuse un erro durante a sincronización do cartafol %s
+ Non hai espazo abondo no disco, cancelouse a sincronización
+ O cartafol %s foi sincronizado correctamente
+ Sincronizando…
Aquí non hai cartafoles
O nome do cartafol non pode estar baleiro
Escoller
@@ -561,7 +575,6 @@
Limpar os datos
Van ser eliminados definitivamente os axustes , base de datos e certificados do servidor de %1$s.\n\nOs ficheiros descargados conservaranse sen cambios.\n\nEste proceso pode levar bastante tempo.
Xestionar o espazo
- Acadou o límite máximo de envío de ficheiros. Envíe menos de 500 ficheiros dunha sentada.
Non é posíbel transmitir o ficheiro multimedia
Non foi posíbel ler o ficheiro multimedia
O ficheiro multimedia ten unha codificación incorrecta
@@ -610,6 +623,8 @@
Produciuse un fallo ao executar a acción.
Amosar notificacións para interactuar co resultado das operacións en segundo plano
Operacións en segundo plano
+ Detecta os cambios nos ficheiros locais
+ Observador do contido
Amosa o progreso da descarga
Descargas
Amosa o progreso e os resultados da sincronización de ficheiros
@@ -631,7 +646,7 @@
Operación pendente
Operación de retirada pendente
Sen conexión a Internet
- Mesmo sen conexión a Internet, pode organizar os seus cartafoles, crear ficheiros. Unha vez que volva estar conectado, as túas accións pendentes sincronizaranse automaticamente.
+ Mesmo sen conexión a Internet, pode organizar os seus cartafoles, crear ficheiros. Unha vez que volva estar conectado, as súas accións pendentes sincronizaranse automaticamente.
Está sen conexión, mais o traballo continúa
O ficheiro aínda non existe. Envíe primeiro o ficheiro.
Non foi posíbel crear %s. Existe un ficheiro co mesmo nome no servidor.
@@ -650,6 +665,7 @@
Introduza o seu código de acceso
Solicitaráselle o código de acceso cada vez que inicie a aplicación
Introduza o seu código de acceso
+ Solicitaráselle o código de acceso cada vez que se abra a aplicación ou se volva abrir após 5 segundos.
Os códigos de acceso non son iguais
Introduza de novo o seu código de acceso
Elimine o seu código de acceso
@@ -665,13 +681,6 @@
Non se atopou unha aplicación coa que definir unha imaxe
Fixar na pantalla de Inicio
Abrir %1$s
- .txt
- 389 KB
- marcador de substitución
- 12:23:45
- Editado recentemente
- Isto é un marcador de substitución
- 18/05/2012 12:23 p.m.
parar
alternar
Seleccione un servidor…
@@ -693,6 +702,7 @@
Sobre
Detalles
Desenvolvemento
+ Ficheiros
Xeral
Máis
Sincronizar
@@ -701,8 +711,8 @@
Localización do almacenamento de datos
Xestionar a localización do almacenamento de datos
Produciuse un erro non agardado ao configurar DAVx⁵ (anteriormente coñecido como DAVdroid)
- O cifrado de extremo a extremo está configurado.
- Mnemotécnico do cifrado E2E
+ A cifraxe de extremo a extremo está configurado.
+ Mnemotécnico da cifraxe E2E
Para amosar o mnemotécnico, active as credenciais do dispositivo
Amosar as notificacións de escaneo de medios
@@ -717,7 +727,7 @@
Arquivar en subcartafoles baseados na data
Usar subcartafoles
Opcións de subcartafol
- Engadir cifrado de extremo a extremo a este cliente
+ Engadir cifraxe de extremo a extremo a este cliente
Licenza
Código de acceso da aplicación
Credenciais do dispositivo activadas
@@ -728,10 +738,10 @@
Código de acceso
Xestionar contas
Recomendar a un amigo
- Retirar o cifrado localmente
- Configurar o cifrado de extremo a extremo
+ Retirar a cifraxe localmente
+ Configurar a cifraxe de extremo a extremo
Amosar o conmutador de aplicacións
- Suxestións da aplicación Nextcloud no título de navegación
+ Suxestión de aplicacións de Nextcloud na cabeceira de navegación
Amosar ficheiros agochados
Obter o código fonte
Xestionar os cartafoles para a envío automático
@@ -766,8 +776,8 @@
Volver cargar
(remoto)
Produciuse un fallo ao atopar o ficheiro!
- Pode retirar o cifrado de extremo a extremo localmente neste cliente
- Pode retirar o cifrado de extremo a extremo localmente neste cliente. Os ficheiros cifrados permanecerán no servidor, mais xa non se sincronizarán con este computador.
+ Pode retirar a cifraxe de extremo a extremo localmente neste cliente
+ Pode retirar a cifraxe de extremo a extremo localmente neste cliente. Os ficheiros cifrados permanecerán no servidor, mais xa non se sincronizarán con este computador.
Produciuse un fallo na eliminación
Retirar a conta local
Retirar a conta do dispositivo e eliminar todos os ficheiros locais
@@ -778,7 +788,7 @@
Non foi posíbel cambiarlle o nome a copia local, ténteo cun un nome diferente
Non foi posíbel cambiarlle o nome, o nome xa está ocupado
Solicitar a eliminación da conta
- Solicitat a eliminación
+ Solicitar a eliminación
Solicitar a eliminación definitiva da conta polo provedor de servizos
A directiva ou os permisos impiden volver compartir
Restaurar ficheiro
@@ -821,7 +831,7 @@
Definir a nota
Estado en liña
Usar a imaxe como
- Durante a configuración do cifrado de extremo a extremo, recibirá un mnemotécnico ao chou de 12 palabras, que necesitará para abrir os seus ficheiros noutros dispositivos. Isto só se almacenará neste dispositivo e pódese amosar de novo nesta pantalla. Anóteo nun lugar seguro!
+ Durante a configuración da cifraxe de extremo a extremo, recibirá un mnemotécnico ao chou de 12 palabras, que necesitará para abrir os seus ficheiros noutros dispositivos. Isto só se almacenará neste dispositivo e pódese amosar de novo nesta pantalla. Anóteo nun lugar seguro!
Compartir
Permitir a descarga e a sincronización
Non foi posíbel actualizar a compartición. Engada unha nota e ténteo de novo.
@@ -885,6 +895,8 @@
Rexistrarse cun provedor
Permitirlle a %1$s acceder a súa conta %2$s en Nextcloud?
Ordenar por
+ Ordenar primeiro os favoritos
+ Ordenar os cartafoles antes que os ficheiros
Agochar
Detalles
Non foi posíbel verificar a identidade do sitio
@@ -904,7 +916,7 @@
Ata:
- Non hai información sobre este erro
Non foi posíbel gardar o certificado
- Non é posíbel amosar o certificado.
+ Non foi posíbel amosar o certificado.
Aínda así, quere fiar neste certificado igualmente?
- O certificado do servidor caducou
- O certificado do servidor non é de confianza
@@ -923,7 +935,7 @@
Acceso completo
Medios de só lectura
Imaxes
- A plataforma de produtividade en aloxamento autónomo que mantén controlada.\n\nCaracterísticas:\n* Interface doada e moderna, totalmente tematizada ao aliñarse co tema do seu servidor\n* Enviar os seus ficheiros ao seu Nextcloud\n* Compartir os seus ficheiros con outras persoas\n* Conservar os seus ficheiros e cartafoles favoritos sincronizados\n* Buscar en todos os cartafoles do servidor\n* Enviar automaticamente fotos e vídeos feitos no seu dispositivo\n* Estar ao día coas notificacións\n* Admite múltiples contas\n* Acceso seguro aos seus datos con pegada dactilar ou PIN\n* Integración con DAVx⁵ (anteriormente coñecido como DAVdroid) para facilitar a configuración da sincronización de calendarios e contactos\n\nAgradecémoslle que nos informe de calquera incidente en https://github.com/nextcloud/android/issues, pode conversar sobre esta aplicación en https://help.nextcloud.com/c/clients/android\n\nE novo en Nextcloud? Nextcloud é un servidor privado para sincronizar e compartir ficheiros. É completamente libre e pode instalalo Vde. ou contratar a unha empresa ou cooperativa para que o faga por Vde. É o camiño para que sexa Vde. quen teña o control sobre as súas fotos, calendario, caderno de contactos, documentos e todo o demais.\n\nCoñeza Nextcloud en https://nextcloud.com
+ A plataforma de produtividade en aloxamento autónomo que mantén controlada.\n\nCaracterísticas:\n* Interface doada e moderna, totalmente tematizada ao aliñarse co tema do seu servidor\n* Enviar os seus ficheiros ao seu Nextcloud\n* Compartir os seus ficheiros con outras persoas\n* Conservar os seus ficheiros e cartafoles favoritos sincronizados\n* Buscar en todos os cartafoles do servidor\n* Enviar automaticamente fotos e vídeos feitos no seu dispositivo\n* Estar ao día coas notificacións\n* Admite múltiples contas\n* Acceso seguro aos seus datos con pegada dactilar ou PIN\n* Integración con DAVx⁵ (anteriormente coñecido como DAVdroid) para facilitar a configuración da sincronización de calendarios e contactos\n\nAgradecémoslle que nos informe de calquera problema en https://github.com/nextcloud/android/issues, pode conversar sobre esta aplicación en https://help.nextcloud.com/c/clients/android\n\nE novo en Nextcloud? Nextcloud é un servidor privado para sincronizar e compartir ficheiros. É completamente libre e pode instalalo Vde. ou contratar a unha empresa ou cooperativa para que o faga por Vde. É o camiño para que sexa Vde. quen teña o control sobre as súas fotos, calendario, caderno de contactos, documentos e todo o demais.\n\nCoñeza Nextcloud en https://nextcloud.com
A plataforma de produtividade en aloxamento autónomo que mantén controlada.\nEsta é a versión oficial de desenvolvemento, que presenta unha mostra diaria de calquera nova funcionalidade aínda non probada, o que pode provocar inestabilidade e perda de datos. Esta aplicación está destinada a usuarios que queren probar e informar de fallos (en caso de producirse). Non a use para o día a día!\n\nTanto a versión oficial de desenvolvemento como a normal están dispoñíbeis no F-Droid e poden instalarse xuntas.
A plataforma de produtividade en aloxamento autónomo que mantén controlada
A plataforma de produtividade en aloxamento autónomo que mantén controlada (versión de vista previa de desenvolvemento)
@@ -951,7 +963,7 @@
Produciuse un fallo na sincronización, acceda de novo.
Os contidos do ficheiro xa están sincronizados
Non foi posíbel completar a sincronización do cartafol %1$s
- A partir da versión 1.3.16, os ficheiros enviados desde este dispositivo cópianse no cartafol local %1$s para evitar a perda de datos cando se sincroniza un único ficheiro con varias contas.\n\nPor mor deste cambio, todos os ficheiros enviados con versións anteriores desta aplicación foron copiados no cartafol %2$s. Porén, un erro impediu que se completara esta operación durante a sincronización da conta. Pode deixar o(s) ficheiro(s) tal e como está(n) e eliminar a ligazón cara a %3$s ou mover o(s) ficheiro(s) para o cartafol %1$s e conservar a ligazón cara a %4$s.\n\nEmbaixo amósanse os ficheiros locais e os ficheiros remotos en %5$s aos que foron enlazados.
+ A partir da versión 1.3.16, os ficheiros enviados desde este dispositivo cópianse no cartafol local %1$s para evitar a perda de datos cando se sincroniza un único ficheiro con varias contas.\n\nPor mor deste cambio, todos os ficheiros enviados con versións anteriores desta aplicación foron copiados no cartafol %2$s. Porén, un erro impediu que se completara esta operación durante a sincronización da conta. Pode deixar o(s) ficheiro(s) tal e como está(n) e eliminar a ligazón cara a %3$s ou mover o(s) ficheiro(s) para o cartafol %1$s e conservar a ligazón cara a %4$s.\n\nEmbaixo amósanse os ficheiros locais e os ficheiros remotos en %5$s aos que foron ligados.
Esquecéronse algúns ficheiros locais
Recuperando a versión mais recente do ficheiro.
Escoller que sincronizar
@@ -995,6 +1007,7 @@
Non se atopou o evento, sempre pode sincronizar para actualizar. Redirixindo á web…
Non se atopou o contacto, sempre pode sincronizar para actualizar. Redirixindo á web…
Requírense permisos para abrir o resultado da busca, se non, vai ser redirixido á web…
+ Neste cartafol
Descoñecido
Desbloquear ficheiro
Existen comentarios sen ler
@@ -1048,7 +1061,7 @@
Non é posíbel enviar os ficheiros cargar sen acceso ao almacenamento local. Toque para conceder permiso.
Envío detido — É necesario permiso de almacenamento
%1$d / %2$d - %3$s
- O cifrado só é posíbel con >= Android 5.0
+ A cifraxe só é posíbel con >= Android 5.0
Non hai espazo abondo para copiar os ficheiros seleccionados no cartafol %1$s. En troques, gustaríalle movelos?
Superouse a cota de almacenamento
Escanear o documento desde a cámara
@@ -1102,6 +1115,8 @@
O certificado do servidor non é fiábel
Recuperando a versión do servidor…
Aplicación finalizada
+ Omitido
+ Xa existe un ficheiro co mesmo nome
Completado
Atopouse o mesmo ficheiro en remoto, omitindo o envío
Produciuse un erro descoñecido
@@ -1195,6 +1210,10 @@
- Exportarase %d ficheiro. Consulte a notificación para obter máis información.
- Exportaranse %d ficheiros. Consulte a notificación para obter máis información.
+
+ - Pode enviar %d ficheiro cada vez.
+ - Pode enviar ata %d ficheiros á vez.
+
- %1$d cartafol
- %1$d cartafoles
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index e832e75..668f5f3 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -490,12 +490,6 @@
Spriječi
Potrebna su dodatna dopuštenja za otpremanje i preuzimanje datoteka.
Nije pronađena nijedna aplikacija za postavljanje slike s
- .txt
- 389 KB
- 12:23:45
- Nedavno uređeno
- Ovo je prazan prostor
- 18. 5. 2012. 12:23
zaustavi
uključi/isključi
Onemogućivanje provjere uštede energije može rezultirati otpremanjem datoteka pri niskoj razini napunjenosti baterije!
@@ -513,6 +507,7 @@
Informacije
Pojedinosti
Dev
+ Datoteke
Općenito
Više
Sinkronizacija
diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml
index 8817eb6..5863a59 100644
--- a/app/src/main/res/values-hu-rHU/strings.xml
+++ b/app/src/main/res/values-hu-rHU/strings.xml
@@ -43,6 +43,7 @@
Proxy portja
Egy modult jelenít meg a irányítópultról
Keresés itt: %s
+ Megjelenés nem kapcsolódottként
Új feladat hozzáadása
Új feladat létrehozása a jobb lentiből
Gépeljen be szöveget
@@ -92,12 +93,17 @@
A %1$s nem támogat több fiókot
A kapcsolat létrehozása sikertelen
Bejelentkezés megszakítása
+ Adjon meg egy érvényes kiszolgálócímet.
+ A bejelentkezés részletei nem kérhetőek le. Próbálja újra.
Probléma volt a bejelentkezési kérés feldolgozása során. Próbálja újra később.
+ Nem érhető el böngésző a hivatkozás megnyitásához.
Fejezze be a bejelentkezési folyamatot a böngészőben
+ Az automatikus feltöltés az akkumulátorkímélő mód miatt szünetel.
megtartva az eredeti mappában, mivel csak olvasható
Feltöltés csak forgalomkorlát nélküli Wi-Fin
/AutoUpload
Ez a mappa már szerepel a szülőmappája szinkronizálásában, ami ismételt feltöltéseket okozhat
+ Fájlok feltöltése innen: %s, ide: %s
Beállítás
Új egyéni mappabeállítás létrehozása
Egyéni mappa beállítása
@@ -341,6 +347,7 @@
Jelenti a hibát a követőbe? (GitHub-fiók szükséges)
Hiba a fájl lekérésekor
Hiba történt a sablonok lékérésekor
+ Hiba az állapotüzenet beállítása során!
Hiba a titkosítás beállítóablakának megjelenítése során!
Hiba a kamera indításakor
Hiba a dokumentumbeolvasás elindítása során
@@ -449,6 +456,7 @@
A fájl nem szinkronizálható. A legfrissebb elérhető verzió megjelenítése.
Átnevezés
A feltöltés sikertelen. Nincs internetkapcsolat.
+ A(z) %s már létezik, nincs ütközés észlelve
Hiba a fájl verziójának visszaállításakor!
Fájl verzió sikeresen visszaállítva.
Részletek
@@ -555,7 +563,6 @@
Adatok törlése
A beállítások, az adatbázisok és a kiszolgáló tanúsítványok véglegesen törlésre kerülnek %1$s adataiból. \n\nA letöltött fájlok érintetlenek maradnak.\n\nEz eltarthat egy darabig.
Tárhelykezelés
- Elérte a fájlfeltöltési korlátot. 500-nál kevesebb fájlt töltsön fel egyszerre.
A médiafájl nem közvetíthető
A médiafájl nem olvasható
A médiafájl kódolása hibás
@@ -578,6 +585,7 @@
Nem helyezhető át egy mappa a saját almappájába
A fájl már létezik a célmappában
A fájl nem helyezhető át. Ellenőrizze, hogy létezik-e.
+ Összes értesítés némítása
Hiba történt a kiszolgálóra várakozás közben. A művelet nem teljesíthető.
Hiba történt a kiszolgálóhoz kapcsolódás során
Hiba történt a kiszolgálóra várakozás közben. A művelet nem teljesíthető.
@@ -611,6 +619,8 @@
Általános értesítések
Zenelejátszó folyamatai
Médialejátszó
+ Az offline fájlműveletek folyamatának megjelenítése
+ Offline műveletek
A kiszolgáló által küldött üzenetek megjelenítése: Említések a megjegyzésekben, új távoli megosztások érkezése, adminisztrátori bejelentések, stb.
Leküldéses értesítések
Megjeleníti a feltöltési folyamatokat
@@ -656,12 +666,6 @@
Nincs alkalmazás a képbeállításhoz
Rögzítés a kezdőképernyőre
A(z) %1$s megnyitása
- .txt
- 389 kB
- 12:23:45
- Nemrég szerkesztve
- Ez egy helykitöltő
- 2012.05.18. 12:23
leállítás
átváltás
Válasszon egy kiszolgálót…
@@ -683,6 +687,7 @@
Névjegy
Részletek
Dev
+ Fájlok
Általános
Több
Szinkronizálás
@@ -806,6 +811,7 @@
Az eszköz valószínűleg nem kapcsolódik az internethez
Beállítás mint
A letöltési korlát nem állítható be. Ellenőrizze az elérhető funkciókat.
+ Üzenet beállítása
Jegyzet beállítása
Elérhető állapot
Kép használata mint
@@ -873,6 +879,8 @@
Regisztráció egy szolgáltatóval
Engedélyezi, hogy a(z) %1$s hozzáférjen a(z) %2$s Nextcloud fiókjához?
Rendezés elve
+ Kedvencek előre rendezése
+ Mappák fájlok elé rendezése
Elrejtés
Részletek
A kiszolgáló személyazonosságát nem sikerült azonosítani
@@ -1001,6 +1009,7 @@ A Nextcloud itt érhető el: https://nextcloud.com
Az esemény nem található, bármikor szinkronizálhat a frissítéshez. Átirányítás a webre…
A névjegy nem található, bármikor szinkronizálhat a frissítéshez. Átirányítás a webre…
Engedély szükséges a keresési találat megnyitásához, különben a webre lesz átirányítva…
+ Ebben a mappában
Ismeretlen
Fájl feloldása
Olvasatlan hozzászólások vannak
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 81aa8c6..ab6183a 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -43,6 +43,7 @@
Proxy Port
Menampilkan satu gawit dari dasbor
Cari dalam %s
+ Tampak offline
Tambahkan tugas baru
Buat tugas baru dari kanan bawah
Tulis beberapa teks
@@ -92,12 +93,19 @@
%1$s tidak mendukung banyak akun
Tidak dapat memulai koneksi
Batalkan Masuk
+ Silakan masukkan alamat server yang valid.
+ Tidak dapat mengambil detail masuk. Silakan coba lagi.
Ada masalah saat memproses permintaan masuk Anda. Coba lagi nanti.
+ Tidak ada peramban yang tersedia untuk membuka tautan ini.
Tolong selesaikan proses login Anda di browser
+ Unggahan otomatis ditunda karena mode Penghemat Baterai aktif.
disimpan di folder original karena readonly
+ Baterai menipis, proses unggah mungkin memakan waktu lebih lama
Hanya unggah di Wi-Fi tak terhitung
/AutoUpload
Folder ini sudah disertakan dalam sinkronisasi folder induk, sehingga dapat menyebabkan unggahan ganda.
+ Menunggu Wi-Fi untuk mulai mengunggah
+ Mengunggah berkas dari %s ke %s
Konfigurasi
Buat kostum folder baru.
Pasang folder kostum.
@@ -130,6 +138,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Pilih gawit
Bersihkan
Gagal membersihkan notifikasi
+ Bersihkan status setelah
Teks tersalin dari %1$s
Tidak ada teks yang disalin ke papan klip.
Link tersalin
@@ -201,6 +210,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Impor gagal dimulai. Coba lagi
Tidak ada berkas ditemukan.
Tidak dapat menemukan pencadangan terakhir kamu!
+ Mendeteksi perubahan konten
Disalin ke papan klip
Terjadi kesalahan ketika mencoba menyalin berkas atau folder ini
Tidak memungkinkan untuk menyalin folder kedalam turunannya
@@ -248,6 +258,13 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Pilih jenis ekspor
Gagal membuat PDF
Membuat PDF…
+ Tidak dapat membuat item di sini: izin buat tidak tersedia.
+ Tidak dapat membuat berkas: izin tidak mencukupi.
+ Tidak dapat membuat folder: izin tidak mencukupi.
+ Tidak dapat menghapus item: izin hapus tidak tersedia.
+ Tidak dapat memindahkan item: izin pemindahan tidak tersedia.
+ Tidak dapat membuka item: izin baca tidak tersedia.
+ Tidak dapat mengganti nama item: izin penggantian nama tidak tersedia.
Selesai
Jangan dihapus
Tidak dapat membuat berkas lokal
@@ -327,12 +344,14 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Error saat mengomentari file
%1$s crash
Galat saat membuat berkas dari templat
+ Tidak dapat mengambil sharees.
Kesalahan menampilkan aksi berkas
Error saat mengubah status kunci file
Laporan
Laporkan masalah ke tracker? (membutuhkan akun GitHub)
Galat saat menerima file
Error saat mengambil template
+ Kesalahan saat mengatur pesan status!
Galat saat menampilkan dialog penyiapan enkripsi!
Galat saat mengaktifkan kamera
Galat memulai pemindaian dokumen
@@ -361,6 +380,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Transfer
Unduh
Unggah
+ Anda tidak memiliki izin untuk membuat atau mengunggah berkas di folder ini.
Berbagi eksternal
Tambahkan atau unggah
Gagal memberikan berkas ke pengelola unduhan
@@ -376,6 +396,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Anda tidak dapat membagi, opsi berbagi sudah aktif dari pengguna ini.
Tidak ada aplikasi yang tersedia untuk memilih kontak
Gagal memuat detil
+ Silakan pilih izin khusus
Berkas
Simpan
Unggah beberapa berkas atau sinkronisasi dengan perangkat anda.
@@ -393,6 +414,8 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Berkas dan folder yang Anda bagikan akan muncul di sini.
Belum ada yang dibagikan.
Tidak ada hasil yang ditemukan untuk kueri Anda
+ Mulai pencarian Anda
+ Ketik di bilah pencarian di atas untuk mencari berkas, kontak, acara kalender, dan lainnya di seluruh akun Anda.
folder
LANGSUNG
Memuat…
@@ -437,6 +460,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Berkas tidak dapat disinkronkan. Menampilkan versi terbaru yang tersedia.
Ubah nama
Unggahan gagal. Tidak ada koneksi internet
+ %s sudah ada, tidak ada konflik yang terdeteksi
Kesalahan terjadi saat memulihkan versi file!
Berhasil memulihkan versi berkas.
Detail
@@ -456,6 +480,11 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Direktori sudah ada
Folder ini lebih baik dilihat di %1$s.
Buat
+ %1$d dari %2$d · %3$s
+ Terjadi kesalahan selama proses sinkronisasi folder %s
+ Ruang disk tidak mencukupi, sinkronisasi dibatalkan
+ %s folder berhasil disinkronkan
+ Menyinkronkan…
Tidak ada folder
Nama folder tidak bisa kosong
Pilih
@@ -513,6 +542,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Pencadangan terakhir: %1$s
Tautan
Nama Tautan
+ Tautan tidak dapat diakses karena pengaturan keamanan.
Pengeditan
Daftar tampilan
Muat lebih banyak hasil
@@ -542,7 +572,6 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Bersihkan data
Pengaturan, database dan sertifikat server dari data %1$s akan dihapus secara permanen. \n\nBerkas terunduh akan tetap terjaga.\n\nProses ini dapat memakan waktu lama.
Kelola ruang
- Anda telah mencapai batas maksimum unggahan file. Silakan unggah kurang dari 500 file dalam satu waktu.
File media tidak dapat di-stream
Tidak dapat membaca berkas media
Berkas media tidak di encoding dengan benar
@@ -591,6 +620,8 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Gagal menjalankan aksi.
Tampilkan notifikasi untuk berinteraksi dengan hasil operasi latar belakang
Beroperasi di latar belakang
+ Mendeteksi perubahan pada berkas lokal
+ Pemantau konten
Menampilkan progres pengunduhan
Unduhan
Menampilkan kemajuan dan hasil sinkronisasi berkas
@@ -599,6 +630,8 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Notifikasi umum
Kemajuan pemutar musik
Pemutar media
+ Menampilkan kemajuan operasi berkas offline
+ Operasi offline
Tampilkan notifikasi push yang dikirim oleh server: Sebutan dalam komentar, penerimaan berbagi jarak jauh baru, pengumuman yang dipos oleh admin, dll.
Notifikasi push
Tampilkan kemajuan unggahan
@@ -613,8 +646,12 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Bahkan tanpa koneksi internet, Anda tetap dapat mengatur folder dan membuat file. Setelah Anda kembali online, semua tindakan yang tertunda akan disinkronkan secara otomatis.
Anda offline, tapi kerja tetap lanjut
File masih belum tersedia. Tolong unggah filenya terlebih dahulu.
+ Tidak dapat membuat %s. Berkas dengan nama yang sama sudah ada di server.
+ Tidak dapat membuat %s. Folder dengan nama yang sama sudah ada di server.
Operasi offline tidak dapat diselesaikan. %s
Operasi Offline
+ Penghapusan %s dibatalkan. Berkas tersebut telah diubah di server.
+ Pemberian nama ulang %s dibatalkan. Berkas dengan nama yang sama sudah ada di server.
Memulai operasi offline
1 jam
Online
@@ -640,11 +677,6 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Tidak ada aplikasi untuk menyetel gambar yang ditemukan.
Sematkan ke tampilan beranda
Buka %1$s
- 389 KB
- 12:23:45
- Baru saja diedit
- Ini adalah placeholder
- 18/05/2012 12:23 PM
hentikan
alihkan
Silahkan pilih server...
@@ -789,6 +821,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Perangkat ini kemungkinan tidak terhubung ke internet
Gunakan sebagai
TIdak dapat mengatur batas unduhan.
+ Atur pesan
Atur catatan
Status online
Gunakan gambar sebagai
@@ -970,6 +1003,7 @@ Berikut ini adalah daftar berkas lokal, dan berkas jarak jauh di %5$s yang terhu
Acara di kalender tidak ditemukan, Anda selalu dapat menyinkronkan untuk memperbarui. Mengarahkan ke web…
Kontak tidak ditemukan, Anda selalu dapat menyinkronkan untuk memperbarui. Mengarahkan ke web…
Izin diperlukan untuk melihat hasil pencarian dan kalau tidak, akan diarahkan ke web...
+ Di dalam folder ini
Tidak diketahui
Buka kunci berkas
Ada komentar yang belum dibaca
@@ -1018,6 +1052,10 @@ Berikut ini adalah daftar berkas lokal, dan berkas jarak jauh di %5$s yang terhu
Berkas tidak dapat disalin ke penyimpanan lokal
Penguncian folder gagal
Unggahan dibatalkan oleh pengguna
+ Izinkan akses ke semua berkas
+ Izin aplikasi
+ Berkas Anda tidak dapat diunggah tanpa akses ke penyimpanan lokal. Ketuk untuk memberikan izin.
+ Unggah dihentikan – Izin penyimpanan diperlukan
%1$d / %2$d - %3$s
Enkripsi hanya bisa dengan > = Android 5.0
Ruang tidak cukup untuk menyalin berkas terpilih ke folder %1$s. Apakah Anda ingin memindahkannya?
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index 5acfddd..9fb05b2 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -10,6 +10,7 @@
Breyta
Hreinsa allar tilkynningar
Tæma ruslið
+ Senda/Deila
Reitasýn
Listasýn
Endurheimta tengiliði og dagatal
@@ -44,13 +45,16 @@
Leita í %s
Birtast ótengt
Bæta við nýju verki
+ Þú getur búið til nýtt verk neðst til hægri
Skrifaðu einhvern texta
Ertu viss um að þú viljir eyða þessu verki?
Eyða verki
+ Hleð inn verkefnalista…
Villa kom upp við að búa til verkið
Tókst að búa til verk
Villa kom upp við að eyða verkinu
Tókst að eyða verki
+ Verkefnalisti er tómur.
Ekki er hægt að sækja verkefnalista, athugaðu nettenginguna þína.
Eyða verki
Yfirlitið yfir verkin er ekki tilbúið.
@@ -89,11 +93,17 @@
%1$s styður ekki fjölaðganga
Gat ekki komið á tengingu
Hætta við innskráningu
+ Settu inn gilt vistfang netþjóns.
+ Tekst ekki að sækja nánari upplýsingar um innskráningu. Reyndu aftur síðar.
+
Upp kom vandamál við vinnslu á innskráningarbeiðnarinnar þinnar. Reyndu aftur síðar.
+ Enginn vafri er ekki tiltækur sem getur opnað þennan tengil.
Ljúktu innskráningarferlinu í vafranum þínum
haldið áfram í upprunalegri möppu, því hún er skrifvarin
Aðeins senda inn á gjaldfrjálsu WiFi-neti
/SjálfvirkInnsending
+ Þessa möppu er nú þegar verið að samstilla úr yfirmöppu, sem getur valdið margföldum innsendingum
+ Sendi inn skrár frá %s til %s
Stilla
Búa til nýja nýja sérsniðna uppsetningu á möppum
Settu upp sérsniðna möppu
@@ -181,6 +191,7 @@
Ertu viss um að þú viljir eyða völdum atriðum og innihaldi þeirra?
Einungis staðvært
Ekki er hægt að búa til glugga til að leysa árekstra
+ Árekstrar milli mappa
Skrá á tölvunni
Ef þú velur báðar útgáfur, þá mun verða bætt tölustaf aftan við heiti afrituðu skrárinnar.
Ef þú velur báðar útgáfur, þá mun verða bætt tölustaf aftan við heiti afrituðu möppunnar.
@@ -193,6 +204,7 @@
Taka öryggisafrit núna
Öryggisafritun er á áætlun og mun hefjast fljótlega
Innflutningur er á áætlun og mun hefjast fljótlega
+ Mistókst að hefja innflutning. Reyndu aftur
Engin skrá fannst
Gat ekki fundið síðasta öryggisafritið þitt!
Afritað á klippispjald
@@ -207,6 +219,7 @@
Tókst ekki að ná í slóð
Búa til
gat ekki búið til möppuna %1
+ Búa til tengil
Nýtt
Nýtt skjal
Ný mappa
@@ -241,6 +254,13 @@
Veldu tegund útflutnings
Gerð PDF tókst ekki
Útbý PDF…
+ Get ekki útbúið atriði hér: vantar búa-til heimild.
+ Get ekki útbúið skrá: vantar heimild.
+ Get ekki útbúið möppu: vantar heimild.
+ Get ekki eytt atriði: vantar eyða-heimild.
+ Get ekki fært atriði: vantar færa-heimild.
+ Get ekki opnað atriði: vantar lesa-heimild.
+ Get ekki endurnefnt atriði: vantar endurnefna-heimild.
Lokið
Ekki hreinsa
Get ekki búið til skrá á tölvu
@@ -297,6 +317,7 @@
Setja upp dulritun
Afkóðun
Loka
+ Settu inn lykilsetninguna þína til að komast í skrárnar þínar
Þessi mappa er ekki tóm.
Útbý nýja dulritunarlykla…
Öll 12 orðin saman gera mjög sterkt lykilorð, þannig að einungis þú getur skoðað og notað dulrituðu skrárnar þínar. Endilega skrifaðu þau niður og geymdu á öruggum stað.
@@ -319,12 +340,14 @@
Villa við að gera athugasemd við skrá
%1$s hrundi
Villa við að búa til skrá út frá sniðmáti
+ Mistókst að sækja sameignir.
Villa við birtingu skráaaðgerða
Villa við að breyta stöðu læsingar á skrá
Skýrsla
Tilkynna um vandamál í verkbeiðnakerfið? (krefst GitHub-aðgangs)
Villa við að ná í skrá
Villa við að ná í sniðmát
+ Villa kom upp við að setja stöðuskilaboð!
Villa við að birta uppsetningarglugga dulritunar!
Villa við að ræsa myndavél
Villa við að ræsa skönnun skjals
@@ -345,6 +368,7 @@
Stöðva prufuverk
Yfirfærslur (uppfærsla forrits)
Kjörstillingar
+ Prófunarhamur forritara
Skráaflutningur
Setja niðurhalsprófun í biðröð
Setja innsendingarprófun í biðröð
@@ -352,6 +376,7 @@
Færa
Sækja
Senda inn
+ Þú hefur ekki réttindi til að búa til eða senda inn skrár í þessari möppu.
Utanaðkomandi sameignir
Bæta við eða senda inn
Mistókst að senda skrá í niðurhalsstýringu
@@ -364,8 +389,10 @@
Skráarheitið er þegar til staðar
Eyða
Villa við að ná í virkni fyrir skrá
+ Þú getur ekki tekið búið til sameign, deiling er þegar virk af hálfu þessa notanda.
Ekkert forrit tiltækt til að velja tengiliði
Mistókst að hlaða inn ítarupplýsingum
+ Veldu sérsniðna heimild
Skrá
Halda
Sendu inn eitthvað efni eða samstilltu við tækin þín!
@@ -383,6 +410,8 @@
Skrár og möppur sem þú deilir birtast hér.
Engu deilt ennþá
Engar niðurstöður fundust fyrir leitina þína
+ Byrja að leita
+ Skrifaðu í leitarstikuna fyrir ofan til að finna skrár, tengiliði, atburði í dagatali og fleira á öllum aðgangnum þínum.
mappa
BEINT
Hleð inn…
@@ -423,9 +452,11 @@
%s er bannað sem heiti
%s. Endurnefndu skrána áður en fært er eða afritað
Skrá fannst ekki
+ Skrá fannst ekki. Gat ekki búið til sameign.
Ekki tókst að samstilla skrána. Birti nýjustu tiltæku útgáfuna.
Endurnefna
Innsending mistókst. Engin internettenging
+ %s er þegar til, engir árekstrar fundust
Villa við að endurheimta útgáfu skráar!
Tókst að endurheimta útgáfu skráar.
Nánar
@@ -443,6 +474,7 @@
Auðlærður vefpóstur, dagatal og tengiliðir
Skjádeiling, netfundir og vefráðstefnur
Mappa er þegar til staðar
+ Þessa möppu er best að skoða í %1$s.
Búa til
Engar möppur hér
Möppuheiti má ekki vera tómt
@@ -501,6 +533,7 @@
Síðasta öryggisafrit: %1$s
Tengill
Heiti tengils
+ Ekki var farið eftir tengli vegna öryggisstillinga.
Breytingar
Framsetning sem listi
Hlaða inn fleiri niðurstöðum
@@ -530,7 +563,6 @@
Hreinsa gögn
Stillingum, gagnagrunni og skilríkjum vefþjóns úr gögnum %1$s verður eytt endanlega. \n\nSóttar skrár verða ekki snertar.\n\nÞetta ferli getur tekið drjúga stund.
Sýsla með geymslurými
- Þú hefur náð hámarksfjölda innsendra skráa. Sendu inn færri en 500 skrár í einu.
Ekki tókst að streyma margmiðlunarskrá
Gat ekki lesið margmiðlunarskrána
Margmiðlunarskráin er með ranga kóðun
@@ -561,7 +593,8 @@
Ný athugasemd
Fann möppu með %1$s.
mynd
- Myndskeið
+ myndskeið
+ Ný tilkynning
Ný útgáfa var útbúin
Engar aðgerðir fyrir þennan notanda
Ekkert forrit tiltækt til að meðhöndla tengla
@@ -577,6 +610,7 @@
Táknmynd fyrir athugasemd
Mistókst að framkvæma aðgerð.
Birta tilkynningar til að meðhöndla niðurstöður bakgrunnsaðgerða
+ Bakgrunnsaðgerðir
Sýnir framvindu niðurhals
Sótt gögn
Sýnir framvindu og niðurstöður samstillingar skráa
@@ -585,6 +619,8 @@
Almennar tilkynningar
Framvinda margmiðlunarspilara
Margmiðlunarspilari
+ Sýnir framvindu ónettengdra aðgerða
+ Ónettengdar aðgerðir
Birta tilkynningar sem ýtt er út frá þjóninum: Ummæli (mentions) í athugasemdum, móttaka nýrra sameigna, tilkynningar sendar inn af stjórnendum, o.fl.
Ýti-tilkynningar
Sýnir framvindu innsendingar
@@ -593,15 +629,23 @@
Fyrirliggjandi eru ólesnar tilkynningar
Engar tilkynningar
Athugaðu aftur síðar.
+ Aðgerð í bið
Fjarlægingaraðgerð í bið
Engin internettenging
Jafnvel án tengingar við internetið geturðu skipulagt möppurnar þínar og útbúið skrár. Um leið og tenging næst aftur, munu aðgerðirnar þínar sem verið hafa í bið, samstillast sjálfkrafa við netþjóninn.
+ Tækið er ekki tengt við netið, en vinnan heldur áfram
Skráin er ekki til, ennþá. Sendu fyrst inn skrána.
+ Ekki tókst að búa til %s. Skrá með sama heiti er þegar til á netþjóninum.
+ Ekki tókst að búa til %s. Mappa með sama heiti er þegar til á netþjóninum.
Ekki tókst að ljúka ónettengdu aðgerðinni. %s
Ónettengdar aðgerðir
+ Hætti við eyðingu á %s. Skránni hefur þegar verið breytt á netþjóninum.
+ Hætti við endurnefningu á %s. Skrá með sama heiti er þegar til á netþjóninum.
+ Byrja ónettengdar aðgerðir
1 klukkustund
Á netinu
Staða á netinu
+ Opnar eftir %1$s
Netþjónninn er kominn að endimörkum líftíma síns, endilega uppfærðu hann!
Valmynd með fleiru
Settu inn lykilkóða
@@ -620,13 +664,8 @@
Krafist er viðbótarheimilda til að senda inn og sækja skrár.
Veldu tengilið til að deila með
Engin forrit fundust til að setja mynd
+ Festa á upphafsskjá
Opna %1$s
- .txt
- KB
- 12:23:45
- Nýlega breytt
- Þetta er frátökutákn
- e.h.
stöðva
víxla
Veldu netþjón…
@@ -643,10 +682,12 @@
Bæta við notandaaðgangi
Samstilla dagatal og tengiliði
Hvorki F-Droid né Google Play eru uppsett
+ Setja upp DAVdroid (áður þekkt sem DAVdroid)(v1.3.0+) fyrir þennan aðgang
Samstillingar dagatals og tengiliða eru uppsettar
Um hugbúnaðinn
Nánar
Dev
+ Skráaforrit
Almennt
Meira
Samstilla
@@ -654,6 +695,7 @@
Daglegt öryggisafrit af tengiliðum
Staðsetning gagnageymslu
Sýsla með staðsetningu gagnageymslu
+ Óvænt villa við að setja upp DAVx⁵ (áður þekkt sem DAVdroid)
Enda-í-enda dulritun er virk!
E2E minnistækni
Til að birta minnishjálp skaltu virkja auðkenningu tækisins.
@@ -754,6 +796,7 @@
Sjálfvirk innsending
fyrir myndirnar þínar og myndskeið
Dagatal og tengiliðir
+ Samstilla við DAVx⁵
Villa við að sækja leitarniðurstöður
Örugg deiling er ekki sett upp fyrir þennan notanda
Örugg sameign…
@@ -762,13 +805,21 @@
Veldu eitt sniðmát
Veldu sniðmát
Senda
+ Senda sameign
Táknmynd á sendihnappi
+ Gat ekki hlaðið inn efni
+ Tækið er líklega ekki tengt við internetið
Setja sem
+ Ekki er hægt að setja niðurhalsmörk. Athugaðu getuupplýsingar.
+ Setja skilaboð
+ Setja minnispunkt
Staða á netinu
Nota mynd sem
Við uppsetningu á enda-í-enda dulritun muntu fá runu 12 tilviljanakenndra orða, sem þú munt þurfa til að opna skrárnar þínar á öðrum tækjum. Þetta mun einungis vera geymt á þessu tæki; hægt er að birta rununa aftur á þessum skjá. Skrifaðu hana niður og geymdu á öruggum stað!
Deila
Leyfa niðurhal og samstillingu
+ Við gátum ekki uppfært sameignina. Bættu við minnispunkti og reyndu aftur.
+ Deila og afrita tengil
Búa til
Sérsniðnar heimildir
Eyða
@@ -789,19 +840,22 @@
til að deila þessari skrá
Settu inn valkvætt lykilorð
Settu inn lykilorð
- Tengill á sameign(%1$s)
+ Tengill á sameign (%1$s)
Setja gildistíma
Skrá lykilorð
Endurdeiling er ekki leyfð á meðan öruggri sleppingu stendur
+ Veldu a.m.k. einn valkost deilingar áður en þú heldur áfram.
Getur breytt
Beiðni um skrá
Örugg slepping skráa
Einungis skoða
+ Heimildir sameignar
Deila
Lesa
%1$s (fjartengt)
%1$s (samtal)
Nafn, skýjasambandsauðkenni eða tölvupóstfang …
+ Bæta við notendum og teymum
Senda nýjan tölvupóst
Minnispunktur til viðtakanda
Stillingar
@@ -825,6 +879,8 @@
Skráðu þig hjá þjónustu
Leyfa %1$s að að fá aðgang að %2$s Nextcloud-aðgangnum þínum?
Raða eftir
+ Raða eftirlætum fremst
+ Raða möppum á undan skrám
Fela
Nánar
Ekki tókst að sannvotta auðkenni þjónsins
@@ -863,6 +919,7 @@
Fullur aðgangur
Skrifvarinn gagnamiðill
Ljósmyndir
+ Sjálfhýsta Nextcloud-kerfið er opið og frjálst, og heldur þér við stjórnvölinn.\n\nEiginleikar:\n* Auðvelt, nútímalegt viðmót, sem hentar þema netþjónsins þíns\n* Sendu skrár inn á Nextcloud-þjóninn þinn\n* Deildu þeim með öðrum\n* Haltu eftirlætisskránum þínum og möppum samstilltum\n* Leitaðu í öllum möppum á netþjóninum þínum\n* Sjálfvirk innsending á myndum og myndskeiðum sem þú tekur á snjalltækinu þínu\n* Haltu öllu uppfærðu í gegnum tilkynningar\n* Styður marga aðganga fyrir hvern notanda\n* Tryggðu aðgang að gögnunum þínum með fingrafari eða PIN-númeri\n* Samþætting við DAVx⁵ (áður þekkt sem DAVdroid) fyrir auðvelda uppssetningu á samstilltu dagatali og tengiliðum\n\nTilkynntu öll vandamál á https://github.com/nextcloud/android/issues og ræddu um þennan hugbúnað á https://help.nextcloud.com/c/clients/android\n\nEr Nextcloud nýtt fyrir þér? Nextcloud er þinn eiginn netþjónn til að samstilla skrár og deila í samstarfs og samskiptaumhverfi. Þetta er frjáls hugbúnaður sem þú getur hýst sjálf/ur eða borgað þjónustuaðila fyrir að gera fyrir þig. Með þessu ert það þú sem ert við stjórnvölinn með gögnin sem þú vilt hafa aðgengileg í gegnum netið, hvort sem það eru ljósmyndir, dagatal, tengiliðir, skjölin þín, og margt fleira.\n\nSkoðaðu Nextcloud á https://nextcloud.com
Sjálfhýsta Nextcloud-kerfið er opið og frjálst, og heldur þér við stjórnvölinn.\nÞetta er opinbera þróunarútgáfan, sem kemur með daglegan skammt af óprófuðum nýjum eiginleikum, sem aftur gætu mögulega valdið truflunum og gagnatapi. Forritið er fyrir þá notendur sem vilja taka þátt í að prófa og tilkynna um villur, ef þær eiga sér stað. Ekki nota þetta í alvöru vinnuumhverfi!\n\nBæði opinbera þróunarútgáfan og venjulega útgáfan eru tiltækar á F-droid, og er hægt að setja þær upp samhliða hvorri annarri.
Sjálfhýsta Nextcloud-kerfið er opið og frjálst, og heldur þér við stjórnvölinn
Sjálfhýsta Nextcloud-kerfið er opið og frjálst, og heldur þér við stjórnvölinn (forútgáfa fyrir hönnuði)
@@ -879,8 +936,10 @@
Einungis myndskeið
Stinga upp á
Samstilla
+ Samstilla samt
Árekstrar fundust
- Mappan %1$ser ekki lengur til
+ Mappan %1$s er ekki lengur til
+ Margtekin samstilling
Gat ekki samstillt %1$s
Rangt lykilorð fyrir %1$s
Skrár merktar fyrir samstillingu mistókust
@@ -897,6 +956,7 @@
Ekki nægilegt pláss
Hnappur fyrir stöðu samstillingar
Skrár
+ Aðvörunarhnappur samstillingar
Stillingahnappur
Stilla möppur
Beinar innsendingar hafa verið algerlega endurhannaðar. Endurstilltu sjálfvirkar innsendingar beint í aðalvalmyndinni\n\nNjóttu góðs af nýju og ítarlegu viðmóti sjálfvirkra innsendinga.
@@ -979,6 +1039,10 @@
Skrá var ekki hægt að afrita í staðværa gagnageymslu
Læsing möppu mistókst
Notandi hætti við innsendingu
+ Leyfa aðgang að öllum skrám
+ Heimildir forrits
+ Ekki er hægt að senda inn skrárnar þínar án aðgangs að gagnageymslu á tækinu. Ýttu til að gefa heimild.
+ Innsending stöðvuð - Krafist heimildar til aðgangs að gagnageymslu
%1$d / %2$d - %3$s
Dulritun er aðeins möguleg með >= Android 5.0
Ónógt pláss hamlar því að hægt sé að afrita valdar skrár í %1$s möppuna. Viltu færa þær þangað í staðinn?
@@ -1075,6 +1139,18 @@
- %d mínúta
- %d mínútur
+
+ - fyrir %d sekúndu síðan
+ - fyrir %d sekúndum síðan
+
+
+ - fyrir %d mínútu síðan
+ - fyrir %d mínútum síðan
+
+
+ - fyrir %d klukkustund síðan
+ - fyrir %d klukkustundum síðan
+
- Ekki var hægt að samstilla %1$d skrá (árekstrar: %2$d)
- Ekki var hægt að samstilla %1$d skrár (árekstrar: %2$d)
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index fa9886a..04bd832 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -531,7 +531,6 @@
Cancella i dati
Impostazioni, database e certificati del server dai dati di %1$s saranno eliminati definitivamente.\n\nI file scaricati non saranno interessati.\n\nQuesto processo può richiedere del tempo.
Gestisci lo spazio
- Hai raggiunto il limite massimo di caricamento file. Carica meno di 500 file alla volta.
Il file multimediale non può essere trasmesso
Impossibile leggere il file multimediale
Il file multimediale ha una codifica non corretta
@@ -623,12 +622,6 @@
Seleziona il contatto con cui condividere
Nessuna applicazione trovata per impostare un\'immagine
Apri %1$s
- .txt
- 389 KB
- 12:23:45
- Modificati di recente
- Questo è un segnaposto
- 2012/05/18 12:23 PM
ferma
attiva
Per favore, seleziona un server...
@@ -649,6 +642,7 @@
Informazioni
Dettagli
Sviluppo
+ File
Generale
Altro
Sincronizzazione
@@ -826,6 +820,8 @@
Registrati a un fornitore
Vuoi consentire a %1$s di accedere al tuo account Nextcloud %2$s?
Ordina per
+ Ordina prima i preferiti
+ Ordina cartelle prima dei files
Nascondi
Dettagli
L\'identità del server non può essere verificata
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 7e2618b..e547b1b 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -449,11 +449,6 @@
לדחות
נדרשות הרשאות נוספות כדי להעלות ולהוריד קבצים
לא נמצא יישומון להגדיר אתו תמונה
- 389 ק״ב
- 12:23:45
- נערכו לאחרונה
- זהו ממלא מקום
- 2012/05/18 12:23 PM
השבתת בדיקת חיסכון בחשמל גורמת להעלאת קבצים כשהסוללה חלשה!
נמחק
נשמר בתיקייה מקורית
@@ -468,6 +463,7 @@
על אודות
פרטים
פיתוח
+ קבצים
כללי
יותר
סנכרון
diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml
index 2ecc71d..0fcd6e6 100644
--- a/app/src/main/res/values-ja-rJP/strings.xml
+++ b/app/src/main/res/values-ja-rJP/strings.xml
@@ -8,12 +8,12 @@
アカウントアイコン
アカウントが見つかりません!
編集
- 全ての通知を削除
+ すべての通知を削除
ゴミ箱を空にする
送信/共有
グリッド表示
リスト表示
- 連絡先とカレンダーを復元
+ 連絡先とカレンダーを復元する
新しいフォルダー
移動またはコピー
次で開く
@@ -24,12 +24,12 @@
並び替え
アクティブなユーザー
まだアクティビティはありません
- 追加、変更、共有はまだありません
+ 追加、変更、共有などのイベントはまだありません
送信
リンク送信…
アクティビティ
- 新規公開共有リンクを追加
- 新しいセキュアなファイルドロップを追加
+ 新しい公開共有リンクを追加する
+ 新しい安全なファイルドロップを追加
%1$s に追加
ベース URL
クリップボードを無効化
@@ -93,12 +93,19 @@
%1$s は複数アカウントをサポートしていません
接続を確立できませんでした
ログインをキャンセル
+ 有効なサーバーアドレスを入力してください。
+ ログインの詳細を取得することができませんでした。もう一度やり直してください。
ログイン要求の処理中に問題が発生しました。しばらくたってからもう一度お試しください。
+ このリンクを開くブラウザーがありません。
ブラウザでログインプロセスを完了してください
+ バッテリーセーバーがONのためオートアップロードを中断しています。
読み取り専用のため元のフォルダに残しました
+ バッテリー残量が低下しているため、アップロードに時間がかかることがあります。
定額制 Wi-Fi でのみアップロード
/AutoUpload
このフォルダは既に親の同期に含まれているため、重複してアップロードされる可能性があります
+ アップロードのためWi-Fiに接続されるのを待っています…
+ ファイルを%sから%sへアップロードしています。
設定
新しいカスタムフォルダセットアップを作成する
カスタムフォルダを設定する
@@ -202,6 +209,7 @@
インポートを開始できませんでした。もう一度やり直してください
ファイルが見つかりません
あなたの最後のバックアップを見つけることができませんでした!
+ コンテンツの変更の検出
クリップボードにコピーされました
このファイルまたはフォルダーをコピーする際にエラーが発生しました
フォルダをその下のフォルダにコピーすることはできません
@@ -249,6 +257,13 @@
エクスポートする種類を選択
PDFの生成に失敗しました
PDFを生成中...
+ 権限が無いため、ここにアイテムを作成することはできません。
+ 権限が無いため、ファイルを作成することができません。
+ 権限が無いため、フォルダーを作成することができません。
+ 削除権限が無いため、項目を削除することができません。
+ 移動権限がないため、項目を移動することができません。
+ 閲覧権限がないため、項目を開くことができません。
+ 名前変更権限がないため、項目の名前を変更することができません
完了
消去しない
ローカルファイルが作成できません
@@ -328,12 +343,14 @@
コメントファイルのエラー
%1$s はクラッシュしました
テンプレートからファイルを作成中にエラーが発生しました
+ 共有を取得できません
ファイル表示操作でエラーが発生しました
ファイルのロック状態変更エラー
報告
問題を報告しますか? (GitHubのアカウントが必要です)
ファイルの取得中にエラーが発生しました
テンプレートの取得中にエラーが発生しました
+ ステータスメッセージの設定中にエラーが発生しました。
暗号化設定ダイアログの表示エラー
カメラ起動エラー
文書スキャンの開始エラー
@@ -362,6 +379,7 @@
転送
ダウンロード
アップロード
+ このフォルダーにファイルを作成・アップロードする権限がありません。
外部共有
追加またはアップロード
ダウンロードマネージャーにファイルを渡せませんでした
@@ -374,8 +392,10 @@
ファイル名が既に存在します
削除
ファイルのアクティビティ取得エラー
+ 共有を作成できません。このユーザーからの共有はすでに有効になっています。
連絡先を選択するためのアプリが利用できません
詳細のロードに失敗しました
+ カスタムの権限を選択してください。
ファイル
保持
コンテンツをアップロードするか、デバイスと同期してください。
@@ -393,6 +413,8 @@
共有したファイルやフォルダーがここに表示されます。
まだ何も共有されていません
検索結果が見つかりませんでした
+ 検索を開始
+ アカウント内のファイル、連絡先、カレンダーの予定などを検索するには、上の検索バーに入力してください。
フォルダー
ライブ
読み込み中…
@@ -433,9 +455,11 @@
%s は禁止されている名前です
%s。 移動またはコピーする前にファイルの名前を変更してください
ファイルが見つかりません
+ 共有を作成することができません。ファイルが見つかりませんでした。
ファイルを同期できませんでした。 最新の利用可能なバージョンを表示します。
名前を変更
アップロードに失敗しました。インターネット接続がありません
+ %sはすでに存在します。強豪は検出されませんでした。
ファイルバージョンの復元中にエラーが発生しました。
ファイルバージョンが正常に復元されました。
詳細
@@ -455,6 +479,10 @@
フォルダーはすでに存在します
このフォルダーは%1$sでの閲覧が最適です
作成
+ フォルダー%sの同期中にエラーが発生しました。
+ ディスク容量が不足しているため同期を中断しました。
+ フォルダー%sの同期に成功しました。
+ 同期中…
フォルダーがありません
フォルダ名を空にすることはできません
選択
@@ -512,6 +540,7 @@
前回のバックアップ: %1$s
リンク
リンク名
+ セキュリティ設定によりリンクを辿れません
編集中
リスト表示
結果をさらに読み込む
@@ -541,7 +570,6 @@
データのクリア
%1$s のデータから 設定、データベース、サーバー証明書が完全に削除されます。\n\n ダウンロードされたファイルはそのまま残ります。\n\n 実行中はしばらく時間がかかります。
管理領域
- ファイルアップロードの上限に達しました。一度にアップロードできるファイルは500ファイル以下にしてください。
このメディアファイルはストリーミングできません
メディアファイルを読み込めません
不正なエンコードのメディアファイルです
@@ -590,6 +618,8 @@
アクションの実行に失敗しました。
バックグラウンド操作の結果に対する通知を表示する
バックグランド処理
+ ローカルファイルの変更が検出されました。
+ コンテンツオブザーバー
ダウンロードの進行状況を表示
ダウンロード
ファイルの同期の進行状況と結果を表示します
@@ -598,6 +628,8 @@
一般的な通知
ミュージックプレーヤーの進行状況
メディアプレーヤー
+ オフラインファイル操作の進捗を表示する
+ オフラインファイル操作
サーバーからのプッシュ通知を表示: 新規コメント受信、新規リモート共有、管理者からの告知など
プッシュ通知
アップロードの進行状況を表示
@@ -612,8 +644,12 @@
インターネット接続がなくてもフォルダの整理やファイルの作成ができます。オンラインに戻ったら、保留中の操作が自動的に同期されます。
オフラインでも作業は続きます
ファイルがまだ存在しません。まずファイルをアップロードしてください。
+ %sを作成することができません。サーバーにはすでに同じ名前のファイルが存在します。
+ %sを作成することができません。サーバーにはすでに同じ名前のフォルダーが存在します。
オフライン操作を完了できません。%s
オフライン操作
+ %s の削除をキャンセルしました。ファイルはサーバー上で変更されています。
+ 名前を%sへ変更することができません。サーバーにはすでに同じ名前のファイルが存在します。
オフライン操作を開始しています
1時間
オンライン
@@ -639,13 +675,6 @@
画像を設定するアプリが見つかりませんでした
ホームスクリーンにピン留めする
%1$sを開く
- .txt
- 389 KB
- プレースホルダー
- 12:23:45
- 最近編集したもの
- これはプレースホルダです
- 2012/05/18 PM 12:23
中止
切替え
サーバを選択してください...
@@ -662,10 +691,12 @@
アカウントを追加
カレンダーと連絡先を同期
F-Droid も Google Play もインストールされていません
+ DAVx⁵ (旧称 DAVdroid) (v1.3.0+) を現在のアカウントに設定する
カレンダーと連絡先の同期セットアップ
アプリについて
詳細
開発者
+ ファイル
一般
もっと見る
同期
@@ -673,6 +704,7 @@
連絡先のデイリーバックアップ
データの保存場所
データの保存場所を管理する
+ DAVx⁵ (旧称 DAVdroid) の設定中に予期しないエラーが発生しました。
End-to-end 暗号化を設定中!
E2Eニーモニック
ニーモニックを表示するには、デバイスクレデンシャルを有効にしてください。
@@ -773,6 +805,7 @@
自動アップロード
あなたの写真とビデオのために
カレンダーと連絡先
+ DAVx⁵で同期
検索結果の取得中にエラーが発生しました
このユーザのためにセキュアな共有が設定されていません
セキュアシェア...
@@ -783,13 +816,18 @@
送信
送信/共有
送信ボタンアイコン
+ コンテンツをロードできませんでした
+ デバイスがインターネットに接続されていないようです
として設定され
+ ダウンロードの上限を設定できません。容量を確認してください。
+ メッセージを設定
ノート
オンラインステータス
画像を使用する
End-to-End 暗号化の設定中に、12語のランダムなニーモニックが表示されます。これは他のデバイスでファイルを開くために必要です。この情報はこのデバイスにのみ保存され、この画面で再表示することができます。安全な場所にメモしておいてください!
共有
ダウンロードと同期を許可
+ 共有を更新できませんでした。ノートを追加してもう一度やり直してください。
リンクをコピーして共有
作成
カスタム権限
@@ -815,6 +853,7 @@
有効期限を設定
パスワードを設定
セキュアファイルドロップ中は再共有できません。
+ 最低1つのオプションを選んでください。
編集可能
ファイルリクエスト
セキュアなファイルドロップ
@@ -849,6 +888,8 @@
他のサービスでサインアップ
%1$s があなたのNextcloudアカウント %2$s にアクセスできるようにしますか?
ソート
+ お気に入りを最初に並べる
+ ファイルよりもフォルダを先に並べ替えます
非表示
詳細
サーバーIDを確認できません
@@ -1006,6 +1047,10 @@
ローカルストレージへにファイルをコピーできませんでした
フォルダのロックに失敗しました
アップロードはユーザーによってキャンセルされました
+ 全てのファイルへのアクセスを許可
+ アプリ権限
+ ファイルのアップロードにはローカルストレージへのアクセス権限が必要です。タップして許可してください。
+ アップロード停止中 ― ストレージへのアクセス権限が必要です
%1$d / %2$d - %3$s
暗号化は、Android 5.0以降で使えます。
十分な空き容量が無い場合、選択したファイルは%1$sにコピーできません。コピーせず移動にしますか?
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index 8c31632..5bf5ffc 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -532,11 +532,6 @@
Additional permissions required to upload and download files.
No app found to set a picture with
Open %1$s
- .txt
- 389 KB
- 12:23:45
- This is a placeholder
- 2012/05/18 12:23 PM
stop
toggle
Disabling power save check might result in uploading files when in low battery state!
@@ -705,6 +700,7 @@
Sign up with provider
Allow %1$s to access your Nextcloud account %2$s?
Sort by
+ Sort favorites first
Hide
Details
The identity of the server could not be verified
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 3313fb5..c59e9b9 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -534,7 +534,6 @@
데이터 지우기
%1$s의 데이터에서 설정, 데이터베이스, 서버 인증서를 삭제합니다.\n\n다운로드한 파일은 변경되지 않습니다.\n\n이 작업은 시간이 걸릴 수 있습니다.
저장 공간 관리
- 최대 파일 업로드 제한에 도달했습니다. 1회 당 500개 이하의 파일을 업로드해 주세요.
미디어 파일을 스트리밍 할 수 없습니다.
미디어 파일을 읽을 수 없음
미디어 파일의 인코딩이 잘못됨
@@ -627,11 +626,6 @@
공유할 연락처를 선택
사진을 설정할 앱을 찾을 수 없음
%1$s 열기
- 389 KB
- 12:23:45
- 최근에 편집됨
- 이것은 자리 비움자입니다
- 2012년 05월 18일 오후 12:23
멈추기
토글
서버를 선택해 주세요...
@@ -652,6 +646,7 @@
정보
자세한 정보
개발
+ 파일
일반
더 보기
동기화
@@ -829,6 +824,8 @@
공급자로 가입
%1$s이(가) Nextcloud 계정%2$s에 액세스하도록 허용 하시겠습니까?
정렬
+ 즐겨찾기를 처음에 나열
+ 폴더를 파일보다 먼저 정렬
숨기기
자세히
서버를 검증할 수 없습니다
diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml
index 3902c0e..939e048 100644
--- a/app/src/main/res/values-lo/strings.xml
+++ b/app/src/main/res/values-lo/strings.xml
@@ -10,9 +10,12 @@
ແກ້ໄຂ
ລ້າງການແຈ້ງເຕືອນທັງຫມົດ
ລ້າງຖັງຂີ້ເຫຍື່ອ
+ Send/share
ເບິ່ງຕາຕະລາງ
ລາຍການທີ່ຈະເບິ່ງ
+ Restore contacts and calendar
ໂຟນເດີໃຫມ່
+ Move or copy
ເປີດດ້ວຍ
ຄົ້ນຫາ
ລາຍລະອຽດ
@@ -28,8 +31,39 @@
ເພິ່ມການແບ່ງປັນສາທາລະນະໃໝ່
ຕື່ມການວາງໄຟລ໌ທີ່ປອດໄພໃໝ່
ເພີ່ມໃສ່ %1$s
+ Base URL
+ Disable Clipboard
+ Disable Intro
+ Disable Log
+ Disable External Sites
+ Disable Multi Account
+ Disable Sharing
+ Enforce Protection
+ Proxy Hostname
+ Proxy Port
ສະແດງໜຶ່ງລາຍການຈາກໜ້າປັດ
ຄົ້ນຫາໃນ%s
+ Appear offline
+ This content was generated by AI and can make mistakes.
+ Add new task
+ Create a new task from bottom right
+ Type some text
+ Are you sure you want to delete this task?
+ Delete task
+ Loading task list…
+ An error occurred while creating the task
+ Task successfully created
+ An error occurred while deleting the task
+ Task successfully deleted
+ Task list is empty.
+ Task list is empty. Check assistant app configuration.
+ Unable to fetch task list, please check your internet connection.
+ Delete Task
+ The task output is not ready yet.
+ Unable to fetch task types, please check your internet connection.
+ Assistant
+ Input
+ Output
ບໍ່ພົບບັນຊີທີ່ກ່ຽວຂ້ອງ!
ການເຂົ້າເຖິງໄດ້ບໍ່ສຳເລັດ%1$s:
ຍັງບໍ່ໄດ້ເພີ່ມບັນຊີໃສ່ໃນອຸປະກອນນີ້
@@ -60,9 +94,20 @@
ບໍ່ສາມາດຊອກຫາເຈົ້າພາບໄດ້
%1$sບໍ່ສະຫນັບສະຫນູນຫຼາຍບັນຊີ
ບໍ່ສາມາດສ້າງການເຊື່ອມຕໍ່
+ Cancel Login
+ Please enter a valid server address.
+ Unable to fetch login details. Please try again.
+ There was an issue processing your login request. Please try again later.
+ No browser is available to open this link.
+ Please complete login process in your browser
+ Auto-upload is paused because Battery Saver is on.
ເກັບໄວ້ໃນໂຟນເດີດັ້ງເດີມ, ເປັນຮຸບແບບອ່ານຢ່າງດຽວ
+ Low battery, upload might take longer
ພຽງແຕ່ອັບໂຫຼດໃນ Wi-Fi ເທົ່ານັ້ນ
/ອັບໂຫຼດອັດຕະໂນມັດ
+ This folder is already included in the parent folder’s sync, which may cause duplicate uploads
+ Waiting for Wi-Fi to start uploading
+ Uploading files from %s to %s
ຕັ້ງຄ່າ Config
ສ້າງການຕັ້ງຄ່າໂຟນເດີດ້ວຍຕົນເອງ
ກຳໜົດການຕັ້ງຄ່າໂຟນເດີດ້ວຍຕົນເອງ
@@ -71,22 +116,30 @@
ຮຸບພາບອາວະຕານ
ອອກໄປ
ສໍາຮອງຂໍ້ມູນການຕັ້ງຄ່າ
+ Contacts and calendar backup
ປີດ
ປີດ
ອຸປະກອນຂອງທ່ານອາດຈະເປີດໃຊ້ງານປະສິດທິພາບ. ການອັບໂຫຼດອັດຕະໂນມັດ ເພື່ອເຮັດວຽກໄດ້ຢ່າງຖືກຕ້ອງ.
ການເພີ່ມປະສີດທິພາບແບັດເຕີລີ
+ Assistant
ລາຍການທີ່ມັກ
ຟາຍທັງໝົດ
ຊື່ມວນຊົນ
+ Busy
ປະຕິທິນ
+ Calendars
ມີບັນຫາໃນການໂຫຼດການຢັ້ງຢືນ
ເວີຊັ້ນຂອງ Changelog
+ Check back later or reload.
ເຄື່ອງໝາຍກ່ອງ
ເລືອກໂຟນເດີພາຍໃນເຄື່ອງ...
ເລືອກໂຟນເດີໄລຍະໄກ...
ກະລຸນາເລືອກແບບຟອມ ແລະ ໃສ່ຊື່ຟາຍ
ເລືອກເອົາຟາຍທີ່ຈະເກັບໄວ້!
+ Choose widget
+ ລຶບລ້າງ
ລ້າງການແຈ້ງເຕືອນບໍ່ສໍາເລັດ.
+ Clear status after
ສໍາເນົາຂໍ້ຄວາມຈາກ%1$s
ບໍ່ໄດ້ຮັບຂໍ້ຄວາມທີ່ຈະສໍາເນົາໄປຍັງຄລິບ
ສຳເນົາລິງ
@@ -101,9 +154,11 @@
ຜິດພາດ
ຄວາມຈຳບໍ່ພໍ
ຂໍ້ຜິດພາດທີ່ບໍ່ຮູ້ຈັກ
+ Leave this share
ກຳລັງໂຫຼດ...
ທັດໄປ
ບໍ່
+ Now
ຕົກລົງ
ທີ່ກໍາລັງລໍຖ້າ
ລຶບ
@@ -133,20 +188,30 @@
ການຊ່ວຍເຫຼືອ ໂດຍການທົດສອບ
ລາຍງານບັນຫາກ່ຽວກັບ GitHub
ຕັ້ງຄ່າຄອຍຟິກ
+ Remove local encryption
ທ່ານຕ້ອງການລືບແທ້ບໍ %1$s?
ທ່ານຕ້ອງການລືບລາຍການທີ່ເລືອກໄວບໍ?
ທ່ານຕ້ອງການ ລຶບ%1$s ແລະ ເນື້ອຫາບໍ?
ທ່ານຕ້ອງການລຶບລາຍການທີ່ເລືອກ ແລະ ເນື້ອຫາແທ້ບໍ?
ຊ່ອງເກັບຢ່າງດຽວ
+ Conflict resolver dialog cannot be created
+ Folder conflict
+ Local file
ຖ້າທ່ານເລືອກເອົາທັງສອງເວີຊັ້ນ, ບ່ອນເກັບຟາຍຈະມີຈໍານວນສະສົມ
+ If you select both versions, the local folder will have a number appended to its name.
+ Server file
+ Contacts backup
+ Contact permission is required.
ລາຍການໄອຄອນຜູ້ຕິດຕໍ່
ບໍ່ໄດ້ຮັບອະນຸຍາດ, ບໍ່ມີຫຍັງນໍາເຂົ້າ.
ຕິດຕໍ່
ສໍາຮອງຂໍ້ມຸນດຽວນີ້
ການສໍາຮອງທີ່ກໍານົດໄວ້ ແລະ ຈະເລີ່ມຕົ້ນໃນໄວໆນີ້
ການນໍາເຂົ້າທີ່ໄດ້ກໍານົດໄວ້ ແລະ ຈະເລີ່ມໃນໄວໆນີ້
+ Import failed to start. Please try again
ບໍ່ພົບຟາຍ
ບໍ່ສາມາດຊອກຫາ ການສໍາຮອງລ່າສຸດຂອງທ່ານໄດ້!
+ Detecting content changes
ສໍາເນົາຄລິບ
ມີຂໍ້ຜິດພາດເກີດຂຶ້ນໃນຂະນະທີ່ພະຍາຍາມສໍາເນົາຟາຍຫຼືໂຟນເດີນີ້
ມັນເປັນໄປບໍ່ໄດ້ທີ່ຈະສໍາເນົາໂຟນເດີ ເປັນຫນຶ່ງໃນໂຟນເດີ ພື້ນຖານ
@@ -159,26 +224,54 @@
ບໍ່ສາມາດເກັບURL ໄດ້
ສ້າງ
ບໍ່ສາມາດສ້າງໂຟນເດີໄດ້
+ Create link
+ New
+ New document
ໂຟນເດີໃຫມ່
+ New presentation
+ New spreadsheet
+ Add folder description
+ Adds folder description
ຂໍ້ມູນປະຈໍາຕົວຖືກປິດ
+ Daily backup
+ Data to back up
ການຢັ້ງຢືນຕົວຕົນທີ່ບໍ່ຖືກຕ້ອງ
ຍ້າຍບັນຊີ
ລົບລາຍການ
+ Delete Link
ເລືອກຄຶນທັງໝົດ
+ Destination filename
ມີເວີຊັນໃຫມ່
ບໍ້ມີຂໍ້ມູນ
ບໍ່ມີເວີຊັ້ນໃຫມ່ວ່າງ.
ປິດ
+ Did not check for duplicates.
algorithm ຫຍ່ອຍບໍ່ພ້ອມໃຊ້ງານໃນໂທລະສັບຂອງທ່ານ
ເຂົ້າສູ້ລະບົບ ຜ່ານການເຊື່ອມຕໍ່ໂດຍກົງບໍ່ສຳເລັດ!
+ Login with %1$s to %2$s
ປິດ
ຍົກເລີກ
ຍົກເລີກການເເຈ້ງເຕືອນ
+ Displays your 12 word passphrase
ຫ້າມລົບກວນ
+ Multiple images
+ PDF file
+ Choose export type
+ PDF generation failed
+ Generating PDF…
+ Can’t create items here: missing create permission.
+ Can’t create file: missing permission.
+ Can’t create folder: missing permission.
+ Can’t delete item: missing delete permission.
+ Can’t move item: missing move permission.
+ Can’t open item: missing read permission.
+ Can’t rename item: missing rename permission.
ສໍາເລັດ
ບໍ່ຈະແຈ້ງ
ບໍ່ສາມາດສ້າງຟາຍໄດ້
+ Invalid filename for local file
ດາວໂຫຼດເວີຊັ້ນ dev ລຸ້ນລ່າສຸດ
+ Download limit
ໂຫຼດບໍ່ໄດ້%1$s
ໂຫຼດບໍ່ໄດ້, ເຂົ້າລະບົບໃໝ່ອີກຄັ້ງ
ດາວໂຫຼດບໍ່ສຳເລັດ
@@ -187,17 +280,23 @@
ກຳລັງດາວໂຫຼດ
%1$sດາວໂຫຼດສຳເລັດ
ດາວໂຫຼດສຳເລັດ
+ Certain files were canceled during the download by user
+ Error occurred while downloading files
ຍັງບໍ່ທັນດາວໂຫຼດເທື່ອ
+ Unexpected error occurred while downloading files
ປິດດ້ານຂ້າງ
ຊຸມຊົນ
ຮູບພາບເບື້ອງຫຼັງຂອງຫົວຂໍ້
ບັນດາກິດຈະກຳ
ຟາຍທັງໝົດ
+ Assistant
ລາຍການທີ່ມັກ
ຊື່ມວນຊົນ
+ Groupfolders
ໜ້າຫຼັກ
ການເເຈ້ງເຕືອນ
ເທິງອຸປະກອນ
+ Personal files
ແປງລ່າສຸດ
ແບ່ງປັນ
ລຶບຟາຍ
@@ -207,10 +306,23 @@
ນຳໃຊ້ %1$sຂອງ%2$s
ນຳໃຊ້%1$s
ອັບໂຫຼດອັດຕະໂນມັດ
+ %s due to too many wrong attempts
+ Counter is too old
+ Hash not found
+ E2E not yet setup
+ Not possible without internet connection
+ Signature does not match
+ Assistant
+ More
+ More Nextcloud Apps
+ Failed to pick email address.
ຕັ້ງເປັນການເຂົ້າລະຫັດ
+ Unable to retrieve server certificate
+ Failed to verify public key
ຕັ້ງການເຂົ້າລະຫັດ
ຖອດລະຫັດລັບ
ປິດ
+ Enter your passphrase to access your files
ໂຟນເດີນີ້ເຕັມ.
ກຳລັງສ້າງກະແຈໃໝ່
ທັງຫມົດ 12 ຄໍາສັບຮ່ວມກັນເຮັດໃຫ້ລະຫັດຜ່ານມີຄວາມປອດໄພ, ພຽງແຕ່ທ່ານນໍາໃຊ້ຟາຍທີ່ເຂົ້າລະຫັດຂອງທ່ານ. ກະລຸນາຂຽນລົງ ແລະ ຮັກສາໄວ້ບ່ອນໃດບ່ອນຫນຶ່ງໃຫ້ປອດໄພ.
@@ -218,20 +330,36 @@
ບັນທຶກການເຂົ້າລະຫັດ 12 ຕົວຂອງທ່ານ
ລະຫັດຜ່ານ
ການເອີ້ນກະເເຈມາໃຊ້
+ Unable to retrieve private key
+ Unable to retrieve public key
ການຈັດເກັບກະເເຈ
ຕັ້ງຄ່າການເຂົ້າລະຫັດລັບ
+ An unexpected error occurred while downloading the keys
ບໍ່ສາມາດບັນທຶກໄດ້, ກະລຸນາລອງອີກເທື່ອຫນຶ່ງ.
ຜິດພາດໃນຂະນະຖອດລະຫັດລັບ. ລະຫັດບໍ່ຖຶກຕ້ອງ
+ Enter destination filename
ກະລຸນາໃສ່ຊື່ຟາຍ
%1$sບໍ່ສາມາດ ສຳເນົາໄປຍັງ %2$sຂອງໂຟນເດິ
ພິດພາດ ບໍ່ສາມາດດໍາເນີນການໄດ້
+ Error choosing date
ຜິດພາດໃນການສະແດງຄວາມຄິດເຫັນ
%1$sຢຸດ
+ Error creating file from template
+ Unable to fetch sharees.
+ Error showing file actions
+ Error changing file lock status
ລາຍງານ
+ Report issue to tracker? (requires a GitHub account)
ຜີດພາດໃນການ ດຶງ ຫຼື ເອີ້ນຟາຍມາໃຊ້
ຜິດພາດໃນການດຶງ ແບບຕົວຢ່າງມາໃຊ້
+ Error setting status message!
+ Error showing encryption setup dialog!
ຂໍ້ຜິດພາດການເລິິມເປີດກ້ອງ
+ Error starting document scan
+ Failed to upload taken media
ບັນຊີ
+ Times run in 48h
+ ສ້າງເມື່ອ
ຊື່ວຽກ
ດຳເນີນການ
ກ່າວ
@@ -245,10 +373,16 @@
ຢຸດທົດລອງວຽກ
ການໂອນຍ້າຍ (ການຍົກລະດັບເເອັບ)
ການເລືອກ
+ Engineering test mode
+ File transfer
ການທົດສອບດາວໂຫຼດ Enqueue
+ Enqueue test upload
ເສັ້ນທາງໄລຍະໄກ
+ Transfer
ດາວໂຫລດ
ອັບໂຫຼດ
+ You don’t have permission to create or upload files in this folder.
+ ການແບ່ງປັນພາຍນອກ
ເພີ່ມ ຫຼື ອັບໂຫຼດ
ບໍ່ສາມາດຜ່ານຟາຍ ເພື່ອຈັດການດາວໂຫລດ
ພິມຟາຍບໍ່ສຳເລັດ
@@ -256,27 +390,45 @@
ອັບເດດ UI ບໍ່ສຳເລັດ
ເພີ່ມລາຍການທີ່ມັກ
ລາຍການທີ່ມັກ
+ Shared file cannot be updated
+ Filename already exists
ລຶບ
ການດຶງຟາຍຂໍ້ມູນຜິດພາດ
+ You cannot create a share, sharing is already active from this user.
+ No app available to select contacts
ການດາວໂຫຼດລາຍລະອຽດບໍ່ສຳເລັດ
+ Please select custom permission
ຟາຍ
ຮັກສາໄວ້
ອັບໂຫຼດເນື້ອຫາບາງອັນ ຫຼື sync ອຸປະກອນຂອງທ່ານ
ບໍ່ມີລາຍການທີ່ມັກເທື່ອ
ຟາຍ ແລະ ໂຟນເດີລາຍການ ທີ່ທ່ານມັກ ຈະສະແດງໃຫ້ເຫັນ ຢູ່ ທີ່ ນີ້ .
+ Found no images or videos
ບໍ່ມີຟາຍ
ບໍ່ມີຜົນ ໃນໂຟນເດີນີ້
ບໍ່ມີຜົນ
+ No file or folder matching your search
ບໍ່ມີຫຍັງໃນນີ້. ທ່ານສາມາດເພີ່ມໂຟນເດີໄດ້.
ຟາຍແລະ ໂຟນ ເດີທີ່ດາວໂຫຼດ ຈະສະແດງໃຫ້ເຫັນຢູ່ທີ່ນີ້ .
ບໍ່ພົບຟາຍດັດແປງພາຍໃນ 7 ວັນລ່າສຸດ
ບາງທີອາດຢູ່ໃນໂຟນເດີທີ່ແຕກຕ່າງກັນ?
ຟາຍ ແລະ ໂຟນເດີ ທີ່ ທ່ານແບ່ງປັນຈະສະແດງ ໃຫ້ເຫັນຢູ່ທີ່ນີ້
ຍັງບໍ່ມີການແບ່ງປັນເທື່ອ
+ No results found for your query
+ Start your search
+ Type in the search bar above to find files, contacts, calendar events, and more across your account.
+ Check your internet connection or try again later
+ Poor connection
ໂຟນເດີ
+ LIVE
ກຳລັງໂຫຼດ
ບໍ່ມີແອັບພລິເຄຊັນທີ່ຕັ້ງໄວ້ເພື່ອຈັດການກັບຟາຍປະເພດນີ້.
ວິນາທີຜ່ານມາ
+ Permissions needed
+ Storage permissions
+ %1$s works best with permissions to access storage. You can choose full access to all files, or read-only access to photos and videos.
+ %1$s needs file management permissions to upload files. You can choose full access to all files, or read-only access to photos and videos.
+ Allow access from other apps
ການກວດກາຈຸດຫມາຍປາຍທາງ...
ກຳລັງລ້າງ
ອັບເດດໂຟນເດີຖານຂໍ້ມຸນ
@@ -287,6 +439,7 @@
ບໍ່ສາມາດຂຽນຫາຟາຍຈຸດຫມາຍປາຍທາງໄດ້
ການຍົກຍ້າຍບໍ່ສຳເລັດ
ບໍ່ສາມາດປັບປຸງໜ້າ indexໄດ້
+ %1$s\n(%2$s / %3$s)
ການເຄື່ອນຍ້າຍຂໍ້ມູນ...
ສຳເລັດ
ປ່ຽນແທນ
@@ -298,13 +451,24 @@
ການປັບປຸງ ໜ້າ index ...
ໃຊ້
ລໍຖ້າການ sync ແບບເຕັມ...
+ Current folder name is invalid, please rename the folder. Redirecting home…
+ Folder path contains reserved names or invalid characters
+ %s is a forbidden file extension
+ Filenames must not contain spaces at the beginning or end
+ Name contains invalid characters: %s
+ %s is a forbidden name
+ %s. Please rename the file before moving or copying
ບໍ່ພົບຟາຍ
+ File not found. Unable to create a share.
ຟາຍບໍ່ສາມາດ synced ໄດ້ . ສະແດງເວີຊັ້ນທີ່ມີຢູ່ລ່າສຸດ.
ປ່ຽນຊື່
+ Upload failed. No internet connection
+ %s already exists, no conflict detected
ຂໍ້ຜິດພາດໃນການກູ້ຄືນເວີຊັ້ນຟາຍ!
ຟາຍທີ່ ໄດ້ ຮັບການກຸ້ຄືນສໍາເລັດ .
ລາຍລະອຽດ
ດາວໂຫຼດ
+ Export
ຟາຍຖືກປ່ຽນຊື່ %1$sໃນລະຫວ່າງການອັບໂຫຼດ
Sync
ບໍ່ໄດ້ເລືອກຟາຍ
@@ -313,10 +477,21 @@
ຊື່ຟາຍມີລັກສະນະທີ່ບໍ່ຖືກຕ້ອງ
ຊື່ຟາຍ
ຮັກສາຂໍ້ມູນຂອງທ່ານໃຫ້ປອດໄພ ແລະ ຢູ່ພາຍໃຕ້ການຄວບຄຸມຂອງທ່ານ
+ Secure collaboration and file exchange
+ Easy-to-use webmail, calendar and contacts
+ Screensharing, online meetings and web conferences
ໂຟນເດີມີຢຸ່ແລ້ວ
+ This folder is best viewed in %1$s.
ສ້າງ
+ %1$d of %2$d · %3$s
+ An error occurred during synchronization of the %s folder
+ Insufficient disk space, synchronization canceled
+ %s folder successfully synchronized
+ Syncing…
ບໍ່ມີໂຟນເດີ
+ Folder name cannot be empty
ເລືອກ
+ Choose target folder
ສຳເນົາ
ຍ້າຍ
ບໍ່ໄດ້ຮັບອານຸຍາດ%s
@@ -333,37 +508,74 @@
ຟາຍທັງໝົດຖືກຍ້າຍ
ໄປຂ້າງຫນ້າ
4 ຊົ່ວໂມງ
+ Google restricted downloading APK/AAB files!
+ This icon indicates availability of live photo
+ Name will result in a hidden file
ຊື່
ຫມາຍເຫດ
ລະຫັດຜ່ານ
ເຊີເວີບໍ່ວ່າງ
ເຊີເວີຂອງທ່ານເອງ
+ Icon for empty list
+ Icon of dashboard widget
+ Icon of widget entry
+ edited
+ Flip horizontally
+ Flip vertically
+ Rotate anti-clockwise
+ Rotate clockwise
+ Unable to edit image.
+ File details
+ Image taking conditions
+ ƒ/%s
+ ISO %s
+ %s MP
+ %s mm
+ %s s
ອັບໂຫຼດຟາຍທີ່ມີຢູ່ແລ້ວ
ພຽງແຕ່ອັບໂຫຼດເທົ່ານັ້ນ
/ອັບໂຫຼດທັນທີ
+ ການແບ່ງປັນພາຍໃນ
+ Internal two way sync
+ Not yet, soon to be synced
+ An internet connection is required to set up the encrypted folder
URL ທີ່ບໍ່ຖືກຕ້ອງ
ເບິ່ງບໍ່ເຫັນ
Label ບໍ່ສາມາດເປົ່າວ່າງໄດ້
+ Last backup: %1$s
ລິງ
+ Link Name
+ Link not followed due to security settings.
+ Editing
ລາຍຊື່
ຜົນLoad ເພີ່ມເຕີມ
ບໍ່ມີຟາຍໃນໂຟນເດີນີ້.
ຟາຍບໍ່ພົບໃນລະບົບຊ່ອງເກັບຂໍ້ມຸນ
%1$s/%2$s
ບໍ່ມີໂຟນເດີເພີ່ມເຕີມ.
+ Locate folder
+ Expires: %1$s
+ Lock file
+ Locked by %1$s
+ Locked by %1$s app
ບັນທຶກແອັບພີເຄເຊິນ%1$s
+ No app for sending logs found. Please install an email client.
+ Logged in as %1$s
ເຂົ້າລະບົບ
ການເຊື່ອມຕໍ່ເວັບໄຊຂອງທ່ານ %1$sເມື່ອທ່ານເປີດມັນໃນເວັບໄຊ
ລຶບ
Refresh
ຊອກຫາການບັນທຶກ
+ Send logs by email
ບັນທຶກ:%1$dkB, ແບບສອບຖາມກົງກັນ%2$d/%3$d ໃນ%4$dວິນາທີ
ກຳລັງໂຫຼດ
ບັນທຶກ:%1$d kB, ບໍ່ມີຕົວກອງ
ບັນທຶກ
+ Server is in maintenance mode
ລ້າງຂໍ້ມູນ
ການຕັ້ງຄ່າ, ຖານຂໍ້ມູນ ແລະ ໃບຢັ້ງຢືນເຊີເວີຈາກຂໍ້ມູນ %1$s\'s ຈະຖືກລຶບອອກຢ່າງຖາວອນ. \n\nຟາຍທີ່ຖືກໂຫຼດບໍ່ໄດ້ເກັບຮັກສາໄວ້.\n\nຂະບວນການນີ້ຈະໃຊ້ເວລາໄລຍະຫນຶ່ງ.
ຈັດການພື້ນທີ່
+ The media file cannot be streamed
ບໍ່ສາມາດອ່ານຟາຍສື່ມວນຊົນໄດ້
ຟາຍສື່ມວນຊົນມີ ການ ເຂົ້າລະຫັດບໍ່ຖືກຕ້ອງ
ການເຂົ້າຟາຍໝົດເວລາ
@@ -385,6 +597,7 @@
ມັນເປັນໄປບໍ່ໄດ້ທີ່ຈະຍ້າຍໂຟນເດີເຂົ້າໄປໃນ ຫນຶ່ງໃນໂຟນເດີພື້ນຖານຂອງມັນເອງ
ຟາຍແມ່ນມີຢູ່ແລ້ວໃນໂຟນເດີຈຸດຫມາຍປາຍທາງ
ບໍ່ສາມາດເຄື່ອນຍ້າຍຟາຍໄດ້. ກະລຸນາກວດເບິ່ງວ່າມີຢູ່ ຫຼື ບໍ່.
+ Mute all notifications
ມີຂໍ້ຜິດພາດເກີດຂຶ້ນໃນຂະນະທີ່ລໍຖ້າເຊີເວີ. ບໍ່ສາມາດດໍາເນີນການໄດ້ຢ່າງຄົບຖ້ວນ.
ເກີດຂໍ້ຜິດພາດໃນລະຫວ່າງການເຊື່ອມຕໍ່ກັບເຊີເວີ
ມີຂໍ້ຜິດພາດເກີດຂຶ້ນໃນຂະນະທີ່ລໍຖ້າເຊີເວີ. ບໍ່ສາມາດດໍາເນີນການໄດ້ຢ່າງຄົບຖ້ວນ.
@@ -393,13 +606,25 @@
ໂຟນເດີສື່ມວນຊົນໃຫມ່%1$sຖືກກວດພົບ.
ຮຸບພາບ
ວິດິໂອ
+ New notification
ເວີຊັ້ນໃຫມ່ໄດ້ຖືກສ້າງຂຶ້ນ
+ No actions for this user
ບໍ່ມີແອັບພລິເຄຊັນທີ່ສາມາດຈັດການກັບລິ້ງໄດ້
+ No calendar exists
+ No App available to handle mail address
+ No items
+ No App available to handle maps
ອະນຸຍາດໃຫ້ບັນຊີດຽວເທົ່ານັ້ນ
ບໍ່ມີແອັບ ທີ່ສາມາດຮັບມືກັບ PDF
+ No app available for sending the selected files
+ Please select at least one permission to share.
ບໍ່ສາມາດສົ່ງຂໍ້ຄວາມໄດ້
ໄອຄອນ
ບໍ່ສາມາດດໍາເນີນການໄດ້.
+ Show notifications to interact result of background operations
+ Background operations
+ Detects local file changes
+ Content observer
ສະແດງຄວາມຄືບຫນ້າຂອງດາວໂຫລດ
ດາວໂຫລດ
ສະແດງຄວາມຄືບໜ້າ ແລະ ຜົນຮັບຂອງການ sync ຟາຍ
@@ -408,38 +633,57 @@
ແຈ້ງເຕືອນທົ່ວໄປ
ຄວາມຄືບໜ້າຂອງເເອັບຫຼິ້ນເພງ
ແອັບສື່ມວນຊົນ
+ Shows progress of offline file operations
+ Offline operations
ສະແດງການແຈ້ງເຕືອນໂດຍເຊີເວີ: ຄໍາເຫັນ, ການນແບ່ງປັນໄລຍະໄກສອກ, ການປະກາດໂດຍ admin ແລະ ອື່ນໆ.
ການແຈ້ງເຕືອນ
ສະແດງຄວາມຄືບຫນ້າໃນການອັບໂຫຼດ
ອັບໂຫຼດ
icon ການແຈ້ງເຕືອນ
+ Unread notifications exist
ບໍ່ມີການແຈ້ງເຕືອນ
ກະລຸນາກວດກາຄືນໃນເວລາຕໍ່ມາ.
+ Pending operation
+ Pending Remove Operation
ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ
+ Even without an internet connection, you can organize your folders, create files. Once you\'re back online, your pending actions will automatically sync.
+ You\'re offline, but work continues
+ File does not exists, yet. Please upload the file first.
+ Could not create %s. A file with the same name exists on the server.
+ Could not create %s. A folder with the same name exists on the server.
+ The offline operation cannot be completed. %s
+ Offline Operations
+ Cancelled deletion of %s. The file has been modified on the server.
+ Cancelled rename of %s. A file with the same name exists on the server.
+ Starting offline operations
1 ຊົ່ວໂມງ
ອອນລາຍ
ສະຖານະພາບອອນລາຍ
+ Open in %1$s
ການເຂົ້າຟາຍເຊີເວີສິ້ນສຸດ, ກະລຸນາຍົກລະດັບ!
ເມນູເພີ່ມເຕີມ
ປ້ອນລະຫັດຂອງທ່ານ
ລະຫັດຜ່ານຈະຖືກຮ້ອງຂໍເມື່ອແອັບພລິເຄຊັນເລີ່ມຕົ້ນ
ກະລຸນາປ້ອນລະຫັດຂອງທ່ານ
+ The passcode will be requested every time the app is opened or reopened after 5 seconds.
ລະຫັດຜ່ານບໍ່ຄືກັນ
ກະລຸນາປ້ອນລະຫັດຂອງທ່ານອີກຄັ້ງ
ລຶບລະຫັດຜ່ານຂອງທ່ານ
ລຶບລະຫັດຜ່ານແລ້ວ
ລະຫັດຜ່ານທີ່ເກັບໄວ້
ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ
+ Unable to open password-protected PDF. Please use an external PDF viewer.
+ Tap on a page to zoom in
ອະນຸຍາດ
ປະຕິເສດ
ການອະນຸຍາດເພີ່ມເຕີມທີ່ຈໍາເປັນເພື່ອອັບໂຫຼດ ແລະ ດາວໂຫລດຟາຍ
+ Pick contact to share with
ບໍ່ພົບແອັບພລິເຄຊັນເພື່ອຕັ້ງຮູບດ້ວຍ
- 389 KB
- 12:23:45
- ຕົວສຳຮອງ
- 2012/05/18 12:23 PM
+ Pin to home screen
+ ເປີດ %1$s
ຢຸດ
ສະລັບ
+ Please select a server…
ການກວດກາການປະຢັດພະລັງງານອາດຈະສົ່ງຜົນໃນອັບໂຫຼດຟາຍເມື່ອແບັດເຕີຣີຕ່ໍາ!
ລຶບແລ້ວ
ເກັບໄວ້ໃນໂຟນເດີເດິມ
@@ -451,14 +695,23 @@
ປ່ຽນຊື່ສະບັບໃຫມ່
ຈະເຮັດແນວໃດຖ້າຟາຍມີຢູ່ແລ້ວ?
ເພີ່ມບັນຊີ
+ Sync calendar and contacts
ທັງ F-Droid ແລະ Google Play ບໍ່ໄດ້ຖືກຕິດຕັ້ງ
+ Set up DAVx⁵ (formerly known as DAVdroid) (v1.3.0+) for current account
+ Calendar and contacts sync set up
ກ່ຽວກັບ
ລາຍລະອຽດ
Dev
+ ຟາຍ
ທົ່ວໄປ
ເພີ່ມເຕີມ
Sync
+ Daily backup of your calendar and contacts
ສໍາຮອງປະຈໍາວັນຂອງການຕິດຕໍ່
+ Data storage location
+ Manage data storage location
+ Unexpected error while setting up DAVx⁵ (formerly known as DAVdroid)
+ End-to-end encryption is set up!
E2E mnemonic
ເພື່ອສະແດງ mnemonic ກະລຸນາເປິດຂໍ້ມູນປະຈຳຕົວຂອງອຸປະກອນ.
ສະແດງການແຈ້ງເຕືຶອນສື່ ມວນ ຊົນ
@@ -468,7 +721,12 @@
ປະທັບ
ຟາຍເດີມຈະ....
ຟາຍເດີມຈະ....
+ Exclude hidden files and folders
+ Exclude hidden
+ Store in subfolders based on date
ໃຊ້ໂຟນເດີຫຍ້ອຍ
+ Subfolder options
+ Add end-to-end encryption to this client
ໃບອະນຸຍາດ
ລະຫັດຜ່ານApp
ການຢັ້ງຢືນອຸປະກອນ
@@ -478,41 +736,70 @@
ການຢັ້ງຢືນອຸປະກອນ
ລະຫັດຜ່ານ
ຈັດການບັນຊີ
+ Recommend to a friend
+ Remove encryption locally
+ Set up end-to-end encryption
+ Show app switcher
+ Nextcloud app suggestions in navigation heading
ສະແດງຟາຍທີ່ເຊື່ອງໄວ້
ຮັບລະຫັດແຫຼ່ງຂໍ້ມູນ
ຈັດການໂຟນເດີສໍາລັບການອັບໂຫລດອັດຕະໂນມັດ
ໂຟນເດີ
ໂຟນໄລຍະໄກ
ຫົວຂໍ້
+ Interval
+ Manage internal folders for two way sync
+ Enable two way sync
ມືດ
ແຈ້ງ
ຕາມລະບົບ
ເບິງຮູບພາບ
+ Downloading image to start the edit screen, please wait…
ບໍ່ມີຟາຍໃນຊ່ອງເກັບທີ່ຈະເບິ່ງລ່ວງຫນ້າ
ບໍ່ສາມາດສະແດງຮູບພາບໄດ້
+ File is not downloaded
+ File is currently locked by another user or process and therefore not deletable. Please try again later.
ຂໍໂທດ
ສ່ວນຕົວ
ປິດການແຈ້ງເຕືອນ ເນື່ອງຈາກການເພິ່ງພາອາໄສການບໍລິການ Google Play ທີ່ເປັນເຈົ້າຂອງ.
ບໍ່ມີການແຈ້ງ ເຕືອນ ເນື່ອງ ຈາກພາກ ການເຂົ້າລະບົບ ສະໄຫມ. ກະລຸນາພິຈາລະນາການເພີ່ມບັນຊີຂອງທ່ານຄືນໃຫມ່.
ການແຈ້ງເຕືອນຊຸກຍູ້ໃນປະຈຸບັນຍັງບໍ່ມີ.
+ QR code could not be read!
+ Folder cannot be found, sync operation is cancelled
+ Unable to find file to upload
ລອງໃສ່ອຸປະກອນຂອງທ່ານ!%1$s
ຂ້າພະເຈົ້າຢາກເຊີນທ່ານໃຊ້ໃນອຸປະກອນຂອງທ່ານ%1$s.nDownload ທີ່ນີ້:%2$s
%1$sຫລື%2$s
+ Recommended files
+ Refresh content
ໂຫຼດຄຶນ
(ໄລຍະໄກ)
ຊອກຫາຟາຍບໍ່ສຳເລັດ
+ You can remove end-to-end encryption locally on this client
+ You can remove end-to-end encryption locally on this client. The encrypted files will remain on server, but will not be synced to this computer any longer.
ການລຶບບໍ່ສຳເລັດ
+ Remove local account
+ Remove account from device and delete all local files
ບໍ່ສາມາດລຶບການແຈ້ງການແຈ້ງເຕືອນໄດ້.
ຍ້າຍອອກ
ລືບ
ໃສ່ຊື່ໃໝ່
ບໍ່ສາມາດປ່ຽນຊື່ສໍາເນົາໃນຊ່ອງເກັບຂໍ້ມູນໄດ້, ລອງໃຊ້ຊື່ອື່ນ
ປ່ຽນຊື່ບໍ່ໄດ້ ມີຊື່ແລ້ວ
+ Request account deletion
+ Request deletion
+ Request permanent deletion of account by service provider
+ Policy or permissions prevent resharing
ກູ້ຟາຍຄຶນ
+ Restore backup
ກຸ້ຟາຍທີ່ລືບ
+ Restore
+ Restore selected
ການເກັບກໍາຂໍ້ມູນຟາຍ...
+ Retry
ບໍ່ສາມາດໂຫຼດເອກະສານໄດ້!
ເຂົ້າລະບົບໂດຍຜ່ານການສະເເກນ QR code
+ Scan page
ການປົກປ້ອງຂໍ້ມູນຂອງທ່ານ
ປະສິດພາບການເຮັດວຽກດ້ວຍໂຕມັນເອງ
ເອິ້ນເບິ່ງ ແລະ ແບ່ງປັນ
@@ -522,19 +809,39 @@
ບັນຊີທັງໝົດ
ໃນບ່ອນດຽວ
ອັບໂຫຼດອັດຕະໂນມັັດ
+ for your photos and videos
+ Calendar and contacts
+ Sync with DAVx⁵
+ Error getting search results
+ Secure sharing is not set up for this user
+ Secure share…
ເລືອກທັງໝົດ
+ Set media folder
ເລືອກຕົວຢ່າງ1 ອັນ
ເລືອກຕົວຢ່າງ
ສົ່ງ
+ Send share
ສົ່ງປຸ່ມ icon
+ Could not load content
+ The device is likely not connected to the internet
ຕັ້ງຄ່າເປັນ
+ Unable to set download limit. Please check capabilities.
+ Set message
+ Set note
ສະຖານະພາບອອນລາຍ
ໃຊ້ຮູບເປັນ
+ During setup of end-to-end encryption, you will receive a random 12 word mnemonic, which you will need to open your files on other devices. This will only be stored on this device, and can be shown again in this screen. Please note it down in a secure place!
ແບ່ງປັນ
+ ອະນຸຍາດໃຫ້ດາວໂຫຼດ ແລະ ຊິ້ງຂໍ້ມູນ
+ We couldn’t update the share. Please add a note and try again.
+ Share and copy link
ສ້າງ
+ ສິດອະນຸຍາດແບບກຳນົດເອງ
ລຶບ
ການແບ່ງປັນ
+ Set download limit
ແກ້ໄຂ
+ %1$s
ແບ່ງປັນ%1$s
(ກຸ່ມ)%1$s
ແບ່ງປັນລິງພາຍໃນ
@@ -551,10 +858,20 @@
ແບ່ງປັນ(%1$s)
ກໍານົດວັນໝົດອາຍຸ
ກຳນົດລະຫັດຜ່ານ
+ Resharing is not allowed during secure file drop
+ Please select at least one sharing option before continuing.
ແກ້ໄຂໄດ້
+ ຄຳຂໍໄຟລ໌
+ Secure file drop
+ ເບິ່ງເທົ່ານັ້ນ
+ Share permissions
ແບ່ງປັນ
+ ອ່ານ
%1$s(ໄລຍະໄກ)
%1$s(ສົນທະນາ)
+ Name, Federated Cloud ID or email address…
+ Add users and teams
+ Send new email
ລະບຸຜູ້ຮັບ
ການຕັ້ງຄ່າ
ເຊື່ອງການດາວໂຫຼດ
@@ -568,9 +885,17 @@
ແບ່ງປັນຜ່ານລິງ
ແບ່ງປັນໂດຍທ່ານ %1$s
ການເພີ່ມການແບ່ງປັນບໍ່ສໍາເລັດ
+ Adding share failed. This file or folder has already been shared with this person or group.
+ ສະແດງທັງໝົດ
+ Show photos
+ ສະແດງໜ້ອຍລົງ
+ Show videos
+ Please manually check terms of service!
ລົງທະບຽນກັບຜູ້ໃຫ້ບໍລິການ
ອະນຸຍາດ%1$sໃຫ້ເຂົ້າບັນຊີ Nextcloud ຂອງທ່ານ%2$s?
ຈັດລຽງໂດຍ
+ Sort favorites first
+ Sort folders before files
ເຊື່ຶອງ
ລາຍລະອຽດ
ການລະບຸຕົວຕົນຂອງເຊີເວີ ບໍ່ສາມາດຢັ້ງຢືນໄດ້
@@ -602,21 +927,34 @@
ເລີ່ມຕົ້ນ
ເອກະສານ
ດາວໂຫລດ
+ External storage
ການເກັບກໍາຂໍ້ມູນພາຍໃນ
ລະຄອນໂທລະທັດ
ເພງ
+ Full access
+ Media read-only
ຮູບພາບ
+ The self-hosted productivity platform that keeps you in control.\n\nFeatures:\n* Easy, modern interface, suited to the theme of your server\n* Upload files to your Nextcloud server\n* Share them with others\n* Keep your favorite files and folders synced\n* Search across all folders on your server\n* Auto Upload for photos and videos taken by your device\n* Keep up to date with notifications\n* Multi-account support\n* Secure access to your data with fingerprint or PIN\n* Integration with DAVx⁵ (formerly known as DAVdroid) for easy setup of calendar and contacts synchronization\n\nPlease report all issues at https://github.com/nextcloud/android/issues and discuss this app at https://help.nextcloud.com/c/clients/android\n\nNew to Nextcloud? Nextcloud is a private file sync and share and communication server. It is libre software, and you can host it yourself or pay a company to do it for you. That way, you are in control of your photos, your calendar and contact data, your documents and everything else.\n\nCheck out Nextcloud at https://nextcloud.com
platform ການເຮັດວຽກ ເປັນ host ດ້ວຍຕົນເອງ ແມ່ນຊ່ວຍໃຫ້ທ່ານຄວບຄຸມໄດ້ .\n ເປັນເວີຊັ້ນການພັດທະນາຢ່າງເປັນທາງການ ຊຶ່ງມີຕົວຢ່າງການເຮັດວຽກທີ່ຍັງບໍ່ໄດ້ທົດລອງອາດເຮັດໃຫ້ເກີດຄວາມບໍ່ສະຖຽນ ແລະ ການສູນເສຍຂອງຂໍ້ມູນ ແອັບນີ້ສຳລັບຜູ້ທີ່ ຍິນດີຈະທົດລອງ ແລະ ລາຍງານຂໍ້ບົກຜ່ອງ. ຫ້າມໃຊ້ສຳລັບວຽກຕົວຈິງຂອງທ່ານ!\n\n ໃຫ້ຜູ້ພັດທະນາ ແລະ ເວີຊັ້ນຢ່າງເປັນທາງການພ້ອມໃຊ້ງານໃນ F-droid ແລະ ສາມາດຕິດຕັ້ງໄດ້ໃນເວລາດຽວກັນ.
ປະສິດພາບການເຮັດວຽກຂອງ ຟາດຟອມ ທີ່ເຮັດໜ້າທີ່ດ້ວຍໂຕມັນເອງ ແລະ ທ່ານຄຸ້ມຄອງໄດ້
ປະສິດພາບການເຮັດວຽກຂອງ ຟາດຟອມ ທີ່ເຮັດໜ້າທີ່ດ້ວຍໂຕມັນເອງ ແລະ ທ່ານຄຸ້ມຄອງໄດ້ (ເວີຊັ້ນຕົວຢ່າງ)
ກະຈາຍດ້ວຍ
ບໍ່ສາມາດກະຈາຍພາຍໃນໄດ້
ກະລຸນາດາວໂຫລດສື່ມວນຊົນແທນ ຫຼື ໃຊ້ແອັບພາຍນອກ
+ Year/Month/Day
+ Year/Month
+ ປີ
\"%1$s\"ໄດ້ຖືກແບ່ງໂດຍທ່ານ
%1$sແບ່ງປັນ\"%2$s\"ດ້ວຍທ່ານ
+ Photos only
+ Photos and videos
+ Videos only
+ Suggest
Sync
+ Sync anyway
ພົບຂໍ້ຜິດພາດ
ໂຟນເດີນີ້%1$sບໍ່ມີອີກແລ້ວ
+ Sync duplication
ບໍ່ສາມາດ sync%1$sໄດ້
ລະຫັດບໍ່ຖືກຕ້ອງສໍາລັບ%1$s
ຟາຍທີ່ ເກັບຮັກສາໄວ້ ໃນ sync ບໍ່ສຳເລັດ
@@ -633,6 +971,7 @@
ພື້ນທີ່ຈັດເກັບບໍ່ພຽງພໍ
ສະຖານະປຸ່ມ Sync
ຟາຍ
+ Sync warning button
ປຸ່ມການຕັ້ງຄ່າ
ກຳນົດຄ່າໂຟນເດີ
ການອັບໂຫຼດທັນທີໄດ້ຮັບການປັບປຸງ. ຕັ້ງຄ່າການອັບໂຫຼດອັດຕະໂນມັດຄຶນຈາກ main menu.\n\nEnjoy ການອັບໂຫຼດອັດຕະໂນມັດ.
@@ -641,22 +980,39 @@
ພິມ
Synced
ປ້າຍກຳກັບ
+ ເງື່ອນໄຂການໃຫ້ບໍລິການ
+ I agree to the above ToS
ທົດສອບການເຊື່ອມຕໍ່ເຊີເວີ
30 ນາທີ
ທິດນີ້
ຮຸບຂະໜາດຫຍໍ້
ຮຸບຂະໜາດຫຍໍ້ຂອງຟາຍທີ່ມີຢູ່
ຮຸບຂະໜາດຫຍໍ້ຂອງຟາຍໃໝ່
+ Loading is taking longer than expected
ມື້ນີ້
ຟາຍທີ່ ຖືກ ລຶບ
ບໍ່ມີຟາຍທີ່ຖືກລຶບອອກ
ທ່ານຈະສາມາດກຸ້ຟາຍທີ່ຖືກລຶບອອກຈາກທີ່ນີ້.
ຟາຍ%1$sບໍ່ສາມາດລຶບໄດ້!
ຟາຍ %1$sບໍ່ສາມາດກູ້ຄືນໄດ້!
+ Delete permanently
+ Loading trash bin failed!
ຟາຍບໍ່ສາມາດລຶບໄດ້ຖາວອນ!
+ Disable for all folders
+ To set up a two way sync folder, please enable it in the details tab of the folder in question.
+ Two way sync not set up
+ Internal two way sync
+ Unexpected error occurred
+ Event not found, you can always sync to update. Redirecting to web…
+ Contact not found, you can always sync to update. Redirecting to web…
+ Permissions are required to open search result otherwise it will redirected to web…
+ In this folder
ບໍ່ຮູ້
+ Unlock file
ຄວາມຄິດເຫັນທີ່ບໍ່ອ່ານ
ຍົກເລີິກການຕັ້ງຄ່າເຂົ້າລະຫັດຜ່ານ
+ Remove from favorites
+ Remove folder from internal two way sync
ເກີດຂໍ້ຜິດພາດໃນຂະນະທີ່ພະຍາຍາມຍົກເລີກຟາຍແບ້ງປັນ ຫຼື ໂຟນເດີນີ້.
ບໍ່ສາມາດແບ່ງປັນໄດ້. ກະລຸນາກວດເບິ່ງວ່າມີຟາຍຢູ່ ຫຼື ບໍ່.
ເພື່ອຍົກເລີກການແບ່ງປັນຟາຍນີ້
@@ -666,11 +1022,20 @@
ບໍ່ສາມາດປັບປຸງໄດ້. ກະລຸນາກວດເບິ່ງວ່າຟາຍມີຢູ່ຫຼືບໍ່.
ເພື່ອປັບປຸງການແບ່ງປັນນີ້
ການປັບປຸງການແບ່ງປັນບໍ່ສໍາເລັດ
+ Clear cancelled uploads
+ Resume cancelled uploads
ການອັບໂຫຼດບໍ່ສຳເລັດ
+ Retry failed uploads
+ Some files no longer exist. These uploads cannot be resumed.
+ Pause all uploads
+ Resume all uploads
ບໍ່ສາມາດສ້າງຟາຍໄດ້
ອັບໂຫຼດຈາກ...
ອັບໂຫລດເນື້ອຫາຈາກແອັບອື່ນໆ
+ Photo
+ Do you want to take a photo or video?
ອັລໂຫຼດຈາກກ້ອງຖ່າຍຮູບ
+ ວິດີໂອ
ຊື່ຟາຍ
ຊະນິດຟາຍ
ຟາຍໜ້າປົກ Google Maps(%s)
@@ -678,16 +1043,27 @@
ຟາຍຂໍ້ຄວາມສ່ວນຫຍ່ອຍ(.txt)
ຊື່ຟາຍ ແລະ ຊະນິດຟາຍ ເພື່ອອັບໂຫຼດຟາຍ
ອັບໂຫຼດຟາຍ
+ All uploads are paused
ອັບໂຫຼດປຸ່ມ ໃນລາຍການການອັບໂຫຼດ
+ Cancel upload
ລຶບ
ບໍ່ມີການອັບໂຫຼດ
ອັບໂຫລດເນື້ອຫາບາງຢ່າງ ຫຼື ເປີດການອັບໂຫລດອັດຕະໂນມັດ.
+ Toggle expansion of header
ແກ້ໄຂຂໍ້ຜິດພາດ
ຊ່ອງເກັບຂໍ້ມູນເຕັມ
ບໍ່ສາມາດສໍາເນົາຟາຍເຂົ້າໃນການເກັບກໍາຂໍ້ມູນໃນເກັບຂໍ້ມູນໄດ້
ການລ໋ອກໂຟນເດີບໍ່ສຳເລັດ
+ Upload was cancelled by user
+ Allow all file access
+ App permissions
+ Your files cannot be uploaded without access to local storage. Tap to grant permission.
+ Upload Stopped – Storage Permission Required
+ %1$d / %2$d - %3$s
ການເຂົ້າລະຫັດສາມາດເຂົ້າໄດ້ດ້ວຍ>= Android 5.0
ພື້ນທີ່ບໍ່ພຽງພໍ ໃນການສໍາເນົາຟາຍທີ່ເລືອກເຂົ້າໄປໃນໂຟນເດີ %1$s. ທ່ານຕ້ອງຍ້າຍໄປຢູ່ຫັ້ນແທນບໍ?
+ Storage quota exceeded
+ Scan document from camera
Syncຜິດພາດ, ກະລຸນາແກ້ໄຂດ້ວຍມື
ຜິດພາດທີ່ບໍ່ຮູ້ຈັກ
ເລືອກ
@@ -696,8 +1072,12 @@
ບໍ່ອະນຸຍາດໃຫ້ອ່ານຟາຍທີ່ໄດ້ຮັບ%1$s
ບໍ່ສາມາດສໍາເນົາຟາຍ ໄປຫາໂຟນເດີຊົ່ວຄາວໄດ້.ລອງສົ່ງອີກຄັ້ງ.
ບໍ່ພົບຟາຍທີ່ຖືກຄັດເລືອກສໍາລັບການອັບໂຫຼດ. ກະລຸນາກວດເບິ່ງວ່າຟາຍມີຢູ່ ຫຼື ບໍ່
+ This file cannot be uploaded
ບໍ່ມີຟາຍທີ່ຈະອັບໂຫຼດ
+ File not found. Are you sure that this file exists or has a previous conflict not been resolved?
+ We couldnt locate the file on server. Another user may have deleted the file
ຊື່ໂຟນເດີ
+ Retry to upload failed local files
ເລືອກໂຟນເດີອັບໂຫລດ
ບໍ່ສາມາດອັບໂຫຼດໄດ້%1$s
ອັບໂຫຼດບໍ່ສຳເລັດ, ເຂົ້າສູ່ລະບົບອີກຄັ້ງ
@@ -734,7 +1114,10 @@
ໃບຢັ້ງຢືນເຊີເວີ ບໍ່ໜ້າເຊື່ອຖື
ກຳລັງນໍາເອົາເວີຊັ້ນເຊີເວີມາ
ແອັບ ໄດ້ຖືກຍົກເລີກ
+ Skipped
+ A file with the same name already exists.
ສຳເລັດ
+ Same file found on remote, skipping upload
ຂໍ້ຜິດພາດ
ກວດພົບໄວຣັສ. ອັບໂຫຼດບໍ່ສໍາເລັດ!
ການລໍຖ້າທີ່ຈະອອກຈາກການປະຢັດພະລັງງານ
@@ -751,33 +1134,97 @@
ເພີ່ມຊື່, ຮູບພາບ ແລະ ລາຍລະອຽດການຕິດຕໍ່ ໃນຫນ້າປົກຂອງທ່ານ.
ຊື່ຜູ້ໃຊ້
ດາວໂຫລດ
+ Video overlay icon
ລໍຖ້າໜ້ອຍໜຶ່ງ...
ການກວດສອບຂໍ້ມູນສວນຕົວ
ການສໍາເນົາຟາຍຈາກຖານເກັບຂໍ້ສ່ວນຕົວ
+ Changing the extension might cause this file to open in a different application
ຮູບພາບໃຫມ່
ຂ້າມໄປ
ໃຫມ່ໃນ%1$s
+ What is your status?
+ Widgets are only available in %1$s 25 or later when the Dashboard app is enabled
ບໍ່ມີ
ກຳລັງອັບໂຫຼດຟາຍນີ້
ສົ່ງອີເມວ
ບໍ່ມີໂຟນເດີເກັບຂໍ້ມູນ!
ນີ້ອາດຈະເປັນຍ້ອນການກູ້ຄືນການສໍາຮອງໃນອຸປະກອນອື່ນ.ການກັບຄືນຄ່າເດີມບໍ່ສຳເລັດ. ກະລຸນາກວດເບິ່ງການຕັ້ງຄ່າເພື່ອປັບຟາຍການເກັບຂໍ້ມູນ.
+
+ - %d hours
+
+
+ - %d minutes
+
+
+ - %d second ago
+
+
+ - %d minutes ago
+
+
+ - %d hours ago
+
- ບໍ່ສາມາດ sync%1$d ຟາຍ (ຜິດພາດ:%2$d )
- ບໍ່ສາມາດສໍາເນົາຟາຍ %1$dຈາກໂຟນເດີ ເຂົ້າໄປໃນ%2$s
+
+ - Wrote %1$d events to %2$s
+
+
+ - Created %1$d fresh UIDs
+
+
+ - Processed %d entries.
+
+
+ - Found %d duplicate entries.
+
+
+ - Exported %d files
+
+
+ - Failed to export %d files
+
+
+ - Exported %d files, skipped rest due to error
+
+
+ - %d files will be exported. See notification for details.
+
+
+ - You can upload up to %d files at once.
+
- ໂຟນເດີ%1$d
- ຟາຍ%1$d
+
+ - %1$d items
+
- ສະແດງໂຟນເດີທີ່ເຊື່ອງໄວ້%1$d
- ເລືອກ%d
-
+
+ - Delayed %d seconds due to too many wrong attempts
+
+
+ - Delayed %d minutes due to too many wrong attempts
+
+
+ - Delayed %d minutes
+
+
+ - %d seconds
+
+
+ - %1$d downloads remaining
+
+
diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml
index c7b29dc..7ccbd41 100644
--- a/app/src/main/res/values-lt-rLT/strings.xml
+++ b/app/src/main/res/values-lt-rLT/strings.xml
@@ -468,7 +468,6 @@
Išvalyti duomenis
Nustatymai, duomenų bazė ir serverio sertifikatai iš %1$s duomenų bus ištrinti negrįžtamai. \n\nAtsisiųsti failai bus išsaugoti.\n\nŠis procesas gali užtrukti.
Tvarkyti vietą
- Pasiektas įkeliamų failų limitas. Kelkite mažiau nei 500 failų.
Medijos failas negali būti transliuojamas
Nepavyko perskaityti medijos failo.
@@ -499,6 +498,7 @@
Nepavyko užbaigti operacijos, Serveris nepasiekiamas.
Naujas komentaras…
Aptiktas naujas %1$s medijos failas.
+ Naujas įspėjimas
Buvo sukurta nauja versija
Nėra programėlės, skirtos apdoroti nuorodas
Nėra jokių kalendorių
@@ -544,11 +544,6 @@
Papildomos teisės reikalingos įkelti šiuos atsisiųstus failus.
Nerasta programa, su kuria būtų galima nustatyti nuotrauką
Atverti %1$s
- 389 KB
- 12:23:45
- Paskiausiai taisyti
- Rezervas
- 2012/05/18 12:23 PM
stabdyti
perjungti
Pasirinkite serverį…
@@ -568,6 +563,7 @@
Apie
Informacija
Dev
+ Failai
Bendra
Daugiau
Sinchronizuoti
@@ -686,6 +682,7 @@
Nustatyti galiojimo pabaigos datą
Nustatyti slaptažodį
Galima redaguoti
+ Failų įkėlimui
Saugus failų įkėlimas
Tik peržiūrėti
Bendrinti
@@ -849,6 +846,7 @@
Įrašykite failo pavadinimą ir tipą įkėlimui
Įkelti failus
Įkelti elemento veiksmo mygtuką
+ Atšaukti įkėlimą
Ištrinti
Atnaujinimų nerasta
Įkelkite failus arba sinchronizuokite su savo įrenginiais
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index b3485f2..0580445 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -63,6 +63,7 @@
%1$s neatbalsta vairākus kontus.
Neizdevās izveidot savienojumu
Atcelt pieteikšanos
+ Nav pieejams neviens pārlūks, lai atvērtu šo saiti.
Lūgums pabeigt pieteikšanos savā pārlūkā
glabāts oriģinālajā mapē, jo tā ir tikai lasāma
Augšupielādēt tikai neirobežotā Wi-Fi
@@ -106,6 +107,7 @@
Ielādē …
Nākamā
Nē
+ Tagad
Labi
Gaida
Izdzēst
@@ -397,11 +399,6 @@
Nav atrasta neviena lietotne, ar ko iestatīt attēlu
Piespraust sākuma ekrānam
Atvērt %1$s
- 389 KB
- 12:23:45
- Nesen labots
- Šis ir viettura teksts
- 2012/05/18 12:23 PM
apturēt
pārslēgt
Lūgums atlasīt serveri…
@@ -422,6 +419,7 @@
Kalendāra un kontaktpersonu sinhronizēšanas iestatīšana
Par
Informācija
+ Datnes
Vispārīgi
Vairāk
Sinhronizēt
@@ -526,6 +524,7 @@
Reģistrēties ar pakalpojumu sniedzēju
Ļaut %1$s piekļūt Tavam Nextcloud kontam %2$s?
Kārtot pēc
+ Kārtot mapes pirms datnēm
Paslēpt
Sīkāka informācija
Servera identitāti nevarēja pārbaudīt
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index e95d84e..2a24194 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -32,6 +32,7 @@
Прикажува еден виџет од контролната табла
Барај во %s
Избриши задача
+ Асистент
Не е пронајдена поврзана сметка!
Неуспешен пристап: %1$s
Сметката сеуште не е додадена на овој уред
@@ -76,6 +77,7 @@
Оневозможи
Вашиот уред може да има овозможено оптимизација на батеријата. Автоматското прикачување работи само ако соодветно ја исклучите оваа апликација од оптимизацијата на батеријата.
Оптимизација на батеријата
+ Асистент
Омилени
Сите датотеки
Медија
@@ -110,6 +112,7 @@
Се вчитува…
Следна
Не
+ Сега
Во ред
Чекање
Избриши
@@ -207,6 +210,7 @@
Background image of drawer header
Активности
Сите датотеки
+ Асистент
Омилени
Медија
Дома
@@ -222,6 +226,7 @@
искористено %1$s од %2$s
искористено %1$s
Автоматско прикачување
+ Асистент
Постави како енкриптирано
Постави енкрипција
Дешифрирање…
@@ -364,6 +369,7 @@
Неправилна URL
Невидливо
Линк
+ Уредување
Listed layout
Вчитај повеќе резултати
Нема датотеки во оваа папка.
@@ -459,11 +465,6 @@
Потебни се дополнителни привилегии за прикачување и превземање на датотеки.
Не е пронајдена апликација за поставување на слики
Отвори %1$s
- 389 KB
- 12:23:45
- Неодамна изменета
- Ова е резервирано место
- 2012/05/18 12:23 PM
стоп
вклучи
Оневозможување на проверката за заштеда на енергијата може да овозможи прикачување на датотеки и кога батеријата е празна
@@ -481,6 +482,7 @@
За
Детали:
Dev
+ Датотеки
Општо
Повеќе
Синхронизирај
@@ -604,6 +606,8 @@
Пријавете се преку провајдер
Дозволи му на %1$s да пристапи на твојата Nextcloud сметка %2$s?
Сортирај по
+ Прво омилените
+ Подреди ги папките пред датотеките
Сокриј
Детали
Идентитетот на серверот не може да се потврди
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index da5b6fe..bbbfabc 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -515,7 +515,6 @@
Nullstill data
Innstillinger, database og serversertifikater fra %1$s\'s filer vil bli slettet. \n\nNedlastede filer blir urørt.\n\nDette kan ta noe tid.
Håndter plass
- Du har nådd den maksimale grensen for filopplasting. Vennligst last opp færre enn 500 filer om gangen.
Mediafilen kan ikke strømmes
Mediafilen kunne ikke leses
Mediafilen er ikke riktig kodet
@@ -600,12 +599,6 @@
Flere tillatelser trengs for å laste opp og ned filer.
Fant ikke noe app å sette bilde med.
Åpne %1$s
- .txt
- 389 KB
- 12:23:45
- Nylig redigert
- Dette er en plassholder
- 18.05.2012 12:23
stopp
av/på
Vennligst velg en tjener…
@@ -626,6 +619,7 @@
Om
Detaljer
Utvikling
+ Filer
Generelt
Mer
Synkroniser
@@ -798,6 +792,8 @@
Logg på med tilbyder
Tillat %1$s adgang til din Nextcloud konto %2$s?
Sorter etter
+ Sorter favoritter først
+ Sorter mapper før filer
Skjul
Detaljer
Serverens identitet kunne ikke bekreftes
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 59fdd1b..1b585db 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -556,7 +556,6 @@
Gegevens verwijderen
Instellingen, database en servercertificaten van de data van %1$s zullen permanent worden verwijderd. \n\nGedownloade bestanden blijven onaangeroerd.\n\nDit proces kan even duren.
Beheer ruimte
- Je hebt het maximum van 500 bestanden per upload overschreden.
Mediabestand kan niet worden gestreamd
Kon het mediabestand niet lezen
Mediabestand niet goed gecodeerd
@@ -658,12 +657,6 @@
Geen app gevonden om afbeelding in te stellen
Vastzetten op startscherm
Open %1$s
- .txt
- 389 KB
- 12:23:45
- Recent bewerkt
- Dit is een plaatshouder
- 2012/05/18 12:23
stop
omschakelen
Selecteer een server...
@@ -685,6 +678,7 @@
Over
Details
Dev
+ Bestanden
Algemeen
Meer
Synchroniseren
@@ -874,6 +868,8 @@
Aanmelden bij provider
Toestaan dat%1$s je Nextcloud account %2$sbenadert?
Sorteer op
+ Sorteer eerst favorieten
+ Sorteer mappen voor bestanden
Verbergen
Details
De identiteit van de server kon niet geverifieerd worden
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index efd283b..1094b12 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -93,12 +93,19 @@
%1$s nie wspiera wielu kont
Nie udało się ustanowić połączenia
Anuluj logowanie
+ Proszę wprowadzić poprawny adres serwera.
+ Nie można pobrać danych logowania. Spróbuj ponownie.
Wystąpił problem podczas przetwarzania żądania logowania. Spróbuj ponownie później.
+ Brak dostępnej przeglądarki do otwarcia tego linku.
Dokończ proces logowania w przeglądarce
+ Automatyczna aktualizacja jest wstrzymana, ponieważ tryb oszczędzania baterii jest włączony.
zachowany w oryginalnym katalogu, jak jest tylko do odczytu
+ Niski poziom baterii, aktualizacja może potrwać dłużej.
Wysyłaj tylko przez Wi-Fi
/AutoUpload
Ten katalog jest już uwzględniony w synchronizacji katalogu nadrzędnego, co może powodować duplikowanie wysyłanych plików
+ Oczekiwanie na Wi-Fi, aby zacząć aktualizowanie
+ Przesyłanie plików z %s do %s
Konfiguruj
Utwórz nową niestandardową konfigurację katalogu
Ustaw własny katalog
@@ -202,6 +209,7 @@
Nie udało się rozpocząć importu. Spróbuj ponownie
Nie znaleziono żadnych plików
Nie odnaleziono Twojej ostatniej kopii zapasowej!
+ Sprawdzanie zmian treści
Skopiowano do schowka
Wystąpił błąd podczas próby kopiowania tego pliku lub katalogu
Nie można skopiować katalogu do jednego z jego podkatalogów
@@ -471,6 +479,11 @@
Katalog już istnieje
Ten katalog najlepiej oglądać w formacie %1$s.
Utwórz
+ %1$d z %2$d · %3$s
+ Wystąpił błąd podczas synchronizacji katalogu %s
+ Brak miejsca na dysku, synchronizacja przewana
+ %s pomyślnie zsynchronizowany
+ Synchronizacja…
Brak katalogów
Nazwa katalogu nie może być pusta
Wybierz
@@ -558,7 +571,6 @@
Wyczyść dane
Ustawienia, bazy danych i certyfikaty serwera %1$s zostaną trwale usunięte.\n\nPobrane pliki pozostaną na swoich miejscach.\n\nTen proces może trochę potrwać.
Zarządzaj przestrzenią
- Osiągnięto maksymalny limit upload. Proszę uploadować mniej niż 500 plików w jednym czasie
Plik multimedialny nie może być przesyłany strumieniowo
Nie można odczytać pliku
Nieprawidłowe kodowanie pliku multimedialnego
@@ -607,6 +619,8 @@
Wykonanie akcji nie powiodło się.
Pokaż powiadomienia o interakcji z wynikami operacji wykonywanych w tle
Operacje w tle
+ Wykryj zmiany lokalne
+ Monitor zawartości
Pokazuje postęp pobierania
Pobrane
Pokazuje postęp synchronizacji plików i jej wynik
@@ -662,13 +676,6 @@
Nie znaleziono aplikacji do ustawienia obrazu
Przypnij do ekranu głównego
Otwórz %1$s
- .txt
- 389 KB
- symbol zastępczy
- 12:23:45
- Ostatnio edytowane
- Tekst zastępczy
- 2012/05/18 12:23 PM
zatrzymaj
przełącz
Wybierz serwer...
@@ -690,6 +697,7 @@
O aplikacji
Szczegóły
Deweloperskie
+ Pliki
Ogólne
Więcej
Synchronizuj
@@ -881,6 +889,8 @@
Zarejestruj się u usługodawcy
Zezwolić %1$s na dostęp do Twojego konta Nextcloud %2$s?
Sortuj według
+ Najpierw sortuj ulubione
+ Sortuj katalogi przed plikami
Ukryj
Szczegóły
Nie można zweryfikować identyfikacji serwera
@@ -991,6 +1001,7 @@
Nie znaleziono zdarzenia, zawsze możesz zsynchronizować, aby zaktualizować. Przekierowanie do strony internetowej…
Nie znaleziono kontaktu, zawsze możesz zsynchronizować, aby zaktualizować. Przekierowanie do strony internetowej…
Aby otworzyć wyniki wyszukiwania, wymagane są uprawnienia, w przeciwnym razie nastąpi przekierowanie do strony internetowej…
+ W tym katalogu
Nieznana
Odblokuj plik
Nieprzeczytane komentarze
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index ad4280f..9a64cbf 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -44,6 +44,7 @@
Mostra um widget do painel
Pesquisar em %s
Aparecer off-line
+ Este conteúdo foi gerado por IA e pode conter erros.
Adicionar nova tarefa
Criar uma nova tarefa no canto inferior direito
Digite algum texto
@@ -55,6 +56,7 @@
Ocorreu um erro ao excluir a tarefa
Tarefa excluída com sucesso
A lista de tarefas está vazia
+ A lista de tarefas está vazia. Verifique a configuração do aplicativo assistente.
Não foi possível buscar a lista de tarefas. Verifique sua conexão com a Internet.
Excluir Tarefa
O resultado da tarefa ainda não está pronto.
@@ -93,12 +95,19 @@
%1$s não tem suporte a múltiplas contas
Não foi possível estabelecer a conexão
Cancelar login
+ Insira um endereço de servidor válido.
+ Não foi possível obter os detalhes de login. Por favor, tente novamente.
Houve um problema ao processar sua solicitação de login. Tente novamente mais tarde.
+ Não há nenhum navegador disponível para abrir este link.
Por favor, conclua o processo de login no seu navegador
+ O upload automático está pausado porque o Economizador de Bateria está ativado.
mantido na pasta original, já que é somente leitura
+ Bateria fraca, o upload pode demorar mais tempo
Enviar só por WiFi não medida
/UploadAutomático
Esta pasta já está incluída na sincronização da pasta pai, o que pode causar uploads duplicados
+ Aguardando o Wi-Fi para iniciar o upload
+ Fazendo upload de arquivos de %s a %s
Configurar
Criar nova configuração de pasta personalizada
Configurar uma pasta personalizada
@@ -202,6 +211,7 @@
Falha ao iniciar a importação. Por favor, tente novamente.
Nenhum arquivo encontrado
Não foi possível encontrar seu último backup!
+ Detectando alterações no conteúdo
Copiado para a área de transferência
Ocorreu um erro ao tentar copiar este arquivo ou pasta
Não é possível copiar uma pasta para uma das suas próprias pastas subjacentes
@@ -337,7 +347,7 @@
Erro ao criar o arquivo a partir do modelo
Não é possível obter os destinatários do compartilhamento.
Erro ao mostrar as ações do arquivo
- Erro ao alterar o status de bloqueio do arquivo
+ Erro ao alterar o status de trancamento do arquivo
Reportar
Reportar problema ao rastreador? (requer uma conta GitHub)
Erro ao recuperar o arquivo
@@ -407,6 +417,8 @@
Nenhum resultado encontrado para sua consulta
Inicie sua pesquisa
Digite na barra de pesquisa acima para encontrar arquivos, contatos, eventos do calendário e muito mais em sua conta.
+ Verifique sua conexão com a Internet ou tente novamente mais tarde
+ Conexão fraca
pasta
AO VIVO
Carregando…
@@ -471,6 +483,11 @@
Pasta já existe
Esta pasta é melhor visualizada em %1$s.
Criar
+ %1$d de %2$d · %3$s
+ Ocorreu um erro durante a sincronização da pasta %s
+ Espaço em disco insuficiente, sincronização cancelada
+ Pasta %s sincronizada com êxito
+ Sincronizando…
Sem pastas aqui
O nome da pasta não pode ficar vazio
Escolher
@@ -538,9 +555,9 @@
Não há outras pastas.
Localizar pasta
Expira: %1$s
- Bloquear ficheiro
- Bloqueado por %1$s
- Bloqueado pelo aplicativo %1$s
+ Trancar arquivo
+ Trancado por %1$s
+ Trancado pelo aplicativo %1$s
%1$s logs do aplicativo Android
Nenhum aplicativo para envio de registros foi encontrado. Instale um cliente de e-mail.
Logado como %1$s
@@ -558,7 +575,6 @@
Limpar dados
Configurações, banco de dados e certificados do servidor de dados de %1$s serão excluídos permanentemente.\n\nArquivos baixados serão mantidos intocados.\n\nEste processo pode levar um tempo.
Gerenciar o espaço
- Você atingiu o limite máximo de upload de arquivos. Por favor, envie menos de 500 arquivos por vez.
O arquivo de mídia não pode ser transmitido
Não foi possível ler o arquivo de mídia
O arquivo de mídia tem uma codificação incorreta
@@ -607,6 +623,8 @@
Erro ao executar a ação.
Mostrar notificações para interagir com o resultado de operações em segundo plano
Operações em segundo plano
+ Detecta alterações em arquivos locais
+ Observador de conteúdo
Mostra o progresso de download
Downloads
Mostra o progresso sincronização de arquivo e resultados
@@ -647,6 +665,7 @@
Digite o código de acesso
O código de acesso será solicitado toda vez que o aplicativo for iniciado
Digite sua senha
+ A senha será solicitada sempre que o aplicativo for aberto ou reaberto após 5 segundos.
Os códigos de acesso não são os mesmos
Por favor, digite seu código de acesso novamente
Excluir sua frase secreta
@@ -662,13 +681,6 @@
Nenhum aplicativo encontrado para definir uma imagem
Fixar na tela inicial
Abrir %1$s
- .txt
- 389 KB
- espaço reservado
- 12:23:45
- Editado recentemente
- Este é um espaço reservado
- 2012/05/18 12:23 PM
para
alternar
Selecione um servidor…
@@ -690,6 +702,7 @@
Sobre
Detalhes
Desenvolvimento
+ Arquivos
Geral
Mais
Sincronização
@@ -745,7 +758,7 @@
Não há arquivos locais para exibir
Não foi possível exibir a imagem
O arquivo não foi baixado
- O arquivo está atualmente bloqueado por outro usuário ou processo e, portanto, não pode ser excluído. Por favor, tente novamente mais tarde.
+ O arquivo está atualmente trancado por outro usuário ou processo e, portanto, não pode ser excluído. Por favor, tente novamente mais tarde.
Desculpe
Privacidade
Notificações push desativadas devido a dependências de serviços proprietários do Google Play.
@@ -881,6 +894,8 @@
Registre-se com um provedor
Permitir %1$s acesso à sua conta %2$s?
Ordenar por
+ Ordenar favoritos primeiro
+ Ordenar pastas antes de arquivos
Ocultar
Detalhes
A identidade do servidor não pôde ser verificada
@@ -991,8 +1006,9 @@
Evento não encontrado, você sempre pode sincronizar para atualizar. Redirecionando para a web…
Contato não encontrado, você sempre pode sincronizar para atualizar. Redirecionando para web…
São necessárias permissões para abrir o resultado da pesquisa, caso contrário, ele será redirecionado para a web…
+ Nesta pasta
Desconhecido
- Desbloquear arquivo
+ Destrancar arquivo
Existem comentários não lidos
Definir como não criptografado
Remover dos favoritos
@@ -1037,7 +1053,7 @@
Resolver conflito
O armazenamento local está cheio.
O arquivo não pôde ser copiado para o armazenamento local
- Falha ao bloquear pasta
+ Falha ao trancar pasta
O upload foi cancelado pelo usuário
Permitir acesso a todos os arquivos
Permissões do aplicativo
@@ -1098,6 +1114,8 @@
Certificado de servidor não confiável
Buscando versão do servidor…
Aplicativo finalizado
+ Ignorado
+ Um arquivo com o mesmo nome já existe.
Concluído
Mesmo arquivo encontrado no servidor remoto, pulando o upload
Erro desconhecido
@@ -1206,6 +1224,11 @@
- %d arquivos serão exportados. Consulte a notificação para obter detalhes.
- %d arquivos serão exportados. Consulte a notificação para obter detalhes.
+
+ - Você pode enviar apenas %d arquivo por vez.
+ - Você pode enviar até %d de arquivos de uma só vez.
+ - Você pode enviar até %d arquivos de uma só vez.
+
- %1$d pasta
- %1$d pastas
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index d86d4a2..5a7f8e4 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -582,11 +582,6 @@
Necessário permissões adicionais para enviar e transferir ficheiros.
Nenhuma aplicação encontrada para definir a imagem
Abrir %1$s
- 389 KB
- 12:23:45
- Editado recentemente
- Isto é uma variável
- 2012/05/18 12:23
parar
alternar
Desabilitar a verificação de poupança de energia pode resultar no envio de ficheiros quando a carga da bateria está baixa!
@@ -604,6 +599,7 @@
Sobre
Detalhes
Desenvolvimento
+ Ficheiros
Geral
Mais
Sincronizar
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 04d7481..e3b4ab2 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -517,7 +517,6 @@
Elimină datele
Setările, bazele de date și certificatele serverului ale %1$s vor fi șterse permanent. \n\nFișierele descărcate vor fi păstrate intacte.\n\nAcest proces poate dura mai mult timp.
Administrare spațiu
- Ați atins limita maximă de încărcare a fișierelor. Vă rugăm să încărcați mai puțin de 500 de fișiere simultan.
Fișierul media nu poate fi difuzat
Nu s-a putut citi fișierul media
Fișierul media are codare incorectă
@@ -599,11 +598,6 @@
Sunt necesare drepturi adiționale pentru a încărca și descărca fișiere.
Nu a fost găsită o aplicație pentru a seta imaginea
Deschide %1$s
- .txt
- 389 KO
- 12:23:45
- Acesta este un substituent
- 2012/05/18 12:23 PM
stop
comută
Dezactivarea modului de economisire a bateriei ar putea rezulta în încărcarea de fișiere când bateria are un nivel scăzut!
@@ -759,6 +753,7 @@
Înscriete cu un furnizor
Permite %1$s să acceseze contul tău Nextcloud %2$s ?
Sortare după
+ Sortați favoritele primele
Ascunde
Detalii
Nu s-a putut verifica identitatea serverului
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 8e51a61..2d91171 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -93,6 +93,7 @@
%1$s не поддерживает множественные аккаунты
Не удается установить соединение
Отменить вход
+ Пожалуйста, введите действующий адрес сервера.
При обработке вашего запроса на вход возникла проблема. Пожалуйста, повторите попытку позже.
Пожалуйста, завершите процесс входа в вашем браузере
оставлен в исходном каталоге, т.к. файл только для чтения
@@ -472,6 +473,7 @@
Папка уже существует
Эту папку лучше всего просматривать в %1$s.
Создать
+ Синхронизация …
Здесь нет каталогов
Имя папки не может быть пустым
Выберите
@@ -559,7 +561,6 @@
Очистить данные
Настройки, сертификаты базы данных и сервера из данных %1$s будут удалены навсегда. \n\nЗагруженные файлы будут сохранены нетронутыми. \n\nЭтот процесс может занять некоторое время.
Управление свободным местом
- Вы достигли максимального лимита на загрузку файлов. Пожалуйста, загружайте не более 500 файлов одновременно.
Невозможно организовать потоковую передачу медиафайла
Медиафайл не может быть прочитан
Медиафайл некорректно закодирован
@@ -663,13 +664,6 @@
Не найдено приложений для обработки изображений
Закрепить на главном экране
Открыть %1$s
- .txt
- 389 Кбайт
- заполнитель
- 12:23:45
- Недавно изменено
- Это заполнитель
- 2012/05/18 12:23
остановить
переключить
Пожалуйста, выберите сервер...
@@ -691,6 +685,7 @@
О программе
Дополнительные
Разрабатываемая версия
+ Файлы
Основные
Больше
Синхронизировать
@@ -816,7 +811,7 @@
Не удалось задать лимит загрузки. Пожалуйста, проверьте доступные возможности.
Установить сообщение
Создать заметку
- Онлайн статус
+ Онлайн подключения
Использовать изображение
При настройке сквозного шифрования будет создана мнемофраза из 12 слов, которая понадобится для открытия файлов на других устройствах. Мнемофраза будет сохранена только на этом устройстве и может быть просмотрена повторно. Сохраните мнемофразу в надежном месте!
Общий ресурс
@@ -882,6 +877,8 @@
Подключиться через поставщика услуги
Разрешить %1$s доступ к вашей учетной записи Nextcloud %2$s?
Порядок сортировки
+ Сначала избранное
+ Начинать список с папок
Скрыть
Подробно
Не удалось проверить подлинность сервера
@@ -907,7 +904,7 @@
- Сертификат сервера не является доверенным
- Срок действия сертификата сервера ещё не начался
- URL не совпадает с именем сервера в сертификате
- Сообщение о состоянии
+ Описание статуса
Камера
Выбрать расположение хранилища
По умолчанию
diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml
index d908fbc..200e619 100644
--- a/app/src/main/res/values-sc/strings.xml
+++ b/app/src/main/res/values-sc/strings.xml
@@ -471,12 +471,6 @@
Nega
Sunt rechertos àteros permissos pro carrigare e iscarrigare documentos.
Peruna aplicatzione agatada pro cunfigurare un\'immàgine
- .txt
- 389 KB
- 12:23:45
- Modificados dae pagu
- Custu est unu sostitutu temporàneu
- 18/05/2012 12:23
firma
parti
Disativare su controllu de su rispàrmiu de energia podet fàghere chi si carrighent documentos cun sa bateria bàscia!
@@ -496,6 +490,7 @@
In contu de
Detàllios
Dispositivu
+ Archìvios
Generale
Àteru
Sincronizatzione
@@ -632,6 +627,8 @@
Registra·ti cun su frunidore
Permìtere a %1$s de intrare in su contu tuo de Nextcloud %2$s?
Assenta segundu
+ Assenta cun is preferidos in antis
+ Assenta cun is cartellas in antis de is archìvios
Cua
Detàllios
No at fatu a averguare s\'identidade de su serbidore
diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml
index fbedb9b..cba4254 100644
--- a/app/src/main/res/values-sk-rSK/strings.xml
+++ b/app/src/main/res/values-sk-rSK/strings.xml
@@ -43,6 +43,7 @@
Brána proxy
Zobrazí jeden widget z hlavného panela
Hľadať v %s
+ Zdá sa byť offline
Pridať novú úlohu
Vytvorte novú úlohu vpravo dole
Napíšte nejaký text
@@ -540,7 +541,6 @@
Vyčistiť dáta
Nastavenie, databázové a servrové certifikáty z dát %1$sbudú trvalo vymazané.\n\nStiahnuté súbory budú zachované.\n\nTento proces môže chvíľu trvať.
Spravovať miesto
- Dosiahli ste maximálny limit počtu súborov pre nahrávanie. Prosím nahrajte menej ako 500 súborov naraz.
Multimediálny súbor sa nedá streamovať
Nepodarilo sa prečítať súbor médií
Súbor médií má neplatné kódovanie
@@ -563,6 +563,7 @@
Nie je možné presunúť priečinok do samého seba
Súbor už v cieľovom priečinku existuje
Nie je možné presunúť súbor. Skontrolujte, či súbor existuje.
+ Stlmiť všetky upozornenia
Pri čakaní na odpoveď servera nastala chyba. Operácia nemohla byť dokončená.
Pri pokuse o pripojenie na server nastala chyba
Pri čakaní na odpoveď servera nastala chyba. Operácia nemohla byť dokončená.
@@ -637,12 +638,6 @@
Aplikácia na nastavenie obrázku sa nenašla
Pripnúť na domovskú obrazovku
Otvoriť %1$s
- .txt
- 389 KB
- 12:23:45
- Nedávno upravené
- Toto je \"placeholder\"
- 2012/05/18 12:23 PM
zastaviť
prepnúť
Prosím vyberte server ...
@@ -663,6 +658,7 @@
O aplikácii
Podrobnosti
Vývojové
+ Súbory
Všeobecné
Viac
Synchronizovať
@@ -846,6 +842,8 @@
Zaregistrovať sa u poskytovateľa
Umožniť %1$s pristupovať k Vášmu Nextcloud účtu %2$s?
Zoradiť podľa
+ Zoradiť od najobľúbenejších
+ Zoradiť adresáre pred súbormi
Skyť
Podrobnosti
Identu servra nemožno overiť
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index dbbb971..6e49f21 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -66,6 +66,7 @@
Gostitelja ni mogoče najti
%1$s ne omogoča podpore več računom
Ni mogoče vzpostaviti povezave
+ Prekliči prijavo
ohranjeno v izvorni mapi, ker je ta le za branje
Pošiljaj le na nemerjeni povezavi Wi-Fi
/SamodejnoPošiljanje
@@ -539,12 +540,6 @@
Za prejemanje oziroma pošiljanje datotek v oblak so zahtevana dodatna dovoljenja.
Ni najdenega programa za nastavitev slike
Odpri %1$s
- .txt
- 389 KB
- 12:23:45
- Nedavno urejano
- To je vsebnik predmetov
- 2012/05/18 12:23 PM
ustavi
preklopi
Onemogočanje varčevanja lahko povzroči pošiljanje datotek, ko je napetost baterije že nizka!
@@ -562,6 +557,7 @@
O projektu
Podrobnosti
Dev
+ Datoteke
Splošno
Več
Uskladi
@@ -712,6 +708,8 @@
Prijava prek ponudnika
Ali dovolite uporabniku %1$s dostop do računa Nextcloud %2$s?
Razvrsti
+ Razvrsti najprej priljubljene
+ Razvrsti mape pred datotekami
Skrij
Podrobnosti
Istovetnosti strežnika ni mogoče preveriti
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 5b9cbab..8a7bb4b 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -402,10 +402,6 @@
Refuzo
Që të ngarkoni dhe shkarkoni skedar kërkohen leje shtesë.
Nuk u gjet asnjë aplikacion për të vendosur foton
- 389 KB
- 12:23:45
- Kjo është një vendmbajtëse
- 2012/05/18 12:23 PM
Çaktivizimi i kontrollit të ruajtjes së energjisë mund të rezultojë në ngarkimin e skedarëve kur gjendeni në gjendje të ulët të baterisë!
të fshira
mbajtur në dosjen origjinale
@@ -415,6 +411,7 @@
Rreth
Hollësi
Dev
+ Skedarë
Të përgjithshme
Më tepër
Sinkronizo
diff --git a/app/src/main/res/values-sr-rSP/strings.xml b/app/src/main/res/values-sr-rSP/strings.xml
index e530ade..f751cb9 100644
--- a/app/src/main/res/values-sr-rSP/strings.xml
+++ b/app/src/main/res/values-sr-rSP/strings.xml
@@ -391,10 +391,6 @@
Odbij
Dodatne dozvole potrebne da se otpremaju i skidaju fajlovi.
Nijedna aplikacija nije nađena da se sa njom postavi slika
- 389 KB
- 12:23:45
- Ovo je mestodržač
- 2012/05/18 12:23 PoP
Isključivanje provere uštede baterije može da dovede do toga da otpremate fajlove sa praznom baterijom!
obrisano
ostavljen u izvornoj fascikli
@@ -404,6 +400,7 @@
O programu
Podaci
Razvojno
+ Fajlovi
Opšte
Ostalo
Sinhronizuj
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 09bc76f..55ab68a 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -98,10 +98,14 @@
Дошло је до проблема при обради вашег захтева за пријаву. Молимо вас да касније покушате поново.
Ниједан прегледач не може да отвори овај линк.
Молимо вас да довршите процес пријаве у интернет прегледачу
+ Ауто-отпремање је паузирано јер је укључена Штедња батерије.
остављен у оригиналној фасцикли јер је само за читање
+ Низак ниво батерије, отпремање би могло да потраје дуже
Отпремај само на бежичним мрежама без ограничења
/Аутоматска отпремања
Овај фолдер је већ укључен у синхронизацију фолдера родитеља, па се могу јавити двострука отпремања
+ Чека се на Wi-Fi за почетак отпремања
+ Отпремају се фајлови са %s на %s
Подеси
Направи нову поставку за посебну фасциклу
Подесите произвољну фасциклу
@@ -205,6 +209,7 @@
Није успело покретање увоза. Молимо вас да покушате поново
Није нађен фајл
Не могу да нађем последњу резерву!
+ Откривају се измене садржаја
Копирано у клипборд
Грешка приликом копирања овог фајла или фасцикле
Фасцикла се не може копирати у неку од својих потфасцикли.
@@ -474,6 +479,11 @@
Фасцикла већ постоји
Овај фолдер се најбоље приказује у %1$s.
Направи
+ %1$d од %2$d · %3$s
+ Дошло је до грешке приликом синхронизације фолдера %s
+ Нема довољно простора на диску, синхронизација је отказана.
+ %s фолдер је успешно синхронизован
+ Синхронизација…
Нема фасцикли овде
Назив фолдера не може да буде празно
Изаберите
@@ -561,7 +571,6 @@
Очисти податке
Поставке, база и серверски сертификати од података са %1$s ће бити заувек обрисани. \n\nВећ преузети фајлови неће бити дирани.\n\nОво може потрајати.
Управљање простором
- Достигли сте границу максималног отпремања фајлова. Молимо вас да отпремате мање од 500 фајлова истовремено.
Медијски фајл не може да се пусти
Не могу да прочитам медијски фајл
Медијски фајл има неисправно кодирање знакова
@@ -610,6 +619,8 @@
Не могу да извршим радњу.
Прикажи обавештења за интеракцију са резултатом операција у позадини
Операције у позадини
+ Открива локалне измене фајлова
+ Посматрач садржаја
Приказ напретка преузимања
Преузимања
Приказ напретка синхронизације фајла и резултата
@@ -665,13 +676,6 @@
Нема апликације којом се поставља слика
Закачи на почетни екран
Отвори %1$s
- .txt
- 389 KB
- чувар места
- 12:23:45
- Недавно уређивано
- Ово је местодржач
- 2012/05/18 12:23 ПоП
стоп
пребацивање
Молимо вас да изаберете сервер
@@ -693,6 +697,7 @@
О програму
Детаљи
Развојно
+ Фајлови
Опште
Остало
Синхронизуј
@@ -884,6 +889,8 @@
Пријавите се преко провајдера
Може ли %1$s да приступи Некстклауд налогу %2$s?
Разврстај
+ Сортирај прво омиљене
+ Поређај фолдере испред фајлова
Сакриј
Детаљи
Идентитет сервера не може да се провери
@@ -994,6 +1001,7 @@
Није пронађен догађај, увек можете да сихронизујете да бисте ажурирали. Преусмеравате се на веб
Није пронађен контакт, увек можете да сихронизујете да бисте ажурирали. Преусмеравате се на веб
За отварање резултата претраге су неопходне дозволе, у супротном бићете преусмерени на веб
+ У овом фолдеру
Непознато
Откључај фајл
Постоје непрочитани коментари
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 713f6d6..b522e05 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -44,6 +44,7 @@
Visa en widget från dashboard
Sök i %s
Visa som frånkopplad
+ Detta innehåll genererades av AI och kan innehålla fel.
Lägg till ny uppgift
Skapa en ny uppgift från nedre högra hörnet
Skriv någon text
@@ -55,6 +56,7 @@
Ett fel uppstod när uppgiften skulle tas bort
Uppgiften har raderats
Uppgiftslistan är tom.
+ Uppgiftslistan är tom. Kontrollera konfigurationen för assistentappen.
Det går inte att hämta uppgiftslistan, kontrollera din internetanslutning.
Ta bort uppgift
Uppgiftens resultat är inte redo än.
@@ -93,12 +95,19 @@
%1$s har inte stöd för multipla konton
Kunde inte upprätta anslutning
Avbryt inloggning
+ Ange en giltig serveradress.
+ Kunde inte hämta inloggningsuppgifter. Försök igen.
Det uppstod ett problem när din inloggningsförfrågan behandlades. Försök igen senare.
+ Ingen webbläsare finns tillgänglig för att öppna denna länk.
Slutför inloggningsprocessen i din webbläsare
+ Automatisk uppladdning är pausad eftersom batterisparläge är aktiverat.
behålls i originalmappen, eftersom den är skrivskyddad
+ Låg batterinivå, uppladdningen kan ta längre tid
Ladda bara upp via obelastad Wi-Fi
/AutomatiskUppladdning
Denna mapp ingår redan i synkroniseringen av den överordnade mappen, vilket kan orsaka dubbla uppladdningar
+ Väntar på Wi-Fi för att starta uppladdning
+ Laddar upp filer från %s till %s
Konfigurera
Skapa en ny anpassad mappkonfiguration
Skapa en anpassad mapp
@@ -202,6 +211,7 @@
Importen kunde inte startas. Försök igen
Ingen fil funnen
Vi kunde inte hitta din senaste backup!
+ Upptäcker innehållsförändringar
Kopierat till urklipp
Ett fel uppstod, kunde inte kopiera filen eller mappen
Det är inte möjligt att kopiera mappen till en av dess undermappar
@@ -407,6 +417,8 @@
Inga sökresultat funna på din sökning
Starta din sökning
Skriv i sökfältet ovan för att hitta filer, kontakter, kalenderhändelser och mer i ditt konto.
+ Kontrollera din internetanslutning eller försök igen senare
+ Dålig anslutning
mapp
LIVE
Läser in…
@@ -471,6 +483,11 @@
Mapp finns redan
Denna mapp visas bäst i %1$s.
Skapa
+ %1$d av %2$d · %3$s
+ Ett fel uppstod under synkronisering av mappen %s
+ Otillräckligt diskutrymme, synkronisering avbruten
+ Mappen %s har synkroniserats
+ Synkroniserar...
Inga mappar här
Mappnamnet får inte vara tomt
Välj
@@ -558,7 +575,6 @@
Rensa data
Inställningar, databas och servercertifikat från %1$s\'s data kommer att raderas permanent. \n\nHämtade filer kommer inte att raderas eller ändras.\n\nDenna process kan ta ett tag.
Hantera utrymme
- Du har nått den maximala filuppladdningsgränsen. Ladda upp färre än 500 filer åt gången.
Media-filen kan inte strömmas
Kunde inte läsa mediafilen
Mediafilen har felaktig kodning
@@ -607,6 +623,8 @@
Misslyckades utföra åtgärd.
Visa aviseringar för att interagera resultatet av bakgrundsoperationer
Bakgrundsoperationer
+ Upptäcker lokala filändringar
+ Innehållsövervakare
Visar hämtningsförloppet
Nerladdningar
Visar filsynkroniserings förlopp och resultat
@@ -647,6 +665,7 @@
Skriv in ditt lösenord
Koden kommer efterfrågas varje gång du startar appen
Vänligen ange ditt lösenord
+ Lösenkoden begärs varje gång appen öppnas eller öppnas igen efter 5 sekunder
Koderna matchar inte
Vänligen ange ditt lösenord igen
Ta bort ditt lösenord
@@ -662,13 +681,6 @@
Ingen app hittades för att ställa in en bild med
Fäst på hemskärmen
Öppna %1$s
- .txt
- 389 KB
- platshållare
- 12:23:45
- Nyligen redigerade
- Detta är en platshållare
- 2012/05/18 12:23 PM
stopp
växla
Välj en server...
@@ -690,6 +702,7 @@
Om
Detaljer
Dev
+ Filer
Allmänt
Mer
Synkronisera
@@ -882,6 +895,8 @@
Registrera hos en leverantör
Tillåt %1$s att komma åt ditt Nextcloud-konto %2$s?
Sortera efter
+ Sortera favoriter först
+ Sortera mappar före filer
Göm
Detaljer
Webbplatsens identitet kunde inte verifieras
@@ -992,6 +1007,7 @@
Händelsen hittades inte, du kan alltid synkronisera för att uppdatera. Omdirigerar till webben...
Kontakten hittades inte, du kan alltid synkronisera för att uppdatera. Omdirigerar till webben...
Behörigheter krävs för att öppna sökresultatet, annars omdirigeras det till webben...
+ I denna mapp
Okänd
Lås upp fil
Olästa kommentarer finns
@@ -1099,6 +1115,8 @@
Otillförlitligt servercertifikat
Hämtar serverversion
App avslutad
+ Överhoppad
+ En fil med samma namn finns redan.
Slutförande
Samma fil hittades på server, hoppar över uppladdning.
Okänt fel
@@ -1192,6 +1210,10 @@
- %d fil kommer exporteras. Se avisering för detaljer.
- %d filer kommer exporteras. Se avisering för detaljer.
+
+ - Du kan bara ladda upp %d fil åt gången.
+ - Du kan ladda upp max %d iler samtidigt.
+
- %1$d mapp
- %1$d mappar
diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml
index 79759d3..1c5561a 100644
--- a/app/src/main/res/values-sw/strings.xml
+++ b/app/src/main/res/values-sw/strings.xml
@@ -44,6 +44,7 @@
Inaonyesha wijeti moja kutoka kwa dashibodi
Tafuta katika %s
Tokea nje ya mtandao
+ Maudhui haya yalitolewa na AI na yanaweza kufanya makosa.
Ongeza jukumu jipya
Ongeza jukumu jipya kutoka chini kulia
Andika maandishi kadhaa
@@ -55,6 +56,7 @@
Hitilafu ilitokea wakati wa kufuta jukumu
Jukumu limefutwa kwa mafanikio
Orodha ya majukumu ni tupu.
+ Orodha ya majukumu iko tupu. Angalia usanidi wa programu ya Mratibu.
Imeshindwa kuleta orodha ya kazi, tafadhali angalia muunganisho wako wa mtandao
Futa jukumu
Toleo la kazi bado halijawa tayari.
@@ -93,12 +95,19 @@
%1$s haitumii akaunti nyingi
Haikuweza kuanzisha muunganisho
Ghairi uingiaji
+ Tafadhali weka anwani sahihi ya seva.
+ Imeshindwa kuleta maelezo ya kuingia. Tafadhali jaribu tena.
Kulikuwa na tatizo la kuchakata ombi lako la kuingia. Tafadhali jaribu tena baadaye.
+ Hakuna kivinjari kinachopatikana ili kufungua kiungo hiki.
Tafadhali kamilisha mchakato wa kuingia katika kivinjari chako
+ Upakiaji otomatiki umesitishwa kwa sababu Kiokoa Betri kimewashwa.
iliyohifadhiwa kwenye folda asili, kwani inasomwa tu
+ Betri iko chini, upakiaji unaweza kuchukua muda mrefu
Pakia kwenye Wi-Fi isiyopimwa pekee
/Pakia Kiotomatiki
Folda hii tayari imejumuishwa katika usawazishaji wa folda kuu, ambayo inaweza kusababisha upakiaji unaorudiwa
+ Inasubiri Wi-Fi kuanza kupakia
+ Inapakia faili kutoka %s hadi %s
Sanidi
Unda usanidi mpya wa folda maalum
Sanidi folda maalum
@@ -147,7 +156,7 @@
Hitilafu isiyojulikana
Ondoa ushirikishaji huu
Inapakia
- Next
+ Ijayo
Hapana
Sasa
SAWA
@@ -202,6 +211,7 @@
Imeshindwa kuanza kuleta. Tafadhali jaribu tena
Hakuna faili iliyopatikana
Haikuweza kupata nakala yako ya mwisho!
+ Inagundua mabadiliko ya maudhui
Nakili katika ubao wa kunakili
Hitilafu ilitokea wakati wa kujaribu kunakili faili au folda hii
Haiwezekani kunakili folda kwenye mojawapo ya folda zake za msingi
@@ -407,6 +417,8 @@
Hakuna matokeo yaliyopatikana kwa swali lako
Anza utafutaji wako
Andika upau wa kutafutia hapo juu ili kupata faili, anwani, matukio ya kalenda na mengine kwenye akaunti yako.
+ Angalia muunganisho wako wa mtandao au ujaribu tena baadaye
+ Muunganisho hafifu
folda
MUBASHARA
Inapakia
@@ -471,6 +483,11 @@
Folda tayari ipo
Folda hii inatazamwa vyema ndani %1$s.
Tengeneza
+ %1$d of %2$d · %3$s
+ Hitilafu ilitokea wakati wa ulandanishi wa folda ya %s
+ Nafasi ya diski haitoshi, maingiliano yameghairiwa
+ Folda %s imesawazishwa
+ Inasawazisha...
Hakuna folda hapa
Jina la folda haliwezi kuwa tupu
Chagua
@@ -558,7 +575,6 @@
Futa data
Mipangilio, hifadhidata na vyeti vya seva kutoka kwa data ya %1$s vitafutwa kabisa. \n\nFaili zilizopakuliwa zitawekwa bila kuguswa.\n\nMchakato huu unaweza kuchukua muda.
Dhibiti nafasi
- Umefikia kikomo cha juu zaidi cha kupakia faili. Tafadhali pakia chini ya faili 500 kwa wakati mmoja.
Faili ya midia haiwezi kutiririshwa
Haikuweza kusoma faili ya midia
Faili ya midia ina usimbaji usio sahihi
@@ -607,6 +623,8 @@
Imeshindwa kutekeleza kitendo.
Onyesha arifa ili kuingiliana na matokeo ya shughuli za usuli
Shughuli za usuli
+ Hutambua mabadiliko ya faili za ndani
+ Mtazamaji wa maudhui
Inaonyesha maendeleo ya upakuaji
Vipakuliwa
Inaonyesha maendeleo ya usawazishaji wa faili na matokeo
@@ -647,6 +665,7 @@
Weka nambari yako ya siri
Nambari ya siri itaombwa kila wakati programu inapoanzishwa
Tafadhali weka nenosiri lako
+ Nambari ya siri itaombwa kila wakati programu inapofunguliwa au kufunguliwa tena baada ya sekunde 5.
Nambari za siri hazifanani
Tafadhali ingiza tena nambari yako ya siri
Futa nambari yako ya siri
@@ -662,13 +681,6 @@
Hakuna programu iliyopatikana ya kuweka picha nayo
Bandika kwenye skrini ya nyumbani
Fungua %1$s
- .txt
- 389 KB
- kishika nafasi
- 12:23:45
- Imehaririwa hivi karibuni
- Hiki ni kishikilia nafasi
- 2012/05/18 12:23 PM
acha
geuza
Tafadhali chagua seva...
@@ -690,6 +702,7 @@
Kuhusu
Maelezo ya kina
Dev
+ Faili
Kuu
Zaidi
Sawazisha
@@ -844,7 +857,7 @@
Ingiza nenosiri
Shiriki ya kiungo (%1$s)
Panga tarehe ya mwisho wa matumizi
- Set password
+ Weka nenosiri
Kushiriki upya hakuruhusiwi wakati wa kudondosha faili salama
Tafadhali chagua angalau chaguo moja la kushiriki kabla ya kuendelea.
Inaweza kuhariri
@@ -881,6 +894,8 @@
Jisajili na mtoa huduma
Ruhusu %1$s kufikia akaunti yako ya Nextcloud %2$s?
Panga kwa
+ Chagua za upendeleo kwanza
+ Chagua vikasha kabla ya mafaili
Ficha
Maelezo ya kina
Utambulisho wa seva haukuweza kuthibitishwa
@@ -991,6 +1006,7 @@
Tukio halijapatikana, unaweza kusawazisha kila wakati ili kusasisha. Inaelekeza kwenye wavuti...
Anwani haijapatikana, unaweza kusawazisha kila wakati ili kusasisha. Inaelekeza kwenye wavuti...
Ruhusa zinahitajika ili kufungua matokeo ya utafutaji vinginevyo yataelekezwa kwenye wavuti...
+ Katika folda hii
Haijulikani
Fungua faili
Kuna maoni ambayo hayajasomwa
@@ -1098,6 +1114,8 @@
Cheti cha seva isiyoaminika
Inaleta toleo la seva...
Programu imekatishwa
+ Imerukwa
+ Faili yenye jina sawa sawa tayari ipo.
Imekamilika
Faili sawa imepatikana kwenye kidhibiti cha mbali, ikiruka upakiaji
Hitilafu isiyojulikana
@@ -1191,6 +1209,10 @@
- %d file will be exported. See notification for details.
- %d faili zitahamishwa. Tazama arifa kwa maelezo.
+
+ - You can upload only %d file at once.
+ - Unaweza kupakia hadi faili %d mara moja.
+
- %1$d folder
- %1$d folda
diff --git a/app/src/main/res/values-th-rTH/strings.xml b/app/src/main/res/values-th-rTH/strings.xml
index 025a5fb..1d98ed5 100644
--- a/app/src/main/res/values-th-rTH/strings.xml
+++ b/app/src/main/res/values-th-rTH/strings.xml
@@ -448,11 +448,6 @@
กรุณากรอกรหัสผ่านของคุณใหม่อีกครั้ง
รหัสยืนยันที่เก็บไว้
รหัสยืนยันไม่ถูกต้อง
- 389 KB
- 12:23:45
- แก้ไขล่าสุด
- นี่คือ placeholder
- 2012/05/18 12:23 PM
เก็บไว้ในโฟลเดอร์ต้นฉบับ
ถูกย้ายไปยังโฟลเดอร์แอพพลิเคชัน
เพิ่มบัญชี
diff --git a/app/src/main/res/values-tk/strings.xml b/app/src/main/res/values-tk/strings.xml
index 1471a16..79123de 100644
--- a/app/src/main/res/values-tk/strings.xml
+++ b/app/src/main/res/values-tk/strings.xml
@@ -427,10 +427,6 @@
inkär et
Faýllary ýüklemek we göçürip almak üçin goşmaça rugsatlar gerek.
Surat goýmak üçin hiç bir programma tapylmady
- 389 KB
- 12:23:45
- Bu bir ýer eýesi
- 2012/05/18 12:23
Dur
üýtgetmek
Kuwwat tygşytlaýyş barlagyny öçürmek, batareýanyň pes ýagdaýynda faýl ýüklemegine sebäp bolup biler!
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 063a438..881041e 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -93,12 +93,19 @@
%1$s birden çok hesabı desteklemiyor
Bağlantı kurulamadı
Oturum açmaktan vazgeç
+ Lütfen geçerli bir sunucu adresi yazın.
+ Oturum açma bilgileri alınamadı. Lütfen yeniden deneyin.
Oturum açma isteğiniz işlenirken bir sorun çıktı. Lütfen bir süre sonra yeniden deneyin.
+ Bu bağlantıyı açabilecek bir tarayıcı bulunamadı.
Lütfen oturum açma işlemini tarayıcınızdan tamamlayın
+ Pil koruma modu açık olduğundan otomatik yükleme duraklatıldı
salt okunur olduğundan özgün klasörde kaldı
+ Pil azaldı. Yükleme işlemi uzun sürebilir
Yalnızca kotasız kablosuz ağda yüklensin
/OtomatikYükleme
Bu klasör, üst klasörün eşitlemesine zaten katılmış olduğundan yinelenen yüklemelere neden olabilir.
+ Yüklemenin başlatılması için Wi-Fi bağlantısı bekleniyor
+ %s üzerindeki dosyalar %s üzerine yükleniyor
Yapılandır
Özel klasör kurulumu ekle
Bir özel klasör kurun
@@ -202,6 +209,7 @@
İçe aktarım başlatılamadı. Lütfen yeniden deneyin
Herhangi bir dosya bulunamadı
Son yedeğiniz bulunamadı!
+ İçerik değişiklikleri denetleniyor
Panoya kopyalandı
Bu dosya ya da klasör kopyalanmaya çalışılırken bir sorun çıktı
Bir klasör kendi alt klasörü olarak kopyalanamaz
@@ -471,6 +479,11 @@
Klasör zaten var
Bu klasör en iyi %1$s ile görüntülenir.
Ekle
+ %1$d / %2$d · %3$s
+ %s klasörü eşitlenirken bir sorun çıktı
+ Disk alanı yetersiz. Eşitleme iptal edildi
+ %s klasörü eşitlendi
+ Eşitleniyor…
Burada herhangi bir klasör yok
Klasör adı boş olamaz
Seçin
@@ -558,7 +571,6 @@
Verileri temizle
%1$s verilerinden ayarlar, veri tabanı ve sunucu sertifikaları kalıcı olarak silinecek. \n\nİndirilen dosyalara dokunulmayacak.\n\nBu işlem biraz zaman alabilir.
Alan yönetimi
- Yüklenebilecek dosya sayısı sınırına ulaştınız. Lütfen bir seferde 500 taneden az dosya yükleyin.
Ortam dosyası akışı sağlanamadı
Ortam dosyası okunamadı
Ortam dosyası doğru şekilde kodlanmamış
@@ -607,6 +619,8 @@
İşlem yapılamadı.
Arka plan işlemlerinin sonucuyla etkileşim kurmak için bildirimleri görüntüle
Arka plan işlemleri
+ Yerel dosya değişikliklerini algılar
+ İçerik izleyici
İndirme ilerlemesini görüntüler
İndirmeler
Dosya eşitleme ilerlemesi ve sonuçlarını görüntüler
@@ -662,13 +676,6 @@
Görselin ayarlanabileceği bir uygulama bulunamadı
Ana sayfaya sabitle
%1$s aç
- .txt
- 389 KB
- yerbelirtici
- 12:23:45
- Son düzenlenenler
- Bu bir yer belirleyicidir
- 2012/05/18 12:23 ÖS
durdur
değiştir
Lütfen bir sunucu seçin…
@@ -690,6 +697,7 @@
Hakkında
Ayrıntılar
Geliştirici
+ Dosyalar
Genel
Daha fazla
Eşitle
@@ -881,6 +889,8 @@
Hizmet sağlayıcı ile hesap aç
%1$s, %2$s Nextcloud hesabınıza erişebilsin mi?
Sıralama
+ Sık kullanılanlar üstte sıralansın
+ Klasörler dosyaların üzerinde sıralansın
Gizle
Ayrıntılar
Sunucunun kimliği doğrulanamadı
@@ -991,6 +1001,7 @@
Etkinlik bulunamadı, güncellemek için eşitleyebilirsiniz. İnternet üzerine yönlendiriliyor…
Kişi bulunamadı, güncellemek için eşitleyebilirsiniz. İnternet üzerine yönlendiriliyor…
Arama sonuçlarını açmak için izinler gereklidir. Yoksa İnternet üzerine yönlendirilir…
+ Bu klasör içindeki
Bilinmiyor
Dosyanın kilidini aç
Okunmamış yorumlar var
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index 235c3c9..1f46d85 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -337,6 +337,7 @@
يۆتكەش
چۈشۈرۈش
يۈكلەش
+ سىرتقى ھەمبەھىر
قوشۇش ياكى يوللاش
باشقۇرغۇچىنى چۈشۈرۈش ئۈچۈن ھۆججەت يوللىيالمىدى
ھۆججەت بېسىش مەغلۇب بولدى
@@ -471,6 +472,7 @@
مەۋجۇت ھۆججەتلەرنىمۇ يۈكلەڭ
توك قاچىلىغاندا ئاندىن يۈكلەڭ
/ InstantUpload
+ ئىچكى ھەمبەھىر
ئىچكى ئىككى خىل ماسقەدەملەش
تېخى ئەمەس ، ئۇزۇن ئۆتمەي ماسقەدەملىنىدۇ
شىفىرلانغان ھۆججەت قىسقۇچنى ئورنىتىش ئۈچۈن تور ئۇلىنىشى تەلەپ قىلىنىدۇ
@@ -509,7 +511,6 @@
سانلىق مەلۇماتنى تازىلاش
%1$s نىڭ سانلىق مەلۇماتلىرىدىن تەڭشەك ، ساندان ۋە مۇلازىمېتىر گۇۋاھنامىسى مەڭگۈلۈك ئۆچۈرۈلىدۇ. \ n \ n چۈشۈرۈلگەن ھۆججەتلەر تۇتۇلمايدۇ. \ n \ n بۇ جەريانغا بىر ئاز ۋاقىت كېتىدۇ.
بوشلۇقنى باشقۇرۇش
- ھۆججەت يوللاشنىڭ ئەڭ يۇقىرى چېكىگە يەتتىڭىز. بىر قېتىمدا 500 دىن ئاز ھۆججەت يوللاڭ.
مېدىيا ھۆججىتىنى ئاقلاشقا بولمايدۇ
مېدىيا ھۆججىتىنى ئوقۇيالمىدى
مېدىيا ھۆججىتىدە كودلاش خاتا
@@ -598,12 +599,6 @@
ھۆججەتلەرنى يوللاش ۋە چۈشۈرۈش ئۈچۈن قوشۇمچە ئىجازەتلەر.
رەسىم ئورنىتىدىغان ھېچقانداق دېتال تېپىلمىدى
%1$s نى ئېچىڭ
- .txt
- 389 KB
- 12:23:45
- يېقىندا تەھرىرلەندى
- بۇ يەر ئىگىسى
- 2012/05/18 12:23 PM
توختا
toggle
مۇلازىمېتىرنى تاللاڭ…
@@ -624,6 +619,7 @@
ھەققىدە
تەپسىلاتى
Dev
+ ھۆججەتلەر
ئادەتتىكى
تېخىمۇ كۆپ
ماسقەدەملەش
@@ -794,6 +790,8 @@
تەمىنلىگۈچى بىلەن تىزىملىتىڭ
%1$s نىڭ Nextcloud ھېساباتىڭىزنى زىيارەت قىلىشىغا يول قويۇڭ%2$s?
تەرتىپلەش
+ ياقتۇرىدىغانلارنى رەتلەڭ
+ ھۆججەتلەرنى ھۆججەتتىن بۇرۇن تەرتىپلەڭ
يوشۇر
تەپسىلاتى
مۇلازىمېتىرنىڭ سالاھىيىتىنى دەلىللىگىلى بولمىدى
@@ -1033,6 +1031,22 @@
ئېلېكترونلۇق خەت ئەۋەتىڭ
سانلىق مەلۇمات ساقلاش قىسقۇچى مەۋجۇت ئەمەس!
بۇ بەلكىم باشقا ئۈسكۈنىدە زاپاسلاشنى ئەسلىگە كەلتۈرگەن بولۇشى مۇمكىن. سۈكۈتكە قايتىش. سانلىق مەلۇمات ساقلاش قىسقۇچىنى تەڭشەش ئۈچۈن تەڭشەكلەرنى تەكشۈرۈڭ.
+
+ - %d سائەت
+ - %d سائەت
+
+
+ - %d مىنۇت
+ - %d مىنۇت
+
+
+ - %d مىنۇت ئىلگىرى
+ - %d مىنۇت ئىلگىرى
+
+
+ - %d سائەت ئىلگىرى
+ - %d سائەت ئىلگىرى
+
- Could not sync %1$d file (conflicts: %2$d)
- Could not sync %1$d files (conflicts: %2$d)
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 0b6fb14..3e5972b 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -93,12 +93,19 @@
%1$s не підтримує одночасно декілька облікових записів
Не вдалося встановити з\'єднання
Скасувати авторизацію
+ Зазначте дійсну адресу сервера
+ Не вдалося отримати дані для входу. Спробуйте ще раз.
Помилка під час обробки запиту на вхід. Спробуйте пізніше.
+ Відсутній бравзер для відкриття цього посилання.
Завершити авторизацію у бравзері
+ Автозавантаження призупинено, оскільки увімкнено заощадження батареї.
залишено у вихідному каталозі, оскільки він доступний лише для читання
+ Низький заряд батареї, завантаження може тривати доше
Завантаження тільки через WiFi
/AutoUpload
Цей каталог вже включено до синхронізації каталогу вищого рівня, що може призвести до задвоєння завантаження
+ Очікується з\'єднання з WiFi для початку завантаження
+ Завантаження файлів з %s до %s
Налаштування
Власний каталог користувача
Каталог користувача
@@ -202,6 +209,7 @@
Не вдалося здійснити імпорт. Спробуйте ще раз
Файл не знайдено
Неможливо знайти вашу останню резервну копію
+ Пошук змін у вмісті
Скопійовано
Виникла помилка під час спроби копіювати цей файл або каталог
Неможливо скопіювати каталог до одного із вкладених підкаталогів
@@ -407,6 +415,8 @@
За вашим запитом нічого не знайдено
Почніть пошук
Зазначте в полі пошуку для пошуку файлів, контактів, календарних подій тощо серед ваших облікових записів.
+ Перевірте з\'єднання з мережею або спробуйте пізніше
+ Слабке з\'єднання
каталог
НАЖИВО
Завантаження…
@@ -471,6 +481,11 @@
Каталог вже існує
Переглядайте каталог у %1$s.
Створити
+ %1$d із %2$d · %3$s
+ Помилка під час синхронізації каталогу %s
+ Недостатньо місця на диску, синхронізацію скасовано
+ %s каталог успішно синхронізовано
+ Синхронізація...
Тут відсутні каталоги
Ім\'я каталогу не може бути порожнім
Вибрати
@@ -558,7 +573,6 @@
Очистити дані
Налаштування, база даних та дані сертифікатів серверу від %1$s буде вилучено без можливості відновлення.\n\nФайли, які було звантажено, буде збережено.\n\nЦей процес триватиме певний час.
Керувати простором
- Досягнуто обмеження на кількість завантаження файлів. За раз можна завантажувати не більше 500 файлів.
Неможливо транслювати мультимедійний файл
Неможливо відкрити мультимедійний файл
Мультимедійний файл має неправильно кодування
@@ -607,6 +621,8 @@
Не вдалося виконати дію.
Показувати сповіщення про результати фонових операцій
Фонові операції
+ Пошук змін у файлі на пристрої
+ Оглядач вмісту
Показує перебіг звантаження
Звантаження
Показує перебіг синхронізації файлів та результати
@@ -662,12 +678,6 @@
Не знайдено застосунків для встановлення зображення для
Закріпити на домівці
Відкрити %1$s
- .txt
- 389 КБ
- 12:23:45
- Нещодавно відредаговано
- Це заповнювач
- 2012/05/18 12:23 PM
стоп
переключитися
Виберіть сервер...
@@ -689,6 +699,7 @@
Про застосунок
Деталі
Розробка
+ Файли
Основне
Більше
Синхронізація
@@ -880,6 +891,8 @@
Зареєструватися у провайдейра
Дозволити %1$s мати доступ до вашого облікового запису у Nextcloud %2$s?
Впорядкувати за
+ Спочатку показувати із зірочкою
+ Показувати каталоги перед файлами
Сховати
Деталі
Неможливо перевірити ідентифікатор сервера.
@@ -990,6 +1003,7 @@
Подію не знайдено. Синхронізуйтеся для оновлення. Переспрямування на вебсайт...
Контакт не знайдено. Синхронізуйтеся для оновлення. Переспрямування на вебсайт...
Для отримання результатів пошуку потрібні окреми дозволи, або перейти на вебсайт...
+ У цьому каталозі
Невідомо
Розблокувати файл
Доступні непрочитані коментарі
@@ -1097,6 +1111,8 @@
Не довірений сертифікат серверу
Отримую версію сервера...
Застосунок зупинено
+ Пропущено
+ Файл з таким саме ім\'ям вже присутній
Завершені
Такий саме файл знайдено у хмарі, пропускаю завантаження
Невідома помилка
@@ -1220,6 +1236,12 @@
- %d файлів буде експортовано. Дивіться сповіщення для деталей.
- %d файлів буде експортовано. Дивіться сповіщення для деталей.
+
+ - Одночасно можна завантажити лише %d файл.
+ - Одночасно можна завантажити до %d файлів.
+ - Одночасно можна завантажити до %d файлів.
+ - Одночасно можна завантажити до %d файлів.
+
- %1$d тека
- %1$d тек
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 1ca1525..16e6f5e 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -493,12 +493,6 @@
Các quyền bổ sung cần thiết để tải lên và tải xuống tệp.
Không tìm thấy ứng dụng nào để đặt ảnh với
Mở trong %1$s
- .txt
- 389 KB
- 12:23:45
- Chỉnh sửa gần đây
- Vị trí này đã được đặt chỗ trước
- 2012/05/18 12:23 PM
dừng
bật/tắt
Việc tắt kiểm tra tiết kiệm năng lượng có thể dẫn đến việc tải tệp lên khi ở trạng thái pin yếu!
@@ -516,6 +510,7 @@
Thông tin
Chi tiết
Phát triển
+ Tệp Tin
Tổng hợp
hơn
Đồng bộ
@@ -651,6 +646,8 @@
Đăng ký với nhà cung cấp
Cho phép %1$s truy cập vào %2$s tài khoản Hệ thống của bạn
Sắp xếp theo
+ Sắp xếp mục yêu thích trước
+ Sắp xếp thư mục trước tập tin
Ẩn
Chi tiết
Không thể xác minh danh tính của máy chủ
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index b4156a0..9f915ab 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -93,12 +93,19 @@
%1$s 不支持多个账号
无法建立连接
取消登录
- 处理你的登录请求时出现问题。请稍后再试。
+ 请输入有效的服务器地址。
+ 无法获取登录详情。请重试。
+ 处理您的登录请求时出现问题。请稍后重试。
+ 没有浏览器可以打开此链接。
请在浏览器中完成登录流程
+ 自动上传已暂停,因为省电模式已开启。
保持为原始的文件夹,即使它是只读的
+ 电池电量低,上传可能需要更长时间
仅通过无流量限制的 Wi-Fi 上传
/自动上传
此文件夹已包含在上级文件夹的同步中,这可能会导致重复上传
+ 正在等待 Wi-Fi 开始上传
+ 正在将文件从 %s 上传到 %s
配置
创建新的自定义文件夹设定
设置一个自定义文件夹
@@ -202,6 +209,7 @@
导入无法开始。请重试
没有文件被发现
无法找到末次备份
+ 正在检测内容更改
复制到剪贴板
尝试复制这个文件或文件夹时发生了错误
将文件夹复制到其自己的底层文件夹中是不可能的
@@ -407,6 +415,8 @@
未找到查询的结果
开始搜索
在上方搜索栏中输入以查找您账号中的文件、联系人、日历事件等。
+ 检查你的网络连接或稍后再试
+ 连接差
文件夹
即时
正在加载...
@@ -471,6 +481,11 @@
目录已经存在
此文件夹最好在 %1$s 中查看。
创建
+ %1$d / %2$d · %3$s
+ 同步 %s 文件夹时出错
+ 磁盘空间不足,同步已取消
+ %s 文件夹已成功同步
+ 正在同步…
这里没有文件夹
文件夹名称不能为空
选择
@@ -558,7 +573,6 @@
清除数据
来自%1$s数据的设置,数据库和服务器证书将被永久删除。\n\n下载的文件将保持不变。\n\n此过程可能需要一段时间。
管理空间
- 你已达到文件上传数量限制,单次请勿超过500个文件。
此媒体文件无法流播
无法读取媒体文件
媒体文件的编码不正确
@@ -607,6 +621,8 @@
执行动作失败。
显示通知以便与后台操作的结果交互
后台操作
+ 检测本地文件更改
+ 内容观察器
显示下载进度
下载
显示文件同步进度和结果
@@ -662,13 +678,6 @@
找不到可以设置图片的应用程序
固定到主屏幕
打开 1%1$s
- .txt
- 389 KB
- 占位符
- 12:23:45
- 最近编辑
- 这是一个占位符
- 2012/05/18 下午12:23
停止
切换
请选择服务器…
@@ -690,6 +699,7 @@
关于
详细信息
开发
+ 文件
常规
更多
同步
@@ -881,6 +891,8 @@
与提供商签约
是否允许%1$s访问您的 Nextcloud 账号%2$s?
排序方式
+ 收藏排序优先
+ 将文件夹排在文件前面
隐藏
详情
无法验证服务器身份
@@ -994,6 +1006,7 @@
未找到活动,你随时可以同步更新。正在重定向至网络…
未找到联系人,你可以随时同步更新。正在重定向至网络…
打开搜索结果需要权限,否则它将被重定向至网页…
+ 在此文件夹中
未知
解锁文件
有未读评论
@@ -1101,6 +1114,8 @@
不受信任的服务器证书
正在获取服务器版本…
应用程序已终止
+ 跳过
+ 相同名称的文件已存在。
已完成
服务端已存在相同的文件,跳过上传
未知错误
@@ -1127,7 +1142,7 @@
有什么新图片
跳过
新建%1$s
- 你什么状态?
+ 您的状态如何?
启用仪表盘应用程序后,小部件仅在 %1$s 25 或更高版本中可用
不可用
正在下载文件…
@@ -1179,6 +1194,9 @@
- %d个文件将被导出。详情见通知。
+
+ - 你一次可以最多上传 %d 个文件。
+
- %1$d个文件夹
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 9225635..2a8372f 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -44,6 +44,7 @@
顯示儀表板中的一個小部件
%s內搜尋
顯示為離線
+ 此內容為人工智能產生,可能有錯。
添加新任務
從右下角創建新任務
輸入一些文字
@@ -55,6 +56,7 @@
刪除任務時發生了錯誤
任務已刪除
任務清單是空的。
+ 任務清單是空的。請檢查小幫手應用程式設定。
無法擷取任務清單,請檢查您的網際網路連線。
刪除任務
任務輸出尚未就緒。
@@ -93,12 +95,19 @@
%1$s 不支援多個帳戶
無法建立連線
取消登入
+ 請輸入有效的伺服器地址。
+ 無法擷取登入詳細信息。請再試一次。
處理您的登入請求時出現問題。請稍後再試。
+ 沒有瀏覽器可以開啟此連結。
請在瀏覽器中完成登入流程
+ 因為省電模式已開啟,因此暫停自動上傳。
以唯讀模式保留在原本的資料夾
+ 低電量,上傳可能會需要較長時間
僅在未計量的Wi-Fi上傳
自動上傳
此資料夾已包含在父資料夾的同步中,這可能會導致重複上傳
+ 等待 Wi-Fi 開始上傳
+ 從 %s 上傳檔案至 %s
設定
新增自訂資料夾
設定自訂資料夾
@@ -202,6 +211,7 @@
匯入無法開始。請再試一次
無檔案
找不到您最新的備份
+ 偵測內容變更
已複製到剪貼板
嘗試複製檔案或資料夾時發生錯誤
無法將資料夾複製到自己的子資料夾中。
@@ -407,6 +417,8 @@
未找到與您的查詢相符的結果
開始搜尋
在上方搜索欄中輸入內容,以在您的帳戶中尋找檔案、聯絡人、日曆活動等。
+ 請檢查您的網際網路連線或稍後再試
+ 連線品質不佳
資料夾
直播
載入中…
@@ -471,6 +483,11 @@
資料夾已存在
此資料夾在 %1$s 中顯示效果最佳。
建立
+ %1$d / %2$d · %3$s
+ 同步 %s 資料夾時發生錯誤
+ 磁碟空間不足,已取消同步
+ 已成功同步 %s 資料夾
+ 同步中 ...
這裡沒有資料夾
資料夾名稱不能為空
選擇
@@ -558,7 +575,6 @@
清除資料
來自%1$s的設定,數據庫與伺服器憑證相關資料將被永久刪除,已下載的檔案將不會變動,此過程需要花費一些時間。
管理空間
- 您已達最大檔案上傳限制。一次上傳僅能上傳少於 500 個檔案。
此媒體檔案無法被串流播放
無法讀取媒體檔案
媒體檔案未被正確的編碼
@@ -607,6 +623,8 @@
無法執行操作。
顯示通知以與背景操作的結果互動
背景操作
+ 偵測近端檔案變更
+ 內容觀察器
顯示下載進度
下載
顯示檔案同步進度和結果
@@ -647,6 +665,7 @@
輸入通行碼
每次應用程式開啟時,都需要輸入通行碼
請輸入通行碼
+ 每次應用程式開啟或 5 秒後重新開啟時,都需要輸入通行碼。
兩個通行碼不相符
再次輸入通行碼
刪除您的通行碼
@@ -662,13 +681,6 @@
沒有找到編輯圖片的應用程式
釘選至主畫面
公開 %1$s
- .txt
- 389 KB
- 佔位字串
- 12:23:45
- 最近編輯
- 這是佔位內容
- 2012/05/18 12:23 PM
停止
切換
請選擇伺服器 ...
@@ -690,6 +702,7 @@
關於
詳細資料
開發版
+ 檔案
一般
更多
同步
@@ -881,6 +894,8 @@
使用第三方登入
允許 %1$s 存取您的 Nextcloud 帳戶 %2$s?
排序方式
+ 先排序最愛
+ 將資料夾在檔案之前排序
隱藏
細節
無法驗證伺服器身分
@@ -991,6 +1006,7 @@
找不到活動,您隨時可以同步更新。正在重新導向至網路 …
找不到聯絡人,您隨時可以同步更新。正在重新導向至網路 …
開啟搜尋結果需要權限,否則其將會重新導向至網路 …
+ 在此資料夾
不詳
解鎖檔案
有未讀留言
@@ -1098,6 +1114,8 @@
不信任的伺服器憑證
取得伺服器的版本中...
App 中斷運作
+ 略過
+ 已存在同名檔案。
完成
遠端找到相同的檔案,跳過上傳。
錯誤不詳
@@ -1176,6 +1194,9 @@
- %d 個檔案將被導出。 詳情見通知。
+
+ - 您一次最多可以上傳 %d 個檔案。
+
- %1$d個資料夾
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index a80fb6d..eb253e7 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -44,6 +44,7 @@
顯示儀表板中的一個小工具
搜尋 %s
顯示為離線
+ 此內容由人工智慧產生,可能會出錯。
新增任務
從右下角建立新任務
輸入一些文字
@@ -55,6 +56,7 @@
刪除工作項目時發生錯誤
已成功刪除工作項目
任務清單為空。
+ 任務清單是空的。請檢查小幫手應用程式設定。
無法擷取工作項目清單,請檢查您的網際網路連線。
刪除工作項目
任務輸出尚未就緒。
@@ -98,10 +100,14 @@
處理您的登入請求時出現問題。請稍後再試。
沒有瀏覽器可以開啟此連結。
請在瀏覽器中完成登入流程
+ 因為省電模式已開啟,因此暫停自動上傳。
以唯讀模式保留在原本的資料夾
+ 低電量,上傳可能會需要較長時間
只在非計量收費的 Wi-Fi 上傳
/AutoUpload
此資料夾已包含在上層資料夾的同步中,這可能會導致重複上傳
+ 等待 Wi-Fi 開始上傳
+ 從 %s 上傳檔案至 %s
設定
新增自訂資料夾
設置自訂資料夾
@@ -205,6 +211,7 @@
匯入無法開始。請再試一次
找不到檔案
找不到您最新的備份!
+ 偵測內容變更
已複製到剪貼簿
嘗試複製此檔案或資料夾時發生錯誤
無法將資料夾複製到自己的子資料夾中
@@ -410,6 +417,8 @@
找不到與您的查詢相符的結果
開始搜尋
在上方搜尋列輸入以在您的帳號中尋找檔案、聯絡人、日曆事件等。
+ 請檢查您的網際網路連線或稍後再試
+ 連線品質不佳
資料夾
即時
正在載入…
@@ -474,6 +483,11 @@
資料夾已經存在
此資料夾最好在 %1$s 中檢視。
建立
+ %1$d / %2$d · %3$s
+ 同步 %s 資料夾時發生錯誤
+ 磁碟空間不足,已取消同步
+ 已成功同步 %s 資料夾
+ 正在同步……
此處無資料夾
資料夾名稱不能為空
選擇
@@ -561,7 +575,6 @@
清除資料
來自 %1$s 的設定,資料庫與伺服器憑證等相關資料將永久刪除。\n\n已下載的檔案將保留不會變動。\n\n此過程需要一點時間。
管理空間
- 您已達最大檔案上傳限制。一次上傳僅能上傳少於 500 個檔案。
此媒體檔案無法以串流播放
無法讀取媒體檔案
媒體檔案編碼不正確
@@ -610,6 +623,8 @@
執行動作失敗。
顯示通知以與背景操作的結果互動
背景操作
+ 偵測本機檔案變更
+ 內容觀察器
顯示下載進度
下載
顯示檔案同步進度和結果
@@ -650,6 +665,7 @@
請輸入通行碼
每次應用程式開啟時,都需要輸入通行碼
請輸入通行碼
+ 每次應用程式開啟或 5 秒後重新開啟時,都需要輸入通行碼。
兩個通行碼不相符
再次輸入通行碼
刪除您的通行碼
@@ -665,13 +681,6 @@
沒有找到圖片可設定的應用程式
釘選至主畫面
開啟 %1$s
- .txt
- 389 KB
- 佔位字串
- 12:23:45
- 最近編輯
- 這是佔位內容
- 2012/05/18 12:23 PM
停止
切換
請選取伺服器……
@@ -693,6 +702,7 @@
關於
詳細資料
開發版
+ 檔案
一般
更多
同步
@@ -884,6 +894,8 @@
向提供者註冊
允許 %1$s 存取您的 Nextcloud 帳號 %2$s?
排序依照
+ 先排序喜愛
+ 將資料夾排序在檔案前
隱藏
詳細資訊
無法驗證伺服器身份
@@ -994,6 +1006,7 @@
找不到活動,您隨時可以同步更新。正在重新導向至網路……
找不到聯絡人,您隨時可以同步更新。正在重新導向至網路……
開啟搜尋結果需要權限,否則其將會重新導向至網路……
+ 在此資料夾
未知
解鎖檔案
有未讀留言
@@ -1101,6 +1114,8 @@
不信任的伺服器憑證
正在擷取伺服器版本…
應用程式已終止
+ 略過
+ 已有相同名稱的檔案。
已完成
遠端找到相同的檔案,略過上傳
未知的錯誤
@@ -1179,6 +1194,9 @@
- 將匯出 %d 個檔案。請查看通知了解更多資訊。
+
+ - 您一次可以上傳最多 %d 個檔案。
+
- %1$d 個資料夾
diff --git a/app/src/main/res/values/dims.xml b/app/src/main/res/values/dims.xml
index d320c92..1394d25 100644
--- a/app/src/main/res/values/dims.xml
+++ b/app/src/main/res/values/dims.xml
@@ -29,14 +29,18 @@
32dp
8dp
4dp
+ 32dp
2dp
16dp
32dp
+ 64dp
8dp
100dp
100dp
4dp
+ 14dp
+
12sp
20dp
10dp
@@ -74,7 +78,6 @@
10dp
5dp
10dp
- 10dp
5dp
55dp
32dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bc542e1..ec872c1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,8 +10,8 @@
Favorites
Assistant
Media
-
-
+ Poor connection
+ Check your internet connection or try again later
%1$s Android app
version %1$s
version %1$s, build #%2$s
@@ -56,7 +56,7 @@
Unable to fetch task types, please check your internet connection.
Unable to fetch task list, please check your internet connection.
- Task list is empty.
+ Task list is empty. Check assistant app configuration.
Assistant
Loading task list…
@@ -78,6 +78,8 @@
Input
Output
+ This content was generated by AI and can make mistakes.
+
Recommended files
Assistant
@@ -172,7 +174,6 @@
File selected for upload not found. Please check whether the file exists.
Could not copy file to a temporary folder. Try to resend it.
Upload option:
- You have reached the maximum file upload limit. Please upload fewer than 500 files at a time.
%s already exists, no conflict detected
Move file to %1$s folder
Keep file in source folder
@@ -237,6 +238,12 @@
%1$d / %2$d - %3$s
+ Syncing…
+ %s folder successfully synchronized
+ An error occurred during synchronization of the %s folder
+ %1$d of %2$d · %3$s
+ Insufficient disk space, synchronization canceled
+
Uploading…
%1$d%% Uploading %2$s
%1$s uploaded
@@ -250,6 +257,9 @@
Cancelled
Uploaded
Completed
+ Skipped
+ A file with the same name already exists.
+
Same file found on remote, skipping upload
Cancelled
Connection error
@@ -328,6 +338,10 @@
- %d file will be exported. See notification for details.
- %d files will be exported. See notification for details.
+
+ - You can upload only %d file at once.
+ - You can upload up to %d files at once.
+
As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded with earlier versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and delete the link to %3$s, or move the file(s) into the %1$s folder and retain the link to %4$s.\n\nListed below are the local file(s), and the remote file(s) in %5$s they were linked to.
The folder %1$s does not exist anymore
Move all
@@ -341,7 +355,7 @@
Credentials disabled
Enter your passcode
- The passcode will be requested every time the app is started
+ The passcode will be requested every time the app is opened or reopened after 5 seconds.
Please reenter your passcode
Delete your passcode
The passcodes are not the same
@@ -451,13 +465,69 @@
The certificate could not be shown.
- No information about the error
- This is a placeholder
- placeholder
- Recently edited
- .txt
- 389 KB
- 2012/05/18 12:23 PM
- 12:23:45
+ 2012/05/18 12:23 PM
+ 12:23:45
+ This is a placeholder
+ Filename
+ Recently edited
+ .txt
+ 389 KB
+ 5 MB
+ 3 MB
+ Current (2)
+ Send button
+ Firstname Lastname
+ Filetype
+ Download
+ Internal share link only works for users with access to this folder
+ Internal share link only works for users with access to this folder
+ All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe.
+ No internet connection
+ ☁️ My custom status
+ https://nextcloud.localhost/nextcloud
+ firstname@example.nextcloud.com
+ 12. Dec 2020 - 23:10:20
+ 10. Dec 2020 - 10:10:10
+ Grant Nextcloud News access to your Nextcloud account incrediblyLong_username_with_123456789_number@Nextcloud_dummy.com?
+ +49 123 456 789 12
+ d7edb387-0b61-4e4e-a728-ffab3055d700
+ Job name
+ ENQUEUED
+ 2020-02-15T20:53:15Z
+ 50%
+ 0
+ 5 downloads remaining
+ View only
+ 6 min ago
+ Success
+ /abc/example
+ Delete file
+ Some additional action info
+ Open in Notes
+ This folder is best viewed in the Notes app
+ 2025
+ October
+ For /storage/emulated/0/DCIM/Camera
+ 📆
+ In a meeting
+ an hour
+ Wednesday • 26 Jul 2023 • 12:27
+ 12 MP • 3024 × 4032 • 923 KB
+ Camera Phone (4th generation)
+ ƒ/1.8 • 1/374 s • 28 mm • ISO 200
+ Mitte, Berlin, Germany
+ © OpenStreetMap contributors
+ Compose email
+ passphrase
+ Show 3 hidden folders
+ Template
+ First line
+ Subline
+ Search in Nextcloud
+ Files
+ in TestFolder
+ example@nextcloud.com
+ Low battery, upload might take longer
Only upload on unmetered Wi-Fi
Only upload when charging
@@ -465,6 +535,9 @@
/InstantUpload
/AutoUpload
+ Low battery, upload might take longer
+ Waiting for Wi-Fi to start uploading
+
File is currently locked by another user or process and therefore not deletable. Please try again later.
Sorry
@@ -791,6 +864,7 @@
shared
More menu
Type
+
Sync status button
Sync warning button
Settings button
@@ -822,6 +896,9 @@
Offline operations
Shows progress of offline file operations
+ Detecting content changes
+ Content observer
+ Detects local file changes
Account not found!
@@ -878,7 +955,7 @@
Show notifications to interact result of background operations
Show push notifications sent by the server: Mentions in comments, reception of new remote shares, announcements posted by an admin etc.
Send button icon
-
+ Uploading files from %s to %s
*
Name
Password
@@ -1318,6 +1395,7 @@
Not yet, soon to be synced
Google restricted downloading APK/AAB files!
No file or folder matching your search
+ In this folder
Event not found, you can always sync to update. Redirecting to web…
Contact not found, you can always sync to update. Redirecting to web…
Permissions are required to open search result otherwise it will redirected to web…
@@ -1356,6 +1434,7 @@
Create link
This folder is best viewed in %1$s.
Open in %1$s
+ Auto-upload is paused because Battery Saver is on.
This folder is already included in the parent folder’s sync, which may cause duplicate uploads
Sync anyway
Sync duplication
@@ -1383,4 +1462,7 @@
Clear
Set message
Error setting status message!
+ Sort folders before files
+ Sort favorites first
+ Files
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index 51549b9..2a8427f 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -6,13 +6,15 @@
~ SPDX-FileCopyrightText: 2017 Andy Scherzinger
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
-->
-
-
+
+
-
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 63a1902..8ab1f37 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -34,15 +34,28 @@
android:key="synced_folders_configure_folders"/>
+
+
+
+
+
+
-
(relaxed = true)
@Mock
private lateinit var params: WorkerParameters
@@ -99,11 +104,22 @@ class BackgroundJobFactoryTest {
@Mock
private lateinit var syncedFolderProvider: SyncedFolderProvider
+ @Mock
+ private lateinit var db: NextcloudDatabase
+
+ @Mock private lateinit var fileDao: FileDao
+
private lateinit var factory: BackgroundJobFactory
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
+ mockkStatic(MainApp::class)
+ every { MainApp.getAppContext() } returns context
+
+ MockitoAnnotations.openMocks(this)
+
+ whenever(db.fileDao()).thenReturn(fileDao)
+
factory = BackgroundJobFactory(
logger,
preferences,
@@ -111,7 +127,6 @@ class BackgroundJobFactoryTest {
clock,
powerManagementService,
{ backgroundJobManager },
- deviceInfo,
accountManager,
resources,
dataProvider,
@@ -123,7 +138,8 @@ class BackgroundJobFactoryTest {
{ viewThemeUtils },
{ localBroadcastManager },
generatePDFUseCase,
- syncedFolderProvider
+ syncedFolderProvider,
+ db
)
}
diff --git a/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt b/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt
index dd0b241..01ad69c 100644
--- a/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt
+++ b/app/src/test/java/com/nextcloud/client/jobs/ContentObserverWorkTest.kt
@@ -1,6 +1,7 @@
/*
* Nextcloud - Android Client
*
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
@@ -12,6 +13,7 @@ import android.net.Uri
import androidx.work.WorkerParameters
import com.nextcloud.client.device.PowerManagementService
import com.owncloud.android.datamodel.SyncedFolderProvider
+import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -42,9 +44,9 @@ class ContentObserverWorkTest {
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
+ MockitoAnnotations.openMocks(this)
worker = ContentObserverWork(
- appContext = context,
+ context = context,
params = params,
syncedFolderProvider = folderProvider,
powerManagementService = powerManagementService,
@@ -56,70 +58,78 @@ class ContentObserverWorkTest {
@Test
fun job_reschedules_self_after_each_run_unconditionally() {
- // GIVEN
- // nothing to sync
- whenever(params.triggeredContentUris).thenReturn(emptyList())
+ runBlocking {
+ // GIVEN
+ // nothing to sync
+ whenever(params.triggeredContentUris).thenReturn(emptyList())
- // WHEN
- // worker is called
- worker.doWork()
+ // WHEN
+ // worker is called
+ worker.doWork()
- // THEN
- // worker reschedules itself unconditionally
- verify(backgroundJobManager).scheduleContentObserverJob()
+ // THEN
+ // worker reschedules itself unconditionally
+ verify(backgroundJobManager).scheduleContentObserverJob()
+ }
}
@Test
@Ignore("TODO: needs further refactoring")
fun sync_is_triggered() {
- // GIVEN
- // power saving is disabled
- // some folders are configured for syncing
- whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
- whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
+ runBlocking {
+ // GIVEN
+ // power saving is disabled
+ // some folders are configured for syncing
+ whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
+ whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
- // WHEN
- // worker is called
- worker.doWork()
+ // WHEN
+ // worker is called
+ worker.doWork()
- // THEN
- // sync job is scheduled
- // TO DO: verify(backgroundJobManager).sheduleFilesSync() or something like this
+ // THEN
+ // sync job is scheduled
+ // TO DO: verify(backgroundJobManager).sheduleFilesSync() or something like this
+ }
}
@Test
@Ignore("TODO: needs further refactoring")
fun sync_is_not_triggered_under_power_saving_mode() {
- // GIVEN
- // power saving is enabled
- // some folders are configured for syncing
- whenever(powerManagementService.isPowerSavingEnabled).thenReturn(true)
- whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
+ runBlocking {
+ // GIVEN
+ // power saving is enabled
+ // some folders are configured for syncing
+ whenever(powerManagementService.isPowerSavingEnabled).thenReturn(true)
+ whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(1)
- // WHEN
- // worker is called
- worker.doWork()
+ // WHEN
+ // worker is called
+ worker.doWork()
- // THEN
- // sync job is scheduled
- // TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
+ // THEN
+ // sync job is scheduled
+ // TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
+ }
}
@Test
@Ignore("TODO: needs further refactoring")
fun sync_is_not_triggered_if_no_folder_are_synced() {
- // GIVEN
- // power saving is disabled
- // no folders configured for syncing
- whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
- whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(0)
+ runBlocking {
+ // GIVEN
+ // power saving is disabled
+ // no folders configured for syncing
+ whenever(powerManagementService.isPowerSavingEnabled).thenReturn(false)
+ whenever(folderProvider.countEnabledSyncedFolders()).thenReturn(0)
- // WHEN
- // worker is called
- worker.doWork()
+ // WHEN
+ // worker is called
+ worker.doWork()
- // THEN
- // sync job is scheduled
- // TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
+ // THEN
+ // sync job is scheduled
+ // TO DO: verify(backgroundJobManager, never()).sheduleFilesSync() or something like this)
+ }
}
}
diff --git a/app/src/test/java/com/nextcloud/client/utils/FileSortOrderBySizeTests.kt b/app/src/test/java/com/nextcloud/client/utils/FileSortOrderBySizeTests.kt
new file mode 100644
index 0000000..9e3d240
--- /dev/null
+++ b/app/src/test/java/com/nextcloud/client/utils/FileSortOrderBySizeTests.kt
@@ -0,0 +1,165 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.utils
+
+import com.owncloud.android.utils.FileSortOrderBySize
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
+import junit.framework.TestCase.assertTrue
+import org.junit.Test
+import org.junit.Before
+import org.junit.After
+import java.io.File
+
+@Suppress("TooManyFunctions", "MagicNumber")
+class FileSortOrderBySizeTests {
+ private lateinit var tempDir: File
+
+ @Before
+ fun setup() {
+ tempDir = File(System.getProperty("java.io.tmpdir"), "test_sort_${System.currentTimeMillis()}")
+ tempDir.mkdirs()
+ }
+
+ @After
+ fun cleanup() {
+ tempDir.deleteRecursively()
+ }
+
+ @Test
+ fun testSortAscendingWhenGivenFilesWithDifferentSizes() {
+ val smallFile = File(tempDir, "small.txt").apply {
+ writeText("x")
+ }
+ val mediumFile = File(tempDir, "medium.txt").apply {
+ writeText("x".repeat(100))
+ }
+ val largeFile = File(tempDir, "large.txt").apply {
+ writeText("x".repeat(1000))
+ }
+
+ val files = mutableListOf(largeFile, smallFile, mediumFile)
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertEquals("small.txt", sorted[0].name)
+ assertEquals("medium.txt", sorted[1].name)
+ assertEquals("large.txt", sorted[2].name)
+ }
+
+ @Test
+ fun testSortDescendingWhenGivenFilesWithDifferentSizes() {
+ val smallFile = File(tempDir, "small.txt").apply {
+ writeText("a")
+ }
+ val mediumFile = File(tempDir, "medium.txt").apply {
+ writeText("a".repeat(50))
+ }
+ val largeFile = File(tempDir, "large.txt").apply {
+ writeText("a".repeat(500))
+ }
+
+ val files = mutableListOf(smallFile, largeFile, mediumFile)
+ val sortOrder = FileSortOrderBySize("test", false)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertEquals("large.txt", sorted[0].name)
+ assertEquals("medium.txt", sorted[1].name)
+ assertEquals("small.txt", sorted[2].name)
+ }
+
+ @Test
+ fun testFoldersComesFirstWhenGivenMixedFilesAndFolders() {
+ val folder1 = File(tempDir, "folderA").apply { mkdirs() }
+ val folder2 = File(tempDir, "folderB").apply { mkdirs() }
+ val file1 = File(tempDir, "file1.txt").apply { writeText("content") }
+ val file2 = File(tempDir, "file2.txt").apply { writeText("data") }
+
+ val files = mutableListOf(file1, folder1, file2, folder2)
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertTrue(sorted[0].isDirectory)
+ assertTrue(sorted[1].isDirectory)
+ assertFalse(sorted[2].isDirectory)
+ assertFalse(sorted[3].isDirectory)
+ }
+
+ @Test
+ fun testSortByFolderSizeWhenGivenFoldersWithDifferentContent() {
+ val smallFolder = File(tempDir, "smallFolder").apply {
+ mkdirs()
+ File(this, "file.txt").writeText("x")
+ }
+
+ val largeFolder = File(tempDir, "largeFolder").apply {
+ mkdirs()
+ File(this, "file1.txt").writeText("x".repeat(100))
+ File(this, "file2.txt").writeText("x".repeat(100))
+ }
+
+ val files = mutableListOf(largeFolder, smallFolder)
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertEquals("smallFolder", sorted[0].name)
+ assertEquals("largeFolder", sorted[1].name)
+ }
+
+ @Test
+ fun testEmptyListWhenGivenNoFiles() {
+ val files = mutableListOf()
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertTrue(sorted.isEmpty())
+ }
+
+ @Test
+ fun testSingleFileWhenGivenOnlyOneFile() {
+ val file = File(tempDir, "single.txt").apply {
+ writeText("content")
+ }
+
+ val files = mutableListOf(file)
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertEquals(1, sorted.size)
+ assertEquals("single.txt", sorted[0].name)
+ }
+
+ @Test
+ fun testSameOrderWhenGivenFilesWithSameSize() {
+ val file1 = File(tempDir, "file1.txt").apply {
+ writeText("same")
+ }
+ val file2 = File(tempDir, "file2.txt").apply {
+ writeText("same")
+ }
+ val file3 = File(tempDir, "file3.txt").apply {
+ writeText("same")
+ }
+
+ val files = mutableListOf(file1, file2, file3)
+ val sortOrder = FileSortOrderBySize("test", true)
+
+ val sorted = sortOrder.sortLocalFiles(files)
+
+ assertEquals(3, sorted.size)
+
+ // All files have same size, so order should be stable
+ sorted.forEach { assertTrue(it.length() == 4L) }
+ }
+}
diff --git a/app/src/test/java/com/nextcloud/client/utils/FileStorageUtilsTest.kt b/app/src/test/java/com/nextcloud/client/utils/FileStorageUtilsTest.kt
index f014510..56441d2 100644
--- a/app/src/test/java/com/nextcloud/client/utils/FileStorageUtilsTest.kt
+++ b/app/src/test/java/com/nextcloud/client/utils/FileStorageUtilsTest.kt
@@ -304,4 +304,22 @@ class FileStorageUtilsTest {
val result = FileStorageUtils.containsBidiControlCharacters("/Foo%e2%80%aedm.exe")
assertTrue(result)
}
+
+ @Test
+ fun testContainsBidiControlCharactersWhenGivenMalformedEncodedSequenceShouldNotThrowAndReturnFalse() {
+ val result = FileStorageUtils.containsBidiControlCharacters("file%")
+ assertFalse(result)
+ }
+
+ @Test
+ fun testContainsBidiControlCharactersWhenGivenBrokenUrlEncodedPatternShouldHandleGracefully() {
+ val result = FileStorageUtils.containsBidiControlCharacters("file%2")
+ assertFalse(result)
+ }
+
+ @Test
+ fun testContainsBidiControlCharactersWhenGivenMultipleBidiCharactersShouldReturnTrue() {
+ val result = FileStorageUtils.containsBidiControlCharacters("safe\u202Ebad\u202Bname.txt")
+ assertTrue(result)
+ }
}
diff --git a/app/src/test/java/com/nextcloud/client/utils/FolderSizeCalculationTests.kt b/app/src/test/java/com/nextcloud/client/utils/FolderSizeCalculationTests.kt
new file mode 100644
index 0000000..788dcfa
--- /dev/null
+++ b/app/src/test/java/com/nextcloud/client/utils/FolderSizeCalculationTests.kt
@@ -0,0 +1,196 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.utils
+
+import com.owncloud.android.utils.FileStorageUtils
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+
+@Suppress("TooManyFunctions", "MagicNumber")
+class FolderSizeCalculationTests {
+
+ private lateinit var testDir: File
+
+ @Before
+ fun setUp() {
+ testDir = File(System.getProperty("java.io.tmpdir"), "test_folder_${System.currentTimeMillis()}")
+ testDir.mkdirs()
+ }
+
+ @After
+ fun tearDown() {
+ testDir.deleteRecursively()
+ }
+
+ @Test
+ fun testReturnZeroWhenGivenNullDirectory() {
+ val result = FileStorageUtils.getFolderSize(null)
+ assertEquals(0L, result)
+ }
+
+ @Test
+ fun testReturnZeroWhenGivenNonExistentDirectory() {
+ val nonExistent = File(testDir, "does_not_exist")
+ val result = FileStorageUtils.getFolderSize(nonExistent)
+ assertEquals(0L, result)
+ }
+
+ @Test
+ fun testReturnZeroWhenGivenRegularFile() {
+ val file = File(testDir, "regular_file.txt")
+ file.writeText("content")
+
+ val result = FileStorageUtils.getFolderSize(file)
+ assertEquals(0L, result)
+ }
+
+ @Test
+ fun testReturnZeroWhenGivenEmptyDirectory() {
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(0L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeForSingleFile() {
+ val file = File(testDir, "file.txt")
+ file.writeText("12345") // 5 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(5L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeForMultipleFiles() {
+ File(testDir, "file1.txt").writeText("123") // 3 bytes
+ File(testDir, "file2.txt").writeText("12345") // 5 bytes
+ File(testDir, "file3.txt").writeText("1234567") // 7 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(15L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithSubdirectory() {
+ File(testDir, "file1.txt").writeText("123") // 3 bytes
+
+ val subDir = File(testDir, "subdir")
+ subDir.mkdirs()
+ File(subDir, "file2.txt").writeText("12345") // 5 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(8L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithNestedSubdirectories() {
+ File(testDir, "file1.txt").writeText("12") // 2 bytes
+
+ val subDir1 = File(testDir, "subdir1")
+ subDir1.mkdirs()
+ File(subDir1, "file2.txt").writeText("123") // 3 bytes
+
+ val subDir2 = File(subDir1, "subdir2")
+ subDir2.mkdirs()
+ File(subDir2, "file3.txt").writeText("1234") // 4 bytes
+
+ val subDir3 = File(subDir2, "subdir3")
+ subDir3.mkdirs()
+ File(subDir3, "file4.txt").writeText("12345") // 5 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(14L, result) // 2 + 3 + 4 + 5
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithEmptySubdirectories() {
+ File(testDir, "file1.txt").writeText("123") // 3 bytes
+
+ val emptyDir1 = File(testDir, "empty1")
+ emptyDir1.mkdirs()
+
+ val emptyDir2 = File(testDir, "empty2")
+ emptyDir2.mkdirs()
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(3L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithLargeFile() {
+ val file = File(testDir, "large_file.txt")
+ val content = "x".repeat(10000) // 10000 bytes
+ file.writeText(content)
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(10000L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithMixedFilesAndDirectories() {
+ // Root level files
+ File(testDir, "root1.txt").writeText("12") // 2 bytes
+ File(testDir, "root2.txt").writeText("123") // 3 bytes
+
+ // First subdirectory
+ val subDir1 = File(testDir, "sub1")
+ subDir1.mkdirs()
+ File(subDir1, "sub1_file1.txt").writeText("1234") // 4 bytes
+ File(subDir1, "sub1_file2.txt").writeText("12345") // 5 bytes
+
+ // Second subdirectory
+ val subDir2 = File(testDir, "sub2")
+ subDir2.mkdirs()
+ File(subDir2, "sub2_file.txt").writeText("123456") // 6 bytes
+
+ // Nested subdirectory
+ val nestedDir = File(subDir1, "nested")
+ nestedDir.mkdirs()
+ File(nestedDir, "nested_file.txt").writeText("1234567") // 7 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(27L, result) // 2 + 3 + 4 + 5 + 6 + 7
+ }
+
+ @Test
+ fun testReturnZeroForDirectoryWithOnlyEmptySubdirectories() {
+ val sub1 = File(testDir, "sub1")
+ sub1.mkdirs()
+
+ val sub2 = File(testDir, "sub2")
+ sub2.mkdirs()
+
+ val nested = File(sub1, "nested")
+ nested.mkdirs()
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(0L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithSpecialCharactersInFilenames() {
+ File(testDir, "file with spaces.txt").writeText("12") // 2 bytes
+ File(testDir, "file-with-dashes.txt").writeText("123") // 3 bytes
+ File(testDir, "file_with_underscores.txt").writeText("1234") // 4 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(9L, result)
+ }
+
+ @Test
+ fun testReturnCorrectSizeWithEmptyFiles() {
+ File(testDir, "empty1.txt").writeText("") // 0 bytes
+ File(testDir, "empty2.txt").writeText("") // 0 bytes
+ File(testDir, "nonempty.txt").writeText("123") // 3 bytes
+
+ val result = FileStorageUtils.getFolderSize(testDir)
+ assertEquals(3L, result)
+ }
+}
diff --git a/app/src/test/java/com/nextcloud/client/utils/OCFileSortTest.kt b/app/src/test/java/com/nextcloud/client/utils/OCFileSortTest.kt
index da8eaaa..4a37404 100644
--- a/app/src/test/java/com/nextcloud/client/utils/OCFileSortTest.kt
+++ b/app/src/test/java/com/nextcloud/client/utils/OCFileSortTest.kt
@@ -33,7 +33,7 @@ class OCFileSortTest {
fun testFileSortOrder() {
val toSort = getShuffledList()
- FileSortOrder.SORT_A_TO_Z.sortCloudFiles(toSort)
+ FileSortOrder.SORT_A_TO_Z.sortCloudFiles(toSort, true, true)
verifySort(toSort)
}
diff --git a/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt
new file mode 100644
index 0000000..7148892
--- /dev/null
+++ b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt
@@ -0,0 +1,319 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.utils
+
+import com.nextcloud.client.jobs.autoUpload.AutoUploadHelper
+import com.nextcloud.client.preferences.SubFolderRule
+import com.owncloud.android.datamodel.MediaFolderType
+import com.owncloud.android.datamodel.SyncedFolder
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.nio.file.Files
+import java.nio.file.attribute.FileTime
+
+class AutoUploadHelperTest {
+
+ private lateinit var tempDir: File
+ private val helper = AutoUploadHelper()
+ private val accountName = "testAccount"
+
+ @Before
+ fun setup() {
+ tempDir = Files.createTempDirectory("auto_upload_test_").toFile()
+ tempDir.mkdirs()
+ assertTrue("Failed to create temp directory", tempDir.exists())
+ }
+
+ @After
+ fun cleanup() {
+ tempDir.deleteRecursively()
+ }
+
+ private fun createTestFolder(
+ localPath: String = tempDir.absolutePath,
+ excludeHidden: Boolean = false,
+ lastScan: Long = -1L,
+ enabledTimestamp: Long = 0L,
+ alsoUploadExistingFiles: Boolean = true,
+ type: MediaFolderType = MediaFolderType.CUSTOM
+ ): SyncedFolder = SyncedFolder(
+ localPath,
+ "",
+ true,
+ false,
+ alsoUploadExistingFiles,
+ false,
+ accountName,
+ 1,
+ 1,
+ true,
+ enabledTimestamp,
+ type,
+ false,
+ SubFolderRule.YEAR_MONTH,
+ excludeHidden,
+ lastScan
+ )
+
+ @Test
+ fun testInsertCustomFolderProcessedCount() {
+ File(tempDir, "file1.txt").apply {
+ writeText("Hello")
+ assertTrue("File1 should exist", exists())
+ }
+ File(tempDir, "file2.txt").apply {
+ writeText("World")
+ assertTrue("File2 should exist", exists())
+ }
+
+ val folder = createTestFolder(
+ localPath = tempDir.absolutePath,
+ type = MediaFolderType.CUSTOM
+ )
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ assertEquals("Should process 2 files", 2, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderWithHiddenFiles() {
+ File(tempDir, "visible.txt").apply { writeText("Visible") }
+ File(tempDir, ".hidden.txt").apply {
+ writeText("Hidden")
+ }
+
+ val folder = createTestFolder(
+ excludeHidden = true,
+ type = MediaFolderType.CUSTOM
+ )
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ assertTrue("Should process at least 1 file", processedCount >= 1)
+ }
+
+ @Test
+ fun testInsertCustomFolderWithLastScanFilter() {
+ val currentTime = System.currentTimeMillis()
+
+ // Create an old file
+ val oldFile = File(tempDir, "old.txt").apply { writeText("Old") }
+ oldFile.setLastModified(currentTime - 10000) // 10 seconds ago
+
+ // Create a new file
+ val newFile = File(tempDir, "new.txt").apply { writeText("New") }
+ newFile.setLastModified(currentTime)
+
+ val folder = createTestFolder(
+ lastScan = currentTime - 5000, // Last scan was 5 seconds ago
+ type = MediaFolderType.CUSTOM
+ )
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ // Should only process the new file (modified after last scan)
+ assertEquals("Should process only 1 new file", 1, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderNotExisting() {
+ val currentTime = System.currentTimeMillis()
+
+ // old file should not be scanned
+ val oldFile = File(tempDir, "old.txt").apply { writeText("Old") }
+ oldFile.setLastModified(currentTime - 10000)
+
+ val newFile = File(tempDir, "new.txt").apply { writeText("New") }
+ newFile.setLastModified(currentTime)
+
+ // Enabled 5 seconds ago
+ val folder = createTestFolder(
+ enabledTimestamp = currentTime - 5000,
+ type = MediaFolderType.CUSTOM
+ ).apply {
+ lastScanTimestampMs = currentTime
+ }
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ // Should only process files newer than enabledTimestamp
+ assertEquals("Should process only files after enabled timestamp", 1, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderEmpty() {
+ val folder = createTestFolder(type = MediaFolderType.CUSTOM)
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ assertEquals("Empty folder should process 0 files", 0, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderNonExistentPath() {
+ val nonExistentPath = File(tempDir, "does_not_exist").absolutePath
+ val folder = createTestFolder(
+ localPath = nonExistentPath,
+ type = MediaFolderType.CUSTOM
+ )
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ assertEquals("Non-existent folder should return 0", 0, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderWithSubdirectories() {
+ val subDir = File(tempDir, "subdir")
+ subDir.mkdirs()
+
+ File(tempDir, "root.txt").writeText("Root file")
+ File(subDir, "nested.txt").writeText("Nested file")
+
+ val folder = createTestFolder(type = MediaFolderType.CUSTOM)
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ assertEquals("Should process files in root and subdirectories", 2, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderWithHiddenDirectory() {
+ val currentTime = System.currentTimeMillis()
+
+ val hiddenDir = File(tempDir, ".hidden_dir")
+ hiddenDir.mkdirs()
+ File(hiddenDir, "file.txt").writeText("Hidden dir file")
+
+ // Create regular file
+ File(tempDir, "regular.txt").writeText("Regular file")
+
+ val folder = createTestFolder(
+ excludeHidden = true,
+ type = MediaFolderType.CUSTOM
+ ).apply {
+ lastScanTimestampMs = currentTime
+ }
+
+ val processedCount = helper.insertCustomFolderIntoDB(folder, null)
+
+ // Should skip hidden directory and its contents
+ assertEquals("Should only process regular file", 1, processedCount)
+ }
+
+ @Test
+ fun testInsertCustomFolderComplexNestedStructure() {
+ // Root folder: FOLDER_A
+ val folderA = File(tempDir, "FOLDER_A")
+ folderA.mkdirs()
+
+ // Subfolders of FOLDER_A
+ val folderB = File(folderA, "FOLDER_B")
+ folderB.mkdirs()
+ val folderC = File(folderA, "FOLDER_C")
+ folderC.mkdirs()
+
+ // Files in FOLDER_A
+ File(folderA, "FILE_A.txt").writeText("File in A")
+
+ // Subfolders of FOLDER_B
+ val folderD = File(folderB, "FOLDER_D")
+ folderD.mkdirs()
+
+ // Files in FOLDER_B
+ File(folderB, "FILE_B.txt").writeText("File in B")
+
+ // Files in FOLDER_C
+ File(folderC, "FILE_A.txt").writeText("File in C")
+ File(folderC, "FILE_B.txt").writeText("Another file in C")
+
+ // Subfolders of FOLDER_D
+ val folderE = File(folderD, "FOLDER_E")
+ folderE.mkdirs()
+
+ // Files in FOLDER_E
+ File(folderE, "FILE_A.txt").writeText("File in E")
+
+ val syncedFolder = createTestFolder(
+ localPath = folderA.absolutePath,
+ type = MediaFolderType.CUSTOM
+ )
+
+ /*
+ * Expected file count with full paths:
+ * ${tempDir.absolutePath}/FOLDER_A/FILE_A.txt -> 1
+ * ${tempDir.absolutePath}/FOLDER_A/FOLDER_B/FILE_B.txt -> 1
+ * ${tempDir.absolutePath}/FOLDER_A/FOLDER_C/FILE_A.txt -> 1
+ * ${tempDir.absolutePath}/FOLDER_A/FOLDER_C/FILE_B.txt -> 1
+ * ${tempDir.absolutePath}/FOLDER_A/FOLDER_B/FOLDER_D/FOLDER_E/FILE_A.txt -> 1
+ * Total = 5 files
+ */
+ val processedCount = helper.insertCustomFolderIntoDB(syncedFolder, null)
+ assertEquals("Should process all files in complex nested structure", 5, processedCount)
+ }
+
+ @Test
+ fun testAlsoUploadExistingFiles() {
+ val currentTime = System.currentTimeMillis()
+
+ // Old file (created before enabling auto-upload)
+ val oldFile = File(tempDir, "old_file.txt").apply {
+ writeText("Old file")
+ }
+ val oldFilePath = oldFile.toPath()
+ Files.setAttribute(
+ oldFilePath,
+ "creationTime",
+ FileTime.fromMillis(currentTime - 60_000) // 1 minute before enabling
+ )
+
+ // New file (created after enabling auto-upload)
+ val newFile = File(tempDir, "new_file.txt").apply {
+ writeText("New file")
+ }
+ val newFilePath = newFile.toPath()
+ Files.setAttribute(
+ newFilePath,
+ "creationTime",
+ FileTime.fromMillis(currentTime + 1_000) // 1 second after enabling
+ )
+
+ val folderSkipOld = createTestFolder(
+ localPath = tempDir.absolutePath,
+ type = MediaFolderType.CUSTOM,
+ alsoUploadExistingFiles = false
+ ).apply {
+ setEnabled(true, currentTime)
+ }
+
+ val processedCountSkipOld = helper.insertCustomFolderIntoDB(folderSkipOld, null)
+ assertEquals(
+ "When 'also upload existing' is disabled, only new files created after enabling should be processed",
+ 1,
+ processedCountSkipOld
+ )
+
+ val folderUploadAll = createTestFolder(
+ localPath = tempDir.absolutePath,
+ type = MediaFolderType.CUSTOM,
+ alsoUploadExistingFiles = true
+ ).apply {
+ setEnabled(true, currentTime)
+ }
+
+ val processedCountAll = helper.insertCustomFolderIntoDB(folderUploadAll, null)
+ assertEquals(
+ "When 'also upload existing' is enabled, should upload all files",
+ 2,
+ processedCountAll
+ )
+ }
+}
diff --git a/appscan/build.gradle.kts b/appscan/build.gradle.kts
new file mode 100644
index 0000000..5311613
--- /dev/null
+++ b/appscan/build.gradle.kts
@@ -0,0 +1,47 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.jetbrains.kotlin.android)
+}
+
+android {
+ namespace = "com.nextcloud.appscan"
+
+ defaultConfig {
+ minSdk = 27
+ compileSdk = 36
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ lint.targetSdk = 36
+ testOptions.targetSdk = 36
+}
+
+kotlin.compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn")
+}
+
+dependencies {
+ implementation(libs.appcompat)
+ implementation(libs.document.scanning.android.sdk)
+ implementation(libs.ui)
+}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..75f16a6
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,37 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.kotlin.compose) apply false
+ alias(libs.plugins.spotless) apply false
+ alias(libs.plugins.kapt) apply false
+ alias(libs.plugins.ksp) apply false
+ alias(libs.plugins.kotlin.serialization) apply false
+ alias(libs.plugins.kotlin.parcelize) apply false
+ alias(libs.plugins.spotbugs) apply false
+ alias(libs.plugins.detekt) apply false
+ // needed to make renovate run without shot, as shot requires Android SDK
+ // https://github.com/pedrovgs/Shot/issues/300
+ alias(libs.plugins.shot) apply false
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
+
+tasks.register("installGitHooks") {
+ description = "Install git hooks"
+
+ val sourceFolder = "${rootProject.projectDir}/scripts/hooks"
+ val destFolder = "${rootProject.projectDir}/.git/hooks"
+
+ from(sourceFolder) { include("*") }
+ into(destFolder)
+ eachFile { println("${sourceFolder}/${file.path} -> ${destFolder}/${file.path}") }
+}
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 8e76554..eb0f2de 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -210,7 +210,7 @@ end
desc "compute version"
private_lane :androidVersion do
- File.open("../app/build.gradle","r") do |f|
+ File.open("../app/build.gradle.kts","r") do |f|
text = f.read
# everything between Document and Authors
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8c5ed14..bcbefe2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,82 +2,92 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
[versions]
-androidCommonLibraryVersion = "0.28.0"
+androidCommonLibraryVersion = "0.29.0"
androidGifDrawableVersion = "1.2.29"
androidImageCropperVersion = "4.6.0"
+androidLibraryVersion = "8c77f600ac942f6eb4e3c4447143fc949d34acc6"
+androidPluginVersion = '8.13.1'
androidsvgVersion = "1.4"
+androidxMediaVersion = "1.5.1"
androidxTestVersion = "1.7.0"
-annotationsVersion = "3.0.1u2"
annotationVersion = "1.9.1"
+annotationsVersion = "3.0.1u2"
appCompatVersion = "1.7.1"
bcpkixJdk18onVersion = "1.81"
cardviewVersion = "1.0.0"
+checker = "3.21.2"
coilVersion = "2.7.0"
commonsHttpclient = "3.1"
commonsIoVersion = "2.20.0"
+composeBom = "2025.10.01"
conscryptAndroidVersion = "2.5.3"
constraintlayoutVersion = "2.2.1"
coreTestingVersion = "2.2.0"
coreVersion = "0.15.0"
-daggerVersion = "2.57.1"
+daggerVersion = "2.57.2"
+detektGradlePlugin = "1.23.8"
dexopenerVersion = "2.0.5"
disklrucacheVersion = "2.0.2"
+documentScannerVersion = "1.2.3"
emojiGoogleVersion = "0.21.0"
espressoVersion = "3.7.0"
eventbusVersion = "3.3.1"
exifinterfaceVersion = "1.4.1"
ezVcardVersion = "0.12.1"
-fbContribVersion = "7.6.14"
+fbContribVersion = "7.6.15"
findsecbugsPluginVersion = "1.14.0"
-firebaseMessagingVersion = "25.0.0"
+firebaseMessagingVersion = "25.0.1"
flexboxVersion = "3.0.0"
fragmentKtxVersion = "1.8.9"
-glide = "5.0.4"
-gsonVersion = "2.13.1"
+glide = "5.0.5"
+gsonVersion = "2.13.2"
ical4jVersion = "3.2.19"
jackrabbitWebdavVersion = "2.13.5"
+jacoco = "0.8.14"
jsonVersion = "20250517"
junit = "4.13.2"
junitVersion = "1.3.0"
juniversalchardetVersion = "2.5.0"
+kotlin = "2.2.21"
+kotlinxSerializationJson = "1.9.0"
+ksp = "2.3.1"
+leakcanary = "2.14"
legacySupportV4Version = "1.0.0"
libraryVersion = "1.3.0"
-lifecycleViewmodelKtxVersion = "2.9.3"
+lifecycleViewmodelKtxVersion = "2.9.4"
loaderviewlibraryVersion = "3.0.0"
markwonVersion = "4.6.2"
+materialIconsCoreVersion = "1.7.8"
materialVersion = "1.13.0"
media3 = "1.8.0"
mockitoKotlinVersion = "4.1.0"
mockitoVersion = "4.11.0"
-mockkVersion = "1.14.5"
+mockkVersion = "1.14.6"
nnioVersion = "0.3.1"
+objenesis = "3.4"
orchestratorVersion = "1.6.1"
orgJbundleUtilOsgiWrappedOrgApacheHttpClientVersion = "4.1.2"
osmdroidAndroidVersion = "6.1.20"
photoviewVersion = "2.3.0"
-playServicesBaseVersion = "18.7.2"
+playServicesBaseVersion = "18.9.0"
prismVersion = "2.0.0"
qrcodescannerVersion = "0.1.2.4"
reviewKtxVersion = "2.0.2"
-roomVersion = "2.7.2"
+roomVersion = "2.8.3"
screengrabVersion = "2.1.1"
sectionedRecyclerviewVersion = "0.6.1"
shotVersion = "6.1.0"
+slfj = "1.7.36"
splash-screen = "1.0.1"
-composeBom = "2025.08.01"
-spotbugsGradlePlugin = "6.3.0"
-detektGradlePlugin = "1.23.8"
+spotbugsGradlePlugin = "6.4.5"
spotless = "7.2.1"
stateless4jVersion = "2.6.0"
webkitVersion = "1.14.0"
-workRuntime = "2.10.3"
-
-kotlin = "2.2.20"
-ksp = "2.2.20-2.0.2"
+workRuntime = "2.10.4"
[libraries]
-
# Crypto
+android-library = { module = "com.github.nextcloud:android-library", version.ref = "androidLibraryVersion" }
conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroidVersion" }
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkixJdk18onVersion" }
@@ -85,8 +95,10 @@ bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bc
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompatVersion" }
cardview = { module = "androidx.cardview:cardview", version.ref = "cardviewVersion" }
core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestVersion" }
+document-scanning-android-sdk = { module = "com.github.Hazzatur:Document-Scanning-Android-SDK", version.ref = "documentScannerVersion" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtxVersion" }
exifinterface = { module = "androidx.exifinterface:exifinterface", version.ref = "exifinterfaceVersion" }
+material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCoreVersion" }
webkit = { module = "androidx.webkit:webkit", version.ref = "webkitVersion" }
splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splash-screen" }
sectioned-recyclerview = { module = "com.github.nextcloud-deps:sectioned-recyclerview", version.ref = "sectionedRecyclerviewVersion" }
@@ -102,6 +114,8 @@ coil = { module = "io.coil-kt:coil", version.ref = "coilVersion" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" }
emoji-google = { module = "com.vanniktech:emoji-google", version.ref = "emojiGoogleVersion" }
+# kotlinx
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
# Other
annotation = { module = "androidx.annotation:annotation", version.ref = "annotationVersion" }
@@ -126,20 +140,26 @@ jackrabbit-webdav = { module = "org.apache.jackrabbit:jackrabbit-webdav", versio
json = { module = "org.json:json", version.ref = "jsonVersion" }
juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardetVersion" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
+leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }
nnio = { module = "org.lukhnos:nnio", version.ref = "nnioVersion" }
org-jbundle-util-osgi-wrapped-org-apache-http-client = { module = "org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client", version.ref = "orgJbundleUtilOsgiWrappedOrgApacheHttpClientVersion" }
osmdroid-android = { module = "org.osmdroid:osmdroid-android", version.ref = "osmdroidAndroidVersion" }
+objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" }
play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "playServicesBaseVersion" }
review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtxVersion" }
+slfj = { module = "org.slf4j:jcl-over-slf4j", version.ref = "slfj" }
# Mockito
mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockitoVersion" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoVersion" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlinVersion" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkVersion" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockkVersion" }
# Dagger
dagger = { module = "com.google.dagger:dagger", version.ref = "daggerVersion" }
+dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "daggerVersion" }
+dagger-processor = { module = "com.google.dagger:dagger-android-processor", version.ref = "daggerVersion" }
dagger-android = { module = "com.google.dagger:dagger-android", version.ref = "daggerVersion" }
dagger-android-support = { module = "com.google.dagger:dagger-android-support", version.ref = "daggerVersion" }
@@ -170,6 +190,7 @@ media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
# Room
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomVersion" }
+room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomVersion" }
room-testing = { module = "androidx.room:room-testing", version.ref = "roomVersion" }
# Espresso
@@ -182,6 +203,7 @@ espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "
# Test
junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
+
core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTestingVersion" }
orchestrator = { module = "androidx.test:orchestrator", version.ref = "orchestratorVersion" }
rules = { module = "androidx.test:rules", version.ref = "androidxTestVersion" }
@@ -205,6 +227,7 @@ stateless4j = { module = "com.github.stateless4j:stateless4j", version.ref = "st
syntax-highlight = { module = "io.noties.markwon:syntax-highlight", version.ref = "markwonVersion" }
core = { module = "io.noties.markwon:core", version.ref = "markwonVersion" }
prism4j = { module = "io.noties:prism4j", version.ref = "prismVersion" }
+prism4j-bundler = { module = "io.noties:prism4j-bundler", version.ref = "prismVersion" }
# Nextcloud libraries
ui = { module = "com.github.nextcloud.android-common:ui", version.ref = "androidCommonLibraryVersion" }
@@ -214,8 +237,55 @@ qrcodescanner = { module = "com.github.nextcloud-deps:qrcodescanner", version.re
work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntime" }
+[bundles]
+media3 = ["media3-ui", "media3-session", "media3-exoplayer", "media3-datasource"]
+espresso = ["espresso-core", "espresso-contrib", "espresso-web", "espresso-accessibility", "espresso-intents", "espresso-idling-resource"]
+ui = ["appcompat", "webkit", "cardview", "exifinterface", "fragment-ktx"]
+markdown-rendering = [
+ "core",
+ "ext-strikethrough",
+ "ext-tables",
+ "ext-tasklist",
+ "html",
+ "syntax-highlight",
+ "prism4j"
+]
+unit-test = [
+ "junit-junit",
+ "test-core",
+ "json",
+ "mockito-kotlin",
+ "mockk",
+ "mockk-android",
+ "mockito-core",
+ "mockito-android",
+ "core-testing"
+]
+mocking = [
+ "dexopener", # required to allow mocking on API 27 and older
+ "mockito-kotlin",
+ "mockk",
+ "mockk-android",
+ "mockito-core",
+ "mockito-android",
+ "screenshot-core"
+]
+gplay = [
+ "firebase-messaging",
+ "play-services-base",
+ "review-ktx"
+]
+
[plugins]
+android-application = { id = "com.android.application", version.ref = "androidPluginVersion" }
+android-library = { id = "com.android.library", version.ref = "androidPluginVersion" }
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
+spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugsGradlePlugin" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detektGradlePlugin" }
+shot = { id = "shot", version.ref = "shotVersion" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index ea57934..3280ec1 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1,4 +1,10 @@
+
true
@@ -42,6 +48,7 @@
+
@@ -369,6 +376,7 @@
+
@@ -840,6 +848,14 @@
+
+
+
+
+
+
+
+
@@ -1169,6 +1185,11 @@
+
+
+
+
+
@@ -1355,6 +1376,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1403,6 +1439,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1451,6 +1511,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1507,6 +1591,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1555,6 +1663,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1603,6 +1735,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1651,6 +1807,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1699,6 +1879,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1747,6 +1951,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1777,6 +2005,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1825,6 +2068,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1921,6 +2188,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1969,6 +2260,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1985,6 +2300,14 @@
+
+
+
+
+
+
+
+
@@ -2001,6 +2324,14 @@
+
+
+
+
+
+
+
+
@@ -2075,6 +2406,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2123,6 +2478,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2131,6 +2510,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2139,6 +2542,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2208,6 +2635,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2256,6 +2707,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2320,6 +2795,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2368,6 +2867,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2432,6 +2955,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2480,6 +3027,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2544,6 +3115,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2592,6 +3187,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2688,6 +3307,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2736,6 +3379,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2766,6 +3433,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2814,6 +3496,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2844,6 +3550,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2892,6 +3613,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2940,6 +3685,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2988,6 +3757,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3052,6 +3845,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3100,6 +3917,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3164,6 +4005,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3212,6 +4077,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -3401,6 +4290,14 @@
+
+
+
+
+
+
+
+
@@ -3445,7 +4342,7 @@
-
+
@@ -3499,6 +4396,14 @@
+
+
+
+
+
+
+
+
@@ -4439,6 +5344,11 @@
+
+
+
+
+
@@ -4447,11 +5357,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4460,11 +5383,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4473,11 +5409,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4486,11 +5435,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4507,6 +5477,14 @@
+
+
+
+
+
+
+
+
@@ -4515,6 +5493,14 @@
+
+
+
+
+
+
+
+
@@ -5039,6 +6025,14 @@
+
+
+
+
+
+
+
+
@@ -5100,6 +6094,14 @@
+
+
+
+
+
+
+
+
@@ -5148,6 +6150,14 @@
+
+
+
+
+
+
+
+
@@ -5253,6 +6263,14 @@
+
+
+
+
+
+
+
+
@@ -5346,6 +6364,14 @@
+
+
+
+
+
+
+
+
@@ -5415,6 +6441,14 @@
+
+
+
+
+
+
+
+
@@ -5487,6 +6521,14 @@
+
+
+
+
+
+
+
+
@@ -5584,6 +6626,14 @@
+
+
+
+
+
+
+
+
@@ -5632,6 +6682,14 @@
+
+
+
+
+
+
+
+
@@ -5671,6 +6729,14 @@
+
+
+
+
+
+
+
+
@@ -5719,6 +6785,14 @@
+
+
+
+
+
+
+
+
@@ -5782,6 +6856,14 @@
+
+
+
+
+
+
+
+
@@ -5830,6 +6912,14 @@
+
+
+
+
+
+
+
+
@@ -5870,6 +6960,14 @@
+
+
+
+
+
+
+
+
@@ -5985,6 +7083,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -6033,6 +7142,14 @@
+
+
+
+
+
+
+
+
@@ -6102,6 +7219,14 @@
+
+
+
+
+
+
+
+
@@ -6193,6 +7318,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -6225,6 +7361,14 @@
+
+
+
+
+
+
+
+
@@ -7102,6 +8246,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7118,6 +8278,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7150,6 +8326,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7182,6 +8374,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7198,6 +8406,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7227,6 +8451,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7259,6 +8499,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7275,6 +8531,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7307,6 +8579,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7323,6 +8611,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7355,6 +8659,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7371,6 +8691,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7419,6 +8755,14 @@
+
+
+
+
+
+
+
+
@@ -7435,6 +8779,14 @@
+
+
+
+
+
+
+
+
@@ -7448,6 +8800,14 @@
+
+
+
+
+
+
+
+
@@ -7464,6 +8824,14 @@
+
+
+
+
+
+
+
+
@@ -7496,6 +8864,14 @@
+
+
+
+
+
+
+
+
@@ -7544,6 +8920,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7560,6 +8952,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7597,6 +9005,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -7613,6 +9037,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -8342,6 +9782,14 @@
+
+
+
+
+
+
+
+
@@ -8390,6 +9838,14 @@
+
+
+
+
+
+
+
+
@@ -8712,6 +10168,11 @@
+
+
+
+
+
@@ -8837,6 +10298,11 @@
+
+
+
+
+
@@ -9202,6 +10668,14 @@
+
+
+
+
+
+
+
+
@@ -10115,6 +11589,9 @@
+
+
+
@@ -11822,6 +13299,14 @@
+
+
+
+
+
+
+
+
@@ -11870,6 +13355,14 @@
+
+
+
+
+
+
+
+
@@ -11918,6 +13411,14 @@
+
+
+
+
+
+
+
+
@@ -12086,6 +13587,14 @@
+
+
+
+
+
+
+
+
@@ -12134,6 +13643,14 @@
+
+
+
+
+
+
+
+
@@ -12182,6 +13699,14 @@
+
+
+
+
+
+
+
+
@@ -12230,6 +13755,14 @@
+
+
+
+
+
+
+
+
@@ -15429,6 +16962,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -15469,6 +17017,11 @@
+
+
+
+
+
@@ -15504,6 +17057,11 @@
+
+
+
+
+
@@ -15787,6 +17345,14 @@
+
+
+
+
+
+
+
+
@@ -15803,6 +17369,14 @@
+
+
+
+
+
+
+
+
@@ -15819,6 +17393,14 @@
+
+
+
+
+
+
+
+
@@ -15843,6 +17425,14 @@
+
+
+
+
+
+
+
+
@@ -15859,6 +17449,14 @@
+
+
+
+
+
+
+
+
@@ -15899,6 +17497,14 @@
+
+
+
+
+
+
+
+
@@ -15947,6 +17553,14 @@
+
+
+
+
+
+
+
+
@@ -15963,14 +17577,6 @@
-
-
-
-
-
-
-
-
@@ -16011,6 +17617,14 @@
+
+
+
+
+
+
+
+
@@ -16027,6 +17641,14 @@
+
+
+
+
+
+
+
+
@@ -16035,6 +17657,14 @@
+
+
+
+
+
+
+
+
@@ -16171,6 +17801,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -16235,6 +17881,14 @@
+
+
+
+
+
+
+
+
@@ -16331,6 +17985,14 @@
+
+
+
+
+
+
+
+
@@ -16363,6 +18025,14 @@
+
+
+
+
+
+
+
+
@@ -16491,6 +18161,14 @@
+
+
+
+
+
+
+
+
@@ -16523,6 +18201,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -16531,6 +18222,14 @@
+
+
+
+
+
+
+
+
@@ -16707,14 +18406,6 @@
-
-
-
-
-
-
-
-
@@ -16867,6 +18558,14 @@
+
+
+
+
+
+
+
+
@@ -16999,6 +18698,14 @@
+
+
+
+
+
+
+
+
@@ -17127,6 +18834,14 @@
+
+
+
+
+
+
+
+
@@ -17159,6 +18874,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17199,6 +18929,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17239,6 +18985,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17359,6 +19121,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17518,6 +19296,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17550,6 +19344,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -17619,6 +19429,14 @@
+
+
+
+
+
+
+
+
@@ -18112,6 +19930,14 @@
+
+
+
+
+
+
+
+
@@ -18176,6 +20002,14 @@
+
+
+
+
+
+
+
+
@@ -18240,6 +20074,14 @@
+
+
+
+
+
+
+
+
@@ -18304,6 +20146,14 @@
+
+
+
+
+
+
+
+
@@ -18368,6 +20218,14 @@
+
+
+
+
+
+
+
+
@@ -18432,6 +20290,14 @@
+
+
+
+
+
+
+
+
@@ -18504,6 +20370,14 @@
+
+
+
+
+
+
+
+
@@ -18564,6 +20438,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18652,6 +20541,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18684,6 +20591,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18833,6 +20756,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18921,6 +20868,14 @@
+
+
+
+
+
+
+
+
@@ -19001,6 +20956,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -19089,6 +21068,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -19218,6 +21221,14 @@
+
+
+
+
+
+
+
+
@@ -19329,6 +21340,11 @@
+
+
+
+
+
@@ -19357,6 +21373,14 @@
+
+
+
+
+
+
+
+
@@ -19410,6 +21434,14 @@
+
+
+
+
+
+
+
+
@@ -19618,6 +21650,14 @@
+
+
+
+
+
+
+
+
@@ -19627,6 +21667,9 @@
+
+
+
@@ -19818,16 +21861,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -20370,6 +22428,14 @@
+
+
+
+
+
+
+
+
@@ -20735,6 +22801,11 @@
+
+
+
+
+
@@ -20759,6 +22830,14 @@
+
+
+
+
+
+
+
+
@@ -20767,6 +22846,14 @@
+
+
+
+
+
+
+
+
@@ -21480,6 +23567,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -22231,6 +24328,11 @@
+
+
+
+
+
@@ -22540,6 +24642,14 @@
+
+
+
+
+
+
+
+
@@ -22596,6 +24706,14 @@
+
+
+
+
+
+
+
+
@@ -22652,6 +24770,14 @@
+
+
+
+
+
+
+
+
@@ -22708,6 +24834,14 @@
+
+
+
+
+
+
+
+
@@ -22764,6 +24898,14 @@
+
+
+
+
+
+
+
+
@@ -22820,6 +24962,14 @@
+
+
+
+
+
+
+
+
@@ -22876,6 +25026,14 @@
+
+
+
+
+
+
+
+
@@ -22932,6 +25090,14 @@
+
+
+
+
+
+
+
+
@@ -22988,6 +25154,14 @@
+
+
+
+
+
+
+
+
@@ -23044,6 +25218,14 @@
+
+
+
+
+
+
+
+
@@ -23100,6 +25282,14 @@
+
+
+
+
+
+
+
+
@@ -23156,6 +25346,14 @@
+
+
+
+
+
+
+
+
@@ -23894,6 +26092,14 @@
+
+
+
+
+
+
+
+
@@ -24109,6 +26315,14 @@
+
+
+
+
+
+
+
+
@@ -24165,6 +26379,14 @@
+
+
+
+
+
+
+
+
@@ -24285,6 +26507,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -24340,6 +26572,14 @@
+
+
+
+
+
+
+
+
@@ -24517,6 +26757,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -24557,6 +26807,14 @@
+
+
+
+
+
+
+
+
@@ -24577,6 +26835,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -24617,6 +26885,14 @@
+
+
+
+
+
+
+
+
@@ -25330,6 +27606,14 @@
+
+
+
+
+
+
+
+
@@ -25354,6 +27638,14 @@
+
+
+
+
+
+
+
+
@@ -25369,6 +27661,11 @@
+
+
+
+
+
@@ -25393,6 +27690,14 @@
+
+
+
+
+
+
+
+
@@ -25417,6 +27722,14 @@
+
+
+
+
+
+
+
+
@@ -25508,6 +27821,14 @@
+
+
+
+
+
+
+
+
@@ -25532,6 +27853,14 @@
+
+
+
+
+
+
+
+
@@ -25596,6 +27925,14 @@
+
+
+
+
+
+
+
+
@@ -25636,6 +27973,14 @@
+
+
+
+
+
+
+
+
@@ -25801,6 +28146,14 @@
+
+
+
+
+
+
+
+
@@ -25899,6 +28252,14 @@
+
+
+
+
+
+
+
+
@@ -25968,6 +28329,14 @@
+
+
+
+
+
+
+
+
@@ -26032,6 +28401,14 @@
+
+
+
+
+
+
+
+
@@ -26136,6 +28513,14 @@
+
+
+
+
+
+
+
+
@@ -26213,6 +28598,14 @@
+
+
+
+
+
+
+
+
@@ -26277,6 +28670,14 @@
+
+
+
+
+
+
+
+
@@ -26354,6 +28755,14 @@
+
+
+
+
+
+
+
+
@@ -26450,6 +28859,14 @@
+
+
+
+
+
+
+
+
@@ -26522,6 +28939,14 @@
+
+
+
+
+
+
+
+
@@ -26594,6 +29019,14 @@
+
+
+
+
+
+
+
+
@@ -26693,6 +29126,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -26765,6 +29209,14 @@
+
+
+
+
+
+
+
+
@@ -26837,6 +29289,14 @@
+
+
+
+
+
+
+
+
@@ -26909,6 +29369,14 @@
+
+
+
+
+
+
+
+
@@ -26991,6 +29459,14 @@
+
+
+
+
+
+
+
+
@@ -27063,6 +29539,14 @@
+
+
+
+
+
+
+
+
@@ -27135,6 +29619,14 @@
+
+
+
+
+
+
+
+
@@ -27175,6 +29667,14 @@
+
+
+
+
+
+
+
+
@@ -27247,6 +29747,14 @@
+
+
+
+
+
+
+
+
@@ -27319,6 +29827,14 @@
+
+
+
+
+
+
+
+
@@ -27404,6 +29920,14 @@
+
+
+
+
+
+
+
+
@@ -27524,6 +30048,14 @@
+
+
+
+
+
+
+
+
@@ -27620,6 +30152,14 @@
+
+
+
+
+
+
+
+
@@ -27692,6 +30232,14 @@
+
+
+
+
+
+
+
+
@@ -27764,6 +30312,14 @@
+
+
+
+
+
+
+
+
@@ -27836,6 +30392,14 @@
+
+
+
+
+
+
+
+
@@ -27908,6 +30472,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -28126,6 +30730,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -28275,6 +30898,11 @@
+
+
+
+
+
@@ -28680,6 +31308,14 @@
+
+
+
+
+
+
+
+
@@ -28752,6 +31388,14 @@
+
+
+
+
+
+
+
+
@@ -28824,6 +31468,14 @@
+
+
+
+
+
+
+
+
@@ -28880,6 +31532,14 @@
+
+
+
+
+
+
+
+
@@ -28888,11 +31548,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -28967,6 +31645,13 @@
+
+
+
+
+
+
+
@@ -29036,6 +31721,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29210,6 +31926,11 @@
+
+
+
+
+
@@ -29442,6 +32163,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -29463,6 +32194,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29487,6 +32234,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29510,6 +32273,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29534,6 +32313,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -29542,6 +32337,14 @@
+
+
+
+
+
+
+
+
@@ -29724,6 +32527,9 @@
+
+
+
@@ -29761,14 +32567,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -30102,6 +32930,14 @@
+
+
+
+
+
+
+
+
@@ -30150,6 +32986,14 @@
+
+
+
+
+
+
+
+
@@ -30165,6 +33009,11 @@
+
+
+
+
+
@@ -30213,6 +33062,14 @@
+
+
+
+
+
+
+
+
@@ -30261,6 +33118,14 @@
+
+
+
+
+
+
+
+
@@ -30309,6 +33174,14 @@
+
+
+
+
+
+
+
+
@@ -30373,6 +33246,14 @@
+
+
+
+
+
+
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8bdaf60c75ab801e22807dde59e12a8735a34077..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644
GIT binary patch
delta 36466
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