diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 3e4388b6488d..7e0d414786ac 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -8,4 +8,10 @@ Unit tests: https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md#uni
Instrumented tests: https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md#instrumented-tests
UI tests: https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md#ui-tests
-->
+### 🖼️ Screenshots
+
+🏚️ Before | 🏡 After
+---|---
+B | A
+
- [ ] Tests written, or not not needed
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8e3717619d62..9907b8679c11 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -62,10 +62,10 @@ configurations.configureEach {
}
// semantic versioning for version code
-val versionMajor = 3
-val versionMinor = 36
+val versionMajor = 33
+val versionMinor = 0
val versionPatch = 0
-val versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
+val versionBuild = 90 // 0-50=Alpha / 51-98=RC / 90-99=stable
val ndkEnv = buildMap {
file("${project.rootDir}/ndk.env").readLines().forEach {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ee6e7fd2bc80..20c10607b707 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -358,7 +358,7 @@
android:exported="false"
android:launchMode="singleInstance" />
diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
index 3001ca445c57..c82192516324 100644
--- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
+++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
@@ -38,6 +38,7 @@ import com.nextcloud.client.database.entity.UploadEntity
import com.nextcloud.client.database.entity.VirtualEntity
import com.nextcloud.client.database.migrations.DatabaseMigrationUtil
import com.nextcloud.client.database.migrations.MIGRATION_88_89
+import com.nextcloud.client.database.migrations.MIGRATION_97_98
import com.nextcloud.client.database.migrations.Migration67to68
import com.nextcloud.client.database.migrations.RoomMigration
import com.nextcloud.client.database.migrations.addLegacyMigrations
@@ -92,8 +93,8 @@ import com.owncloud.android.db.ProviderMeta
AutoMigration(from = 93, to = 94, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
AutoMigration(from = 94, to = 95, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
AutoMigration(from = 95, to = 96),
- AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
- AutoMigration(from = 97, to = 98)
+ AutoMigration(from = 96, to = 97, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
+ // manual migration used for 97 to 98
],
exportSchema = true
)
@@ -131,6 +132,7 @@ abstract class NextcloudDatabase : RoomDatabase() {
.addMigrations(RoomMigration())
.addMigrations(Migration67to68())
.addMigrations(MIGRATION_88_89)
+ .addMigrations(MIGRATION_97_98)
.build()
}
return instance!!
diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt
index 91a03044afde..af363b743283 100644
--- a/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt
+++ b/app/src/main/java/com/nextcloud/client/database/dao/FileSystemDao.kt
@@ -8,6 +8,7 @@
package com.nextcloud.client.database.dao
import androidx.room.Dao
+import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@@ -19,6 +20,9 @@ interface FileSystemDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(filesystemEntity: FilesystemEntity)
+ @Delete
+ fun delete(entity: FilesystemEntity)
+
@Query(
"""
DELETE FROM ${ProviderMeta.ProviderTableMeta.FILESYSTEM_TABLE_NAME}
diff --git a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt
index 7f09f4aa0543..2e1d86d726d5 100644
--- a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt
+++ b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt
@@ -41,7 +41,7 @@ interface UploadDao {
"WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName " +
"AND ${ProviderTableMeta.UPLOADS_REMOTE_PATH} = :remotePath"
)
- fun deleteByAccountAndRemotePath(remotePath: String, accountName: String)
+ fun deleteByRemotePathAndAccountName(remotePath: String, accountName: String)
@Query(
"SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
@@ -51,7 +51,7 @@ interface UploadDao {
)
fun getUploadById(id: Long, accountName: String): UploadEntity?
- @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(entity: UploadEntity): Long
@Query(
diff --git a/app/src/main/java/com/nextcloud/client/database/entity/UploadEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/UploadEntity.kt
index 7322a20e50a3..ff4ff9e91c6f 100644
--- a/app/src/main/java/com/nextcloud/client/database/entity/UploadEntity.kt
+++ b/app/src/main/java/com/nextcloud/client/database/entity/UploadEntity.kt
@@ -17,6 +17,7 @@ import com.owncloud.android.db.OCUpload
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
import com.owncloud.android.db.UploadResult
import com.owncloud.android.files.services.NameCollisionPolicy
+import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.lib.resources.status.OCCapability
import java.lang.IllegalArgumentException
@@ -71,6 +72,7 @@ fun UploadEntity.toOCUpload(capability: OCCapability? = null): OCUpload? {
val upload = try {
OCUpload(localPath, remotePath, accountName)
} catch (_: IllegalArgumentException) {
+ Log_OC.e("UploadEntity", "OCUpload conversion failed")
return null
}
@@ -92,9 +94,21 @@ fun UploadEntity.toOCUpload(capability: OCCapability? = null): OCUpload? {
fun OCUpload.toUploadEntity(): UploadEntity {
val id = if (uploadId == -1L) {
+ Log_OC.d(
+ "UploadEntity",
+ "UploadEntity: No existing ID provided (uploadId = -1). " +
+ "Will insert as NEW record and let Room auto-generate the primary key."
+ )
+
// needed for the insert new records to the db so that insert DAO function returns new generated id
null
} else {
+ Log_OC.d(
+ "UploadEntity",
+ "UploadEntity: Using existing ID ($uploadId). " +
+ "This will update/replace the existing database record."
+ )
+
uploadId
}
diff --git a/app/src/main/java/com/nextcloud/client/database/migrations/Migration97to98.kt b/app/src/main/java/com/nextcloud/client/database/migrations/Migration97to98.kt
new file mode 100644
index 000000000000..6a35c00ccabd
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/database/migrations/Migration97to98.kt
@@ -0,0 +1,34 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.database.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.nextcloud.client.database.migrations.model.SQLiteColumnType
+import com.owncloud.android.db.ProviderMeta
+
+@Suppress("MagicNumber")
+val MIGRATION_97_98 = object : Migration(97, 98) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ DatabaseMigrationUtil.addColumnIfNotExists(
+ db,
+ ProviderMeta.ProviderTableMeta.UPLOADS_TABLE_NAME,
+ ProviderMeta.ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP_LONG,
+ SQLiteColumnType.INTEGER_DEFAULT_NULL
+ )
+
+ DatabaseMigrationUtil.addColumnIfNotExists(
+ db,
+ ProviderMeta.ProviderTableMeta.CAPABILITIES_TABLE_NAME,
+ ProviderMeta.ProviderTableMeta.CAPABILITIES_CLIENT_INTEGRATION_JSON,
+ SQLiteColumnType.TEXT_DEFAULT_NULL
+ )
+
+ DatabaseMigrationUtil.resetCapabilities(db)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
index dc7194f78b0a..954fb16f8c3b 100644
--- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
+++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
@@ -99,6 +99,7 @@
import com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragment;
import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
import com.owncloud.android.ui.dialog.TermsOfServiceDialog;
+import com.owncloud.android.ui.dialog.ThemeSelectionDialog;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.fragment.ExtendedListFragment;
import com.owncloud.android.ui.fragment.FeatureFragment;
@@ -423,6 +424,9 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract ChooseStorageLocationDialogFragment chooseStorageLocationDialogFragment();
+ @ContributesAndroidInjector
+ abstract ThemeSelectionDialog themeSelectionDialog();
+
@ContributesAndroidInjector
abstract SharePasswordDialogFragment sharePasswordDialogFragment();
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
index 4b1dd1d659b2..8b6012c0e8db 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
@@ -179,7 +179,7 @@ class BackgroundJobFactory @Inject constructor(
powerManagementService = powerManagementService,
syncedFolderProvider = syncedFolderProvider,
backgroundJobManager = backgroundJobManager.get(),
- repository = FileSystemRepository(dao = database.fileSystemDao(), context),
+ repository = FileSystemRepository(dao = database.fileSystemDao(), uploadsStorageManager, context),
viewThemeUtils = viewThemeUtils.get(),
localBroadcastManager = localBroadcastManager.get()
)
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
index abde68ad4040..0dfef42aba27 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
@@ -120,9 +120,7 @@ interface BackgroundJobManager {
fun startImmediateFilesExportJob(files: Collection): LiveData
- fun schedulePeriodicFilesSyncJob(syncedFolder: SyncedFolder)
-
- fun startAutoUploadImmediately(
+ fun startAutoUpload(
syncedFolder: SyncedFolder,
overridePowerSaving: Boolean = false,
contentUris: Array = arrayOf()
diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
index 0ad01e66c7ad..8a8647e53d5f 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
@@ -43,6 +43,7 @@ import com.owncloud.android.operations.DownloadType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import java.time.Duration
import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit
@@ -101,8 +102,6 @@ internal class BackgroundJobManagerImpl(
const val JOB_TEST = "test_job"
- const val MAX_CONTENT_TRIGGER_DELAY_MS = 10000L
-
const val TAG_PREFIX_NAME = "name"
const val TAG_PREFIX_USER = "user"
const val TAG_PREFIX_CLASS = "class"
@@ -269,13 +268,15 @@ internal class BackgroundJobManagerImpl(
return workInfo.map { it -> it.map { fromWorkInfo(it) ?: JobInfo() }.sortedBy { it.started }.reversed() }
}
+ @Suppress("MagicNumber")
override fun scheduleContentObserverJob() {
val constrains = Constraints.Builder()
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
- .setTriggerContentMaxDelay(MAX_CONTENT_TRIGGER_DELAY_MS, TimeUnit.MILLISECONDS)
+ .setTriggerContentUpdateDelay(Duration.ofSeconds(5))
+ .setTriggerContentMaxDelay(Duration.ofSeconds(10))
.build()
val request = oneTimeRequestBuilder(ContentObserverWork::class, JOB_CONTENT_OBSERVER)
@@ -477,40 +478,7 @@ internal class BackgroundJobManagerImpl(
)
}
- override fun schedulePeriodicFilesSyncJob(syncedFolder: SyncedFolder) {
- val syncedFolderID = syncedFolder.id
-
- val arguments = Data.Builder()
- .putLong(AutoUploadWorker.SYNCED_FOLDER_ID, syncedFolderID)
- .build()
-
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .setRequiresCharging(syncedFolder.isChargingOnly)
- .build()
-
- val request = periodicRequestBuilder(
- jobClass = AutoUploadWorker::class,
- jobName = JOB_PERIODIC_FILES_SYNC + "_" + syncedFolderID,
- intervalMins = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
- constraints = constraints
- )
- .setBackoffCriteria(
- BackoffPolicy.LINEAR,
- DEFAULT_BACKOFF_CRITERIA_DELAY_SEC,
- TimeUnit.SECONDS
- )
- .setInputData(arguments)
- .build()
-
- workManager.enqueueUniquePeriodicWork(
- JOB_PERIODIC_FILES_SYNC + "_" + syncedFolderID,
- ExistingPeriodicWorkPolicy.KEEP,
- request
- )
- }
-
- override fun startAutoUploadImmediately(
+ override fun startAutoUpload(
syncedFolder: SyncedFolder,
overridePowerSaving: Boolean,
contentUris: Array
diff --git a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
index 6d421d23a9d4..e691aaf432d8 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
@@ -7,18 +7,12 @@
*/
package com.nextcloud.client.jobs
-import android.app.Notification
import android.content.Context
-import androidx.core.app.NotificationCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.nextcloud.client.device.PowerManagementService
-import com.nextcloud.utils.ForegroundServiceHelper
-import com.owncloud.android.R
-import com.owncloud.android.datamodel.ForegroundServiceType
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.lib.common.utils.Log_OC
-import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.FilesSyncHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -26,13 +20,12 @@ import kotlinx.coroutines.withContext
/**
* This work is triggered when OS detects change in media folders.
*
- * It fires media detection job and sync job and finishes immediately.
+ * It fires media detection worker and auto upload worker and finishes immediately.
*
- * This job must not be started on API < 24.
*/
@Suppress("TooGenericExceptionCaught")
class ContentObserverWork(
- private val context: Context,
+ context: Context,
private val params: WorkerParameters,
private val syncedFolderProvider: SyncedFolderProvider,
private val powerManagementService: PowerManagementService,
@@ -41,8 +34,6 @@ class ContentObserverWork(
companion object {
private const val TAG = "🔍" + "ContentObserverWork"
- private const val CHANNEL_ID = NotificationUtils.NOTIFICATION_CHANNEL_CONTENT_OBSERVER
- private const val NOTIFICATION_ID = 774
}
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
@@ -53,10 +44,6 @@ class ContentObserverWork(
try {
if (params.triggeredContentUris.isNotEmpty()) {
Log_OC.d(TAG, "📸 content observer detected file changes.")
-
- val notificationTitle = context.getString(R.string.content_observer_work_notification_title)
- val notification = createNotification(notificationTitle)
- updateForegroundInfo(notification)
checkAndTriggerAutoUpload()
// prevent worker fail because of another worker
@@ -69,8 +56,6 @@ class ContentObserverWork(
Log_OC.d(TAG, "⚠️ triggeredContentUris is empty — nothing to sync.")
}
- rescheduleSelf()
-
val result = Result.success()
backgroundJobManager.logEndOfWorker(workerName, result)
Log_OC.d(TAG, "finished")
@@ -78,37 +63,12 @@ class ContentObserverWork(
} catch (e: Exception) {
Log_OC.e(TAG, "❌ Exception in ContentObserverWork: ${e.message}", e)
Result.retry()
+ } finally {
+ Log_OC.d(TAG, "🔄" + "re-scheduling job")
+ backgroundJobManager.scheduleContentObserverJob()
}
}
- private suspend fun updateForegroundInfo(notification: Notification) {
- val foregroundInfo = ForegroundServiceHelper.createWorkerForegroundInfo(
- NOTIFICATION_ID,
- notification,
- ForegroundServiceType.DataSync
- )
- setForeground(foregroundInfo)
- }
-
- private fun createNotification(title: String): Notification = NotificationCompat.Builder(context, CHANNEL_ID)
- .setContentTitle(title)
- .setSmallIcon(R.drawable.ic_find_in_page)
- .setOngoing(true)
- .setSound(null)
- .setVibrate(null)
- .setOnlyAlertOnce(true)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setSilent(true)
- .build()
-
- /**
- * Re-schedules this observer to ensure continuous monitoring of media changes.
- */
- private fun rescheduleSelf() {
- Log_OC.d(TAG, "🔁 Rescheduling ContentObserverWork for continued observation.")
- backgroundJobManager.scheduleContentObserverJob()
- }
-
private suspend fun checkAndTriggerAutoUpload() = withContext(Dispatchers.IO) {
if (powerManagementService.isPowerSavingEnabled) {
Log_OC.w(TAG, "⚡ Power saving mode active — skipping file sync.")
@@ -124,15 +84,15 @@ class ContentObserverWork(
val contentUris = params.triggeredContentUris.map { uri ->
// adds uri strings e.g. content://media/external/images/media/2281
uri.toString()
- }.toTypedArray()
+ }.toTypedArray()
Log_OC.d(TAG, "📄 Content uris detected")
try {
- FilesSyncHelper.startAutoUploadImmediatelyWithContentUris(
+ FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(
syncedFolderProvider,
backgroundJobManager,
- false,
- contentUris
+ contentUris,
+ false
)
Log_OC.d(TAG, "✅ auto upload triggered successfully for ${contentUris.size} file(s).")
} catch (e: Exception) {
diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt
new file mode 100644
index 000000000000..bd6b8a208d5c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt
@@ -0,0 +1,18 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.jobs.autoUpload
+
+import com.nextcloud.client.database.entity.UploadEntity
+import com.owncloud.android.db.OCUpload
+
+sealed class AutoUploadEntityResult {
+ data object NonRetryable : AutoUploadEntityResult()
+ data object CreationError : AutoUploadEntityResult()
+ data object Uploaded : AutoUploadEntityResult()
+ data class Success(val data: Pair) : AutoUploadEntityResult()
+}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt
index 15b23e20fbe7..fc1b9d7be6e9 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadHelper.kt
@@ -31,16 +31,6 @@ class AutoUploadHelper {
}
fun insertEntries(folder: SyncedFolder, repository: FileSystemRepository) {
- val enabledTimestampMs = folder.enabledTimestampMs
- if (!folder.isEnabled || (!folder.isExisting && enabledTimestampMs < 0)) {
- Log_OC.w(
- TAG,
- "Skipping insertDBEntries: enabled=${folder.isEnabled}, " +
- "exists=${folder.isExisting}, enabledTs=$enabledTimestampMs"
- )
- return
- }
-
when (folder.type) {
MediaFolderType.IMAGE -> {
repository.insertFromUri(MediaStore.Images.Media.INTERNAL_CONTENT_URI, folder)
diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt
index ac0aa3769d39..639a66ed8c75 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt
@@ -9,15 +9,12 @@ package com.nextcloud.client.jobs.autoUpload
import android.app.Notification
import android.content.Context
-import android.content.res.Resources
-import androidx.exifinterface.media.ExifInterface
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
-import com.nextcloud.client.database.entity.UploadEntity
import com.nextcloud.client.database.entity.toOCUpload
import com.nextcloud.client.database.entity.toUploadEntity
import com.nextcloud.client.device.PowerManagementService
@@ -26,33 +23,28 @@ import com.nextcloud.client.jobs.upload.FileUploadBroadcastManager
import com.nextcloud.client.jobs.upload.FileUploadWorker
import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager
import com.nextcloud.client.network.ConnectivityService
-import com.nextcloud.client.preferences.SubFolderRule
+import com.nextcloud.utils.extensions.getLog
+import com.nextcloud.utils.extensions.isNonRetryable
import com.nextcloud.utils.extensions.updateStatus
import com.owncloud.android.R
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
import com.owncloud.android.datamodel.FileDataStorageManager
-import com.owncloud.android.datamodel.MediaFolderType
import com.owncloud.android.datamodel.SyncedFolder
import com.owncloud.android.datamodel.SyncedFolderProvider
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload
import com.owncloud.android.db.UploadResult
+import com.owncloud.android.files.services.NameCollisionPolicy
import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.UploadFileOperation
import com.owncloud.android.ui.activity.SettingsActivity
-import com.owncloud.android.utils.FileStorageUtils
-import com.owncloud.android.utils.MimeType
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
-import java.text.ParsePosition
-import java.text.SimpleDateFormat
-import java.util.Locale
-import java.util.TimeZone
@Suppress("LongParameterList", "TooManyFunctions", "TooGenericExceptionCaught")
class AutoUploadWorker(
@@ -78,6 +70,7 @@ class AutoUploadWorker(
}
private val helper = AutoUploadHelper()
+ private val syncFolderHelper = SyncFolderHelper(context)
private val fileUploadBroadcastManager = FileUploadBroadcastManager(localBroadcastManager)
private lateinit var syncedFolder: SyncedFolder
private val notificationManager = AutoUploadNotificationManager(context, viewThemeUtils, NOTIFICATION_ID)
@@ -89,6 +82,8 @@ class AutoUploadWorker(
syncedFolder = syncedFolderProvider.getSyncedFolderByID(syncFolderId)
?.takeIf { it.isEnabled } ?: return Result.failure()
+ Log_OC.d(TAG, syncedFolder.getLog())
+
/**
* Receives from [com.nextcloud.client.jobs.ContentObserverWork.checkAndTriggerAutoUpload]
*/
@@ -98,10 +93,18 @@ class AutoUploadWorker(
return Result.retry()
}
+ if (powerManagementService.isPowerSavingEnabled) {
+ Log_OC.w(TAG, "power saving mode enabled")
+ }
+
collectFileChangesFromContentObserverWork(contentUris)
uploadFiles(syncedFolder)
- Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} finished checking files.")
+ // only update last scan time after uploading files
+ syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
+ syncedFolderProvider.updateSyncFolder(syncedFolder)
+
+ Log_OC.d(TAG, "✅ ${syncedFolder.remotePath} completed")
Result.success()
} catch (e: Exception) {
Log_OC.e(TAG, "❌ failed: ${e.message}")
@@ -190,11 +193,6 @@ class AutoUploadWorker(
val currentTime = System.currentTimeMillis()
val passedScanInterval = totalScanInterval <= currentTime
- Log_OC.d(TAG, "lastScanTimestampMs: " + syncedFolder.lastScanTimestampMs)
- Log_OC.d(TAG, "totalScanInterval: $totalScanInterval")
- Log_OC.d(TAG, "currentTime: $currentTime")
- Log_OC.d(TAG, "passedScanInterval: $passedScanInterval")
-
if (!passedScanInterval && contentUris.isNullOrEmpty() && !overridePowerSaving) {
Log_OC.w(
TAG,
@@ -203,6 +201,8 @@ class AutoUploadWorker(
return true
}
+ Log_OC.d(TAG, "starting ...")
+
return false
}
@@ -212,10 +212,14 @@ class AutoUploadWorker(
*/
@Suppress("MagicNumber", "TooGenericExceptionCaught")
private suspend fun collectFileChangesFromContentObserverWork(contentUris: Array?) = try {
+ Log_OC.d(TAG, "collecting file changes")
+
withContext(Dispatchers.IO) {
if (contentUris.isNullOrEmpty()) {
+ Log_OC.d(TAG, "inserting all entries")
helper.insertEntries(syncedFolder, repository)
} else {
+ Log_OC.d(TAG, "inserting changed entries")
val isContentUrisStored = helper.insertChangedEntries(syncedFolder, contentUris, repository)
if (!isContentUrisStored) {
Log_OC.w(
@@ -226,20 +230,11 @@ class AutoUploadWorker(
helper.insertEntries(syncedFolder, repository)
}
}
- syncedFolder.lastScanTimestampMs = System.currentTimeMillis()
- syncedFolderProvider.updateSyncFolder(syncedFolder)
}
} catch (e: Exception) {
Log_OC.d(TAG, "Exception collectFileChangesFromContentObserverWork: $e")
}
- private fun prepareDateFormat(): SimpleDateFormat {
- val currentLocale = context.resources.configuration.locales[0]
- return SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply {
- timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
- }
- }
-
private fun getUserOrReturn(syncedFolder: SyncedFolder): User? {
val optionalUser = userAccountManager.getUser(syncedFolder.account)
if (!optionalUser.isPresent) {
@@ -274,13 +269,10 @@ class AutoUploadWorker(
@Suppress("LongMethod", "DEPRECATION", "TooGenericExceptionCaught")
private suspend fun uploadFiles(syncedFolder: SyncedFolder) = withContext(Dispatchers.IO) {
- val dateFormat = prepareDateFormat()
val user = getUserOrReturn(syncedFolder) ?: return@withContext
val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context)
val client = OwnCloudClientManagerFactory.getDefaultSingleton()
.getClientFor(ocAccount, context)
- val lightVersion = context.resources.getBoolean(R.bool.syncedFolder_light)
- val currentLocale = context.resources.configuration.locales[0]
trySetForeground()
updateNotification()
@@ -294,29 +286,22 @@ class AutoUploadWorker(
Log_OC.w(TAG, "no more files to upload at lastId: $lastId")
break
}
- Log_OC.d(TAG, "Processing batch: lastId=$lastId, count=${filePathsWithIds.size}")
+ Log_OC.d(TAG, "started, processing batch: lastId=$lastId, count=${filePathsWithIds.size}")
filePathsWithIds.forEachIndexed { batchIndex, (path, id) ->
val file = File(path)
val localPath = file.absolutePath
- val remotePath = getRemotePath(
- file,
- syncedFolder,
- dateFormat,
- lightVersion,
- context.resources,
- currentLocale
- )
+ val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
try {
- val result = createEntityAndUpload(user, localPath, remotePath)
- if (result == null) {
+ val entityResult = getEntityResult(user, localPath, remotePath)
+ if (entityResult !is AutoUploadEntityResult.Success) {
repository.markFileAsHandled(localPath, syncedFolder)
- Log_OC.d(TAG, "Marked file as handled due to existing conflict: $localPath")
+ Log_OC.d(TAG, "marked file as handled: $localPath")
continue
}
- var (uploadEntity, upload) = result
+ var (uploadEntity, upload) = entityResult.data
// if local file deleted, upload process cannot be started or retriable thus needs to be removed
if (path.isEmpty() || !file.exists()) {
@@ -337,7 +322,6 @@ class AutoUploadWorker(
val result = operation.execute(client)
fileUploadBroadcastManager.sendStarted(operation, context)
- uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess)
UploadErrorNotificationManager.handleResult(
context,
@@ -403,11 +387,7 @@ class AutoUploadWorker(
}
@Suppress("ReturnCount")
- private fun createEntityAndUpload(
- user: User,
- localPath: String,
- remotePath: String
- ): Pair? {
+ private fun getEntityResult(user: User, localPath: String, remotePath: String): AutoUploadEntityResult {
val (needsCharging, needsWifi, uploadAction) = getUploadSettings(syncedFolder)
Log_OC.d(TAG, "creating oc upload for ${user.accountName}")
@@ -419,16 +399,27 @@ class AutoUploadWorker(
)
val lastUploadResult = uploadEntity?.lastResult?.let { UploadResult.fromValue(it) }
- if (lastUploadResult == UploadResult.SYNC_CONFLICT) {
- Log_OC.w(TAG, "Conflict already exists, skipping auto-upload: $localPath")
- return null
+ if (lastUploadResult?.isNonRetryable() == true) {
+ Log_OC.w(
+ TAG,
+ "last upload failed with ${lastUploadResult.value}, skipping auto-upload: $localPath"
+ )
+ return AutoUploadEntityResult.NonRetryable
}
val upload = try {
uploadEntity?.toOCUpload(null) ?: OCUpload(localPath, remotePath, user.accountName)
} catch (_: IllegalArgumentException) {
Log_OC.e(TAG, "cannot construct oc upload")
- return null
+ return AutoUploadEntityResult.CreationError
+ }
+
+ // only valid for skip collision policy other scenarios will be handled in UploadFileOperation.java
+ if (upload.lastResult == UploadResult.UPLOADED &&
+ syncedFolder.nameCollisionPolicy == NameCollisionPolicy.SKIP
+ ) {
+ Log_OC.d(TAG, "no need to create and process this entity file is already uploaded")
+ return AutoUploadEntityResult.Uploaded
}
upload.apply {
@@ -445,7 +436,7 @@ class AutoUploadWorker(
}
}
- return upload.toUploadEntity() to upload
+ return AutoUploadEntityResult.Success(upload.toUploadEntity() to upload)
}
private fun createUploadFileOperation(upload: OCUpload, user: User): UploadFileOperation = UploadFileOperation(
@@ -464,79 +455,6 @@ class AutoUploadWorker(
FileDataStorageManager(user, context.contentResolver)
)
- private fun getRemotePath(
- file: File,
- syncedFolder: SyncedFolder,
- sFormatter: SimpleDateFormat,
- lightVersion: Boolean,
- resources: Resources,
- currentLocale: Locale
- ): String {
- val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter)
-
- val (remoteFolder, useSubfolders, subFolderRule) = if (lightVersion) {
- Triple(
- resources.getString(R.string.syncedFolder_remote_folder),
- resources.getBoolean(R.bool.syncedFolder_light_use_subfolders),
- SubFolderRule.YEAR_MONTH
- )
- } else {
- Triple(
- syncedFolder.remotePath,
- syncedFolder.isSubfolderByDate,
- syncedFolder.subfolderRule
- )
- }
-
- return FileStorageUtils.getInstantUploadFilePath(
- file,
- currentLocale,
- remoteFolder,
- syncedFolder.localPath,
- lastModificationTime,
- useSubfolders,
- subFolderRule
- )
- }
-
- private fun hasExif(file: File): Boolean {
- val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
- return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true)
- }
-
- @Suppress("NestedBlockDepth")
- private fun calculateLastModificationTime(
- file: File,
- syncedFolder: SyncedFolder,
- formatter: SimpleDateFormat
- ): Long {
- var lastModificationTime = file.lastModified()
- if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
- Log_OC.d(TAG, "calculateLastModificationTime exif found")
-
- @Suppress("TooGenericExceptionCaught")
- try {
- val exifInterface = ExifInterface(file.absolutePath)
- val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
- if (!exifDate.isNullOrBlank()) {
- val pos = ParsePosition(0)
- val dateTime = formatter.parse(exifDate, pos)
- if (dateTime != null) {
- lastModificationTime = dateTime.time
- Log_OC.w(TAG, "calculateLastModificationTime calculatedTime is: $lastModificationTime")
- } else {
- Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty")
- }
- } else {
- Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty")
- }
- } catch (e: Exception) {
- Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
- }
- }
- return lastModificationTime
- }
-
private fun sendUploadFinishEvent(operation: UploadFileOperation, result: RemoteOperationResult<*>) {
fileUploadBroadcastManager.sendFinished(
operation,
diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt
index f9c12b36b9a1..17c3bddb4806 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/FileSystemRepository.kt
@@ -15,19 +15,37 @@ import com.nextcloud.client.database.entity.FilesystemEntity
import com.nextcloud.utils.extensions.shouldSkipFile
import com.nextcloud.utils.extensions.toFile
import com.owncloud.android.datamodel.SyncedFolder
+import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.SyncedFolderUtils
import java.io.File
import java.util.zip.CRC32
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "MagicNumber", "ReturnCount")
-class FileSystemRepository(private val dao: FileSystemDao, private val context: Context) {
+class FileSystemRepository(
+ private val dao: FileSystemDao,
+ private val uploadsStorageManager: UploadsStorageManager,
+ private val context: Context
+) {
+ private val syncFolderHelper = SyncFolderHelper(context)
companion object {
private const val TAG = "FilesystemRepository"
const val BATCH_SIZE = 50
}
+ fun deleteAutoUploadAndUploadEntity(syncedFolder: SyncedFolder, localPath: String, entity: FilesystemEntity) {
+ Log_OC.d(TAG, "deleting auto upload entity and upload entity")
+
+ val file = File(localPath)
+ val remotePath = syncFolderHelper.getAutoUploadRemotePath(syncedFolder, file)
+ uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(
+ remotePath = remotePath,
+ accountName = syncedFolder.account
+ )
+ dao.delete(entity)
+ }
+
suspend fun deleteByLocalPathAndId(path: String, id: Int) {
dao.deleteByLocalPathAndId(path, id)
}
@@ -39,20 +57,23 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
val entities = dao.getAutoUploadFilesEntities(syncedFolderId, BATCH_SIZE, lastId)
val filtered = mutableListOf>()
- entities.forEach {
- it.localPath?.let { path ->
+ entities.forEach { entity ->
+ entity.localPath?.let { path ->
val file = File(path)
if (!file.exists()) {
Log_OC.w(TAG, "Ignoring file for upload (doesn't exist): $path")
+ deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else if (!SyncedFolderUtils.isQualifiedFolder(file.parent)) {
Log_OC.w(TAG, "Ignoring file for upload (unqualified folder): $path")
+ deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else if (!SyncedFolderUtils.isFileNameQualifiedForAutoUpload(file.name)) {
Log_OC.w(TAG, "Ignoring file for upload (unqualified file): $path")
+ deleteAutoUploadAndUploadEntity(syncedFolder, path, entity)
} else {
Log_OC.d(TAG, "Adding path to upload: $path")
- if (it.id != null) {
- filtered.add(path to it.id)
+ if (entity.id != null) {
+ filtered.add(path to entity.id)
} else {
Log_OC.w(TAG, "cant adding path to upload, id is null")
}
@@ -160,22 +181,21 @@ class FileSystemRepository(private val dao: FileSystemDao, private val context:
}
val entity = dao.getFileByPathAndFolder(localPath, syncedFolder.id.toString())
- val fileSentForUpload = (entity != null && entity.fileSentForUpload == 1)
- if (fileSentForUpload) {
- Log_OC.d(TAG, "File was sent for upload, checking if it changed...")
- }
val fileModified = (lastModified ?: file.lastModified())
- if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
+ val hasNotChanged = entity?.fileModified == fileModified
+ val fileSentForUpload = entity?.fileSentForUpload == 1
+
+ if (hasNotChanged && fileSentForUpload) {
+ Log_OC.d(TAG, "File hasn't changed since last scan. skipping: $localPath")
return
}
- if (fileSentForUpload) {
- Log_OC.d(TAG, "File was sent for upload before but has changed, will re-upload: $localPath")
+ if (syncedFolder.shouldSkipFile(file, fileModified, creationTime, fileSentForUpload)) {
+ return
}
val crc = getFileChecksum(file)
-
val newEntity = FilesystemEntity(
id = entity?.id,
localPath = localPath,
diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt
new file mode 100644
index 000000000000..75896f372d0b
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/SyncFolderHelper.kt
@@ -0,0 +1,106 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.client.jobs.autoUpload
+
+import android.content.Context
+import androidx.exifinterface.media.ExifInterface
+import com.nextcloud.client.preferences.SubFolderRule
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.MediaFolderType
+import com.owncloud.android.datamodel.SyncedFolder
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.MimeType
+import java.io.File
+import java.text.ParsePosition
+import java.text.SimpleDateFormat
+import java.util.TimeZone
+
+class SyncFolderHelper(private val context: Context) {
+
+ companion object {
+ private const val TAG = "SyncFolderHelper"
+ }
+
+ fun getAutoUploadRemotePath(syncedFolder: SyncedFolder, file: File): String {
+ val resources = context.resources
+ val isLightVersion = resources.getBoolean(R.bool.syncedFolder_light)
+ val lastModificationTime = calculateLastModificationTime(file, syncedFolder)
+
+ val remoteFolder: String
+ val useSubfolders: Boolean
+ val subFolderRule: SubFolderRule
+
+ if (isLightVersion) {
+ remoteFolder = resources.getString(R.string.syncedFolder_remote_folder)
+ useSubfolders = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders)
+ subFolderRule = SubFolderRule.YEAR_MONTH
+ } else {
+ remoteFolder = syncedFolder.remotePath
+ useSubfolders = syncedFolder.isSubfolderByDate
+ subFolderRule = syncedFolder.subfolderRule
+ }
+
+ val result = FileStorageUtils.getInstantUploadFilePath(
+ file,
+ resources.configuration.locales[0],
+ remoteFolder,
+ syncedFolder.localPath,
+ lastModificationTime,
+ useSubfolders,
+ subFolderRule
+ )
+
+ Log_OC.d(TAG, "auto upload remote path: $result")
+
+ return result
+ }
+
+ @Suppress("NestedBlockDepth")
+ private fun calculateLastModificationTime(file: File, syncedFolder: SyncedFolder): Long {
+ val resources = context.resources
+ val currentLocale = resources.configuration.locales[0]
+ val formatter = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale).apply {
+ timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
+ }
+ var lastModificationTime = file.lastModified()
+ if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
+ Log_OC.d(TAG, "calculateLastModificationTime exif found")
+
+ @Suppress("TooGenericExceptionCaught")
+ try {
+ val exifInterface = ExifInterface(file.absolutePath)
+ val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
+ if (!exifDate.isNullOrBlank()) {
+ val pos = ParsePosition(0)
+ val dateTime = formatter.parse(exifDate, pos)
+ if (dateTime != null) {
+ lastModificationTime = dateTime.time
+ Log_OC.w(
+ TAG,
+ "calculateLastModificationTime calculatedTime is: $lastModificationTime"
+ )
+ } else {
+ Log_OC.w(TAG, "calculateLastModificationTime dateTime is empty")
+ }
+ } else {
+ Log_OC.w(TAG, "calculateLastModificationTime exifDate is empty")
+ }
+ } catch (e: Exception) {
+ Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
+ }
+ }
+ return lastModificationTime
+ }
+
+ private fun hasExif(file: File): Boolean {
+ val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
+ return mimeType.equals(MimeType.JPEG, ignoreCase = true) ||
+ mimeType.equals(MimeType.TIFF, ignoreCase = true)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt
index 809d24c8e66d..5bb40b30969d 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt
@@ -19,9 +19,9 @@ import com.nextcloud.client.device.BatteryStatus
import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.BackgroundJobManager
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
-import com.nextcloud.client.notifications.AppWideNotificationManager
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
+import com.nextcloud.client.notifications.AppWideNotificationManager
import com.nextcloud.utils.extensions.getUploadIds
import com.owncloud.android.MainApp
import com.owncloud.android.R
@@ -261,7 +261,7 @@ class FileUploadHelper {
}
fun removeFileUpload(remotePath: String, accountName: String) {
- uploadsStorageManager.uploadDao.deleteByAccountAndRemotePath(remotePath, accountName)
+ uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(remotePath, accountName)
}
fun updateUploadStatus(remotePath: String, accountName: String, status: UploadStatus) {
@@ -478,8 +478,13 @@ class FileUploadHelper {
}
}
- @Suppress("MagicNumber")
- fun isSameFileOnRemote(user: User, localFile: File, remotePath: String, context: Context): Boolean {
+ @Suppress("MagicNumber", "ReturnCount", "ComplexCondition")
+ fun isSameFileOnRemote(user: User?, localFile: File?, remotePath: String?, context: Context?): Boolean {
+ if (user == null || localFile == null || remotePath == null || context == null) {
+ Log_OC.e(TAG, "cannot compare remote and local file")
+ return false
+ }
+
// Compare remote file to local file
val localLastModifiedTimestamp = localFile.lastModified() / 1000 // remote file timestamp in milli not micro sec
val localCreationTimestamp = FileUtil.getCreationTimestamp(localFile)
diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt
index ea0bac258db8..e64ca25ca296 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt
@@ -256,10 +256,8 @@ class FileUploadWorker(
)
val result = withContext(Dispatchers.IO) {
- upload(operation, user, client)
+ upload(upload, operation, user, client)
}
- val entity = uploadsStorageManager.uploadDao.getUploadById(upload.uploadId, accountName)
- uploadsStorageManager.updateStatus(entity, result.isSuccess)
currentUploadFileOperation = null
if (result.code == ResultCode.QUOTA_EXCEEDED) {
@@ -330,6 +328,7 @@ class FileUploadWorker(
@Suppress("TooGenericExceptionCaught", "DEPRECATION")
private suspend fun upload(
+ upload: OCUpload,
operation: UploadFileOperation,
user: User,
client: OwnCloudClient
@@ -346,10 +345,17 @@ class FileUploadWorker(
fileUploadBroadcastManager.sendStarted(operation, context)
} catch (e: Exception) {
Log_OC.e(TAG, "Error uploading", e)
- result = RemoteOperationResult(e)
+ uploadsStorageManager.run {
+ uploadDao.getUploadById(upload.uploadId, user.accountName)?.let { entity ->
+ updateStatus(
+ entity,
+ UploadsStorageManager.UploadStatus.UPLOAD_FAILED
+ )
+ }
+ }
+ result = RemoteOperationResult(e)
} finally {
if (!isStopped) {
- uploadsStorageManager.updateDatabaseUploadResult(result, operation)
UploadErrorNotificationManager.handleResult(
context,
notificationManager,
diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt
index 21aa3619b85d..efe129f8d765 100644
--- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt
+++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt
@@ -78,7 +78,6 @@ class UploadTask(
val client = clientProvider()
uploadsStorageManager.updateDatabaseUploadStart(op)
val result = op.execute(client)
- uploadsStorageManager.updateDatabaseUploadResult(result, op)
return Result(file, result.isSuccess)
}
}
diff --git a/app/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt b/app/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
index 214750cbcffc..cac5c86fca11 100644
--- a/app/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
+++ b/app/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt
@@ -85,6 +85,7 @@ class LogsActivity : ToolbarActivity() {
android.R.id.home -> finish()
R.id.action_delete_logs -> vm.deleteAll()
R.id.action_send_logs -> vm.send()
+ R.id.action_export_logs -> vm.export()
R.id.action_refresh_logs -> vm.load()
else -> retval = super.onOptionsItemSelected(item)
}
diff --git a/app/src/main/java/com/nextcloud/client/logger/ui/LogsEmailSender.kt b/app/src/main/java/com/nextcloud/client/logger/ui/LogsEmailSender.kt
index cbcdd2c8c734..9055ee527f5e 100644
--- a/app/src/main/java/com/nextcloud/client/logger/ui/LogsEmailSender.kt
+++ b/app/src/main/java/com/nextcloud/client/logger/ui/LogsEmailSender.kt
@@ -6,20 +6,27 @@
*/
package com.nextcloud.client.logger.ui
+import android.app.DownloadManager
+import android.app.NotificationManager
+import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.net.Uri
import android.os.Build
import android.widget.Toast
+import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import com.nextcloud.client.core.AsyncRunner
import com.nextcloud.client.core.Cancellable
import com.nextcloud.client.core.Clock
import com.nextcloud.client.logger.LogEntry
import com.owncloud.android.R
+import com.owncloud.android.ui.notifications.NotificationUtils
+import com.owncloud.android.utils.FileExportUtils
import java.io.File
-import java.io.FileWriter
+import java.security.SecureRandom
import java.util.TimeZone
class LogsEmailSender(private val context: Context, private val clock: Clock, private val runner: AsyncRunner) {
@@ -36,13 +43,17 @@ class LogsEmailSender(private val context: Context, private val clock: Clock, pr
) : Function0 {
override fun invoke(): Uri? {
- file.parentFile.mkdirs()
- val fo = FileWriter(file, false)
- logs.forEach {
- fo.write(it.toString(tz))
- fo.write("\n")
+ file.parentFile?.mkdirs()
+
+ file.outputStream().use { outputStream ->
+ outputStream.writer(Charsets.UTF_8).buffered().use { writer ->
+ logs.forEach {
+ writer.write(it.toString(tz))
+ writer.newLine()
+ }
+ }
}
- fo.close()
+
return FileProvider.getUriForFile(context, context.getString(R.string.file_provider_authority), file)
}
}
@@ -59,6 +70,16 @@ class LogsEmailSender(private val context: Context, private val clock: Clock, pr
}
}
+ fun export(logs: List) {
+ if (task == null) {
+ val outFile = File(context.cacheDir, "attachments/logs.txt")
+ task = runner.postQuickTask(Task(context, logs, outFile, clock.tz), onResult = {
+ task = null
+ export(outFile)
+ })
+ }
+ }
+
fun stop() {
if (task != null) {
task?.cancel()
@@ -66,6 +87,61 @@ class LogsEmailSender(private val context: Context, private val clock: Clock, pr
}
}
+ private fun export(file: File) {
+ FileExportUtils().exportFile(
+ "Nextcloud Android Files Logs",
+ "text/plain",
+ context.contentResolver,
+ null,
+ file
+ )
+ showSuccessNotification(1)
+ }
+
+ fun showSuccessNotification(successfulExports: Int) {
+ showNotification(
+ context.resources.getQuantityString(
+ R.plurals.export_successful,
+ successfulExports,
+ successfulExports
+ )
+ )
+ }
+
+ private fun showNotification(message: String) {
+ val notificationId = SecureRandom().nextInt()
+
+ val notificationBuilder = NotificationCompat.Builder(
+ context,
+ NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD
+ )
+ .setSmallIcon(R.drawable.notification_icon)
+ .setContentTitle(message)
+ .setAutoCancel(true)
+
+ val actionIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).apply {
+ flags = FLAG_ACTIVITY_NEW_TASK
+ }
+ val actionPendingIntent = PendingIntent.getActivity(
+ context,
+ notificationId,
+ actionIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or
+ PendingIntent.FLAG_IMMUTABLE
+ )
+ notificationBuilder.addAction(
+ NotificationCompat.Action(
+ null,
+ context.getString(R.string.locate_folder),
+ actionPendingIntent
+ )
+ )
+
+ val notificationManager = context
+ .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.notify(notificationId, notificationBuilder.build())
+ }
+
private fun send(uri: Uri?) {
task = null
val intent = Intent(Intent.ACTION_SEND_MULTIPLE)
@@ -76,12 +152,12 @@ class LogsEmailSender(private val context: Context, private val clock: Clock, pr
intent.putExtra(Intent.EXTRA_TEXT, getPhoneInfo())
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.flags = FLAG_ACTIVITY_NEW_TASK
intent.type = LOGS_MIME_TYPE
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, arrayListOf(uri))
try {
context.startActivity(intent)
- } catch (ex: ActivityNotFoundException) {
+ } catch (_: ActivityNotFoundException) {
Toast.makeText(context, R.string.log_send_no_mail_app, Toast.LENGTH_SHORT).show()
}
}
diff --git a/app/src/main/java/com/nextcloud/client/logger/ui/LogsViewModel.kt b/app/src/main/java/com/nextcloud/client/logger/ui/LogsViewModel.kt
index 46e4a8cbd566..6831aed28f23 100644
--- a/app/src/main/java/com/nextcloud/client/logger/ui/LogsViewModel.kt
+++ b/app/src/main/java/com/nextcloud/client/logger/ui/LogsViewModel.kt
@@ -48,6 +48,12 @@ class LogsViewModel @Inject constructor(
}
}
+ fun export() {
+ entries.value?.let {
+ sender.export(it)
+ }
+ }
+
fun load() {
if (isLoading.value != true) {
logsRepository.load(this::onLoaded)
diff --git a/app/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java b/app/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
index 713cea72d5cc..ad6f07a0456b 100644
--- a/app/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
+++ b/app/src/main/java/com/nextcloud/client/network/ConnectivityServiceImpl.java
@@ -71,6 +71,7 @@ public void isNetworkAndServerAvailable(@NonNull GenericCallback callba
if (hasInternet) {
result = !isInternetWalled();
} else {
+ Log_OC.e(TAG, "network and server not available");
result = false;
}
@@ -84,6 +85,7 @@ public boolean isConnected() {
NetworkCapabilities actNw = platformConnectivityManager.getNetworkCapabilities(nw);
if (actNw == null) {
+ Log_OC.e(TAG, "network capabilities is null");
return false;
}
@@ -101,6 +103,7 @@ public boolean isConnected() {
return true;
}
+ Log_OC.e(TAG, "network is not connected");
return false;
}
@@ -108,6 +111,10 @@ public boolean isConnected() {
public boolean isInternetWalled() {
final Boolean cachedValue = walledCheckCache.getValue();
if (cachedValue != null) {
+ if (cachedValue) {
+ Log_OC.e(TAG, "network is walled, cached value is used");
+ }
+
return cachedValue;
} else {
Server server = accountManager.getUser().getServer();
@@ -116,6 +123,8 @@ public boolean isInternetWalled() {
boolean result;
Connectivity c = getConnectivity();
if (c != null && c.isConnected() && c.isWifi() && !c.isMetered() && !baseServerAddress.isEmpty()) {
+ Log_OC.d(TAG, "checking network status");
+
GetMethod get = requestBuilder.invoke(baseServerAddress + CONNECTIVITY_CHECK_ROUTE);
PlainClient client = clientFactory.createPlainClient();
@@ -129,9 +138,29 @@ public boolean isInternetWalled() {
" assuming connectivity is impaired");
}
} else {
+ Log_OC.e(TAG, "cannot check network status, connectivity is not eligible");
+
+ if (c != null) {
+ if (c.isMetered()) {
+ Log_OC.e(TAG, "network is metered");
+ }
+
+ if (!c.isWifi()) {
+ Log_OC.e(TAG, "network is not connected to wi-fi");
+ }
+
+ if (!c.isConnected()) {
+ Log_OC.e(TAG, "network is not connected");
+ }
+ }
+
result = (c != null && !c.isConnected());
}
+ if (result) {
+ Log_OC.e(TAG, "network is walled");
+ }
+
walledCheckCache.setValue(result);
return result;
}
@@ -143,7 +172,8 @@ public Connectivity getConnectivity() {
try {
networkInfo = platformConnectivityManager.getActiveNetworkInfo();
} catch (Throwable t) {
- networkInfo = null; // no network available or no information (permission denied?)
+ Log_OC.e(TAG, "no network available or no information: ", t);
+ networkInfo = null;
}
if (networkInfo != null) {
@@ -152,6 +182,19 @@ public Connectivity getConnectivity() {
boolean isMetered;
isMetered = isNetworkMetered();
boolean isWifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI || hasNonCellularConnectivity();
+
+ if (isMetered) {
+ Log_OC.w(TAG, "getConnectivity(): network is metered");
+ }
+
+ if (!isWifi) {
+ Log_OC.w(TAG, "getConnectivity(): network is not wi-fi");
+ }
+
+ if (!isConnected) {
+ Log_OC.e(TAG, "getConnectivity(): network is not connected");
+ }
+
return new Connectivity(isConnected, isMetered, isWifi, null);
} else {
return Connectivity.DISCONNECTED;
diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
index 699b2d927e87..b7d6818ea5a1 100644
--- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
+++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java
@@ -396,4 +396,7 @@ default void onDarkThemeModeChanged(DarkMode mode) {
String getLastDisplayedAccountName();
void setLastDisplayedAccountName(String lastDisplayedAccountName);
+
+ boolean startAutoUploadOnStart();
+ void setLastAutoUploadOnStartTime(long timeInMillisecond);
}
diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
index 682e211fcd5a..a16beaeaaad9 100644
--- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
+++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java
@@ -111,6 +111,9 @@ public final class AppPreferencesImpl implements AppPreferences {
private static final String PREF_LAST_DISPLAYED_ACCOUNT_NAME = "last_displayed_user";
+ private static final String AUTO_PREF__LAST_AUTO_UPLOAD_ON_START = "last_auto_upload_on_start";
+
+
private static final String LOG_ENTRY = "log_entry";
private final Context context;
@@ -118,6 +121,9 @@ public final class AppPreferencesImpl implements AppPreferences {
private final UserAccountManager userAccountManager;
private final ListenerRegistry listeners;
+ private static final int AUTO_UPLOAD_ON_START_DEBOUNCE_IN_MINUTES = 10;
+ private static final long AUTO_UPLOAD_ON_START_DEBOUNCE_MS = AUTO_UPLOAD_ON_START_DEBOUNCE_IN_MINUTES * 60 * 1000L;
+
/**
* Adapter delegating raw {@link SharedPreferences.OnSharedPreferenceChangeListener} calls with key-value pairs to
* respective {@link com.nextcloud.client.preferences.AppPreferences.Listener} method.
@@ -849,4 +855,16 @@ public String getLastDisplayedAccountName() {
public void setLastDisplayedAccountName(String lastDisplayedAccountName) {
preferences.edit().putString(PREF_LAST_DISPLAYED_ACCOUNT_NAME, lastDisplayedAccountName).apply();
}
+
+ @Override
+ public boolean startAutoUploadOnStart() {
+ long lastRunTime = preferences.getLong(AUTO_PREF__LAST_AUTO_UPLOAD_ON_START, 0L);
+ long now = System.currentTimeMillis();
+ return lastRunTime == 0L || (now - lastRunTime) >= AUTO_UPLOAD_ON_START_DEBOUNCE_MS;
+ }
+
+ @Override
+ public void setLastAutoUploadOnStartTime(long timeInMillisecond) {
+ preferences.edit().putLong(AUTO_PREF__LAST_AUTO_UPLOAD_ON_START, timeInMillisecond).apply();
+ }
}
diff --git a/app/src/main/java/com/nextcloud/ui/ChooseStorageLocationDialogFragment.kt b/app/src/main/java/com/nextcloud/ui/ChooseStorageLocationDialogFragment.kt
index fc9760fd92b9..6a67415c0168 100644
--- a/app/src/main/java/com/nextcloud/ui/ChooseStorageLocationDialogFragment.kt
+++ b/app/src/main/java/com/nextcloud/ui/ChooseStorageLocationDialogFragment.kt
@@ -14,7 +14,9 @@ import android.preference.PreferenceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
+import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.preferences.AppPreferencesImpl
@@ -25,6 +27,7 @@ import com.owncloud.android.datastorage.DataStorageProvider
import com.owncloud.android.datastorage.StoragePoint
import com.owncloud.android.datastorage.StoragePoint.PrivacyType
import com.owncloud.android.datastorage.StoragePoint.StorageType
+import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.File
@@ -46,6 +49,16 @@ class ChooseStorageLocationDialogFragment :
private val selectedPrivacyType
get() = if (binding.allowMediaIndexSwitch.isChecked) PrivacyType.PUBLIC else PrivacyType.PRIVATE
+ override fun onStart() {
+ super.onStart()
+ val alertDialog = dialog as AlertDialog
+
+ val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton
+ positiveButton?.let {
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
+ }
+ }
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogDataStorageLocationBinding.inflate(layoutInflater)
@@ -151,19 +164,13 @@ class ChooseStorageLocationDialogFragment :
?: return
val resultBundle = Bundle().apply {
- putString(KEY_RESULT_STORAGE_LOCATION, newPath.path)
+ putString(ExtendedSettingsActivityDialog.StorageLocation.key, newPath.path)
}
- parentFragmentManager.setFragmentResult(KEY_RESULT_STORAGE_LOCATION, resultBundle)
+ parentFragmentManager.setFragmentResult(ExtendedSettingsActivityDialog.StorageLocation.key, resultBundle)
}
companion object {
- const val KEY_RESULT_STORAGE_LOCATION = "KEY_RESULT_STORAGE_LOCATION"
- const val STORAGE_LOCATION_RESULT_CODE = 100
-
- @JvmStatic
- fun newInstance() = ChooseStorageLocationDialogFragment()
-
@JvmStatic
val TAG: String = Companion::class.java.simpleName
}
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt
index 0a28f83f7616..06e4ff1a9b11 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/OwnCloudClientExtensions.kt
@@ -21,9 +21,9 @@ fun OwnCloudClient.toNextcloudClient(context: Context): NextcloudClient = OwnClo
isFollowRedirects
)
-fun OwnCloudClient.getPreviewEndpoint(localFileId: Long, x: Int, y: Int): String = baseUri
+fun OwnCloudClient.getPreviewEndpoint(remoteId: String, x: Int, y: Int): String = baseUri
.toString() +
"/index.php/core/preview?fileId=" +
- localFileId +
+ remoteId +
"&x=" + (x / 2) + "&y=" + (y / 2) +
"&a=1&mode=cover&forceIcon=0"
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt
index caf0ad8b99f8..3a9f73c81f5f 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt
@@ -9,13 +9,10 @@ package com.nextcloud.utils.extensions
import com.owncloud.android.MainApp
import com.owncloud.android.R
-import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
-import com.owncloud.android.lib.resources.files.model.RemoteFile
import com.owncloud.android.utils.ErrorMessageAdapter
-import com.owncloud.android.utils.FileStorageUtils
@Suppress("ReturnCount")
fun Pair?, RemoteOperation<*>?>?.getErrorMessage(): String {
@@ -45,20 +42,3 @@ fun ResultCode.isFileSpecificError(): Boolean {
return !errorCodes.contains(this)
}
-
-@Suppress("Deprecation")
-fun RemoteOperationResult<*>?.toOCFile(): List? = if (this?.isSuccess == true) {
- data?.toOCFileList()
-} else {
- null
-}
-
-private fun ArrayList.toOCFileList(): List = this.mapNotNull {
- val remoteFile = (it as? RemoteFile)
-
- remoteFile?.let {
- remoteFile.toOCFile()
- }
-}
-
-private fun RemoteFile?.toOCFile(): OCFile = FileStorageUtils.fillOCFile(this)
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt
index 7f8cac806f2f..991b9b0ee904 100644
--- a/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt
+++ b/app/src/main/java/com/nextcloud/utils/extensions/SyncedFolderExtensions.kt
@@ -11,6 +11,7 @@ import com.nextcloud.client.device.PowerManagementService
import com.nextcloud.client.jobs.BackgroundJobManagerImpl
import com.nextcloud.client.network.ConnectivityService
import com.owncloud.android.R
+import com.owncloud.android.datamodel.MediaFolderType
import com.owncloud.android.datamodel.SyncedFolder
import com.owncloud.android.datamodel.SyncedFolderDisplayItem
import com.owncloud.android.lib.common.utils.Log_OC
@@ -100,3 +101,61 @@ fun SyncedFolder.calculateScanInterval(
else -> defaultIntervalMillis to null
}
}
+
+/**
+ * Builds a structured debug string of the SyncedFolder configuration.
+ *
+ * uploadAction:
+ * Represents the UI option:
+ * 👉 "Original file will be..."
+ * (e.g., kept, deleted, moved after upload)
+ *
+ * nameCollisionPolicy:
+ * Represents the UI option:
+ * 👉 "What to do if the file already exists?"
+ * (e.g., rename, overwrite, skip)
+ *
+ * subfolderByDate:
+ * Represents the UI toggle:
+ * 👉 "Use subfolders"
+ *
+ * existing:
+ * Represents the UI option:
+ * 👉 "Also upload existing files"
+ * If false → only files created AFTER enabling are uploaded.
+ */
+fun SyncedFolder.getLog(): String {
+ val mediaType = when (type) {
+ MediaFolderType.IMAGE -> "🖼️ Images"
+ MediaFolderType.VIDEO -> "🎬 Videos"
+ MediaFolderType.CUSTOM -> "📁 Custom"
+ }
+
+ return """
+ 📦 Synced Folder
+ ─────────────────────────
+ 🆔 ID: $id
+ 👤 Account: $account
+
+ 📂 Local: $localPath
+ ☁️ Remote: $remotePath
+
+ $mediaType
+ 📅 Subfolder rule: ${subfolderRule ?: "None"}
+ 🗂️ By date: $isSubfolderByDate
+ 🙈 Exclude hidden: $isExcludeHidden
+ 👀 Hidden config: $isHidden
+
+ 📶 Wi-Fi only: $isWifiOnly
+ 🔌 Charging only: $isChargingOnly
+
+ 📤 Upload existing files: $isExisting
+ ⚙️ Upload action: $uploadAction
+ 🧩 Name collision: $nameCollisionPolicy
+
+ ✅ Enabled: $isEnabled
+ 🕒 Enabled at: $enabledTimestampMs
+ 🔍 Last scan: $lastScanTimestampMs
+ ─────────────────────────
+ """.trimIndent()
+}
diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt
new file mode 100644
index 000000000000..9c1f1218e57a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt
@@ -0,0 +1,34 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.utils.extensions
+
+import com.owncloud.android.db.UploadResult
+
+fun UploadResult.isNonRetryable(): Boolean = when (this) {
+ UploadResult.FILE_NOT_FOUND,
+ UploadResult.FILE_ERROR,
+ UploadResult.FOLDER_ERROR,
+ UploadResult.CANNOT_CREATE_FILE,
+ UploadResult.SYNC_CONFLICT,
+ UploadResult.LOCAL_STORAGE_NOT_COPIED,
+ UploadResult.VIRUS_DETECTED,
+ UploadResult.QUOTA_EXCEEDED,
+ UploadResult.SAME_FILE_CONFLICT,
+ UploadResult.PRIVILEGES_ERROR,
+ UploadResult.CREDENTIAL_ERROR,
+
+ // most cases covered and mapped from RemoteOperationResult. Most likely UploadResult.UNKNOWN this error will
+ // occur again
+ UploadResult.UNKNOWN,
+
+ // user's choice
+ UploadResult.CANCELLED -> true
+
+ // everything else may succeed after retry
+ else -> false
+}
diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java
index 5f564a23ac5a..f46ed83f14e8 100644
--- a/app/src/main/java/com/owncloud/android/MainApp.java
+++ b/app/src/main/java/com/owncloud/android/MainApp.java
@@ -17,7 +17,6 @@
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -31,7 +30,6 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
-import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
@@ -125,8 +123,8 @@
/**
- * Main Application of the project.
- * Contains methods to build the "static" strings. These strings were before constants in different classes.
+ * Main Application of the project. Contains methods to build the "static" strings. These strings were before constants
+ * in different classes.
*/
public class MainApp extends Application implements HasAndroidInjector, NetworkChangeListener {
public static final OwnCloudVersion OUTDATED_SERVER_VERSION = NextcloudVersion.nextcloud_30;
@@ -142,7 +140,7 @@ public class MainApp extends Application implements HasAndroidInjector, NetworkC
private static boolean mOnlyOnDevice;
private static boolean mOnlyPersonalFiles;
-
+
@Inject
protected AppPreferences preferences;
@@ -338,6 +336,10 @@ public void onCreate() {
} catch (Exception e) {
Log_OC.d("Debug", "Failed to disable uri exposure");
}
+
+ Log_OC.d(TAG, "scheduleContentObserverJob, called");
+ backgroundJobManager.scheduleContentObserverJob();
+
initSyncOperations(this,
preferences,
uploadsStorageManager,
@@ -347,8 +349,7 @@ public void onCreate() {
backgroundJobManager,
clock,
viewThemeUtils,
- walledCheckCache,
- syncedFolderProvider);
+ walledCheckCache);
initContactsBackup(accountManager, backgroundJobManager);
notificationChannels();
@@ -371,9 +372,9 @@ public void onCreate() {
if (!MDMConfig.INSTANCE.sendFilesSupport(this)) {
disableDocumentsStorageProvider();
}
-
-
- }
+
+
+ }
public void disableDocumentsStorageProvider() {
String packageName = getPackageName();
@@ -386,6 +387,14 @@ public void disableDocumentsStorageProvider() {
private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> {
if (event == Lifecycle.Event.ON_START) {
Log_OC.d(TAG, "APP IN FOREGROUND");
+
+ if (preferences.startAutoUploadOnStart()) {
+ FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider,
+ backgroundJobManager,
+ new String[]{},
+ false);
+ preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis());
+ }
} else if (event == Lifecycle.Event.ON_STOP) {
passCodeManager.setCanAskPin(true);
Log_OC.d(TAG, "APP IN BACKGROUND");
@@ -403,7 +412,7 @@ private void setProxyForNonBrandedPlusClients() {
Log_OC.d(TAG, "Error caught at setProxyForNonBrandedPlusClients: " + e);
}
}
-
+
public static boolean isClientBranded() {
return getAppContext().getResources().getBoolean(R.bool.is_branded_client);
}
@@ -415,7 +424,8 @@ public static boolean isClientBrandedPlus() {
private final IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
- @Override public void onReceive(Context context, Intent intent) {
+ @Override
+ public void onReceive(Context context, Intent intent) {
setProxyConfig();
}
};
@@ -605,8 +615,7 @@ public static void initSyncOperations(
final BackgroundJobManager backgroundJobManager,
final Clock clock,
final ViewThemeUtils viewThemeUtils,
- final WalledCheckCache walledCheckCache,
- final SyncedFolderProvider syncedFolderProvider) {
+ final WalledCheckCache walledCheckCache) {
updateToAutoUpload(context);
cleanOldEntries(clock);
updateAutoUploadEntries(clock);
@@ -620,11 +629,9 @@ public static void initSyncOperations(
}
if (!preferences.isAutoUploadInitialized()) {
- FilesSyncHelper.startAutoUploadImmediately(syncedFolderProvider, backgroundJobManager, false);
preferences.setAutoUploadInit(true);
}
- FilesSyncHelper.scheduleFilesSyncForAllFoldersIfNeeded(appContext.get(), syncedFolderProvider, backgroundJobManager);
FilesSyncHelper.restartUploadsIfNeeded(
uploadsStorageManager,
accountManager,
@@ -857,7 +864,6 @@ private static void updateToAutoUpload(Context context) {
}
}
-
private static void showAutoUploadAlertDialog(Context context) {
new MaterialAlertDialogBuilder(context, R.style.Theme_ownCloud_Dialog)
diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
index 2213a77b7439..72cfadc35f03 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
@@ -1270,7 +1270,7 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana
int pxW = p.x;
int pxH = p.y;
- if (file.isDown()) {
+ if (file.isDown() && MimeTypeUtil.isImage(file)) {
Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH);
if (bitmap != null) {
if (OCFileExtensionsKt.isPNG(file)) {
@@ -1283,7 +1283,7 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana
GetMethod getMethod = null;
try {
- String uri = OwnCloudClientExtensionsKt.getPreviewEndpoint(mClient, file.getLocalId(), pxW, pxH);
+ String uri = OwnCloudClientExtensionsKt.getPreviewEndpoint(mClient, file.getRemoteId(), pxW, pxH);
Log_OC.d(TAG, "generating resized image: " + file.getFileName() + " URI: " + uri);
getMethod = new GetMethod(uri);
diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
index b339655b438c..244f87cecfe2 100644
--- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
+++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
@@ -550,59 +550,53 @@ public void clearSuccessfulUploads() {
}
}
- /**
- * Updates the persistent upload database with upload result.
- */
public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
- // result: success or fail notification
Log_OC.d(TAG, "updateDatabaseUploadResult uploadResult: " + uploadResult + " upload: " + upload);
if (uploadResult.isCancelled()) {
- removeUpload(
- upload.getUser().getAccountName(),
- upload.getRemotePath()
- );
- } else {
- String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
- ? upload.getStoragePath() : null;
-
- if (uploadResult.isSuccess()) {
- updateUploadStatus(
- upload.getOCUploadId(),
- UploadStatus.UPLOAD_SUCCEEDED,
- UploadResult.UPLOADED,
- upload.getRemotePath(),
- localPath
- );
- } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT &&
- new FileUploadHelper().isSameFileOnRemote(
- upload.getUser(), new File(upload.getStoragePath()), upload.getRemotePath(), upload.getContext())) {
-
- updateUploadStatus(
- upload.getOCUploadId(),
- UploadStatus.UPLOAD_SUCCEEDED,
- UploadResult.SAME_FILE_CONFLICT,
- upload.getRemotePath(),
- localPath
- );
- } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) {
- updateUploadStatus(
- upload.getOCUploadId(),
- UploadStatus.UPLOAD_SUCCEEDED,
- UploadResult.FILE_NOT_FOUND,
- upload.getRemotePath(),
- localPath
- );
- } else if (uploadResult.getCode() != RemoteOperationResult.ResultCode.USER_CANCELLED){
- updateUploadStatus(
- upload.getOCUploadId(),
- UploadStatus.UPLOAD_FAILED,
- UploadResult.fromOperationResult(uploadResult),
- upload.getRemotePath(),
- localPath
- );
+ Log_OC.w(TAG, "upload is cancelled, removing upload");
+ removeUpload(upload.getUser().getAccountName(), upload.getRemotePath());
+ return;
+ }
+
+ String localPath = (upload.getLocalBehaviour() == FileUploadWorker.LOCAL_BEHAVIOUR_MOVE)
+ ? upload.getStoragePath() : null;
+
+
+ Log_OC.d(TAG, "local behaviour: " + upload.getLocalBehaviour());
+ Log_OC.d(TAG, "local path of upload: " + localPath);
+
+ UploadStatus status = UploadStatus.UPLOAD_FAILED;
+ UploadResult result = UploadResult.fromOperationResult(uploadResult);
+ RemoteOperationResult.ResultCode code = uploadResult.getCode();
+
+ if (uploadResult.isSuccess()) {
+ status = UploadStatus.UPLOAD_SUCCEEDED;
+ result = UploadResult.UPLOADED;
+ } else if (code == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
+ boolean isSame = new FileUploadHelper().isSameFileOnRemote(
+ upload.getUser(), new File(upload.getStoragePath()), upload.getRemotePath(), upload.getContext());
+
+ if (isSame) {
+ result = UploadResult.SAME_FILE_CONFLICT;
+ status = UploadStatus.UPLOAD_SUCCEEDED;
+ } else {
+ result = UploadResult.SYNC_CONFLICT;
}
+ } else if (code == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) {
+ // upload status is SUCCEEDED because user cannot take action about it, it will always fail
+ status = UploadStatus.UPLOAD_SUCCEEDED;
+ result = UploadResult.FILE_NOT_FOUND;
}
+
+ Log_OC.d(TAG, String.format(
+ "Upload Finished [%s] | RemoteCode: %s | internalResult: %s | FinalStatus: %s | Path: %s",
+ uploadResult.isSuccess() ? "✅" : "❌",
+ code,
+ result.name(),
+ status,
+ upload.getRemotePath()));
+ updateUploadStatus(upload.getOCUploadId(), status, result, upload.getRemotePath(), localPath);
}
/**
diff --git a/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java b/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
index 0e367ca7665e..8eb62f17a9c9 100644
--- a/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
+++ b/app/src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
@@ -23,7 +23,6 @@
import com.nextcloud.client.network.WalledCheckCache;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.MainApp;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@@ -49,7 +48,6 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
@Inject Clock clock;
@Inject ViewThemeUtils viewThemeUtils;
@Inject WalledCheckCache walledCheckCache;
- @Inject SyncedFolderProvider syncedFolderProvider;
/**
* Receives broadcast intent reporting that the system was just boot up. *
@@ -71,9 +69,9 @@ public void onReceive(Context context, Intent intent) {
backgroundJobManager,
clock,
viewThemeUtils,
- walledCheckCache,
- syncedFolderProvider
- );
+ walledCheckCache);
+ Log_OC.d(TAG, "scheduleContentObserverJob, called");
+ backgroundJobManager.scheduleContentObserverJob();
MainApp.initContactsBackup(accountManager, backgroundJobManager);
} else {
Log_OC.d(TAG, "Getting wrong intent: " + intent.getAction());
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 fa8b2a13c72a..cfe827731dee 100644
--- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
+++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
@@ -14,6 +14,7 @@
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
+import android.text.format.Formatter;
import com.nextcloud.client.account.User;
import com.nextcloud.client.device.BatteryStatus;
@@ -43,7 +44,6 @@
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.network.ProgressiveDataTransfer;
import com.owncloud.android.lib.common.operations.OperationCancelledException;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
@@ -83,7 +83,9 @@
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -413,6 +415,7 @@ public boolean isMissingPermissionThrown() {
@Override
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
protected RemoteOperationResult run(OwnCloudClient client) {
+ Log_OC.d(TAG, "------- Upload File Operation Started -------");
if (TextUtils.isEmpty(getStoragePath())) {
Log_OC.e(TAG, "Upload cancelled for " + getStoragePath() + ": file path is null or empty.");
return new RemoteOperationResult<>(new UploadFileException.EmptyOrNullFilePath());
@@ -435,26 +438,51 @@ protected RemoteOperationResult run(OwnCloudClient client) {
mUploadStarted.set(true);
updateSize(0);
+ Log_OC.d(TAG, "file size set to 0KB before upload");
String remoteParentPath = new File(getRemotePath()).getParent();
- remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
- remoteParentPath = AutoRename.INSTANCE.rename(remoteParentPath, getCapabilities());
+ if (remoteParentPath == null) {
+ Log_OC.e(TAG, "remoteParentPath is null: " + getRemotePath());
+ return new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR);
+ }
+ remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR)
+ ? remoteParentPath
+ : remoteParentPath + OCFile.PATH_SEPARATOR;
+
+ final String renamedRemoteParentPath = AutoRename.INSTANCE.rename(remoteParentPath, getCapabilities());
+ if (!remoteParentPath.equals(renamedRemoteParentPath)) {
+ Log_OC.w(TAG, "remoteParentPath was renamed: " + remoteParentPath + " → " + renamedRemoteParentPath);
+ }
+ remoteParentPath = renamedRemoteParentPath;
OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
+ Log_OC.d(TAG, "parent lookup for path: " + remoteParentPath + " → " +
+ (parent == null ? "not found in DB" : "found, id=" + parent.getFileId()));
// in case of a fresh upload with subfolder, where parent does not exist yet
if (parent == null && (mFolderUnlockToken == null || mFolderUnlockToken.isEmpty())) {
- // try to create folder
+ Log_OC.d(TAG, "parent not in DB and no unlock token, attempting to grant folder existence: "
+ + remoteParentPath);
final var result = grantFolderExistence(remoteParentPath, client);
if (!result.isSuccess()) {
+ Log_OC.e(TAG, "grantFolderExistence failed for: " + remoteParentPath + ", code: " +
+ result.getCode() + ", message: " + result.getMessage());
return result;
}
parent = getStorageManager().getFileByPath(remoteParentPath);
+ if (parent == null) {
+ Log_OC.e(TAG, "parent still null after grantFolderExistence: " + remoteParentPath);
+ return new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR);
+ }
+
+ Log_OC.d(TAG, "parent created and retrieved successfully: " + remoteParentPath + ", id=" +
+ parent.getFileId());
}
if (parent == null) {
+ Log_OC.e(TAG, "parent is null, cannot proceed: " + remoteParentPath + "," + " unlock token: " + mFolderUnlockToken);
return new RemoteOperationResult<>(false, "Parent folder not found", HttpStatus.SC_NOT_FOUND);
}
@@ -466,10 +494,10 @@ protected RemoteOperationResult run(OwnCloudClient client) {
mFile.setEncrypted(encryptedAncestor);
if (encryptedAncestor) {
- Log_OC.d(TAG, "encrypted upload");
+ Log_OC.d(TAG, "⬆️🔗" + "encrypted upload");
return encryptedUpload(client, parent);
} else {
- Log_OC.d(TAG, "normal upload");
+ Log_OC.d(TAG, "⬆️" + "normal upload");
return normalUpload(client);
}
}
@@ -577,6 +605,9 @@ private RemoteOperationResult encryptedUpload(OwnCloudClient client, OCFile pare
result = new RemoteOperationResult<>(e);
} finally {
result = cleanupE2EUpload(fileLock, channel, e2eFiles, result, object, client, token);
+
+ // update upload status
+ uploadsStorageManager.updateDatabaseUploadResult(result, this);
}
completeE2EUpload(result, e2eFiles, client);
@@ -1005,26 +1036,21 @@ private RemoteOperationResult checkConditions(File originalFile) {
}
private RemoteOperationResult normalUpload(OwnCloudClient client) {
- RemoteOperationResult result = null;
+ RemoteOperationResult> result = null;
File temporalFile = null;
File originalFile = new File(mOriginalStoragePath);
File expectedFile = null;
- FileLock fileLock = null;
- FileChannel channel = null;
-
- long size;
try {
- // check conditions
+ Log_OC.d(TAG, "checking conditions");
result = checkConditions(originalFile);
-
if (result != null) {
return result;
}
- // check name collision
final var collisionResult = checkNameCollision(null, client, null, false);
if (collisionResult != null) {
+ Log_OC.e(TAG, "name collision detected");
result = collisionResult;
return collisionResult;
}
@@ -1034,20 +1060,19 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) {
result = copyFile(originalFile, expectedPath);
if (!result.isSuccess()) {
+ Log_OC.e(TAG, "file copying failed");
return result;
}
// Get the last modification date of the file from the file system
long lastModifiedTimestamp = originalFile.lastModified() / 1000;
-
final Long creationTimestamp = FileUtil.getCreationTimestamp(originalFile);
- try {
- channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
- fileLock = channel.tryLock();
- } catch (FileNotFoundException e) {
- // this basically means that the file is on SD card
- // try to copy file to temporary dir if it doesn't exist
+ Path filePath = Paths.get(mFile.getStoragePath());
+
+ // file does not exists in storage
+ if (!Files.exists(filePath)) {
+ Log_OC.e(TAG, "file not found exception: normal upload, probably file in sd card");
String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
mFile.getRemotePath();
mFile.setStoragePath(temporalPath);
@@ -1056,105 +1081,128 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) {
Files.deleteIfExists(Paths.get(temporalPath));
result = copy(originalFile, temporalFile);
- if (result.isSuccess()) {
- if (temporalFile.length() == originalFile.length()) {
- channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
- fileLock = channel.tryLock();
- } else {
- result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED);
- }
+ if (!result.isSuccess()) return result;
+
+ if (temporalFile.length() != originalFile.length()) {
+ Log_OC.e(TAG, "temporal file and original file lengths are not same - result is LOCK_FAILED");
+ result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED);
}
+ filePath = temporalFile.toPath();
}
- try {
- size = channel.size();
- } catch (Exception exception) {
- Log_OC.e(TAG, "normalUpload, size cannot be determined from channel: " + exception);
- size = new File(mFile.getStoragePath()).length();
- }
+ // file exists in storage
+ try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
+ FileLock fileLock = null;
+ try {
+ // request a shared lock instead of exclusive one, since we are just reading file
+ fileLock = channel.tryLock(0L, Long.MAX_VALUE, true);
+ Log_OC.d(TAG ,"🔒" + "file locked");
+ } catch (OverlappingFileLockException e) {
+ Log_OC.e(TAG, "shared lock overlap detected; proceeding safely.");
+ }
- updateSize(size);
+ // determine size
+ long size;
+ try {
+ size = channel.size();
+ } catch (Exception e) {
+ Log_OC.e(TAG, "failed to determine file size from channel: ", e);
- // perform the upload
- if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
- boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
-
- mUploadOperation = new ChunkedFileUploadRemoteOperation(mFile.getStoragePath(),
- mFile.getRemotePath(),
- mFile.getMimeType(),
- mFile.getEtagInConflict(),
- lastModifiedTimestamp,
- creationTimestamp,
- onWifiConnection,
- mDisableRetries);
- } else {
- mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
- mFile.getRemotePath(),
- mFile.getMimeType(),
- mFile.getEtagInConflict(),
- lastModifiedTimestamp,
- creationTimestamp,
- mDisableRetries);
- }
+ try {
+ size = Files.size(filePath);
+ } catch (Exception exception) {
+ Log_OC.e(TAG, "failed to determine file size from nio.File: ", exception);
+ result = new RemoteOperationResult<>(ResultCode.FILE_NOT_FOUND);
+ return result;
+ }
+ }
- /**
- * Adds the onTransferProgress in FileUploadWorker
- * {@link FileUploadWorker#onTransferProgress(long, long, long, String)()}
- */
- for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
- mUploadOperation.addDataTransferProgressListener(mDataTransferListener);
- }
+ final var formattedFileSize = Formatter.formatFileSize(mContext, size);
+ updateSize(size);
+ Log_OC.d(TAG, "file size set to " + formattedFileSize);
- if (mCancellationRequested.get()) {
- throw new OperationCancelledException();
- }
+ // decide whether chunked or not
+ if (size > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) {
+ Log_OC.d(TAG, "chunked upload operation will be used");
+
+ boolean onWifiConnection = connectivityService.getConnectivity().isWifi();
+ mUploadOperation = new ChunkedFileUploadRemoteOperation(
+ mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimeType(),
+ mFile.getEtagInConflict(), lastModifiedTimestamp, creationTimestamp,
+ onWifiConnection, mDisableRetries);
+ } else {
+ Log_OC.d(TAG, "upload file operation will be used");
+
+ mUploadOperation = new UploadFileRemoteOperation(
+ mFile.getStoragePath(), mFile.getRemotePath(), mFile.getMimeType(),
+ mFile.getEtagInConflict(), lastModifiedTimestamp, creationTimestamp,
+ mDisableRetries);
+ }
- if (result.isSuccess() && mUploadOperation != null) {
- result = mUploadOperation.execute(client);
+ Log_OC.d(TAG, "upload type operation determined");
- /// move local temporal file or original file to its corresponding
+ /**
+ * Adds the onTransferProgress in FileUploadWorker
+ * {@link FileUploadWorker#onTransferProgress(long, long, long, String)()}
+ */
+ for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
+ mUploadOperation.addDataTransferProgressListener(mDataTransferListener);
+ }
+
+ if (mCancellationRequested.get()) {
+ Log_OC.e(TAG, "upload operation cancelled");
+ throw new OperationCancelledException();
+ }
+
+ // execute
+ if (result.isSuccess() && mUploadOperation != null) {
+ Log_OC.d(TAG, "upload operation completed");
+ result = mUploadOperation.execute(client);
+ }
+
+ // move local temporal file or original file to its corresponding
// location in the Nextcloud local folder
if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
+ Log_OC.e(TAG, "upload operation failed with SC_PRECONDITION_FAILED");
result = new RemoteOperationResult<>(ResultCode.SYNC_CONFLICT);
}
+
+ if (fileLock != null && fileLock.isValid()) {
+ fileLock.release();
+ Log_OC.d(TAG ,"🔓" + "file lock released");
+ }
}
} catch (FileNotFoundException e) {
- Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
+ Log_OC.e(TAG, "normalupload(): file not found exception");
result = new RemoteOperationResult<>(ResultCode.LOCAL_FILE_NOT_FOUND);
- } catch (OverlappingFileLockException e) {
- Log_OC.d(TAG, "Overlapping file lock exception");
- result = new RemoteOperationResult<>(ResultCode.LOCK_FAILED);
} catch (Exception e) {
+ Log_OC.e(TAG, "normalupload(): exception: ", e);
result = new RemoteOperationResult<>(e);
} finally {
- mUploadStarted.set(false);
+ Log_OC.d(TAG, "normalupload(): finally block");
- if (fileLock != null) {
- try {
- fileLock.release();
- } catch (IOException e) {
- Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath);
- }
- }
+ mUploadStarted.set(false);
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException e) {
- Log_OC.w(TAG, "Failed to close file channel");
+ // clean up temporal file if it exists
+ try {
+ if (temporalFile != null) {
+ if (temporalFile.exists() && !temporalFile.delete()) {
+ Log_OC.e(TAG, "Could not delete temporal file");
+ }
+ } else {
+ Log_OC.d(TAG, "temporal file is null - internal storage is used instead of sd-card");
}
- }
-
- if (temporalFile != null && !originalFile.equals(temporalFile)) {
- boolean isTempFileDeleted = temporalFile.delete();
- Log_OC.d(TAG, "normalUpload, temp folder deletion: " + isTempFileDeleted);
+ } catch (Exception e) {
+ Log_OC.e(TAG, "an exception occurred during deletion of temporal file: ", e);
}
if (result == null) {
+ Log_OC.e(TAG, "result is null, UNKNOWN_ERROR");
result = new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR);
}
logResult(result, mOriginalStoragePath, mRemotePath);
+ uploadsStorageManager.updateDatabaseUploadResult(result, this);
}
if (result.isSuccess()) {
@@ -1163,10 +1211,7 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) {
getStorageManager().saveConflict(mFile, mFile.getEtagInConflict());
}
- // delete temporal file
- if (temporalFile != null && temporalFile.exists() && !temporalFile.delete()) {
- Log_OC.e(TAG, "Could not delete temporal file " + temporalFile.getAbsolutePath());
- }
+ Log_OC.d(TAG, "returning normalupload() result");
return result;
}
@@ -1179,7 +1224,7 @@ private void updateSize(long size) {
}
}
- private void logResult(RemoteOperationResult result, String sourcePath, String targetPath) {
+ private void logResult(RemoteOperationResult> result, String sourcePath, String targetPath) {
if (result.isSuccess()) {
Log_OC.i(TAG, "Upload of " + sourcePath + " to " + targetPath + ": " + result.getLogMessage());
} else {
@@ -1212,7 +1257,7 @@ private RemoteOperationResult copyFile(File originalFile, String expectedPath) t
throw new OperationCancelledException();
}
- return new RemoteOperationResult(ResultCode.OK);
+ return new RemoteOperationResult<>(ResultCode.OK);
}
@CheckResult
@@ -1248,7 +1293,24 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile,
break;
case ASK_USER:
Log_OC.d(TAG, "Name collision; asking the user what to do");
- return new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+
+ // check if its real SYNC_CONFLICT
+ boolean isSameFileOnRemote = false;
+ if (mFile != null) {
+ String localPath = mFile.getStoragePath();
+
+ if (localPath != null) {
+ File localFile = new File(localPath);
+ isSameFileOnRemote = FileUploadHelper.Companion.instance()
+ .isSameFileOnRemote(user, localFile, mRemotePath, mContext);
+ }
+ }
+
+ if (isSameFileOnRemote) {
+ return new RemoteOperationResult<>(ResultCode.OK);
+ } else {
+ return new RemoteOperationResult<>(ResultCode.SYNC_CONFLICT);
+ }
}
}
@@ -1287,6 +1349,7 @@ private void handleLocalBehaviour(File temporalFile,
switch (mLocalBehaviour) {
case FileUploadWorker.LOCAL_BEHAVIOUR_DELETE:
+ Log_OC.d(TAG, "DELETE local behaviour will be handled");
try {
Files.delete(originalFile.toPath());
} catch (IOException e) {
@@ -1298,6 +1361,7 @@ private void handleLocalBehaviour(File temporalFile,
break;
case FileUploadWorker.LOCAL_BEHAVIOUR_COPY:
+ Log_OC.d(TAG, "COPY local behaviour will be handled");
if (temporalFile != null) {
try {
move(temporalFile, expectedFile);
@@ -1322,6 +1386,7 @@ private void handleLocalBehaviour(File temporalFile,
break;
case FileUploadWorker.LOCAL_BEHAVIOUR_MOVE:
+ Log_OC.d(TAG, "MOVE local behaviour will be handled");
String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
File newFile = new File(expectedPath);
@@ -1339,6 +1404,7 @@ private void handleLocalBehaviour(File temporalFile,
break;
default:
+ Log_OC.d(TAG, "DEFAULT local behaviour will be handled");
mFile.setStoragePath("");
saveUploadedFile(client);
break;
@@ -1359,9 +1425,9 @@ private OCCapability getCapabilities() {
* @param pathToGrant Full remote path whose existence will be granted.
* @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
*/
- private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) {
- RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
- RemoteOperationResult result = operation.execute(client);
+ private RemoteOperationResult> grantFolderExistence(String pathToGrant, OwnCloudClient client) {
+ var operation = new ExistenceCheckRemoteOperation(pathToGrant, false);
+ var result = operation.execute(client);
if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) {
SyncOperation syncOp = new CreateFolderOperation(pathToGrant, user, getContext(), getStorageManager());
result = syncOp.execute(client);
@@ -1372,9 +1438,9 @@ private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudC
parentDir = createLocalFolder(pathToGrant);
}
if (parentDir != null) {
- result = new RemoteOperationResult(ResultCode.OK);
+ result = new RemoteOperationResult<>(ResultCode.OK);
} else {
- result = new RemoteOperationResult(ResultCode.CANNOT_CREATE_FILE);
+ result = new RemoteOperationResult<>(ResultCode.CANNOT_CREATE_FILE);
}
}
return result;
@@ -1474,7 +1540,7 @@ private static boolean existsFile(OwnCloudClient client,
return false;
} else {
ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, false);
- RemoteOperationResult result = existsOperation.execute(client);
+ final var result = existsOperation.execute(client);
return result.isSuccess();
}
}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ChooseStorageLocationActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ChooseStorageLocationActivity.kt
deleted file mode 100644
index 8d13d53e7ddf..000000000000
--- a/app/src/main/java/com/owncloud/android/ui/activity/ChooseStorageLocationActivity.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2024 ZetaTom <70907959+ZetaTom@users.noreply.github.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-package com.owncloud.android.ui.activity
-
-import android.content.Intent
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import com.nextcloud.ui.ChooseStorageLocationDialogFragment
-
-class ChooseStorageLocationActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val chooseStorageLocationDialogFragment = ChooseStorageLocationDialogFragment.newInstance()
- supportFragmentManager.setFragmentResultListener(
- KEY_RESULT_STORAGE_LOCATION,
- this
- ) { _, result ->
- setResult(
- ChooseStorageLocationDialogFragment.STORAGE_LOCATION_RESULT_CODE,
- Intent().putExtra(
- KEY_RESULT_STORAGE_LOCATION,
- result.getString(KEY_RESULT_STORAGE_LOCATION)
- )
- )
- }
- chooseStorageLocationDialogFragment.show(supportFragmentManager, "choose_storage_location")
- }
-
- companion object {
- const val KEY_RESULT_STORAGE_LOCATION = ChooseStorageLocationDialogFragment.KEY_RESULT_STORAGE_LOCATION
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt
new file mode 100644
index 000000000000..d96c91f2c0c8
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt
@@ -0,0 +1,60 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog
+
+class ExtendedSettingsActivity : AppCompatActivity() {
+
+ private var dialogShown = false
+
+ @Suppress("ReturnCount")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ dialogShown = savedInstanceState.getBoolean(KEY_DIALOG_SHOWN, false)
+ }
+
+ if (dialogShown) {
+ return
+ }
+
+ val dialogKey = intent.getStringExtra(EXTRA_DIALOG_TYPE) ?: run {
+ finish()
+ return
+ }
+
+ val dialogType = ExtendedSettingsActivityDialog.entries.find { it.key == dialogKey } ?: run {
+ finish()
+ return
+ }
+
+ dialogType.showDialog(this)
+ dialogShown = true
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(KEY_DIALOG_SHOWN, dialogShown)
+ }
+
+ companion object {
+ private const val EXTRA_DIALOG_TYPE = "dialog_type"
+ private const val KEY_DIALOG_SHOWN = "dialog_shown"
+
+ fun createIntent(context: Context, dialogType: ExtendedSettingsActivityDialog): Intent =
+ Intent(context, ExtendedSettingsActivity::class.java).apply {
+ putExtra(EXTRA_DIALOG_TYPE, dialogType.key)
+ }
+ }
+}
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 f137102a71d7..95f0192d22dc 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
@@ -1935,18 +1935,17 @@ class FileDisplayActivity :
}
fun previewImageWithSearchContext(file: OCFile, searchFragment: Boolean, currentSearchType: SearchType?) {
- // preview image - it handles the download, if needed
- if (searchFragment) {
- val type = when (currentSearchType) {
+ val type = if (searchFragment) {
+ when (currentSearchType) {
SearchType.FAVORITE_SEARCH -> VirtualFolderType.FAVORITE
SearchType.GALLERY_SEARCH -> VirtualFolderType.GALLERY
else -> VirtualFolderType.NONE
}
-
- startImagePreview(file, type, file.isDown)
} else {
- startImagePreview(file, file.isDown)
+ null
}
+
+ startImagePreview(file, file.isDown, type)
}
fun previewFile(file: OCFile, setFabVisible: CompletionCallback?) {
@@ -2472,43 +2471,29 @@ class FileDisplayActivity :
}
}
- fun startImagePreview(file: OCFile, showPreview: Boolean) {
- val showDetailsIntent = Intent(this, PreviewImageActivity::class.java)
- showDetailsIntent.putExtra(EXTRA_FILE, file)
- showDetailsIntent.putExtra(EXTRA_LIVE_PHOTO_FILE, file.livePhotoVideo)
- showDetailsIntent.putExtra(
- EXTRA_USER,
- user.orElseThrow(Supplier { RuntimeException() })
- )
- if (showPreview) {
- startActivity(showDetailsIntent)
- } else {
- val fileOperationsHelper =
- FileOperationsHelper(this, userAccountManager, connectivityService, editorUtils)
- fileOperationsHelper.startSyncForFileAndIntent(file, showDetailsIntent)
+ fun startImagePreview(file: OCFile, showPreview: Boolean, type: VirtualFolderType? = null) {
+ if (user.isEmpty) {
+ Log_OC.e(TAG, "cannot start image preview")
+ return
}
- }
- fun startImagePreview(file: OCFile, type: VirtualFolderType?, showPreview: Boolean) {
- val showDetailsIntent = Intent(this, PreviewImageActivity::class.java)
- showDetailsIntent.putExtra(EXTRA_FILE, file)
- showDetailsIntent.putExtra(EXTRA_LIVE_PHOTO_FILE, file.livePhotoVideo)
- showDetailsIntent.putExtra(
- EXTRA_USER,
- user.orElseThrow(Supplier { RuntimeException() })
- )
- showDetailsIntent.putExtra(PreviewImageActivity.EXTRA_VIRTUAL_TYPE, type)
+ val intent = Intent(this, PreviewImageActivity::class.java).apply {
+ putExtra(EXTRA_FILE, file)
+ putExtra(EXTRA_LIVE_PHOTO_FILE, file.livePhotoVideo)
+ putExtra(EXTRA_USER, user.get())
+ type?.let { putExtra(PreviewImageActivity.EXTRA_VIRTUAL_TYPE, it) }
+ }
if (showPreview) {
- startActivity(showDetailsIntent)
+ startActivity(intent)
} else {
- val fileOperationsHelper = FileOperationsHelper(
+ val helper = FileOperationsHelper(
this,
userAccountManager,
connectivityService,
editorUtils
)
- fileOperationsHelper.startSyncForFileAndIntent(file, showDetailsIntent)
+ helper.startSyncForFileAndIntent(file, intent)
}
}
@@ -2772,8 +2757,8 @@ class FileDisplayActivity :
val virtualType = bundle.get(PreviewImageActivity.EXTRA_VIRTUAL_TYPE) as VirtualFolderType?
startImagePreview(
file,
- virtualType,
- true
+ true,
+ virtualType
)
} else {
startImagePreview(file, true)
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 ba6db7333a81..65e70c062977 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
@@ -70,6 +70,7 @@
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
+import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog;
import com.owncloud.android.utils.ClipboardUtil;
import com.owncloud.android.utils.DeviceCredentialUtils;
import com.owncloud.android.utils.DisplayUtils;
@@ -80,7 +81,6 @@
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
@@ -121,7 +121,6 @@ public class SettingsActivity extends PreferenceActivity
private static final int ACTION_REQUEST_CODE_DAVDROID_SETUP = 10;
private static final int ACTION_SHOW_MNEMONIC = 11;
private static final int ACTION_E2E = 12;
- private static final int ACTION_SET_STORAGE_LOCATION = 13;
private static final int TRUE_VALUE = 1;
private static final String DAV_PATH = "/remote.php/dav";
@@ -879,41 +878,42 @@ private void setupGeneralCategory() {
prefDataLoc = findPreference(AppPreferencesImpl.DATA_STORAGE_LOCATION);
if (prefDataLoc != null) {
prefDataLoc.setOnPreferenceClickListener(p -> {
- Intent intent = new Intent(MainApp.getAppContext(), ChooseStorageLocationActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- startActivityForResult(intent, ACTION_SET_STORAGE_LOCATION);
+ Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.StorageLocation);
+ startActivityForResult(intent, ExtendedSettingsActivityDialog.StorageLocation.getResultId());
return true;
});
}
- ListPreference themePref = (ListPreference) findPreference("darkMode");
+ final var themePref = findPreference("darkMode");
+ if (themePref != null) {
+ updateThemePreferenceSummary(preferences.getDarkThemeMode().name());
- List themeEntries = new ArrayList<>(3);
- themeEntries.add(getString(R.string.prefs_value_theme_light));
- themeEntries.add(getString(R.string.prefs_value_theme_dark));
- themeEntries.add(getString(R.string.prefs_value_theme_system));
-
- List themeValues = new ArrayList<>(3);
- themeValues.add(DarkMode.LIGHT.name());
- themeValues.add(DarkMode.DARK.name());
- themeValues.add(DarkMode.SYSTEM.name());
+ themePref.setOnPreferenceClickListener(preference -> {
+ Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.ThemeSelection);
+ startActivityForResult(intent, ExtendedSettingsActivityDialog.ThemeSelection.getResultId());
+ return true;
+ });
+ }
+ }
- themePref.setEntries(themeEntries.toArray(new String[0]));
- themePref.setEntryValues(themeValues.toArray(new String[0]));
+ private void updateThemePreferenceSummary(String themeValue) {
+ Preference themePref = findPreference("darkMode");
+ if (themePref == null) return;
- if (TextUtils.isEmpty(themePref.getEntry())) {
- themePref.setValue(DarkMode.SYSTEM.name());
- themePref.setSummary(TextUtils.isEmpty(themePref.getEntry()) ? DarkMode.SYSTEM.name() : themePref.getEntry());
+ DarkMode mode;
+ try {
+ mode = DarkMode.valueOf(themeValue);
+ } catch (IllegalArgumentException e) {
+ mode = DarkMode.SYSTEM;
}
- themePref.setOnPreferenceChangeListener((preference, newValue) -> {
- DarkMode mode = DarkMode.valueOf((String) newValue);
- preferences.setDarkThemeMode(mode);
- MainApp.setAppTheme(mode);
- setListBackground();
+ String summary = switch (mode) {
+ case LIGHT -> getString(R.string.prefs_value_theme_light);
+ case DARK -> getString(R.string.prefs_value_theme_dark);
+ default -> getString(R.string.prefs_value_theme_system);
+ };
- return true;
- });
+ themePref.setSummary(summary);
}
private void setListBackground() {
@@ -1052,14 +1052,21 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
} else if (requestCode == ACTION_E2E && data != null && data.getBooleanExtra(SetupEncryptionDialogFragment.SUCCESS, false)) {
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
- } else if (requestCode == ACTION_SET_STORAGE_LOCATION && data != null) {
- String newPath = data.getStringExtra(ChooseStorageLocationActivity.KEY_RESULT_STORAGE_LOCATION);
-
+ } else if (requestCode == ExtendedSettingsActivityDialog.StorageLocation.getResultId() && data != null) {
+ String newPath = data.getStringExtra(ExtendedSettingsActivityDialog.StorageLocation.getKey());
if (storagePath != null && !storagePath.equals(newPath)) {
StorageMigration storageMigration = new StorageMigration(this, user, storagePath, newPath, viewThemeUtils);
storageMigration.setStorageMigrationProgressListener(this);
storageMigration.migrate();
}
+ } else if (requestCode == ExtendedSettingsActivityDialog.ThemeSelection.getResultId() && data != null) {
+ String selectedTheme = data.getStringExtra(ExtendedSettingsActivityDialog.ThemeSelection.getKey());
+ if (selectedTheme != null) {
+ updateThemePreferenceSummary(selectedTheme);
+
+ // needed for to change status bar color
+ recreate();
+ }
} else if (requestCode == REQ_ALL_FILES_ACCESS) {
final PreferenceCategory preferenceCategorySync = (PreferenceCategory) findPreference("sync");
setupAllFilesAccessPreference(preferenceCategorySync);
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 c12acc61e2c5..d361e128f4c0 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
@@ -580,7 +580,7 @@ class SyncedFoldersActivity :
}
}
if (syncedFolderDisplayItem.isEnabled) {
- backgroundJobManager.startAutoUploadImmediately(syncedFolderDisplayItem, overridePowerSaving = false)
+ backgroundJobManager.startAutoUpload(syncedFolderDisplayItem, overridePowerSaving = false)
showBatteryOptimizationDialogIfNeeded()
}
}
@@ -743,7 +743,7 @@ class SyncedFoldersActivity :
// existing synced folder setup to be updated
syncedFolderProvider.updateSyncFolder(item)
if (item.isEnabled) {
- backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false)
+ backgroundJobManager.startAutoUpload(item, overridePowerSaving = false)
} else {
val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id
val arbitraryDataProvider =
@@ -760,7 +760,7 @@ class SyncedFoldersActivity :
if (storedId != -1L) {
item.id = storedId
if (item.isEnabled) {
- backgroundJobManager.startAutoUploadImmediately(item, overridePowerSaving = false)
+ backgroundJobManager.startAutoUpload(item, overridePowerSaving = false)
} else {
val syncedFolderInitiatedKey = KEY_SYNCED_FOLDER_INITIATED_PREFIX + item.id
arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey)
diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt
index 3ebe6b08d380..83b9d152a4dd 100644
--- a/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt
+++ b/app/src/main/java/com/owncloud/android/ui/adapter/LinkShareViewHolder.kt
@@ -134,11 +134,11 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite
if (TextUtils.isEmpty(permissionName) || (isSecureFileDrop(publicShare) && encrypted)) {
binding.permissionName.visibility = View.GONE
- return
+ } else {
+ binding.permissionName.visibility = View.VISIBLE
+ binding.permissionName.text = permissionName
}
- binding.permissionName.text = permissionName
- binding.permissionName.visibility = View.VISIBLE
viewThemeUtils?.androidx?.colorPrimaryTextViewElement(binding.permissionName)
}
diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/ThemeSelectionDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/ThemeSelectionDialog.kt
new file mode 100644
index 000000000000..1d282c6943ab
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/dialog/ThemeSelectionDialog.kt
@@ -0,0 +1,100 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.dialog
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.appcompat.app.AlertDialog
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.setFragmentResult
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.client.preferences.DarkMode
+import com.owncloud.android.MainApp
+import com.owncloud.android.R
+import com.owncloud.android.databinding.DialogThemeSelectionBinding
+import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import javax.inject.Inject
+
+class ThemeSelectionDialog :
+ DialogFragment(),
+ Injectable {
+
+ @Inject
+ lateinit var preferences: AppPreferences
+
+ @Inject
+ lateinit var viewThemeUtils: ViewThemeUtils
+
+ private lateinit var binding: DialogThemeSelectionBinding
+
+ override fun onStart() {
+ super.onStart()
+ val alertDialog = dialog as AlertDialog
+
+ val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton
+ positiveButton?.let {
+ viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogThemeSelectionBinding.inflate(layoutInflater)
+
+ val currentTheme = preferences.getDarkThemeMode() ?: DarkMode.SYSTEM
+ val radioGroup = binding.themeRadioGroup
+
+ viewThemeUtils.platform.run {
+ colorTextView(binding.dialogTitle)
+ themeRadioButton(binding.themeDark)
+ themeRadioButton(binding.themeLight)
+ themeRadioButton(binding.themeSystem)
+ }
+
+ when (currentTheme) {
+ DarkMode.LIGHT -> radioGroup.check(R.id.theme_light)
+ DarkMode.DARK -> radioGroup.check(R.id.theme_dark)
+ DarkMode.SYSTEM -> radioGroup.check(R.id.theme_system)
+ }
+
+ radioGroup.setOnCheckedChangeListener { _, checkedId ->
+ val selectedMode = when (checkedId) {
+ R.id.theme_light -> DarkMode.LIGHT
+ R.id.theme_dark -> DarkMode.DARK
+ R.id.theme_system -> DarkMode.SYSTEM
+ else -> DarkMode.SYSTEM
+ }
+
+ applyTheme(selectedMode)
+ }
+
+ val builder = MaterialAlertDialogBuilder(requireContext())
+ .setView(binding.root)
+ .setPositiveButton(R.string.common_ok) { _, _ ->
+ dismiss()
+ }
+
+ viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireContext(), builder)
+
+ return builder.create()
+ }
+
+ private fun applyTheme(mode: DarkMode) {
+ preferences.setDarkThemeMode(mode)
+ MainApp.setAppTheme(mode)
+
+ setFragmentResult(
+ ExtendedSettingsActivityDialog.ThemeSelection.key,
+ bundleOf(ExtendedSettingsActivityDialog.ThemeSelection.key to mode.name)
+ )
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/events/SearchEvent.kt b/app/src/main/java/com/owncloud/android/ui/events/SearchEvent.kt
index c0baa03e7f64..e7827d36eeff 100644
--- a/app/src/main/java/com/owncloud/android/ui/events/SearchEvent.kt
+++ b/app/src/main/java/com/owncloud/android/ui/events/SearchEvent.kt
@@ -33,6 +33,8 @@ data class SearchEvent(val searchQuery: String, val searchType: SearchRemoteOper
SearchRemoteOperation.SearchType.FAVORITE_SEARCH -> SearchType.FAVORITE_SEARCH
SearchRemoteOperation.SearchType.RECENTLY_MODIFIED_SEARCH -> SearchType.RECENTLY_MODIFIED_SEARCH
SearchRemoteOperation.SearchType.SHARED_FILTER -> SearchType.SHARED_FILTER
+ SearchRemoteOperation.SearchType.PHOTO_SEARCH -> SearchType.GALLERY_SEARCH
+ SearchRemoteOperation.SearchType.GALLERY_SEARCH -> SearchType.GALLERY_SEARCH
else -> null
}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
index 3fc30c1057bf..6be0df6ba90d 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java
@@ -299,6 +299,8 @@ private void setupView() {
viewThemeUtils.platform.colorImageView(binding.searchViewIcon, ColorRole.ON_SURFACE_VARIANT);
viewThemeUtils.platform.colorImageView(binding.pickContactEmailBtn, ColorRole.ON_SURFACE_VARIANT);
+ viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.sendCopyBtn);
+
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(binding.sharesListInternalShowAll);
viewThemeUtils.material.colorMaterialTextButton(binding.sharesListInternalShowAll);
binding.sharesListInternalShowAll.setOnClickListener(view -> {
diff --git a/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt b/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt
new file mode 100644
index 000000000000..4316d78f7755
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt
@@ -0,0 +1,50 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2026 Alper Ozturk
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.ui.model
+
+import android.app.Activity.RESULT_OK
+import android.content.Intent
+import com.nextcloud.ui.ChooseStorageLocationDialogFragment
+import com.owncloud.android.ui.activity.ExtendedSettingsActivity
+import com.owncloud.android.ui.dialog.ThemeSelectionDialog
+
+@Suppress("MagicNumber")
+enum class ExtendedSettingsActivityDialog(val tag: String, val key: String, val resultId: Int) {
+ StorageLocation("choose_storage_location", "storage_selection_result", 13),
+ ThemeSelection("theme_selection", "theme_selection_result", 14);
+
+ fun showDialog(activity: ExtendedSettingsActivity) {
+ activity.run {
+ if (supportFragmentManager.findFragmentByTag(tag) != null) {
+ return
+ }
+
+ supportFragmentManager.setFragmentResultListener(
+ key,
+ this
+ ) { _, result ->
+ setResult(
+ RESULT_OK,
+ Intent().putExtra(
+ key,
+ result.getString(key)
+ )
+ )
+ finish()
+ }
+
+ if (this@ExtendedSettingsActivityDialog == StorageLocation) {
+ ChooseStorageLocationDialogFragment()
+ } else {
+ ThemeSelectionDialog()
+ }.run {
+ show(supportFragmentManager, tag)
+ }
+ }
+ }
+}
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 018fdccc6001..ecd7ac821d0c 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
@@ -166,13 +166,8 @@ class PreviewImageActivity :
preferences
)
} else {
- // get parent from path
- var parentFolder = file?.let { storageManager.getFileById(it.parentId) }
-
- if (parentFolder == null) {
- // should not be necessary
- parentFolder = storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
- }
+ val parentFolder = file?.let { storageManager.getFileById(it.parentId) }
+ ?: storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
previewImagePagerAdapter = PreviewImagePagerAdapter(
this,
diff --git a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
index 6f8fdc011f09..e87ef399d635 100644
--- a/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
+++ b/app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt
@@ -250,7 +250,9 @@ class EncryptionUtilsV2 {
)
}
- verifyMetadata(metadataFile, decryptedFolderMetadataFile, oldCounter, signature)
+ if (!verifyMetadata(metadataFile, decryptedFolderMetadataFile, oldCounter, signature)) {
+ throw IllegalStateException("Metadata is corrupt!")
+ }
val transferredFiledrop = filesDropCountBefore > 0 &&
decryptedFolderMetadataFile.metadata.files.size == filesBefore + filesDropCountBefore
@@ -953,10 +955,10 @@ class EncryptionUtilsV2 {
decryptedFolderMetadataFile: DecryptedFolderMetadataFile,
oldCounter: Long,
signature: String
- ) {
+ ): Boolean {
if (decryptedFolderMetadataFile.metadata.counter < oldCounter) {
MainApp.showMessage(R.string.e2e_counter_too_old)
- return
+ return false
}
val message = EncryptionUtils.serializeJSON(encryptedFolderMetadataFile, true)
@@ -965,14 +967,15 @@ class EncryptionUtilsV2 {
if (certs.isNotEmpty() && !verifySignedData(signedData, certs)) {
MainApp.showMessage(R.string.e2e_signature_does_not_match)
- return
+ return false
}
val hashedMetadataKey = hashMetadataKey(decryptedFolderMetadataFile.metadata.metadataKey)
if (!decryptedFolderMetadataFile.metadata.keyChecksums.contains(hashedMetadataKey)) {
MainApp.showMessage(R.string.e2e_hash_not_found)
- return
+ return false
}
+ return true
}
private fun getSignedData(base64encodedSignature: String, message: String): CMSSignedData {
@@ -994,7 +997,7 @@ class EncryptionUtilsV2 {
return certs.any { cert ->
runCatching {
- signer.verify(verifierBuilder.build(cert))
+ signer.verify(verifierBuilder.build(cert.publicKey))
}.getOrElse {
Log_OC.e(TAG, "Exception verifySignedData: $it")
false
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 b9988cbdba1f..c31e21a6a48d 100644
--- a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
+++ b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.java
@@ -588,6 +588,11 @@ public static void checkIfFileFinishedSaving(OCFile file) {
* @return true if file itself or ancestor is encrypted
*/
public static boolean checkEncryptionStatus(OCFile file, FileDataStorageManager storageManager) {
+ if (file == null) {
+ Log_OC.e(TAG, "checkEncryptionStatus called with null file");
+ return false;
+ }
+
if (file.isEncrypted()) {
return true;
}
diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
deleted file mode 100644
index 60ac53201200..000000000000
--- a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Nextcloud - Android Client
- *
- * SPDX-FileCopyrightText: 2024 Jonas Mayer
- * SPDX-FileCopyrightText: 2020 Chris Narkiewicz
- * SPDX-FileCopyrightText: 2017 Mario Danic
- * SPDX-FileCopyrightText: 2017 Nextcloud GmbH
- * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
- */
-package com.owncloud.android.utils;
-
-import android.content.Context;
-
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.nextcloud.client.jobs.upload.FileUploadHelper;
-import com.nextcloud.client.network.ConnectivityService;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.datamodel.UploadsStorageManager;
-import com.owncloud.android.lib.common.utils.Log_OC;
-
-/**
- * Various utilities that make auto upload tick
- */
-public final class FilesSyncHelper {
- public static final String TAG = "FileSyncHelper";
-
- public static final String GLOBAL = "global";
-
- private FilesSyncHelper() {
- // utility class -> private constructor
- }
-
- public static void restartUploadsIfNeeded(final UploadsStorageManager uploadsStorageManager,
- final UserAccountManager accountManager,
- final ConnectivityService connectivityService,
- final PowerManagementService powerManagementService) {
- Log_OC.d(TAG, "restartUploadsIfNeeded, called");
- FileUploadHelper.Companion.instance().retryFailedUploads(
- uploadsStorageManager,
- connectivityService,
- accountManager,
- 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);
- }
- }
- if (context != null) {
- jobManager.scheduleContentObserverJob();
- } else {
- Log_OC.w(TAG, "cant scheduleContentObserverJob, context is null");
- }
- }
-
- 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.startAutoUploadImmediately(syncedFolder, overridePowerSaving, contentUris);
- }
- }
- }
-
- 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[]{});
- }
- }
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt
new file mode 100644
index 000000000000..8a2e12274acd
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.kt
@@ -0,0 +1,56 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Jonas Mayer
+ * SPDX-FileCopyrightText: 2020 Chris Narkiewicz
+ * SPDX-FileCopyrightText: 2017 Mario Danic
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
+ */
+package com.owncloud.android.utils
+
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.jobs.BackgroundJobManager
+import com.nextcloud.client.jobs.upload.FileUploadHelper.Companion.instance
+import com.nextcloud.client.network.ConnectivityService
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.lib.common.utils.Log_OC
+
+object FilesSyncHelper {
+ private const val TAG: String = "FileSyncHelper"
+ const val GLOBAL: String = "global"
+
+ @JvmStatic
+ fun restartUploadsIfNeeded(
+ uploadsStorageManager: UploadsStorageManager,
+ accountManager: UserAccountManager,
+ connectivityService: ConnectivityService,
+ powerManagementService: PowerManagementService
+ ) {
+ Log_OC.d(TAG, "restartUploadsIfNeeded, called")
+ instance().retryFailedUploads(
+ uploadsStorageManager,
+ connectivityService,
+ accountManager,
+ powerManagementService
+ )
+ }
+
+ @JvmStatic
+ fun startAutoUploadForEnabledSyncedFolders(
+ provider: SyncedFolderProvider,
+ manager: BackgroundJobManager,
+ uris: Array,
+ overridePowerSaving: Boolean
+ ) {
+ Log_OC.d(TAG, "start auto upload worker for each enabled folder")
+
+ provider.syncedFolders.forEach {
+ if (it.isEnabled) {
+ manager.startAutoUpload(it, overridePowerSaving, uris)
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/dialog_theme_selection.xml b/app/src/main/res/layout/dialog_theme_selection.xml
new file mode 100644
index 000000000000..447ccc7c83eb
--- /dev/null
+++ b/app/src/main/res/layout/dialog_theme_selection.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 8bc5eb8be1de..ddafa2b559d4 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
@@ -22,7 +22,7 @@
android:background="@drawable/round_bgnd"
android:contentDescription="@string/share"
android:id="@+id/copy_internal_link_icon"
- android:layout_gravity="top"
+ android:layout_gravity="center"
android:layout_height="@dimen/share_icon_size"
android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginStart="@dimen/standard_margin"
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 e7cf42512914..4e063e1a1ad2 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
@@ -63,22 +63,25 @@
-
+ app:iconTint="@color/secondary_text_color"
+ app:icon="@drawable/ic_content_copy"
+ app:iconGravity="textStart" />
-
+ app:icon="@drawable/ic_dots_vertical"
+ app:iconTint="@color/secondary_text_color"
+ app:iconGravity="textStart" />
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 facdb0cdaa42..0ae9e82c0588 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
@@ -51,24 +51,30 @@
-
+ app:icon="@drawable/ic_dots_vertical"
+ app:iconTint="@color/secondary_text_color"
+ app:iconGravity="textStart" />
diff --git a/app/src/main/res/layout/file_details_sharing_fragment.xml b/app/src/main/res/layout/file_details_sharing_fragment.xml
index 14a903dcd4ef..4ee05b28248a 100644
--- a/app/src/main/res/layout/file_details_sharing_fragment.xml
+++ b/app/src/main/res/layout/file_details_sharing_fragment.xml
@@ -136,8 +136,7 @@
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/standard_double_margin"
- android:layout_marginEnd="@dimen/standard_double_margin"
+ android:layout_marginHorizontal="@dimen/standard_margin"
android:layout_marginTop="@dimen/standard_half_padding"
android:layout_marginBottom="@dimen/standard_half_padding"
app:icon="@drawable/file_link"
diff --git a/app/src/main/res/menu/activity_logs.xml b/app/src/main/res/menu/activity_logs.xml
index 08a14cf62093..1fc5f4bbe9b6 100644
--- a/app/src/main/res/menu/activity_logs.xml
+++ b/app/src/main/res/menu/activity_logs.xml
@@ -25,7 +25,14 @@
android:title="@string/logs_menu_send"
app:showAsAction="never"
android:orderInCategory="200"
- android:icon="@drawable/ic_send"/>
+ android:icon="@drawable/ic_send" />
+
+
- السمة
الفسحة الزمنية
إدارة المجلدات الداخلية للمزامنة في الاتجاهين
- تمكين المزامنة في الاتجاهين
غامق
فاتح
اتبع النظام
@@ -990,7 +989,6 @@
حمل الملفات
كل عمليات الرفع مجمدة
زر إجراء رفع العنصر
- إلغاء الرفع
حذف
لا توجد تحميلات
قم بتحميل بعض الملفات أو قم بتفعيل التحميل التلقائي.
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index 4d9cc07ca1a9..e93297c10552 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -584,7 +584,6 @@
Nome de ficheru
Triba de ficheru
Toles descargues tán posaes
- Encaboxar xuba
Desaniciar
Nun hai xubes disponibles
L\'usuariu anuló la xuba
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 7d953b5f1232..10e3f74b5aeb 100644
--- a/app/src/main/res/values-b+en+001/strings.xml
+++ b/app/src/main/res/values-b+en+001/strings.xml
@@ -53,6 +53,7 @@
Delete task
Try sending a message to spark a conversation.
Hello there! What can I help you with today?
+ Please select task
Send message
Open conversation list
An error occurred while creating the task
@@ -117,7 +118,7 @@
/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
+ Uploading files from %1$s to %2$s
Configure
Create new custom folder setup
Set up a custom folder
@@ -223,7 +224,6 @@
Import failed to start. Please try again
No file found
Could not find your last backup!
- Detecting content changes
Failed to create conversation
Delete conversation
Failed to delete conversation
@@ -582,6 +582,7 @@
Log in
The link to your %1$s web interface when you open it in the browser.
Delete logs
+ Export logs
Refresh
Search logs
Send logs by email
@@ -767,7 +768,6 @@
Theme
Interval
Manage internal folders for two way sync
- Enable two way sync
Two way sync
Dark
Light
@@ -1016,6 +1016,12 @@
Thumbnail for new file
Loading is taking longer than expected
Today
+ Enter text to translate…
+ Translate from:
+ Translate to:
+ Press the button to translate
+ Translation is taking longer than expected.
+ Translating…
Deleted files
No deleted files
You will be able to recover deleted files from here.
@@ -1071,7 +1077,6 @@
Upload files
All uploads are paused
Upload item action button
- Cancel upload
Delete
No uploads available
Upload some content or activate auto upload.
@@ -1085,6 +1090,7 @@
App permissions
Your files cannot be uploaded without access to local storage. Tap to grant permission.
Upload Stopped – Storage Permission Required
+ You can remove or resume it from Uploads
%1$d / %2$d - %3$s
Encryption is only possible with >= Android 5.0
Insufficient space prevents copying the selected files into the %1$s folder. Would you like to move them there instead?
diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml
index 60730b04cb53..e023afebbafe 100644
--- a/app/src/main/res/values-bg-rBG/strings.xml
+++ b/app/src/main/res/values-bg-rBG/strings.xml
@@ -816,7 +816,6 @@
Въведете име на файл и тип на файл за качване
Качи файлове
Бутон за действие, за качване на елемент
- Откажи качването
Изтриване
Няма файлове
Качете съдържание или активирайте автоматичното качване.
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 130cb4106388..e90d223ee438 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -683,7 +683,6 @@
Tema
Interval
Gestioneu les carpetes internes per a la sincronització bidireccional
- Habilita la sincronització bidireccional
Fosc
Clar
Seguir al sistema
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index 3bb4b8a76ffb..f78074b036b4 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -53,6 +53,7 @@
Smazat úkol
Zkuste poslat zprávu pro rozproudění konverzace.
Zdravím! Jak vám mohu pomoci?
+ Vyberte úkol
Poslat zprávu
Otevřít seznam konverzací
Při vytváření úlohy se vyskytla chyba
@@ -117,7 +118,7 @@
/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
+ Nahrávání souborů z %1$s na %2$s
Nastavit
Vytvořit nové uživatelsky určené nastavení složky
Nastavit uživatelsky určenou složku
@@ -223,7 +224,6 @@
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
Vytváření konverzace se nezdařilo
Odstranit konverzaci
Odstranění konverzace se nezdařilo
@@ -582,6 +582,7 @@
Přihlásit
Odkaz na webové rozhraní vámi využívané instance %1$s, když ho otevíráte ve webovém prohlížeči.
Smazat záznamy událostí
+ Exportovat záznamy událostí
Načíst znovu
Prohledat záznamy událostí
Posílat záznamy událostí e-mailem
@@ -767,7 +768,6 @@
Motiv vzhledu
Interval
Spravovat interní složky pro dvoucestnou synchronizaci
- Zapnout obousměrnou synchronizaci
Obousměrná synchronizace
Tmavý
Světlý
@@ -1016,6 +1016,12 @@
Náhled pro nový soubor
Načítání trvá déle než očekáváno
Dnes
+ Zadejte text který přeložit…
+ Přeložit z:
+ Přeložit do:
+ Přeložíte kliknutím na tlačítko
+ Překládání trvá déle než očekáváno.
+ Překládání…
Smazané soubory
Žádné smazané soubory
Odsud je možné obnovit smazané soubory.
@@ -1071,7 +1077,6 @@
Nahrát soubory
Veškerá nahrávání jsou pozastavena
Tlačítko akce Nahrát položku
- Zrušit nahrávání
Smazat
Nic k nahrání
Nahrajte nějaký obsah nebo zapněte automatické nahrávání.
@@ -1085,6 +1090,7 @@
Oprávnění aplikace
Bez přístupu k lokálnímu úložišti není možné vaše soubory nahrávat. Klepnutím udělte oprávnění.
Nahrávání zastaveno – je zapotřebí oprávnění pro úložiště
+ Je možné odebrat nebo pokračovat z Nahrávání
%1$d / %2$d - %3$s
Šifrování je možné pouze na systému Android verze 5.0 a novějším
Pro zkopírování vybraných souborů do složky %1$s není dostatek volného místa. Chcete je tam namísto toho přesunout?
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 9e50d67eebb4..6dc2b6628854 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -101,7 +101,6 @@
/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
@@ -731,7 +730,6 @@
Tema
Interval
Styr interne mapper for to-vejs synkronisering
- Aktivér to-vejs synkronisering
Mørk
Lys
Følg system
@@ -1025,7 +1023,6 @@
Send filer
Alle uploads er pauserede
Send artikel knap
- Annullér upload
Slet
Ingen uploads tilgængelige
Upload noget indhold eller aktivér auto upload.
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 6552cfeab12d..cd3a2dcd9b70 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -53,6 +53,7 @@
Aufgabe löschen
Versuchen Sie, eine Nachricht zu senden, um eine Unterhaltung anzustoßen.
Hallo! Womit kann ich Ihnen heute helfen?
+ Bitte eine Aufgabe auswählen
Nachricht senden
Liste der Unterhaltungen öffnen
Es ist ein Fehler beim Erstellen der Aufgabe aufgetreten
@@ -117,7 +118,7 @@
/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
+ Lade Dateien von %1$s nach %2$s hoch
Einrichten
Erstellen Sie ein Setup für den eigenen Ordner
Erstellen Sie einen eigenen Ordner
@@ -223,7 +224,6 @@
Der Import konnte nicht gestartet werden. Bitte erneut versuchen
Keine Datei gefunden
Wir können Ihr letztes Backup nicht finden!
- Erkennen von Inhaltsänderungen
Unterhaltung konnte nicht erstellt werden
Unterhaltung löschen
Unterhaltung konnte nicht gelöscht werden
@@ -582,6 +582,7 @@
Anmelden
Der Link zu Ihrer %1$s Webseite, wenn Sie diese im Browser öffnen.
Lösche Logs
+ Protokolle exportieren
Aktualisieren
Suche Logs
Protokolldateien per E-Mail versenden
@@ -767,7 +768,6 @@
Design
Intervall
Interne Ordner für 2-Wege-Synchronisierung verwalten
- 2-Wege-Synchronisierung aktivieren
2-Wege-Synchronisierung
Dunkel
Hell
@@ -1016,6 +1016,12 @@
Miniaturbild für neue Datei
Laden dauert länger als erwartet
Heute
+ Text zum Übersetzen eingeben …
+ Übersetzen von:
+ Übersetzen nach:
+ Zum Übersetzen den Knopf drücken
+ Die Übersetzung dauert länger als erwartet.
+ Übersetze …
Gelöschte Dateien
Keine gelöschten Dateien
Hier können Sie gelöschte Dateien wiederherstellen.
@@ -1071,7 +1077,6 @@
Dateien hochladen
Alle Uploads wurden pausiert
Button, um Objekt hochzuladen
- Hochladen abbrechen
Löschen
Keine Uploads verfügbar
Laden Sie Inhalte hoch oder aktivieren Sie den Auto Upload.
@@ -1085,6 +1090,7 @@
App-Berechtigungen
Ohne Zugriff auf den lokalen Speicher können Ihre Dateien nicht hochgeladen werden. Tippen Sie, um die Berechtigung zu erteilen.
Hochladen gestoppt – Speicherberechtigung erforderlich
+ Sie können es entfernen oder aus den Uploads fortsetzen
%1$d / %2$d - %3$s
Verschlüsselung ist nur möglich mit >= Android 5.0
Es steht nicht genügend Speicherplatz zur Verfügung, um die ausgewählten Dateien in das Verzeichnis %1$s zu kopieren. Sollen diese stattdessen verschoben werden?
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 7bed658c0196..3e6be3b43641 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -824,7 +824,6 @@
Εισαγωγή ονόματος και τύπου αρχείου μεταφόρτωσης
Μεταφόρτωση αρχείων
Κουμπί μεταφόρτωσης αντικειμένου
- Ακύρωση μεταφόρτωσης
Διαγραφή
Μη διαθέσιμες μεταφορτώσεις
Μεταφορτώστε περιεχόμενο ή ενεργοποιήστε την αυτόματη μεταφόρτωση.
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index fed7ff75ea89..f609f31b1a02 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -572,7 +572,6 @@
Entajpu la alŝutotan dosiernomon kaj ĝian tipon
Alŝuti dosierojn
Butono pri alŝuto
- Nuligi alŝuton
Forigi
Neniu alŝuto disponeblas
Alŝutu ion aŭ aktivigu aŭtomatan alŝuton.
diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml
index aa373ac17397..2c4838a1f968 100644
--- a/app/src/main/res/values-es-rAR/strings.xml
+++ b/app/src/main/res/values-es-rAR/strings.xml
@@ -873,7 +873,6 @@
Subiendo archivos
Todas las cargas están pausadas
Subir botón de acción del elemento
- Cancelar carga
Eliminar
No hay cargas disponibles
Cargue algún contenido o active la carga automática
diff --git a/app/src/main/res/values-es-rCO/strings.xml b/app/src/main/res/values-es-rCO/strings.xml
index 9dea1687926f..0afcfaa7f12b 100644
--- a/app/src/main/res/values-es-rCO/strings.xml
+++ b/app/src/main/res/values-es-rCO/strings.xml
@@ -658,7 +658,6 @@
Ingresa el nombre y el tipo del archivo a cargar
Cargar archivos
Botón de cargar elemento
- Cancelar carga
Borrar
No hay cargas disponibles
Carga algún contenido o activa la carga automática
diff --git a/app/src/main/res/values-es-rEC/strings.xml b/app/src/main/res/values-es-rEC/strings.xml
index 80e8902fc444..205f4edff568 100644
--- a/app/src/main/res/values-es-rEC/strings.xml
+++ b/app/src/main/res/values-es-rEC/strings.xml
@@ -810,7 +810,6 @@
Ingresa el nombre y el tipo del archivo a cargar
Cargar archivos
Botón de cargar elemento
- Cancelar carga
Borrar
No hay cargas disponibles
Carga algún contenido o activa la carga automática
diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml
index 82ce268c8ed1..027ec545980c 100644
--- a/app/src/main/res/values-es-rMX/strings.xml
+++ b/app/src/main/res/values-es-rMX/strings.xml
@@ -889,7 +889,6 @@
Cargar archivos
Todas las cargas están pausadas
Botón de cargar elemento
- Cancelar carga
Eliminar
No hay cargas disponibles
Carga algún contenido o activa la carga automática
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 91036514847e..5cdb36e79ed2 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -13,6 +13,7 @@
Enviar/compartir
Vista en cuadrícula
Vista de lista
+ Acción ejecutada
Restaurar los contactos y el calendario
Nueva carpeta
Mover o copiar
@@ -45,21 +46,35 @@
Buscar en %s
Aparecer como desconectado
Esta salida fue generada mediante IA. Asegúrese de revisar de nuevo.
+ Fallo al mandar un mensaje
+ Fallo al cargar los mensajes del chat
+ Volver a la página de asistente
¿Está seguro de querer eliminar esta tarea?
Eliminar tarea
Intente enviar un mensaje para iniciar una conversación.
¡Hola!, ¿En qué te puedo ayudar hoy?
+ Selecciona una tarea
+ Enviar mensaje
+ Abrir la lista de chats
Ocurrió un error al crear la tarea
Se creó la tarea
Ocurrió un error al eliminar la tarea
+ Tarea borrada
+ Lista de tareas vacía. Comprueba la configuración de la app de asistente.
No se pudo obtener la lista de tareas, por favor, revise su conexión a internet.
Eliminar tarea
El resultado de la tarea no está listo aún.
+ Texto copiado desde otra app
Asistente
Entrada
Salida
+ fallado
funcionando
+ programado
+ exitosa
+ Estado de la tarea: %1$s
desconocido
+ Pensando …
¡Cuenta asociada no encontrada!
Acceso fallido: %1$s
La cuenta no se ha añadido aún en este dispositivo
@@ -96,11 +111,14 @@
Ha habido un problema al procesar su petición de acceso. Por favor, vuelva a intentarlo más tarde.
No hay un navegador disponible para abrir este enlace.
Por favor, complete el proceso de inicio de sesión en su navegador
+ Auto-Subida está pausada porque el Ahorro de Batería está activo.
se mantiene en la carpeta original, pues es de solo lectura
+ Batería baja, la subida podría tomar más tiempo
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
+ Esperando al Wi-Fi para iniciar la carga
+ Subiendo archivos desde %1$s a %2$s
Configurar
Crear nueva configuración de una carpeta especifica
Configure una carpeta especifica
@@ -206,9 +224,12 @@
La importación falló al iniciar. Por favor, intente de nuevo
No se ha encontrado ningún archivo
¡No podemos encontrar tu última copia de seguridad!
+ Fallo al crear la conversación
Borrar conversación
+ Fallo al borrar la conversación
No se han encontrado conversaciones
No hay conversaciones todavía
+ Fallo al cargar la lista de chats
Conversaciones
Copiado
Se ha producido un error al tratar de copiar este archivo o carpeta
@@ -310,9 +331,11 @@
El cifrado de extremo a extremo (E2E) todavía no está configurado
No es posible sin conexión a internet
La firma no coincide
+ No se pueden verificar los metadatos, la firma está vacía.
Asistente
Más
Más apps de Nextcloud
+ No se puede abrir el selector de archivos
Fallo al seleccionar dirección de correo.
Establecer como cifrado
No fue posible recuperar el certificado del servidor
@@ -382,8 +405,10 @@
No tiene permisos para crear o cargar archivos en esta carpeta.
Recursos compartidos externos
Añadir o subir
+ Fallo al crear el diálogo de conflicto
Error al enviar el fichero al gestor de descargas
Fallo al imprimir el archivo
+ ¡Fallo al iniciar la acción!
Fallo al inicial el editor
Fallo al actualizar la UI
Añadir a favoritos
@@ -415,6 +440,8 @@
No se han encontrado resultados para su consulta
Inicie su búsqueda
Escriba en la barra de búsqueda más arriba para encontrar archivos, contactos, eventos de calendario, y más en toda su cuenta.
+ Comprueba tu conexión a internet o inténtalo de nuevo más tarde
+ Conexión pobre
carpeta
EN VIVO
Cargando…
@@ -476,6 +503,11 @@
La carpeta ya existe
Esta carpeta se ve mejor en %1$s.
Crear
+ %1$d de %2$d · %3$s
+ Ha ocurrido un error durante la sincronización de la carpeta %s
+ Espacio insuficiente la sincronización se ha cancelado
+ %s carpetas sincronizadas
+ Sincronizando…
No hay carpetas aquí
El nombre de la carpeta no puede estar vacío
Seleccione
@@ -550,6 +582,7 @@
Iniciar sesión
El enlace a tu interface web %1$s cuando lo abres en tu navegador.
Eliminar registros
+ Exportar registros
Recargar
Buscar registros
Enviar los registros por email
@@ -609,6 +642,8 @@
Fallo al ejecutar la acción.
Muestra notificaciones para interactuar con el resultado de las operaciones en segundo plano
Operaciones en segundo plano
+ Detecta cambios en archivos locales
+ Vigilante del contenido
Muestra el progreso de la descarga
Descargas
Muestra progreso y resultados de la sincronización de archivos
@@ -632,6 +667,7 @@
No hay conexión a internet
Incluso sin una conexión a internet, puede organizar sus carpetas o crear archivos. Una vez vuelva a estar en línea, las acciones pendientes se sincronizarán automáticamente.
Está desconectado, pero el trabajo continúa
+ El archivo aún no existe. Sube el archivo primero por favor.
No se pudo crear %s. Un archivo con el mismo nombre existe en el servidor.
No se pudo crear %s. Una carpeta con el mismo nombre existe en el servidor.
La operación sin conexión no se ha podido completar. %s
@@ -647,6 +683,7 @@
Menú
Introduce tu código de acceso
Por favor introduzca su código de acceso
+ El código será requerido cada vez que se abra la app o se vuelva abrir tras 5 segundos.
Los códigos de acceso no son iguales
Por favor, vuelve a introducir tu código de acceso
Borra tu código de acceso
@@ -675,6 +712,8 @@
Renombrar la nueva versión
¿Cómo proceder si ya existe el archivo?
Agregar cuenta
+ Permitir a la app acceder y administrar todos los archivos de tu dispositivo
+ Acceso a todos los archivos
Sincronizar calendario y contactos
Ni F-Droid ni Google Play están instalados
Configurar DAVx⁵ (antes conocido como DAVdroid) (v1.3.0+) para la cuenta actual
@@ -729,7 +768,7 @@
Tema
Intervalo
Administrar carpetas internas para sincronización bidireccional
- Activar sincronización bidireccional
+ Sincronizar de dos formas
Oscuro
Claro
Seguir el del sistema
@@ -912,6 +951,10 @@
Almacenamiento interno
Películas
Música
+ Acceso a todos los archivos
+ Se requiere el permiso de Almacenamiento para la Auto-Subida.
+ Se requiere el permiso de Almacenamiento para subir archivos.
+ No preguntar
Medios en sólo lectura
Imágenes
La plataforma de productividad auto-hospedada que le mantiene en control.\n\nCaracterísticas;\n* Interfaz moderna y fácil de usar adecuada al tema de su servidor\n* Cargue archivos a su servidor Nextcloud\n* Comparta los mismos con otros\n* Mantenga sus archivos y carpetas favoritas sincronizados\n* Busque en todas las carpetas de su servidor\n* Carga automática para fotos y videos capturados en su dispositivo\n* Manténgase al día con notificaciones\n* Soporte para múltiples cuentas\n* Acceda a su información de forma segura usando su huella dactilar o PIN\n* Integración con DAVx5 (anteriormente conocido como DAVdroid) para configurar fácilmente la sincronización del calendario y contactos\n\nPor favor, reporte cualquier problema en https://github.com/nextcloud/android/issues y discuta acerca de esta aplicación en https://help.nextcloud.com/c/clients/android\n\n¿Es Ud. nuevo en Nextcloud? Nextcloud es un servidor privado para sincronizar y compartir archivos, y comunicación. Es un programa de uso libre, y lo puede hospedar Ud. mismo o pagar a una compañía para hacerlo por Ud. De esa manera, Ud. está en control de sus fotos, su calendario e información de sus contactos, sus documentos y todo lo demás.\n\nConozca Nextcloud en https://nextcloud.com
@@ -932,6 +975,9 @@
Sugerir
Sincronizar
Sincronizar de todas formas
+ Resolver conflictos
+ Se han detectado conflictos en la carga. Abre para resolverlo.
+ Conflictos en la carga de archivos
Se han encontrado conflictos
La carpeta %1$s ya no existe
Duplicación de sincronización
@@ -970,6 +1016,12 @@
Miniatura para el archivo nuevo
La carga está tardando más de lo esperado
Hoy
+ Introduce el texto para traducir…
+ Traducir desde:
+ Traducir a:
+ Pulsa el botón para traducir
+ La traducción está tardando más de lo esperado.
+ Traduciendo…
Archivos eliminados
No hay archivos eliminados
Podrás recuperar los archivos eliminados desde aquí.
@@ -986,6 +1038,7 @@
Evento no encontrado, siempre puede sincronizar para actualizar. Redirigiendo a la web…
Contacto no encontrado, siempre puede sincronizar para actualizar. Redirigiendo a la web…
Se requieren permisos para abrir el resultado de la búsqueda. De otra forma, se redirigirá a la web…
+ En esta carpeta
Desconocido
Desbloquear archivo
Hay comentarios no leídos
@@ -1024,7 +1077,6 @@
Subir archivos
Todas las cargas están pausadas
Botón de acción de subida de objeto
- Cancelar la subida
Eliminar
No hay subidas disponibles
Sube algún contenido o active la subida automática.
@@ -1038,6 +1090,7 @@
Permisos de la App
Sus archivos no pueden ser subidos sin acceso al almacenamiento local. Toque para conceder los permisos.
Subida Detenida – Se Requieren Permisos para el Almacenamiento
+ Puedes borrar o continuar desde Cargas
%1$d / %2$d - %3$s
El cifrado solo es posible con >= Android 5.0
No hay suficiente espacio para copiar los archivos seleccionados en la carpeta %1$s. En su lugar, ¿le gustaría moverlos?
@@ -1092,6 +1145,8 @@
El certificado del servidor no es de confianza
Obteniendo la versión del servidor…
La aplicación se ha interrumpido
+ Saltado
+ Un archivo con el mismo nombre ya existe.
Completado
Se encontró el mismo archivo en el remoto, omitiendo subida
Error desconocido
@@ -1195,6 +1250,11 @@
- %d archivos exportados, el resto han sido omitidos por un error
- %d archivos exportados, el resto han sido omitidos por un error
+
+ - Puedes subir solo %d archivo a la vez.
+ - Puedes subir hasta %d archivos a la vez.
+ - Puedes subir hasta %d archivos a la vez.
+
- %1$d carpeta
- %1$d carpetas
diff --git a/app/src/main/res/values-et-rEE/strings.xml b/app/src/main/res/values-et-rEE/strings.xml
index 48618ed0cb59..bac7c2993a9b 100644
--- a/app/src/main/res/values-et-rEE/strings.xml
+++ b/app/src/main/res/values-et-rEE/strings.xml
@@ -51,8 +51,9 @@
Tagasi Abilise lehele
Kas sa oled kindel, et soovid selle ülesande kustutada?
Kustuta ülesanne
- Vestluse algatamiseks proovi sata üks sõnum.
+ Vestluse algatamiseks proovi saata üks sõnum.
Hei! Kuidas saan sind täna aidata?
+ Palun vali ülesanne
Saada sõnum
Ava vestluste loend
Ülesande loomisel tekkis viga
@@ -117,7 +118,7 @@
/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
+ Laadin faile üles: „%1$s“ → „%2$s“
Seadista
Loo uus kohandatud kausta seadistus
Seadista kohandatud kaust
@@ -223,7 +224,6 @@
Ei õnnestunud käivitada importimist. Palun proovi uuesti
Faili ei leitud
Sinu viimast varukoopiat ei leidu!
- Tuvastan sisu muudatusi
Vestluse loomine ei õnnestunud
Kustuta vestlus
Vestluse kustutamine ei õnnestunud
@@ -767,7 +767,6 @@
Teema
Välp
Halda sisemisi kaustu kahepoolse sünkroonimise jaoks
- Luba kahepoolne sünkroonimine
Kahepoolne sünkroonimine
Hele
Tume
@@ -1016,6 +1015,12 @@
Uue faili pisipilt
Laadimiseks kulub rohkem aega, kui peaks
Täna
+ Sisesta tõlgitav tekst…
+ Lähtekeel tõlkimisel:
+ Tõlke sihtkeel:
+ Tõlkimiseks klõpsa nuppu
+ Tõlkimiseks kulub rohkem aega, kui peaks…
+ Tõlgin…
Kustutatud failid
Kustutatud faile pole
Siit saad kustutatud faile taastada.
@@ -1071,7 +1076,6 @@
Laadi failid üles
Kõik üleslaadimised on peatatud
Tegevusnupp üleslaadimisel
- Tühista üleslaadimine
Kustuta
Üleslaadimisi pole saadaval
Laadi üles mõned failid või lülita sisse automaatne üleslaadimine.
@@ -1085,6 +1089,7 @@
Rakenduse õigused
Sinu faile ei saa kohalikust andmeruumist üles laadida. Klõpsa õiguste lubamiseks.
Üleslaadimine on peatunud - vajalikud on andmeruumi kasutamise õigused
+ Sa saad ta eemaldada või jätkata Üleslaadimiste kaustast
%1$d / %2$d - %3$s
Krüptimine on võimalik vaid >= Android 5.0 puhul
Piisava ruumi puudumisel pole võimalik kopeerida valitud faile „%1$s“ kausta. Kas pigem tahaksid nad sinna teisaldada?
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index c900e5d952fc..55ea73e60929 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -700,7 +700,6 @@
Gaia
Tarteak
Kudeatu barruko karpetak bi norabideko sinkronizaziorako
- Gaitu bi norabideko sinkronizazioa
Iluna
Argia
Sistemaren berdina
@@ -986,7 +985,6 @@
Igo fitxategiak
Igoera guztiak pausatuta daude
Elementua igotzeko ekintza botoia
- Ezeztatu kargatzea
Ezabatu
Ezin dira fitxategiak igo
Edukiak igo edo auto-igotzea aktiba ezazu.
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 1ae77810a6fb..c9d96d49c8d1 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -869,7 +869,6 @@
بارگذاری فایل ها
همه آپلودها به طور موقت متوقف شدند
دکمه فعالسازی موارد بارگذاری
- متوقف کردن بار گذاری
حذف
هیچ بارگذاری وجود ندارد
تعدادی عکس آپلود کنید یا آپلود خودکار را فعال کنید
diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml
index 73e725beb8be..d718f7fc2684 100644
--- a/app/src/main/res/values-fi-rFI/strings.xml
+++ b/app/src/main/res/values-fi-rFI/strings.xml
@@ -191,7 +191,6 @@
Tuonti on ajastettu ja se alkaa pian
Tiedostoa ei löytynyt
Viimeisintä varmuuskopiota ei löytynyt!
- Havaitaan sisältömuutoksia
Keskustelun luominen epäonnistui
Poista keskustelu
Keskustelun poistaminen epäonnistui
@@ -941,7 +940,6 @@ GNU yleinen lisenssi, versio 2
Lähetä tiedostoja
Kaikki lähetykset on tauotettu
Lataa tiedosto nappi
- Peruuta lähetys
Poista
Ei lähetettäviä saatavilla
Lähetä sisältöä tai aktivoi automaattinen lähetys.
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index a21e2d1e9dc0..cb269a8169cf 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -13,6 +13,7 @@
Envoyer / Partager
Vue en grille
Vue en liste
+ Action déclenchée
Restaurer les contacts et l\'agenda
Nouveau dossier
Déplacer ou copier
@@ -51,19 +52,26 @@
Supprimer la tâche
Essayez d\'envoyer un message pour déclencher une conversation.
Bonjour ! En quoi puis-je vous aider aujourd’hui ?
+ Veuillez sélectionner une tâche
+ Envoyer un message
+ Ouvrir la liste des conversations
Une erreur est survenue lors de la création de la tâche
Tâche créée
Une erreur est survenue lors de la suppression de la tâche
+ Tâche supprimée
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.
+ Texte copié depuis une autre application
Assistant
Entrée
Sortie
défectueux
en cours
planifié
+ réussite
+ Etat de la tâche : %1$s
Inconnu
En cours de réflexion...
Compte associé introuvable !
@@ -109,7 +117,7 @@
/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
+ Téléversement des fichiers de %1$s vers %2$s
Configurer
Créer une nouvelle configuration de dossier personnalisé
Définir un dossier personnalisé
@@ -215,7 +223,6 @@
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
Échec de la conversation
Supprimer la conversation
Impossible de supprimer la conversation
@@ -327,6 +334,7 @@
Assistant
Plus
Plus d\'applications Nextcloud
+ Impossible d\'ouvrir le sélecteur de fichiers
Le choix de l\'adresse e-mail a échoué.
Définir comme chiffré
Impossible de retrouver le certificat du serveur
@@ -398,6 +406,7 @@
Ajouter ou téléverser
Transmission du fichier au gestionnaire de téléchargement échoué
Impossible d\'imprimer le fichier
+ Impossible de lancer l\'action !
Impossible de lancer l\'éditeur
Échec de la mise à jour de l\'interface utilisateur
Ajouter aux favoris
@@ -755,7 +764,6 @@
Thème
Intervalle
Gestion les dossiers internes pour une synchronisation bidirectionnelle
- Activer la synchronisation bidirectionnelle
Synchronisation bidirectionnelle
Sombre
Clair
@@ -827,6 +835,7 @@
Veuillez sélectionner un modèle
Sélectionnez un modèle
Envoyer
+ Envoyer une copie à
Envoyer Partager
Icône du bouton d\'envoi
Impossible de charger le contenu
@@ -1002,6 +1011,12 @@
Vignette pour nouveau fichier
Le chargement prend plus de temps que prévu
Aujourd\'hui
+ Entrez du texte à traduire...
+ Traduire depuis :
+ Traduire vers :
+ Appuyez sur le bouton pour traduire
+ La traduction prend plus de temps que prévu.
+ Traduction en cours...
Fichiers supprimés
Aucun fichier supprimé
Les fichiers supprimés s\'afficheront ici et vous pourrez les restaurer.
@@ -1057,7 +1072,6 @@
Téléverser des fichiers
Tous les téléversements sont suspendus
Bouton d\'action d\'upload
- Annuler l\'envoi
Supprimer
Aucun téléversement disponible
Téléversez du contenu ou activez le téléversement automatique.
diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml
index c188fd153d83..b801efe58684 100644
--- a/app/src/main/res/values-ga/strings.xml
+++ b/app/src/main/res/values-ga/strings.xml
@@ -53,6 +53,7 @@
Scrios tasc
Bain triail as teachtaireacht a sheoladh chun tús a chur le comhrá.
Dia duit ann! Cad is féidir liom cabhrú leat inniu?
+ Roghnaigh tasc le do thoil
Seol teachtaireacht
Oscail an liosta comhrá
Tharla earráid agus an tasc á chruthú
@@ -117,13 +118,13 @@
/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
+ Ag uaslódáil comhad ó %1$s go %2$s
Cumraigh
Cruthaigh socrú fillteán saincheaptha nua
Socraigh fillteán saincheaptha
Díchumasaigh seiceáil sábhála cumhachta
Folaigh fillteán
- Avatar
+ Abhatár
Amach
Socruithe cúltaca
Teagmhálacha agus cúltaca féilire
@@ -223,7 +224,6 @@
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
Theip ar chomhrá a chruthú
Scrios an comhrá
Theip ar an gcomhrá a scriosadh
@@ -582,13 +582,14 @@
Logáil isteach
An nasc chuig do chomhéadan gréasáin %1$s nuair a osclaíonn tú é sa bhrabhsálaí.
Scrios logs
+ Easpórtáil logaí
Athnuaigh
Logchomhaid chuardaigh
Seol logs trí ríomhphost
Logchomhaid: %1$d kB, freagraíodh an cheist %2$d / %3$d i %4$d ms
Á lódáil…
Logchomhaid: %1$d kB, gan scagaire
- Logs
+ Logaí
Tá an freastalaí i mód cothabhála
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.
@@ -719,7 +720,7 @@
Féilire agus sioncrónú teagmhálaithe socraithe
Faoi
Sonraí
- Dev
+ Forbróir
Comhaid
Ginearálta
Tuilleadh
@@ -767,7 +768,6 @@
Téama
Eatramh
Bainistigh fillteáin inmheánacha le haghaidh sioncronaithe dhá bhealach
- Cumasaigh sioncronú dhá bhealach
Sioncrónú dhá bhealach
Dorcha
Solas
@@ -983,7 +983,7 @@
Sioncronaigh dúbailt
Níorbh fhéidir %1$s a shioncronú
Pasfhocal mícheart le haghaidh %1$s
- Kept-in-sync files failed
+ Theip ar chomhaid a choinneáil sioncrónaithe
Theip ar shioncronú
Theip ar shioncronú, logáil isteach arís
Tá ábhar an chomhaid sioncronaithe cheana féin
@@ -1016,6 +1016,12 @@
Mionsamhail don chomhad nua
Tógann an luchtú níos faide ná mar a ceapadh
Inniu
+ Cuir isteach téacs le haistriú…
+ Aistrigh ó:
+ Aistrigh go:
+ Brúigh an cnaipe chun aistriúchán a dhéanamh
+ Tá an t-aistriúchán ag glacadh níos faide ná mar a bhíothas ag súil leis.
+ Ag aistriú…
Comhaid scriosta
Níl aon chomhaid scriosta
Beidh tú in ann comhaid a scriosadh a ghnóthú ó anseo.
@@ -1071,7 +1077,6 @@
Uaslódáil comhaid
Tá gach uaslódáil curtha ar sos
Íoslódáil an cnaipe gníomh
- Cealaigh an uaslódáil
Scrios
Níl aon uaslódála ar fáil
Uaslódáil roinnt ábhar nó cuir an uaslódáil uathoibríoch i ngníomh.
@@ -1085,6 +1090,7 @@
Ceadanna aipeanna
Ní féidir do chuid comhad a uaslódáil gan rochtain ar stóras áitiúil. Tapáil chun cead a dheonú.
Uaslódáil Stoptha – Cead Stórála Riachtanach
+ Is féidir leat é a bhaint nó a atosú ó Uaslódálacha
%1$d / %2$d - %3$s
Ní féidir criptiúchán ach amháin le 1>= Android 5.0
Ní bheidh dóthain spáis ann na comhaid roghnaithe a chóipeáil isteach san fhillteán %1$s. Ar mhaith leat iad a bhogadh ansin ina n-ionad?
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index d23c82ec7ddb..fd0c7383b839 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -53,6 +53,7 @@
Eliminar tarefa
Probe a enviar unha mensaxe para iniciar unha conversa.
Ola! En que podo axudarlle hoxe?
+ Seleccione a tarefa
Enviar a mensaxe
Lista de conversas abertas
Produciuse un erro ao crear a tarefa
@@ -117,7 +118,7 @@
/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
+ Enviar ficheiros desde %1$s cara a %2$s
Configurar
Crear un cartafol personalizado novo
Configurar un cartafol personalizado
@@ -223,7 +224,6 @@
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
Produciuse un fallo ao crear a conversa
Eliminar a conversa
Produciuse un fallo ao eliminar a conversa
@@ -582,6 +582,7 @@
Acceder
A ligazón á súa interface web %1$s cando a abre no navegador.
Eliminar rexistros
+ Exportar os rexistros
Actualizar
Buscar rexistros
Enviar rexistros por correo-e
@@ -768,7 +769,6 @@
Tema
Intervalo
Xestionar os cartafoles internos para a sincronización bidireccional
- Activar a sincronización bidireccional
Sincronización bidireccional
Escuro
Claro
@@ -1017,6 +1017,12 @@
Miniatura do novo ficheiro
A carga está a levar máis tempo do agardado
Hoxe
+ Introduza o texto a traducir
+ Traducir desde:
+ Traducir a:
+ Prema no botón para traducir
+ A tradución está a levar máis tempo do agardado.
+ Traducindo…
Ficheiros eliminados
Non hai ficheiros eliminados
Poderá recuperar ficheiros eliminados de aquí.
@@ -1072,7 +1078,6 @@
Enviar ficheiros
Todos os envíos están en pausa
Botón da acción do envío de elemento
- Cancelar o envío
Eliminar
Non hai envíos dispoñíbeis
Envíe algún contido ou active o envío automático.
@@ -1086,6 +1091,7 @@
Permisos da aplicación
Non é posíbel enviar os ficheiros cargar sen acceso ao almacenamento local. Toque para conceder permiso.
Envío detido — É necesario permiso de almacenamento
+ Pode retiralo ou retomalo desde Envíos
%1$d / %2$d - %3$s
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?
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index d5774bea2fa8..ae733ba72277 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -13,6 +13,8 @@
Šalji/dijeli
Prikaz rešetke
Prikaz popisa
+ Radnja je pokrenuta
+ Vrati kontakte i kalendar
Nova mapa
Premjesti ili kopiraj
Otvori s
@@ -28,14 +30,51 @@
Pošalji poveznicu na...
Aktivnost
Dodaj novu poveznicu za javno dijeljenje
+ Dodaj novi sigurni prijenos datoteka
Dodaj na %1$s
Osnovni URL
+ Onemogući međuspremnik
+ Onemogući uvod
+ Onemogući zapisnik
+ Onemogući vanjske stranice
+ Onemogući više računa
+ Onemogući dijeljenje
+ Prisili zaštitu
+ Naziv proxy poslužitelja
Proxy port
+ Prikazuje jedan widget s nadzorne ploče
Traži u %s
Prikaži se kao izvan mreže
+ Prikazani izlaz generirala je umjetna inteligencija. Obavezno ga uvijek provjerite.
+ Slanje poruke nije uspjelo
+ Dohvat poruka chata nije uspio
+ Natrag na stranicu asistenta
+ Jeste li sigurni da želite izbrisati ovaj zadatak?
Izbriši zadatak
+ Pošaljite poruku kako biste započeli razgovor.
+ Bok! Kako vam mogu pomoći danas?
+ Odaberite zadatak
+ Pošalji poruku
+ Otvori popis razgovora
+ Došlo je do pogreške pri stvaranju zadatka
+ Zadatak je stvoren
+ Došlo je do pogreške pri brisanju zadatka
+ Zadatak je izbrisan
+ Popis zadataka je prazan. Provjerite konfiguraciju aplikacije Asistent.
+ Nije moguće dohvatiti popis zadataka, provjerite internetsku vezu.
+ Izbriši zadatak
+ Izlaz zadatka još nije spreman.
+ Tekst je kopiran iz druge aplikacije
+ Asistent
+ Ulaz
+ Izlaz
+ neuspješno
radi
+ zakazano
+ uspješno
+ Status zadatka: %1$s
nepoznato
+ Razmišljam …
Pripadajući račun nije pronađen!
Neuspjeli pristup: %1$s
Račun još nije dodan na ovaj uređaj
@@ -66,9 +105,20 @@
Nije moguće pronaći računalo
%1$s ne podržava višestruke račune
Nije moguće uspostaviti vezu
+ Odustani od prijave
+ Unesite valjanu adresu poslužitelja.
+ Nije moguće dohvatiti podatke za prijavu. Pokušajte ponovno.
+ Došlo je do problema pri obradi zahtjeva za prijavu. Pokušajte ponovno kasnije.
+ Nema dostupnog preglednika za otvaranje ove poveznice.
+ Dovršite postupak prijave u pregledniku
+ Automatski prijenos je pauziran jer je uključen štedni način rada baterije.
nalazi se u izvornoj mapi jer je samo za čitanje
+ Slaba baterija, prijenos bi mogao potrajati dulje
Otpremite samo putem nemjerene bežične (Wi-Fi) mreže
/AutoUpload
+ Ova je mapa već uključena u sinkronizaciju nadređene mape, što može uzrokovati dvostruke prijenose
+ Čeka se Wi‑Fi za početak prijenosa
+ Prijenos datoteka iz %1$s u %2$s
Konfiguriraj
Stvorite novu postavku za prilagođenu mapu
Postavite prilagođenu mapu
@@ -77,10 +127,12 @@
Avatar
Odsutan
Postavke sigurnosne kopije
+ Sigurnosna kopija kontakata i kalendara
Zatvori
Onemogući
Možda je omogućena optimizacija rada baterije na uređaju. Mogućnost automatskog otpremanja AutoUpload djeluje samo ako izuzmete ovu aplikaciju iz optimizacije baterije.
Optimizacija baterije
+ Asistent
Favoriti
Sve datoteke
Medij
@@ -90,11 +142,13 @@
Cancel
Došlo je do poteškoća prilikom učitavanja vjerodajnice.
Zapis promjena razvojne inačice
+ Provjerite kasnije ili ponovno učitajte.
Potvrdni okvir
Odaberite lokalnu mapu...
Odaberite udaljenu mapu...
Odaberite predložak i unesite naziv datoteke.
Odaberite datoteku koju želite zadržati!
+ Odaberite widget
Izbriši
Brisanje obavijesti nije uspjelo.
Izbriši status nakon
@@ -116,6 +170,7 @@
Učitavanje…
Dalje
Ne
+ Sada
U redu
Na čekanju
Izbriši
@@ -144,26 +199,39 @@
Pronašli ste nedostatak? Pogrešku u radu?
Pomozite nam testiranjem
Prijavite problem na GitHubu
+ Nextcloud Asistent
Konfiguriraj
+ Ukloni lokalno šifriranje
Želite li zaista izbrisati %1$s?
Želite li zaista izbrisati odabrane stavke?
Želite li zaista izbrisati %1$s i pripadajući sadržaj?
Želite li zaista izbrisati odabrane stavke i pripadajući sadržaj?
Samo lokalno
+ Nije moguće stvoriti dijalog za rješavanje sukoba
+ Sukob mape
Lokalna datoteka
Ako odaberete obje inačice, lokalna će datoteka uz naziv imati i broj.
+ Ako odaberete obje verzije, lokalnoj će se mapi u naziv dodati broj.
Datoteka na poslužitelju
+ Sigurnosna kopija kontakata
+ Potrebno je dopuštenje za kontakte.
Ikona korisnika za popis kontakata
Nema dopuštenja, ništa nije uvezeno.
Kontakti
Sigurnosno kopiraj
Sigurnosno kopiranje je zakazano i počet će uskoro
Uvoz je zakazan i počet će uskoro
+ Uvoz se nije uspio pokrenuti. Pokušajte ponovno
Nije pronađena datoteka
Nije moguće pronaći zadnju sigurnosnu kopiju!
+ Nije uspjelo stvaranje razgovora
Obriši razgovor
+ Nije uspjelo brisanje razgovora
Nije pronađen nijedan razgovor
+ Još nema razgovora
+ Nije uspio dohvat popisa razgovora
Razgovori
+ Kopirano
Došlo je do pogreške prilikom kopiranja ove datoteke ili mape
Nije moguće kopirati mapu u jednu od svojih osnovnih mapa
Datoteka je već prisutna u odredišnoj mapi
@@ -175,11 +243,14 @@
Nije moguće dohvatiti URL
Stvori
Nije moguće stvoriti mapu
+ Stvori poveznicu
Novo
Novi dokument
Nova mapa
Nova prezentacija
Nova proračunska tablica
+ Dodaj opis mape
+ Dodaje opis mape
Vjerodajnice onemogućene
Svakodnevno sigurnosno kopiranje
Podaci za sigurnosno kopiranje
@@ -196,13 +267,28 @@
Provjera postojanja duplikata nije izvršena.
Algoritam sažetka nije dostupan na vašem uređaju.
Prijava putem izravne poveznice nije uspjela!
+ Prijavite se s %1$s na %2$s
Onemogući
Zanemari
Zanemari obavijest
+ Prikazuje vašu šifru od 12 riječi
Ne ometaj
+ Više slika
+ PDF datoteka
+ Odaberite vrstu izvoza
+ Stvaranje PDF-a nije uspjelo
+ Stvaranje PDF-a…
+ Ovdje nije moguće stvarati stavke: nedostaje dopuštenje za stvaranje.
+ Nije moguće stvoriti datoteku: nedostaje dopuštenje.
+ Nije moguće stvoriti mapu: nedostaje dopuštenje.
+ Nije moguće izbrisati stavku: nedostaje dopuštenje za brisanje.
+ Nije moguće premjestiti stavku: nedostaje dopuštenje za premještanje.
+ Nije moguće otvoriti stavku: nedostaje dopuštenje za čitanje.
+ Nije moguće preimenovati stavku: nedostaje dopuštenje za preimenovanje.
Gotovo
Ne briši
Nije moguće stvoriti lokalnu datoteku
+ Neispravan naziv lokalne datoteke
Preuzmi najnoviju razvojnu inačicu
Ograničenje preuzimanja
Nije moguće preuzeti %1$s
@@ -213,14 +299,19 @@
Preuzimanje...
%1$s je preuzeto
Preuzeto
+ Korisnik je otkazao preuzimanje nekih datoteka
+ Došlo je do pogreške pri preuzimanju datoteka
Nema preuzimanja
+ Došlo je do neočekivane pogreške pri preuzimanju datoteka
Zatvori bočnu traku
Zajednica
Pozadinska slika zaglavlja ladice
Aktivnosti
Sve datoteke
+ Asistent
Favoriti
Medij
+ Grupe mape
Početna
Obavijesti
Na uređaju
@@ -234,11 +325,25 @@
Iskorišteno %1$s od %2$s
Upotrijebljeno %1$s
Automatsko otpremanje
+ %s zbog previše pogrešnih pokušaja
+ Brojač je prestar
+ Hash nije pronađen
+ E2E još nije postavljen
+ Nije moguće bez internetske veze
+ Potpis se ne podudara
+ Nije moguće provjeriti metapodatke, potpis je prazan.
+ Asistent
Više
+ Više Nextcloud aplikacija
+ Nije moguće otvoriti odabir datoteke
+ Odabir adrese e‑pošte nije uspio.
Postavi kao šifrirano
+ Nije moguće dohvatiti certifikat poslužitelja
+ Neuspjela provjera javnog ključa
Postavi šifriranje
Dešifriranje u tijeku...
Zatvori
+ Unesite svoju šifrirnu frazu za pristup datotekama
Ova mapa nije prazna.
Generiranje novih ključeva...
Svih 12 riječi zajedno čine vrlo složenu zaporku koja samo vama omogućuje da pregledavate i koristite se šifriranim datotekama. Zapišite je i čuvajte na sigurnom mjestu.
@@ -246,8 +351,11 @@
Zabilježite zaporku za šifriranje od 12 riječi
Zaporka…
Dohvaćanje ključeva...
+ Nije moguće dohvatiti privatni ključ
+ Nije moguće dohvatiti javni ključ
Pohranjivanje ključeva
Postavi šifriranje
+ Došlo je do neočekivane pogreške pri preuzimanju ključeva
Spremanje ključeva nije uspjelo, pokušajte ponovno.
Došlo je do pogreške tijekom dešifriranja. Pogrešna zaporka?
Unesite naziv odredišne datoteke
@@ -258,13 +366,21 @@
Pogreška pri komentiranju datoteke
Ispad %1$s
Pogreška kod stvaranja datoteke iz predloška
+ Nije moguće dohvatiti primatelje dijeljenja.
+ Pogreška pri prikazu radnji nad datotekom
Greška kod promjene statusa zaključavanja datoteke
Prijavi
Prijaviti grešku razvojnom timu? (potreban GitHub korisnički račun)
Pogreška pri dohvaćanju datoteke
Pogreška pri dohvaćanju predložaka
+ Pogreška pri postavljanju poruke statusa!
+ Pogreška pri prikazu dijaloga postavljanja šifriranja!
Pogreška pri pokretanju kamere
+ Pogreška pri pokretanju skeniranja dokumenta
+ Neuspješan prijenos snimljenog medija
Računi
+ Broj pokretanja u 48 h
+ Stvoreno
Naziv zadatka
Napredak
Stanje
@@ -278,6 +394,7 @@
Zaustavi ispitni zadatak
Migracije (nadogradnja aplikacije)
Preferencije
+ Inženjerski testni način rada
Prijenos datoteka
Stavi testno preuzimanje u red čekanja
Stavi testno otpremanje u red čekanja
@@ -285,18 +402,24 @@
Prijenos
Preuzmi
Otpremi
+ Nemate dopuštenje za stvaranje ili prijenos datoteka u ovoj mapi.
Vanjska dijeljenja
Dodaj ili otpremi
+ Nije uspjelo stvaranje dijaloga sukoba
Neuspješno prosljeđivanje datoteke upravitelju preuzimanja
Ispisivanje datoteke nije uspjelo
+ Nije uspjelo pokretanje radnje!
Nije uspjelo pokretanje uređivača
Ažuriranje korisničkog sučelja nije uspjelo
Dodaj u favorite
Favorit
15 minuta
+ Dijeljenu datoteku nije moguće ažurirati
Datoteka tog naziva već postoji
Izbriši
Pogreška pri dohvaćanju aktivnosti za datoteku
+ Ne možete stvoriti dijeljenje; dijeljenje je već aktivno od ovog korisnika.
+ Nema dostupne aplikacije za odabir kontakata
Učitavanje pojedinosti nije uspjelo
Datoteka
Zadrži
@@ -307,6 +430,7 @@
Ovdje nema datoteka
Nema rezultata u ovoj mapi
Nema rezultata
+ Nema datoteka ili mapa koje odgovaraju pretraživanju
Ovdje nema ničega. Možete dodati mapu.
Ovdje će se prikazati preuzete datoteke i mape.
Nije pronađena nijedna datoteka izmijenjena u posljednjih 7 dana
@@ -314,6 +438,10 @@
Ovdje će se prikazati datoteke i mape koje dijelite.
Još ništa nije dijeljeno
Nema rezultata za vaš upit
+ Započnite pretraživanje
+ Upišite u traku za pretraživanje iznad kako biste pronašli datoteke, kontakte, događaje u kalendaru i još mnogo toga na svom računu.
+ Provjerite internetsku vezu ili pokušajte ponovno kasnije
+ Loša veza
mapa
UŽIVO
Učitavanje…
@@ -321,6 +449,7 @@
prije nekoliko sekundi
Dopuštenja za pohranu
%1$sradi najbolje s dozvoljenim pristupom prostoru za pohranu. Možete odabrati potpuni pristup svim datotekama ili samo mogućnost \"čitanja\" za fotografije i video materijale.
+ Dopusti pristup iz drugih aplikacija
Provjera odredišta...
Čišćenje…
Ažuriranje mape za pohranu datoteka
@@ -331,6 +460,8 @@
Nije moguće pisati u odredišnu datoteku
Neuspjeh tijekom migracije
Neuspješno ažuriranje indeksa
+ %1$s
+(%2$s / %3$s)
Premještanje podataka...
Završeno
Zamijeni
@@ -342,9 +473,19 @@
Ažuriranje indeksa...
Koristi
Čeka se potpuna sinkronizacija...
+ Trenutačni naziv mape nije valjan, preimenujte mapu. Preusmjeravanje na početnu…
+ Putanja mape sadrži rezervirane nazive ili neispravne znakove
+ %s je zabranjena ekstenzija datoteke
+ Nazivi datoteka ne smiju sadržavati razmake na početku ili kraju
+ Naziv sadrži neispravne znakove: %s
+ %s je zabranjen naziv
+ %s. Preimenujte datoteku prije premještanja ili kopiranja
Datoteka nije pronađena
+ Datoteka nije pronađena. Nije moguće stvoriti dijeljenje.
Datoteku nije moguće sinkronizirati. Prikazana je posljednja dostupna inačica.
Preimenuj
+ Prijenos nije uspio. Nema internetske veze
+ %s već postoji, nije otkriven sukob
Pogreška pri vraćanju inačice datoteke!
Pojedinosti
Preuzmi
@@ -357,9 +498,19 @@
Naziv datoteke sadrži barem jedan nevažeći znak
Naziv datoteke
Čuvajte svoje podatke sigurnim i pod kontrolom
+ Sigurna suradnja i razmjena datoteka
+ Jednostavan webmail, kalendar i kontakti
+ Dijeljenje zaslona, online sastanci i web konferencije
Mapa već postoji
+ Ovu mapu najbolje je pregledavati u %1$s.
Stvori
+ %1$d od %2$d · %3$s
+ Došlo je do pogreške tijekom sinkronizacije mape %s
+ Nedovoljno prostora na disku, sinkronizacija je otkazana
+ Mapa %s je sinkronizirana
+ Sinkronizacija…
Ovdje nema mapa
+ Naziv mape ne može biti prazan
Odaberite
Odaberi ciljnu mapu
Kopirajte
@@ -377,22 +528,44 @@
Udaljeno: %1$s
Naprijed
4 sata
+ Google je ograničio preuzimanje APK/AAB datoteka!
+ Ova ikona označava dostupnost live fotografije
Datoteka s ovakvim nazivom će biti skrivena
Naziv
Bilješka
Zaporka
Poslužitelj nije dostupan
Postavi vlastiti poslužitelj
+ Ikona za prazan popis
+ Ikona widgeta nadzorne ploče
+ Ikona stavke widgeta
+ uređeno
+ Okreni vodoravno
+ Okreni okomito
+ Rotiraj suprotno od kazaljke na satu
+ Rotiraj u smjeru kazaljke na satu
+ Nije moguće urediti sliku.
+ Detalji datoteke
+ Uvjeti snimanja slike
+ ƒ/%s
+ ISO %s
+ %s MP
+ %s mm
+ %s s
Također otpremi postojeće datoteke
Otpremanje samo tijekom punjenja
/InstantUpload
Unutarnja dijeljenja
+ Interna dvosmjerna sinkronizacija
+ Još ne, uskoro će se sinkronizirati
+ Za postavljanje šifrirane mape potrebna je internetska veza
Neispravan URL
Nevidljiva
Oznaka ne može biti prazna
Zadnja sigurnosna kopija: %1$s
Poveznica
Naziv pozivnice
+ Poveznica nije otvorena zbog sigurnosnih postavki.
Uređivanje
Navedeni izgled
Učitaj više rezultata
@@ -452,11 +625,14 @@
Otkrivena je nova medijska mapa %1$s.
fotografija
videozapis
+ Nova obavijest
Stvorena je nova inačica
Nema radnji za ovog korisnika
Nema dostupnih aplikacija za upravljanje poveznicama
Ne postoji kalendar
Nema dostupnih aplikacija za upravljanje adresama e-pošte
+ Nema stavki
+ Nema dostupne aplikacije za karte
Dopušten je samo jedan račun
Nema dostupnih aplikacija za upravljanje PDF datotekama
Aplikacija za slanje odabranih datoteka ne postoji na uređaju ili nije dostupna
@@ -464,6 +640,10 @@
Nije moguće poslati bilješku
Ikona bilješke
Izvršavanje radnje nije uspjelo.
+ Prikazuj obavijesti za rezultate pozadinskih operacija
+ Pozadinske operacije
+ Prikazuj obavijesti o promjenama sadržaja
+ Promjene sadržaja
Prikazuje napredak preuzimanja
Preuzimanja
Prikazuje napredak i rezultate sinkronizacije datoteka
@@ -472,21 +652,38 @@
Opće obavijesti
Napredak alata za reprodukciju glazbe
Alat za reprodukciju medijskih datoteka
+ Prikazuj obavijesti za izvanmrežne operacije
+ Izvanmrežne operacije
Prikazuje push obavijesti koje šalje poslužitelj: navode u komentarima, primanje novih udaljenih dijeljenja, obavijesti administratora itd.
Push obavijesti
Prikazuje napredak otpreme
Otpreme
Ikona obavijesti
+ Ikona obavijesti
Nema obavijesti
Provjerite kasnije.
+ Na čekanju za izvanmrežnu obradu
+ Uklonit će se kad se ponovno povežete
Nema internetske veze
+ U izvanmrežnom načinu rada dostupne su samo datoteke spremljene za izvanmrežnu upotrebu. Promjene će se sinkronizirati kada se ponovno povežete.
+ Izvanmrežni način rada
+ Datoteka još ne postoji
+ Sukob pri stvaranju datoteke %s. Na poslužitelju već postoji datoteka istog naziva.
+ Sukob pri stvaranju mape %s. Na poslužitelju postoji mapa s istim imenom.
+ Pogreška pri izvanmrežnim operacijama. %s
+ Izvanmrežne operacije
+ Sukob pri uklanjanju %s. Datoteka je izmijenjena na poslužitelju.
+ Sukob pri preimenovanju %s. Na poslužitelju postoji datoteka s istim imenom.
+ Obrada izvanmrežnih promjena
1 sat
Na mreži
Status na mreži
+ Otvori u %1$s
Poslužitelj je na kraju radnog vijeka, nadogradite ga!
Više izbornika
Unesite zaporku
Unesite zaporku
+ Postavite šifru za zaštitu aplikacije.
Zaporke se ne podudaraju
Ponovno unesite zaporku
Izbrišite zaporku
@@ -494,12 +691,16 @@
Zaporka je spremljena
Neispravna zaporka
Pauza
+ PDF je zaštićen lozinkom
Dopusti
Spriječi
+ Odaberite kontakt za dijeljenje
Nije pronađena nijedna aplikacija za postavljanje slike s
+ Prikvači na početni zaslon
Otvori %1$s
zaustavi
uključi/isključi
+ Odaberite poslužitelj
Onemogućivanje provjere uštede energije može rezultirati otpremanjem datoteka pri niskoj razini napunjenosti baterije!
izbrisano
spremljeno u izvornoj mapi
@@ -511,7 +712,12 @@
Preimenuj novu inačicu
Što učiniti ako datoteka već postoji?
Dodaj račun
+ Omogućuje aplikaciji pristup svim datotekama na uređaju (prema Androidovim pravilima).
+ Pristup svim datotekama
+ Kalendar i kontakti
Nisu instalirani F-Droid ni Google Play
+ Postavke sinkronizacije kalendara i kontakata
+ Postavljanje sinkronizacije je uspješno
Informacije
Pojedinosti
Dev
@@ -519,7 +725,12 @@
Općenito
Više
Sinkronizacija
+ Stvaraj sigurnosnu kopiju jednom dnevno
Svakodnevno sigurnosno kopiranje vaših kontakata
+ Lokacija pohrane podataka
+ Odaberite gdje će aplikacija spremati svoje podatke
+ Neuspjelo postavljanje DAVx⁵
+ Šifriranje s kraja na kraj je aktivno
E2E mnemonička oznaka
Omogućite vjerodajnice uređaja kako biste prikazali mnemoničku oznaku.
Prikaži obavijesti o skeniranju medija
@@ -529,7 +740,12 @@
Bilješke o izdanju
Izvorna datoteka bit će...
Izvorna datoteka bit će...
+ Ne prenosi skrivene datoteke i mape
+ Preskoči skrivene stavke
+ Organiziraj prijenose po podmapama prema datumu
Koristi podmape
+ Pravilo podmape za automatski prijenos
+ Ključevi već postoje
Licenca
Zaporka aplikacije
Omogućene su vjerodajnice uređaja
@@ -540,32 +756,49 @@
Zaporka
Upravljaj računima
Preporuči prijatelju
+ Ukloni šifriranje s kraja na kraj
+ Postavi šifriranje s kraja na kraj
+ Prikaži aplikacije ekosustava
+ Prikaži preporučene aplikacije iz Nextcloud ekosustava
Prikaz skrivenih datoteka
Preuzmi izvorni kod
Upravljanje mapama za automatsko otpremanje
Lokalna mapa
Udaljena mapa
Tema
+ Interval dvosmjerne sinkronizacije
+ Sinkroniziraj promjene na uređaju i na poslužitelju
+ Dvosmjerna sinkronizacija
Tamno
Svijetlo
Prati sustav
Pretpregled slike
+ Preuzimanje slike za uređivanje…
Nema lokalne datoteke za pretpregled
Nije moguće prikazati sliku
+ Datoteka nije preuzeta
+ Poslužitelj je vratio neočekivani HTTP kod.
Oprosti
Privatnost
Push obavijesti su onemogućene zbog ovisnosti o vlasničkim servisima trgovine Google Play.
Nema push obavijesti zbog zastarjele prijave. Razmislite o ponovnom dodavanju računa.
Push obavijesti trenutno nisu dostupne.
Čitanje QR kôda nije uspjelo!
+ Mapa za sinkronizaciju ne postoji. Stvorite je pa pokušajte ponovno.
+ Nije moguće pronaći datoteku za prijenos.
Isprobajte %1$s na uređaju!
Želim vas pozvati da koristite %1$s na svom uređaju.\nPreuzmite ovdje: %2$s
%1$s ili %2$s
Preporučene datoteke
+ Osvježi sadržaj
Ponovno učitaj
(na daljinu)
Datoteka nije pronađena!
+ Ukloni E2E šifriranje
+ Ovo će ukloniti lokalne podatke za šifriranje s kraja na kraj na ovom uređaju. Šifrirane datoteke na poslužitelju neće biti izbrisane.
Brisanje nije uspjelo
+ Ukloni lokalni račun
+ Ovo će ukloniti račun s ovog uređaja. Datoteke na poslužitelju ostaju netaknute.
Nije uspjelo uklanjanje obavijesti.
Ukloni
Izbrisano
@@ -573,6 +806,9 @@
Nije moguće preimenovati lokalnu kopiju, pokušajte s drugim nazivom
Preimenovanje nije moguće, naziv je već zauzet
Zatraži brisanje računa
+ Zatraži brisanje računa
+ Poslat ćemo zahtjev za brisanje vašeg računa administratoru poslužitelja.
+ Ponovno dijeljenje nije dopušteno
Vrati datoteku
Vrati sigurnosnu kopiju
Vrati izbrisanu datoteku
@@ -582,6 +818,7 @@
Pokušaj ponovno
Učitavanje dokumenta nije uspjelo!
Prijavite se putem QR koda
+ Skeniraj stranicu
Zaštita podataka
samopostavljeno pospješivanje produktivnosti
Pretražujte i dijelite
@@ -591,17 +828,34 @@
Svi vaši računi
na jednom mjestu
Automatsko otpremanje
+ Automatski prenesite fotografije i videozapise u pozadini.
+ Sinkronizirajte kontakte i kalendar
+ Povežite kontakte i kalendar s uređajem putem DAVx⁵.
Pogreška pri dohvaćanju rezultata pretraživanja
+ Sigurno dijeljenje nije postavljeno
+ Pretraži za sigurno dijeljenje
Odaberi sve
Podesi mapu za medijske datoteke
Odaberite jedan predložak
Odaberi predložak
Pošalji
+ Pošalji kopiju u
+ Pošalji dijeljenje
Ikona gumba za slanje
+ Poslužitelj nije dostupan
+ Nije moguće uspostaviti vezu s poslužiteljem. Provjerite internetsku vezu i pokušajte ponovno.
Postavi kao
+ Neuspjelo postavljanje ograničenja preuzimanja
+ Postavi poruku
+ Postavi napomenu
+ Postavi online status
+ Nije moguće dohvatiti ili postaviti status. Provjerite vezu s poslužiteljem.
Koristi sliku kao
+ Postavi E2E šifriranje
Dijeli
Dopusti preuzimanje i sinkronizaciju
+ Napomena ne može biti prazna
+ Kopiraj poveznicu
Stvori
Prilagođena dopuštenja
Izbriši
@@ -625,13 +879,19 @@
Dijeli poveznicu (%1$s)
Postavi datum isteka
Postavi zaporku
+ Dijeljenje nije dopušteno kada je uključeno primanje datoteka
+ Potrebno je odabrati opciju dijeljenja
Uređivanje moguće
Zahtjev za datotekom
+ Siguran prijenos datoteka
Samo za gledanje
+ Dopuštenja dijeljenja
Dijeli
Čitaj
%1$s (udaljeno)
%1$s (razgovor)
+ Pretraži korisnike ili grupe
+ Pretraži unutar instance
Pošalji novu poruku e-pošte
Obavijest primatelju
Postavke
@@ -645,10 +905,12 @@
dijeljeno putem poveznice
S vama podijelio %1$s
Dodavanje osobe za dijeljenje nije uspjelo
+ Ovaj je primatelj već dodan
Prikaži sve
Prikaži fotografije
Prikaži manje
Prikaži video
+ Neuspjelo prihvaćanje uvjeta korištenja
Prijavite se kod davatelja usluga
Dopusti %1$s pristup Nextcloud računu %2$s?
Razvrstaj prema
@@ -689,23 +951,37 @@
Unutarnja pohrana
Filmovi
Glazba
+ Dopusti pristup svim datotekama
+ Za automatski prijenos potrebna su dopuštenja za pristup datotekama.
+ Za prijenos datoteka potrebna su dopuštenja za pristup datotekama.
+ Ne pitaj ponovno
Medijske datoteke samo za čitanje
Slike
+ Samostalno hostirana platforma za produktivnost koja vam omogućuje kontrolu. n\nZnačajke:\n* Jednostavno, moderno sučelje prilagođeno temi vašeg poslužitelja\n* Učitavanje datoteka na vaš Nextcloud poslužitelj\n* Dijeljenje s drugima\n* Sinkronizacija omiljenih datoteka i mapa\n* Pretraživanje svih mapa na vašem poslužitelju\n* Automatsko učitavanje fotografija i videozapisa snimljenih vašim uređajem\n* Ostanite u tijeku uz obavijesti\n* Podrška za više računa\n* Sigurn pristup vašim podacima pomoću otiska prsta ili PIN-a\n* Integracija s DAVx⁵ (ranije poznatim kao DAVdroid) za jednostavno postavljanje sinkronizacije kalendara i kontakata\n\nMolimo prijavite sve probleme na https://github.com/nextcloud/android/issues i raspravljajte o ovoj aplikaciji na https://help.nextcloud.com/c/clients/android\n\nNovi ste na Nextcloudu? Nextcloud je privatni poslužitelj za sinkronizaciju i dijeljenje datoteka te komunikaciju. To je slobodni softver i možete ga sami hostati ili platiti tvrtki da to učini za vas. Na taj način imate kontrolu nad svojim fotografijama, podacima kalendara i kontakata, dokumentima i svime ostalim.\n\nPogledajte Nextcloud na https://nextcloud.com
+
Samopostavljena platforma pospješuje produktivnost i omogućuje vam da zadržite kontrolu.\nTo je službena razvojna inačica koja se isporučuje s dnevnim uzorkom svake nove neispitane funkcionalnosti, što može uzrokovati nestabilnost i gubitak podataka. Aplikacija je namijenjena korisnicima koji žele testirati i prijaviti pogreške, ako se iste pojave. Nemojte je upotrebljavati za važne radne zadatke!\n\nSlužbena razvojna i uobičajena inačica dostupne su putem F-droida i mogu biti istovremeno instalirane.
Samopostavljena platforma pospješuje produktivnost i omogućuje vam da zadržite kontrolu
Samopostavljena platforma pospješuje produktivnost i omogućuje vam da zadržite kontrolu (razvojna inačica za pretpregled)
Strujanje s...
Unutarnje strujanje nije moguće
Umjesto toga preuzmite medijske datoteke ili se koristite vanjskom aplikacijom.
+ Po danu
+ Po mjesecu
Godina
\"%1$s\" je dijeljen s vama
%1$s dijeli \"%2$s\" s vama
Samo fotografije
+ Fotografije i videozapisi
Samo video materijali
Predloži
Sinkronizacija
+ Svejedno sinkroniziraj
+ Riješi sukob
+ Neke datoteke imaju sukob. Odaberite verziju koju želite zadržati.
+ Sukob pri sinkronizaciji
Postoje nepodudaranja
Mapa %1$s više ne postoji
+ Duplikati pri sinkronizaciji
Nije moguće sinkronizirati %1$s
Pogrešna zaporka za %1$s
Značajka trajne sinkronizacije datoteka nije uspjela
@@ -722,6 +998,7 @@
Nema dovoljno prostora
Gumb statusa sinkronizacije
Datoteke
+ U redu
Gumb postavki
Konfiguriraj mape
Trenutačno otpremanje datoteka potpuno je obnovljeno. Ponovno konfigurirajte automatsko otpremanje iz glavnog izbornika.\n\nUživajte u novoj i proširenoj značajci automatskog otpremanja datoteka.
@@ -731,13 +1008,21 @@
Sinkronizirano
Oznake
Uvjeti pružanja usluge
+ Slažem se
Testirajte vezu s poslužiteljem
30 minuta
Ovaj tjedan
Umanjeni prikaz
Umanjeni prikaz postojeće datoteke
Umanjeni prikaz nove datoteke
+ Istek vremena pri povezivanju s Richdocuments
Danas
+ Unesite tekst za prijevod
+ S
+ Na
+ Započni prijevod
+ Prijevod traje duže nego što se očekivalo.
+ Prevođenje…
Izbrisane datoteke
Nema izbrisanih datoteka
Ovdje ćete moći vratiti izbrisane datoteke.
@@ -746,11 +1031,21 @@
Trajno izbrišite
Učitavanje kante za smeće nije uspjelo!
Datoteke nije moguće trajno izbrisati!
+ Onemogući sve
+ Nema stavki za dvosmjernu sinkronizaciju.
+ Nema stavki
+ Dvosmjerna sinkronizacija
Došlo je do neočekivane pogreške
+ Događaj u kalendaru nije pronađen
+ Kontakt nije pronađen
+ Potrebno je dopuštenje
+ Pretraži u ovoj mapi
Nepoznata pogreška
Otključaj datoteku
Postoje nepročitani komentari
Ukloni šifriranje
+ Ukloni iz omiljenih
+ Isključi internu dvosmjernu sinkronizaciju
Došlo je do pogreške prilikom prestanka dijeljenja ove datoteke ili mape.
Nije moguće poništiti dijeljenje. Provjerite postoji li datoteka.
kako biste poništili dijeljenje ove datoteke
@@ -760,13 +1055,20 @@
Nije moguće ažurirati. Provjerite postoji li datoteka.
za ažuriranje ovog dijeljenja
Ažuriranje dijeljenja nije uspjelo
+ Očisti otkazano
+ Nastavi otkazano
Izbriši neuspješne otpreme
Ponovno pokušaj neuspjele otpreme
+ Datoteka više ne postoji.
+ Pauziraj sve prijenose
+ Nastavi sve prijenose
Nije moguće stvoriti lokalnu datoteku
Otpremi iz...
Otpremi sadržaj iz drugih aplikacija
Fotografija
+ Prenijeti ovu datoteku sada?
Otpremanje iz kamere
+ Videozapis
Naziv datoteke
Vrsta datoteke
Datoteka prečaca za Google Maps (%s)
@@ -774,18 +1076,26 @@
Tekstna datoteka isječka (.txt)
Unesite naziv datoteke i vrstu datoteke za otpremanje
Otpremi datoteke
+ Prijenosi su pauzirani
Gumb za otpremanje stavke
- Otkaži otpremanje
Izbriši
Nema dostupnih otprema
Otpremite neki sadržaj ili aktivirajte automatsko otpremanje.
+ Prikaži više
Neriješeno nepodudaranje
Lokalni prostor za pohranu je pun
Datoteku nije moguće kopirati u lokalni prostor za pohranu
Zaključavanje mape nije uspjelo
Korisnik je prekinuo prilaganje
+ Dopusti pristup datotekama
+ Postavke dopuštenja aplikacije
+ Za prijenos je potrebno dopustiti pristup datotekama na uređaju.
+ Nedostaje dopuštenje za pohranu
+ Otvori status prijenosa
+ %1$d / %2$d - %3$s
Šifriranje je moguće samo na inačici >= Androida 5.0
Nedostatak prostora sprječava kopiranje odabranih datoteka u mapu %1$s. Želite li ih premjestiti tamo?
+ Kvote je premašena
Skeniraj dokument pomoću kamere
Nepodudaranje tijekom sinkronizacije, ručno otklonite poteškoću
Nepoznata pogreška
@@ -797,6 +1107,8 @@
Datoteka odabrana za otpremanje nije pronađena. Provjerite postoji li datoteka.
Ovu datoteku nije moguće otpremiti
Nema datoteke za otpremu
+ Datoteka nije pronađena na uređaju.
+ Datoteka nije pronađena na poslužitelju.
Naziv mape
Odaberi mapu za otpremu
Otpremanje %1$s nije uspjelo
@@ -834,7 +1146,10 @@
Nepouzdana vjerodajnica poslužitelja
Dohvaćanje inačice poslužitelja...
Prekinut rad aplikacije
+ Preskočeno
+ Razlog preskakanja
Završeno
+ Uspješno (datoteka je već postojala)
Nepoznata pogreška
Otkriven je virus. Nije moguće dovršiti otpremanje!
Čekanje izlaza iz načina uštede energije
@@ -851,18 +1166,46 @@
Dodajte ime, sliku i kontaktne podatke na stranicu profila.
Korisničko ime
Preuzmi
+ Ikona preklopa za video
Pričekajte…
U tijeku je provjera spremljenih vjerodajnica
Kopiranje datoteke iz osobnog podatkovnog prostora
+ Promjena ekstenzije može učiniti datoteku neupotrebljivom.
Što je nova slika
Preskoči
Novo u %1$s
Koji je vaš status?
+ Widgeti su dostupni samo u verziji %1$s 25 ili novijoj kada je aplikacija Dashboard omogućena
Nije dostupno
Preuzimanje datoteka...
Pošalji poruku e-pošte
Put pohrane ne postoji!
To može biti zbog vraćanja sigurnosne kopije na drugom uređaju. Provjerite postavke i prilagodite put pohrane.
+
+ - %d sat
+ - %d sata
+ - %d sati
+
+
+ - %d minuta
+ - %d minute
+ - %d minuta
+
+
+ - prije %d sekundu
+ - prije %d sekunde
+ - prije %d sekundi
+
+
+ - prije %d minutu
+ - prije %d minute
+ - prije %d minuta
+
+
+ - prije %d sat
+ - prije %d sata
+ - prije %d sati
+
- Nije moguće sinkronizirati datoteku %1$d (nepodudaranja: %2$d)
- Nije moguće sinkronizirati datoteke %1$d (nepodudaranja: %2$d)
@@ -908,6 +1251,11 @@
- Broj izvezenih datoteka: %d. Ostale su preskočene zbog greške
- Broj izvezenih datoteka: %d. Ostale su preskočene zbog greške
+
+ - Možete prenijeti najviše %d datoteku odjednom.
+ - Možete prenijeti najviše %d datoteke odjednom.
+ - Možete prenijeti najviše %d datoteka odjednom.
+
- Mapa %1$d
- Mape %1$d
@@ -918,6 +1266,11 @@
- Datoteke %1$d
- Datoteke %1$d
+
+ - %d datoteka
+ - %d datoteke
+ - %d datoteka
+
- Prikaži %1$d skrivenu mapu
- Prikaži %1$d skrivene mape
@@ -928,4 +1281,29 @@
- Odabrano je %d
- Odabrano je %d
-
+
+ - Pričekajte %d sekundu
+ - Pričekajte %d sekunde
+ - Pričekajte %d sekundi
+
+
+ - Pričekajte %d minutu
+ - Pričekajte %d minute
+ - Pričekajte %d minuta
+
+
+ - %d minuta
+ - %d minute
+ - %d minuta
+
+
+ - %d sekunda
+ - %d sekunde
+ - %d sekundi
+
+
+ - Ova poveznica dopušta još %d preuzimanje.
+ - Ova poveznica dopušta još %d preuzimanja.
+ - Ova poveznica dopušta još %d preuzimanja.
+
+
diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml
index e1bc2a2c3a30..95a05e07d968 100644
--- a/app/src/main/res/values-hu-rHU/strings.xml
+++ b/app/src/main/res/values-hu-rHU/strings.xml
@@ -117,7 +117,6 @@
/AutoUpload
Ez a mappa már szerepel a szülőmappája szinkronizálásában, ami ismételt feltöltéseket okozhat
Várakozás a Wi-Fire a feltöltés megkezdéséhez
- 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
@@ -223,7 +222,6 @@
Az importálás elkezdése sikertelen. Próbálja újra.
Nincs fájl
A legutóbbi biztonsági mentés nem található!
- Tartalmi változások észlelése
Nem sikerült létrehozni a beszélgetést
Beszélgetés törlése
Nem sikerült a beszélgetés törlése
@@ -767,7 +765,6 @@
Téma
Időköz
Belső mappák kezelése kétirányú szinkronizáláshoz
- Kétirányú szinkronizálás engedélyezése
Kétirányú szinkronizálás
Sötét
Világos
@@ -1089,7 +1086,6 @@ A Nextcloud itt érhető el: https://nextcloud.com
Fájlok feltöltése
Összes feltöltés szüneteltetve
Elem feltöltése műveletgomb
- Feltöltés megszakítása
Törlés
Nincsenek feltöltések
Töltsön fel valamilyen tartalmat, vagy aktiválja az automatikus feltöltést.
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index b3986f09d1c6..e84ca988024c 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -102,7 +102,6 @@
/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.
@@ -209,7 +208,6 @@ 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
Belum ada percakapan
Tersalin
Terjadi kesalahan ketika mencoba menyalin berkas atau folder ini
@@ -682,6 +680,7 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Ganti nama versi baru
Apa yang harus dilakukan jika berkas sudah ada?
Tambah akun
+ Akses semua berkas
Sinkronkan kalender dan kontak
F-Droid maupun Google Play tidak terinstal
Siapkan DAVx⁵ (sebelumnya dikenal sebagai DAVdroid) (versi 1.3.0 ke atas) untuk akun saat ini
@@ -736,7 +735,6 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Tema
Interval
Kelola folder internal untuk sinkronisasi dua arah
- Nyalakan sinkronisasi dua arah
Gelap
Terang
Ikuti sistem
@@ -917,6 +915,9 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Penyimpanan internal
Film
Musik
+ Akses semua berkas
+ Izin penyimpanan dibutuhkan untuk Unggah Otomatis
+ Jangan tanya
Media hanya baca
Gambar
Platform produktivitas mandiri yang memberi Anda kendali penuh.\n\Fitur:\n* Mudah, tampilan modern, dan cocok dengan tema server Anda\n* Unggah file ke server Nextcloud Anda\n* Bagikan dengan yang lain\n* Buat file dan folder favorit Anda tetap tersinkron\n* Telusuri semua folder di server Anda\n* Unggah otomatis untuk foto dan video yang diambil dari perangkat Anda\n* Tetap terkabarkan dengan notifikasi\n* Mendukung penggunaan banyak akun (multi-account)\n* Akses data Anda dengan aman dengan sidik jari atau PIN\n* Integrasi dengan DAVx⁵ (dulu dikenal sebagai DAVdroid) untuk pemasangan sinkronisasi kalender dan kontak yang mudah\n\nTolong laporkan semua masalah aplikasi ini di https://github.com/nextcloud/android/issues dan diskusikan aplikasi ini di https://help.nextcloud.com/c/clients/android\n\nBaru mencoba Nextcloud? Nextcloud adalah server pribadi untuk sinkronisasi dan berbagi file serta berkomunikasi. Nextcloud adalah software libre sehingga Anda dapat meng-host sendiri atau membayar perusahaan untuk melakukannya untuk Anda. Dengan begitu, Anda memiliki kendali atas foto Anda, kalender dan data kontak Anda, dokumen dan semuanya yang Anda miliki.\n\nYuk, cek Nextcloud di https://nextcloud.com
@@ -937,6 +938,8 @@ Otomatis unggah hanya bekerja dengan baik apabila Anda mengeluarkan aplikasi ini
Sarankan
Sinkron
Tetap sinkronkan
+ Perbaiki konflik
+ Berkas unggahan konflik
Konflik ditemukan
Folder %1$s sudah tidak ada lagi
Duplikasi sinkronisasi
@@ -979,6 +982,11 @@ Berikut ini adalah daftar berkas lokal, dan berkas jarak jauh di %5$s yang terhu
Thumbnail untuk berkas baru
Pemuatan membutuhkan waktu lebih lama dari yang diperkirakan.
Hari ini
+ Masukkan teks untuk di terjemahkan
+ Terjemahkan dari :
+ Terjemahkan ke :
+ Tekan tombol nya untuk menerjemahkan
+ Menerjemahkan...
Berkas terhapus
Tidak ada berkas yang dihapus.
Anda akan dapat memulihkan berkas yang dihapus dari sini.
@@ -1034,7 +1042,6 @@ Berikut ini adalah daftar berkas lokal, dan berkas jarak jauh di %5$s yang terhu
Unggah berkas
Semua unggahan dihentikan sebentar
Tombol aksi unggah item
- Batal unggah
Hapus
Tidak ada unggahan yang tersedia
Unggah beberapa foto atau aktifkan unggah otomatis.
@@ -1102,6 +1109,7 @@ Berikut ini adalah daftar berkas lokal, dan berkas jarak jauh di %5$s yang terhu
Sertifikat server tidak terpecaya
Mendapatkan versi server...
Aplikasi ditutup
+ Ter lewat kan
Selesai
File yang sama ditemukan saat transfer jarak jauh, melewati unggahan
Kesalahan tidak diketahui
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
index d792f75412e4..da38ee7e98b4 100644
--- a/app/src/main/res/values-is/strings.xml
+++ b/app/src/main/res/values-is/strings.xml
@@ -97,7 +97,6 @@
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
@@ -722,7 +721,6 @@
Þema
Millibil
Sýsla með möppur vegna tvíátta samstillingar
- Virkja tvíátta samstillingu
Dökkt
Ljóst
Fylgja kerfinu
@@ -1015,7 +1013,6 @@
Hlaða inn skrám
Allar innsendingar eru í bið
Aðgerðarhnappur til að senda inn atriði
- Hætta við innsendingu
Eyða
Engar innsendingar tiltækar
Sendu inn einhverjar myndir eða virkjaðu sjálfvirka innsendingu.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index befe0b60f620..6dba7ab5a402 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -107,7 +107,6 @@
/AutoUpload
Questa cartella è già inclusa nella sincronizzazione della cartella principale, il che potrebbe causare caricamenti duplicati.
In attesa del Wi-Fi per avviare il caricamento
- Caricando i file da %s a %s
Configura
Crea nuova configurazione di cartella personalizzata
Configura una cartella personalizzata
@@ -213,7 +212,6 @@
Impossibile avviare l\'importazione. Riprova.
Nessun file trovato
Non troviamo il tuo ultimo backup!
- Rilevamento delle modifiche ai contenuti
Impossibile creare conversazione
Elimina conversazione
Impossibile eliminare la conversazione
@@ -711,7 +709,6 @@
Tema
Intervallo
Gestisci le cartelle interne per la sincronizzazione bidirezionale
- Abilita sincronizzazione bidirezionale
Sincronizzazione bidirezionale
Scuro
Chiaro
@@ -1005,7 +1002,6 @@
Carica file
Tutti i caricamenti sono in pausa
Pulsante azione Carica elemento
- Annulla caricamento
Elimina
Nessun caricamento disponibile
Carica dei contenuti o attiva il caricamento automatico.
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 15f33eb22c89..21c924ece773 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -695,7 +695,6 @@
נא לציין שם קובץ וסוג קובץ לשליחה
העלאת קבצים
כפתור פעולת העלאת פריט
- ביטול העלאה
מחיקה
אין העלאות זמינות
יש להעלות תוכן כלשהו או להפעיל העלאה אוטומטית.
diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml
index 0000b27928ba..08ad47251083 100644
--- a/app/src/main/res/values-ja-rJP/strings.xml
+++ b/app/src/main/res/values-ja-rJP/strings.xml
@@ -49,17 +49,25 @@
タスクを削除
メッセージを送って会話を始めてみましょう。
こんにちは!何かお手伝いできることはありますか?
+ タスクを選択して下さい
+ メッセージを送信する
タスクの作成中にエラーが発生しました。
タスクを作成しました
タスクの削除中にエラーが発生しました。
+ 削除されたタスク
タスクリストを取得できません。インターネット接続を確認してください。
タスクを削除
タスクの出力はまだ準備ができていません。
アシスタント
入力
出力
+ 失敗しました
実行中
+ 予定済み
+ 成功しました
+ タスクの状況:%1$s
不明
+ 考え中...
関連付けられたアカウントが見つかりません!
アクセスに失敗しました: %1$s
このアカウントはまだこのデバイスに追加されていません
@@ -103,7 +111,7 @@
/AutoUpload
このフォルダは既に親の同期に含まれているため、重複してアップロードされる可能性があります
アップロードのためWi-Fiに接続されるのを待っています…
- ファイルを%sから%sへアップロードしています。
+ ファイルを %1$s から %2$s へアップロードしています
設定
新しいカスタムフォルダセットアップを作成する
カスタムフォルダを設定する
@@ -209,7 +217,6 @@
インポートを開始できませんでした。もう一度やり直してください
ファイルが見つかりません
あなたの最後のバックアップを見つけることができませんでした!
- コンテンツの変更の検出
会話が見つかりません
まだ会話はありません
会話
@@ -737,7 +744,6 @@
テーマ
間隔
内部フォルダを管理して双方向同期を行う
- 双方向同期を有効にする
暗い
明るい
システムの設定に従う
@@ -975,6 +981,9 @@
新規ファイルのサムネイル
ローディング中 期待した時間より長くかかっています
今日
+ 翻訳元:
+ 翻訳先:
+ 翻訳中...
ゴミ箱
削除されたファイルはありません
ここから削除されたファイルを元に戻すことができます。
@@ -1029,7 +1038,6 @@
アップロードファイル
すべてのアップロードが一時停止しています
アイテムアクションのアップロードボタン
- アップロードをキャンセル
削除
アップロードは利用できません
何かコンテンツをアップロードするか、自動アップロードを有効にしてください。
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 0622603118ed..80383c68b5e7 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -689,7 +689,6 @@
테마
간격
내부 폴더 양방향 동기화 관리
- 양방향 동기화 활성화
어둡게
밝게
시스템을 따르기
@@ -965,7 +964,6 @@
파일 업로드
모든 업로드가 일시 정지됨
항목 업로드 동작 단추
- 업로드 취소
삭제
업로드 없음
콘텐츠를 업로드하거나 자동 업로드를 활성화하십시오.
diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml
index 14070c12dc94..80fd5d432f6e 100644
--- a/app/src/main/res/values-lo/strings.xml
+++ b/app/src/main/res/values-lo/strings.xml
@@ -105,7 +105,6 @@
/ອັບໂຫຼດອັດຕະໂນມັດ
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
ສ້າງການຕັ້ງຄ່າໂຟນເດີດ້ວຍຕົນເອງ
ກຳໜົດການຕັ້ງຄ່າໂຟນເດີດ້ວຍຕົນເອງ
@@ -211,7 +210,6 @@
Import failed to start. Please try again
ບໍ່ພົບຟາຍ
ບໍ່ສາມາດຊອກຫາ ການສໍາຮອງລ່າສຸດຂອງທ່ານໄດ້!
- Detecting content changes
Delete conversation
No conversations found
ຍັງບໍ່ມີການສົນທະນາ
@@ -744,7 +742,6 @@
ຫົວຂໍ້
Interval
Manage internal folders for two way sync
- Enable two way sync
ມືດ
ແຈ້ງ
ຕາມລະບົບ
@@ -1039,7 +1036,6 @@
ອັບໂຫຼດຟາຍ
All uploads are paused
ອັບໂຫຼດປຸ່ມ ໃນລາຍການການອັບໂຫຼດ
- Cancel upload
ລຶບ
ບໍ່ມີການອັບໂຫຼດ
ອັບໂຫລດເນື້ອຫາບາງຢ່າງ ຫຼື ເປີດການອັບໂຫລດອັດຕະໂນມັດ.
diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml
index 9961f8449b2b..0a42dc8c7bd3 100644
--- a/app/src/main/res/values-lt-rLT/strings.xml
+++ b/app/src/main/res/values-lt-rLT/strings.xml
@@ -590,7 +590,6 @@
Apipavidalinimas
Intervalas.
Administruoti vidines dvipusio sinchronizavimo direktorijas
- Įjungti dvipusį sinchronizavimą.
Tamsus
Šviesus
Pagal sistemą
@@ -837,7 +836,6 @@
Į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 27099f22a6fd..e267d806a069 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -457,7 +457,6 @@
Attālā mape
Izskats
Pārvaldīt iekšējās mapes divvirzienu sinhronizēšanai
- Iespējot divvirzienu sinhronizēšanu
Tumšs
Gaišs
Izmantot sistēmas
@@ -605,7 +604,6 @@
Interneta saīsnes datne(%s)
Maza teksta datne(.txt)
Augšupielādēt datnes
- Atcelt augšupielādi
Izdzēst
Augšupielādes nav pieejamas.
Augšupielādē kaut ko vai ieslēdz automātisko augšupielādi!
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 72c2f87a6664..225fb9cabc7f 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -715,7 +715,6 @@
Внесете име на датотеката и видот на датотеката за да ја прикачите
Прикачи датотеки
Копче за прикачување
- Откажи прикачување
Избриши
Нема прикачувања
Прикачете некоја содржина или активирајте автоматско прикачување.
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 582fd9b7f94f..d60398598ae7 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -661,7 +661,6 @@
Tema
Intervall
Administrer interne mapper for toveis synkronisering
- Slå på toveis-synkronisering
Mørk
Lys
Følg system
@@ -931,7 +930,6 @@
Last opp filer
Alle opplastinger er satt på pause
Last opp element handlingsknapp
- Avbryt opplasting
Slett
Ingen opplastinger tilgjengelig
Last opp noe innhold eller aktiver automatisk opplasting!
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 1b385a22a5a9..13ea6de9c973 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -96,7 +96,6 @@
Upload alleen met Wi-Fi
/AutomatischUploaden
Deze map is al opgenomen in de synchronisatie van de bovenliggende map, wat kan leiden tot dubbele uploads.
- Bestanden aan het uploaden van %s naar %s
Configureren
Maak aangepaste nieuwe map aan
Instellen aangepaste map
@@ -730,7 +729,6 @@
Thema
Interval
Interne mappen beheren voor tweeweg-synchronisatie
- Tweeweg-synchronisatie inschakelen
Tweeweg-synchronisatie
Donker
Licht
@@ -1027,7 +1025,6 @@
Bestanden uploaden
Alle uploads zijn gepauzeerd
Upload object actie knop
- Upload afbreken
Verwijderen
Geen uploads beschikbaar
Upload enkele gegevens of activeer auto-upload.
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 3dc585cb6fed..eee6e3c1765c 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -53,6 +53,7 @@
Usuń zadanie
Spróbuj wysłać wiadomość, aby rozpocząć rozmowę.
Cześć! W czym mogę Ci dziś pomóc?
+ Wybierz zadanie
Wyślij wiadomość
Otwórz listę rozmowy
Wystąpił błąd podczas tworzenia zadania
@@ -117,7 +118,7 @@
/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
+ Przesyłanie plików z %1$s do %2$s
Konfiguruj
Utwórz nową niestandardową konfigurację katalogu
Ustaw własny katalog
@@ -223,7 +224,6 @@
Nie udało się rozpocząć importu. Spróbuj ponownie
Nie znaleziono żadnych plików
Nie odnaleziono Twojej ostatniej kopii zapasowej!
- Sprawdzanie zmian treści
Nie udało się utworzyć rozmowy
Usunąć rozmowę
Nie udało się usunąć rozmowy
@@ -582,6 +582,7 @@
Zaloguj
Link do interfejsu internetowego %1$s, aby otworzyć w przeglądarce.
Usuń dziennik
+ Eksportuj logi
Odśwież
Przeszukaj dziennik
Wyślij dziennik e-mailem
@@ -767,7 +768,6 @@
Motyw
Interwał
Zarządzaj wewnętrznymi katalogami w celu synchronizacji dwukierunkowej
- Włącz synchronizację dwukierunkową
Synchronizacja dwukierunkowa
Ciemny
Jasny
@@ -1016,6 +1016,12 @@
Miniaturka dla nowego pliku
Ładowanie trwa dłużej niż oczekiwano
Dzisiaj
+ Wpisz tekst do przetłumaczenia...
+ Tłumacz z:
+ Tłumacz na:
+ Naciśnij przycisk, aby przetłumaczyć
+ Tłumaczenie trwa dłużej niż oczekiwano.
+ Tłumaczenie...
Usunięte pliki
Brak usuniętych plików
Tutaj będziesz mógł odzyskać usunięte pliki.
@@ -1071,7 +1077,6 @@
Wyślij pliki
Wszystkie przesyłania zostały wstrzymane
Przycisk wysyłania elementu
- Anuluj wysyłanie
Usuń
Brak wysłanych plików
Wyślij pliki lub włącz automatyczne wysyłanie.
@@ -1085,6 +1090,7 @@
Uprawnienia aplikacji
Twoje pliki nie mogą być przesłane bez dostępu do pamięci lokalnej. Kliknij, aby udzielić pozwolenia.
Przesyłanie zatrzymane – wymagane uprawnienie do pamięci
+ Możesz usunąć lub wznowić w sekcji Przesyłane pliki
%1$d / %2$d - %3$s
Szyfrowanie jest możliwe tylko przy >= Android 5.0
Brak wystarczającego miejsca, aby skopiować wybrane pliki do katalogu %1$s. Czy chcesz je tam przenieść?
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 2e6a50258c29..5a58371b2c7c 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -53,6 +53,7 @@
Excluir tarefa
Tente enviar uma mensagem para iniciar uma conversa.
Oi! Em que posso ajudá-lo hoje?
+ Selecione uma tarefa
Enviar mensagem
Abrir lista de conversas
Ocorreu um erro ao criar a tarefa
@@ -117,7 +118,7 @@
/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
+ Fazendo upload de arquivos de %1$s para %2$s
Configurar
Criar nova configuração de pasta personalizada
Configurar uma pasta personalizada
@@ -223,7 +224,6 @@
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
Falha ao criar conversa
Excluir conversa
Falha ao excluir conversa
@@ -767,7 +767,6 @@
Tema
Intervalo
Gerenciar pastas internas para sincronização bidirecional
- Ativar sincronização bidirecional
Sincronização bidirecional
Escuro
Claro
@@ -1016,6 +1015,12 @@
Miniatura para arquivo novo
O carregamento está demorando mais do que o esperado
Hoje
+ Insira o texto a ser traduzido…
+ Traduzir do:
+ Traduzir para:
+ Pressione o botão para traduzir
+ A tradução está demorando mais do que o esperado.
+ Traduzindo…
Arquivos excluídos
Nenhum arquivo excluído
Você poderá recuperar arquivos excluídos aqui.
@@ -1071,7 +1076,6 @@
Enviar arquivos
Todos os uploads estão pausados
Enviar botão de ação do item
- Cancelar upload
Excluir
Nenhum upload disponível
Faça upload de algum conteúdo ou ative o upload automático.
@@ -1085,6 +1089,7 @@
Permissões do aplicativo
Seus arquivos não podem ser enviados sem acesso ao armazenamento local. Toque para conceder permissão.
Upload Interrompido – Permissão de Armazenamento Necessária
+ Você pode removê-lo ou retomá-lo em Uploads.
%1$d / %2$d - %3$s
A criptografia só é possível com Android >= 5.0
O espaço insuficiente está impedindo a cópia dos arquivos selecionados para a pasta %1$s. Em vez de copiá-los, gostaria de movê-los para lá?
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 1f0e686c12f4..0224d70fff33 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -871,7 +871,6 @@ Aproveite o novo e melhorado envio automático.
Introduza o nome e tipo de ficheiro a enviar
Carregar ficheiros
Botão de acção de envio de item
- Cancelar envio
Eliminar
Sem envios disponíveis
Envie algum conteúdo ou ative o envio automático.
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 9afbfe70d0e6..56d448e1442c 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -870,7 +870,6 @@
Introdu numele și tipul fișierului de încărcat
Încarcă fișiere
Butonul de încărcare
- Anulează încărcarea
Șterge
Nu există încărcări disponibile
Încărcați conținut sau activați încărcarea automată.
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index bec50872ce02..a31430726736 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -115,7 +115,6 @@
/Автозагрузка
Эта папка уже включена в синхронизацию родительской папки, что может привести к дублированию загрузок
Ожидание Wi-Fi для начала загрузки
- Загрузка файлов из %s в %s
Настроить
Создать новую настройку пользовательского каталога
Задать пользовательский каталог
@@ -221,7 +220,6 @@
Не удалось запустить импорт. Пожалуйста, попробуйте снова
Файл не найден
Не удалось найти последнюю резервную копию!
- Проверка изменений в папках
Не удалось создать чат
Удалить обсуждение
Не удалось создать чат.
@@ -754,7 +752,6 @@
Оформление
Интервал
Управление внутренними папками для двусторонней синхронизации
- Включить двустороннюю синхронизацию
Двусторонняя синхронизация
Тёмное
Светлое
@@ -1054,7 +1051,6 @@
Отправить файлы
Все загрузки приостановлены
Кнопка «выгрузить объект»
- Отменить передачу
Удалить
Нет файлов, ожидающих передачу на сервер.
Сохраните здесь что-нибудь или включите автоматическую передачу файлов!
diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml
index 2d6756478a23..477b3ea1159b 100644
--- a/app/src/main/res/values-sk-rSK/strings.xml
+++ b/app/src/main/res/values-sk-rSK/strings.xml
@@ -48,13 +48,18 @@
Výstup zobrazený tu je generovaný umelou inteligenciou. Uistite sa, že vždy dôkladne skontrolujete.
Nepodarilo sa odoslať správu
Nepodarilo sa načítať správy chatu
+ Vrátiť sa na stránku asistenta
Naozaj chcete vymazať túto úlohu?
Odstrániť úlohu
Skúste poslať správu, aby ste rozprúdili konverzáciu.
Dobrý deň! S čím vám dnes môžem pomôcť?
+ Vyberte úlohu
+ Odoslať správu
+ Otvor zoznam rozhovorov
Pri vytváraní úlohy nastala chyba
Úloha vytvorená
Pri odstraňovaní úlohy nastala chyba
+ Úloha vymazaná
Zoznam úloh je prázdny. Skontrolujte konfiguráciu aplikácie asistenta.
Nie je možné načítať zoznam úloh, skontrolujte svoje internetové pripojenie.
Vymazať Úlohu
@@ -63,7 +68,11 @@
Asistent
Vstup
Výstup
+ zlyhalo
spustené
+ naplánované
+ úspešné
+ Stav úlohy: %1$s
neznámy
Premýšľam …
Priradený účet sa nenašiel
@@ -109,7 +118,7 @@
/AutomatickéNahrávanie
Tento adresár je už zahrnutý v synchronizácii nadradeného adresára, čo môže spôsobiť duplicitné nahrávania
Čaká sa na Wi-Fi pre spustenie nahrávania.
- Nahrávanie súborov z %s do %s
+ Nahrávanie súborov z %1$s do %2$s
Nastaviť
Nastavenie vytvorenia vlastného priečinku
Nastaviť vlastný priečinok
@@ -215,7 +224,6 @@
Spustenie mportu zlyhalo. Prosím skúste to znova
Nenašiel sa žiadny súbor
Nenašla sa posledná záloha!
- Detekcia zmien obsahu
Nepodarilo sa vytvoriť konverzáciu
Zmazať konverzáciu
Nepodarilo sa vymazať konverzáciu
@@ -498,6 +506,7 @@
%1$d z %2$d · %3$s
Došlo k chybe počas synchronizácie priečinka %s.
Nedostatok miesta na disku, synchronizácia zrušená
+ %s zložka synchronizovaná
Synchronizuje sa...
Nie sú tu žiadne priečinky
Názov adresára nemôže byť prázdny
@@ -758,7 +767,6 @@
Motív vzhľadu
Interval
Spravovať interné adresáre pre obojsmernú synchronizáciu
- Povoliť obojstrannú synchronizáciu
Obojsmerná synchronizácia
Tmavý
Svetlý
@@ -1007,6 +1015,12 @@
Náhľad nového súboru
Načítavanie trvá dlhšie ako sa očakávalo
Dnes
+ Zadajte text na preklad…
+ Preložiť z:
+ Preložiť do:
+ Stlačte tlačidlo na preklad
+ Preklad trvá dlhšie, než sa očakávalo.
+ Prekladám…
Zmazané súbory
Žiadne zmazané súbory
Tu budete mať možnosť obnoviť zmazané súbory.
@@ -1062,7 +1076,6 @@
Nahrať súbory
Všetky nahrávania sú pozastavené.
Tlačítko akcie Nahrať položku
- Zrušiť nahrávanie
Zmazať
Žiadne súbory na nahratie nie sú dostupné
Nahrajte nejaký obsah alebo si zapnite automatické nahrávanie.
@@ -1076,6 +1089,7 @@
Povolenia aplikácie
Vaše súbory nemožno nahrať bez prístupu k miestnemu úložisku. Klepnite pre udelenie povolenia.
Nahrávanie zastavené – Vyžaduje sa povolenie pre úložisko
+ Môžete to odstrániť alebo pokračovať v nahrávaní z nahratých súborov
%1$d / %2$d - %3$s
Šifrovanie je možné iba pri >= Android 5.0
Pre skopírovanie vybratých súborov do adresára %1$s nie je dostatok voľného miesta. Chcete ich namiesto toho presunúť?
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index e8a7be916e66..56d3fb49dcfb 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -836,7 +836,6 @@
Ime in zvrst datoteke za pošiljanje
Pošlji datoteke
Gumb za pošiljanje
- Prekliči pošiljanje
Izbriši
Ni pripravljenih datotek za pošiljanje
Pošljite kakšno vsebino, ali pa omogočite samodejno nalaganje.
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 05755bfaca80..7c334e28e944 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -629,7 +629,6 @@
Vendos emrin dhe tipin e dokumentit të aploduar
Ngarko skedarët
Butoni i veprimit të ngarkimit të një artikulli
- Anullo ngarkimin
Delete
Nuk ka asnjë ngarkim në gjëndje
Ngarko disa përmbajtje ose aktivizo ngarkimin direkt.
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index ff8777330dfc..d78ebf7ba731 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -103,7 +103,6 @@
/Аутоматска отпремања
Овај фолдер је већ укључен у синхронизацију фолдера родитеља, па се могу јавити двострука отпремања
Чека се на Wi-Fi за почетак отпремања
- Отпремају се фајлови са %s на %s
Подеси
Направи нову поставку за посебну фасциклу
Подесите произвољну фасциклу
@@ -209,7 +208,6 @@
Није успело покретање увоза. Молимо вас да покушате поново
Није нађен фајл
Не могу да нађем последњу резерву!
- Откривају се измене садржаја
Обриши разовор
Није пронађен ниједан разговор
Још увек нема разговора
@@ -739,7 +737,6 @@
Тема
Интервал
Управљање интерним фолдерима за двосмерну синхронизацију
- Укључи двосмерну синхронизацију
тамна
светла
системска
@@ -1034,7 +1031,6 @@
Отпреми фајлове
Сва отпремања су паузирана
Дугме за отпремање
- Откажи отпремање
Избриши
Нема доступних отпремања
Отпремите нешто или укључите аутоматско отпремање.
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 375cc2005b4c..7f90973f8d1a 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -106,7 +106,6 @@
/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
@@ -212,7 +211,6 @@
Importen kunde inte startas. Försök igen
Ingen fil funnen
Vi kunde inte hitta din senaste backup!
- Upptäcker innehållsförändringar
Kunde inte skapa konversationen
Ta bort konversationen
Kunde inte ta bort konversationen
@@ -755,7 +753,6 @@
Tema
Intervall
Hantera interna mappar för tvåvägssynk
- Aktivera tvåvägssynk
Mörkt
Ljust
Följ systemet
@@ -1054,7 +1051,6 @@
Ladda upp filer
Alla uppladdningar är pausade
Ladda upp objekthändelse-knapp
- Avbryt uppladdning
Ta bort
Inga uppladdningar tillgängliga
Ladda upp något innehåll eller aktivera automatisk uppladdning.
diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml
index 6c07b38b760e..23849331306d 100644
--- a/app/src/main/res/values-sw/strings.xml
+++ b/app/src/main/res/values-sw/strings.xml
@@ -53,6 +53,7 @@
Futa jukumu
Jaribu kutuma ujumbe ili kuzua mazungumzo.
Hujambo! Nikusaidie nini leo?
+ Tafadhali chagua kazi
Tuma ujumbe
Fungua orodha ya mazungumzo
Hitilafu ilitokea wakati wa kuunda jukumu
@@ -117,7 +118,7 @@
/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
+ Inapakia faili kutoka %1$s hadi %2$s
Sanidi
Unda usanidi mpya wa folda maalum
Sanidi folda maalum
@@ -223,7 +224,6 @@
Imeshindwa kuanza kuleta. Tafadhali jaribu tena
Hakuna faili iliyopatikana
Haikuweza kupata nakala yako ya mwisho!
- Inagundua mabadiliko ya maudhui
Imeshindwa kuunda mazungumzo
Futa mazungumzo
Imeshindwa kufuta mazungumzo
@@ -767,7 +767,6 @@
Mandhari
Muda
Dhibiti folda za ndani kwa usawazishaji wa njia mbili
- Washa usawazishaji wa njia mbili
Usawazishaji wa njia mbili
Giza
Mwanga
@@ -1016,6 +1015,12 @@
Kijipicha cha faili mpya
Upakiaji unachukua muda mrefu kuliko ilivyotarajiwa
Leo
+ Weka maandishi ili kutafsiri...
+ Tafsiri kutoka:
+ Tafsiri kwenye:
+ Bonyeza kitufe ili kutafsiri
+ Tafsiri inachukua muda mrefu kuliko ilivyotarajiwa.
+ Inatafsiri...
Faili zilizofutwa
Hakuna faili zilizofutwa
Utaweza kurejesha faili zilizofutwa kutoka hapa.
@@ -1071,7 +1076,6 @@
Pakia faili
Upakiaji wote umesitishwa
Kitufe cha kitendo cha kupakia
- Ghairi upakiaji
Futa
Hakuna vipakiwa vinavyopatikana
Pakia baadhi ya maudhui au uwashe upakiaji kiotomatiki.
@@ -1085,6 +1089,7 @@
Ruhusa za programu
Faili zako haziwezi kupakiwa bila ufikiaji wa hifadhi ya ndani. Gusa ili kutoa ruhusa.
Upakiaji Umesimamishwa - Ruhusa ya Kuhifadhi Inahitajika
+ Unaweza kuiondoa au kuirejesha kutoka kwa Vipakiwa
%1$d / %2$d - %3$s
Usimbaji fiche unawezekana tu kwa >= Android 5.0
Nafasi haitoshi huzuia kunakili faili zilizochaguliwa kwenye folda ya %1$s. Je, ungependa kuzihamishia hapo badala yake?
diff --git a/app/src/main/res/values-th-rTH/strings.xml b/app/src/main/res/values-th-rTH/strings.xml
index 33e25a2a9c56..b0a165573e53 100644
--- a/app/src/main/res/values-th-rTH/strings.xml
+++ b/app/src/main/res/values-th-rTH/strings.xml
@@ -580,7 +580,6 @@
ชื่อไฟล์
ประเภทไฟล์
อัพโหลดไฟล์จากเครื่อง
- ยกเลิกการอัพโหลด
ลบ
แสกนเอกสารโดยใช้กล้อง
เกิดข้อขัดแย้งของการซิงค์ โปรดแก้ไขด้วยตนเอง
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 96fc0829f413..e0647c660716 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -110,7 +110,6 @@
/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
@@ -216,7 +215,6 @@
İçe aktarım başlatılamadı. Lütfen yeniden deneyin
Herhangi bir dosya bulunamadı
Son yedeğiniz bulunamadı!
- İçerik değişiklikleri denetleniyor
Görüşme oluşturulamadı
Görüşmeyi sil
Görüşme silinemedi
@@ -759,7 +757,6 @@
Tema
Aralık
İç klasörleri iki yönlü eşitleme ile yönetin
- İki yönlü eşitlemeyi aç
İki yönlü eşitleme
Koyu
Açık
@@ -1063,7 +1060,6 @@
Dosyaları yükle
Tüm yüklemeler duraklatıldı
Öge yükleme işlemi düğmesi
- Yüklemeyi iptal et
Sil
Herhangi bir yükleme yok
Bazı içerikler yükleyin ya da otomatik yükleme özelliğini açın.
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index 02740fa01a22..c159b641aaf6 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -108,7 +108,6 @@
/ ئۆزلىكىدىن چىقىرىش
بۇ قىسقۇچ بولشا باش قىسقۇچنىڭ ئىچىدە بار، شۇڭلاشقا قوشلاپ چىقىرىلىشنى كەلتۈرۈپ چىقىرىشى مۇمكىن
چىقىرىش ئۈچۈن ۋايف
- ھۆججەتلەرنى %s دىن %s
سەپلەڭ
يېڭى خاس ھۆججەت قىسقۇچ قۇرۇش
ئىختىيارى ھۆججەت قىسقۇچ ئورنىتىڭ
@@ -214,7 +213,6 @@
كىرگۈزۈشنى باشلىغىلى بولمىدى. قايتا سىناڭ
ھۆججەت تېپىلمىدى
ئاخىرقى زاپاسلاشنى تاپالمىدىڭىز!
- مەزمۇن ئۆزگ
سۆھبەت قۇرۇش مەغلۇپ بولدى
سۆھبەتنى ئۆچۈرۈڭ
سۆھبەتنى ئۆچۈرۈش مەغلۇپ بولدى
@@ -756,7 +754,6 @@
باشتېما
ئىنتىرۋال
ئىككى خىل ماسقەدەملەش ئۈچۈن ئىچكى قىسقۇچلارنى باشقۇرۇڭ
- ئىككى يۆنىلىشلىك ماس-قەدەملەشنى قوزغات
قاراڭغۇ
نۇر
سىستېمىغا ئەگىشىڭ
@@ -1056,7 +1053,6 @@
ھۆججەتلەرنى يۈكلەڭ
بارلىق يۈكلەش توختىتىلدى
تۈر ھەرىكەت كۇنۇپكىسىنى يۈكلەڭ
- يۈكلەشتىن ۋاز كەچ
ئۆچۈرۈش
يوللاشقا بولمايدۇ
بەزى مەزمۇنلارنى يۈكلەڭ ياكى ئاپتوماتىك يوللاشنى قوزغىتىڭ.
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index b20fe4cb321b..580b3f8f8d1a 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -52,7 +52,8 @@
Дійсно вилучити це завдання?
Вилучити завдання
Спробуйте надіслати повідомлення, щоб розпочати розмову.
- Привіт! Чим я можу вам сьогодні допомогти?
+ Вітаю! Чим я можу вам сьогодні допомогти?
+ Виберіть завдання
Надіслати повідомлення
Відкрити список розмов
Помилка під час додавання завдання
@@ -104,7 +105,7 @@
Вузол не знайдено
%1$s не підтримує одночасно декілька облікових записів
Не вдалося встановити з\'єднання
- Скасувати авторизацію
+ Скасувати вхід
Зазначте дійсну адресу сервера
Не вдалося отримати дані для входу. Спробуйте ще раз.
Помилка під час обробки запиту на вхід. Спробуйте пізніше.
@@ -117,7 +118,7 @@
/AutoUpload
Цей каталог вже включено до синхронізації каталогу вищого рівня, що може призвести до задвоєння завантаження
Очікується з\'єднання з WiFi для початку завантаження
- Завантаження файлів з %s до %s
+ Завантаження файлів з %1$s до %2$s
Налаштування
Власний каталог користувача
Каталог користувача
@@ -223,7 +224,6 @@
Не вдалося здійснити імпорт. Спробуйте ще раз
Файл не знайдено
Неможливо знайти вашу останню резервну копію
- Пошук змін у вмісті
Не вдалося створити розмову
Вилучити розмову
Не вдалося вилучити розмову
@@ -582,6 +582,7 @@
Увійти
Посилання на ваш веб-інтерфейс %1$s, коли ви відкриєте його у веб-переглядачі.
Вилучити журнал
+ Експорт журналу
Оновити
Шукати у журналі
Надіслати журнал ел.поштою
@@ -767,7 +768,6 @@
Тема
Інтервал
Керувати внутрішніми каталогами для двосторонньої синхронізації
- Увімкнути двосторонню синхронізацію
Двостороння синхронізація
Темна
Світла
@@ -1016,6 +1016,12 @@
Ескіз нового файлу
Завантаження займає більше часу, ніж очікувалося
Сьогодні
+ Додайте текст для перекладу...
+ Перекласти з:
+ Перекласти мовою:
+ Натисніть кнопку, щоб перекласти
+ Переклад триває довше очікуваного часу.
+ Триває переклад...
Кошик
Кошик порожній
Тут можна відновити файли, які було вилучено.
@@ -1071,7 +1077,6 @@
Завантажити файл
Всі завантаження призупинено
Кнопка завантаження
- Скасувати завантаження
Вилучити
Ви ще нічого не завантажили
Завантажте вміст або увімкніть автоматичне завантаження
@@ -1085,6 +1090,7 @@
Дозволи застосунків
Неможливо завантажити ваші файли без доступу до місця зберігання файлів на вашому пристрої. Натисніть, щоби надати доступ.
Завантаження зупинено - потрібний доступ до місця зберігання файлів
+ Ви можете вилучити або повторити це із завантажень
%1$d/%2$d-%3$s
Шифрування можливе лише на пристроях з версію Android 5.0+
Нестача місця не дозволяє скопіювати файли до каталогу%1$s. Чи бажаєте перемістити їх замість копіювання?
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index b7ad0fef2bc2..f369e9ee82b0 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -767,7 +767,6 @@
Nhập tên tệp và loại tệp để tải lên
Tải lên tập tin
Nút hành động tải mục lên
- Hủy tải lên
Xóa
Không có tải lên nào có sẵn
Tải lên một vài nội dung hoặc kích hoạt tải lên tự động
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index b5e185a44656..cc38d3718500 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -117,7 +117,6 @@
/自动上传
此文件夹已包含在上级文件夹的同步中,这可能会导致重复上传
正在等待 Wi-Fi 开始上传
- 正在将文件从 %s 上传到 %s
配置
创建新的自定义文件夹设定
设置一个自定义文件夹
@@ -223,7 +222,6 @@
导入无法开始。请重试
没有文件被发现
无法找到末次备份
- 正在检测内容更改
无法创建对话
删除对话
无法删除对话
@@ -767,7 +765,6 @@
主题
间隔
管理内部文件夹以实现双向同步
- 启用双向同步
双向同步
深色
浅色
@@ -1074,7 +1071,6 @@
上传文件
所有上传已暂停
上传项操作按钮
- 取消上传
删除
没有可以上传的
上传一些内容或启用自动上传。
diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml
index 71f1f49283dc..4579bf190498 100644
--- a/app/src/main/res/values-zh-rHK/strings.xml
+++ b/app/src/main/res/values-zh-rHK/strings.xml
@@ -53,6 +53,7 @@
刪除任務
試著發送一條訊息來引發對話。
你好!今天我能幫你什麼忙呢?
+ 請選擇任務
傳送訊息
打開對話清單
建立任務項目時發生錯誤
@@ -117,7 +118,7 @@
自動上傳
此資料夾已包含在父資料夾的同步中,這可能會導致重複上傳
等待 Wi-Fi 開始上傳
- 從 %s 上傳檔案至 %s
+ 正從 %1$s 上傳檔案至 %2$s
設定
新增自訂資料夾
設定自訂資料夾
@@ -223,7 +224,6 @@
匯入無法開始。請再試一次
無檔案
找不到您最新的備份
- 偵測內容變更
無法創建對話
刪除對話
無法刪除對話
@@ -582,6 +582,7 @@
登入
在瀏覽器中打開 %1$s 網絡界面的連結。
刪除紀錄檔案
+ 導出記錄
重整
搜尋紀錄檔案
通過電郵發送紀錄
@@ -767,7 +768,6 @@
佈景主題
間距
管理內部資料夾以進行雙向同步
- 啟用雙向同步
雙向同步
深色
淺色
@@ -1016,6 +1016,12 @@
新增檔案的縮圖
載入時間比預期久
今日
+ 輸入要翻譯的文字 ...
+ 翻譯從:
+ 翻譯至:
+ 按下按鈕翻譯
+ 翻譯時間比預期長。
+ 正在翻譯 ...
已刪除檔案
無已刪除檔案
您可以從這裡還原已刪除的檔案
@@ -1071,7 +1077,6 @@
上傳檔案
所有上傳皆已暫停
上傳操作按鈕
- 取消上戴
刪除
沒有上傳的檔案
上傳一些內容或者啟動自動上傳。
@@ -1085,6 +1090,7 @@
應用程式權限
您的檔案無法上傳,因為沒有訪問本地存儲的權限。請點擊以授予權限。
上傳已停止 – 需要儲存權限
+ 您可以從上傳清單中刪除或還原它
%1$d / %2$d - %3$s
加密功能只適用於 Android 5.0 及以上版本
空間不足導致無法將所選檔案複製到 %1$s 資料夾中。您想改為將檔案移到那裡嗎?
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 7e3e6f6b0c8c..c24b5061fc66 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -53,6 +53,7 @@
刪除工作項目
嘗試傳送訊息來引發對話。
嗨!我現在能怎麼協助您呢?
+ 請選取任務
傳送訊息
開啟對話清單
建立工作項目時發生錯誤
@@ -117,7 +118,7 @@
/AutoUpload
此資料夾已包含在上層資料夾的同步中,這可能會導致重複上傳
等待 Wi-Fi 開始上傳
- 從 %s 上傳檔案至 %s
+ 正在從 %1$s 上傳檔案到 %2$s
設定
新增自訂資料夾
設置自訂資料夾
@@ -223,7 +224,6 @@
匯入無法開始。請再試一次
找不到檔案
找不到您最新的備份!
- 偵測內容變更
建立對話失敗
刪除對話
刪除對話失敗
@@ -767,7 +767,6 @@
佈景主題
間隔
管理內部資料夾以進行雙向同步
- 啟用雙向同步
雙向同步
深色
淺色
@@ -1016,6 +1015,12 @@
新檔案的縮圖
載入時間比預期久
今天
+ 輸入要翻譯的文字……
+ 翻譯自:
+ 翻譯至:
+ 按下按鈕翻譯
+ 翻譯時間比預期長。
+ 正在翻譯……
刪除的檔案
無刪除的檔案
您可以從這裡還原刪除的檔案。
@@ -1071,7 +1076,6 @@
上傳檔案
所有上傳皆已暫停
上傳項目動作按鈕
- 取消上傳
刪除
沒有上傳的檔案
上傳一些內容或者啟用自動上傳。
@@ -1085,6 +1089,7 @@
應用程式權限
若無法存取本機儲存空間,則無法上傳您的檔案。輕點即可授予權限。
上傳已停止 – 需要儲存空間權限
+ 您可以從上傳清單中移除或還原它
%1$d / %2$d - %3$s
加密功能僅適用於 Android 5.0 及後續版本
空間不足以將選擇的檔案複製到 %1$s 資料夾。是否要改成移動它們?
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97f04a8862af..c1363c6d6fee 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -660,6 +660,7 @@
Logs
Refresh
Send logs by email
+ Export logs
Delete logs
No app for sending logs found. Please install an email client.
%1$s Android app logs
@@ -927,7 +928,6 @@
Offline operations
Shows progress of offline file operations
- Detecting content changes
Content observer
Detects local file changes
@@ -986,7 +986,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
+ Uploading files from %1$s to %2$s
*
Name
Password
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index adada5180474..ff17922ff1f8 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -17,7 +17,7 @@
android:title="@string/prefs_data_storage_location"
android:key="data_storage_location"
android:summary="@string/prefs_data_storage_location_summary" />
-
diff --git a/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt
index a7aff50a8cc3..5eb368139729 100644
--- a/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt
+++ b/app/src/test/java/com/owncloud/android/utils/AutoUploadHelperTest.kt
@@ -15,6 +15,7 @@ import com.nextcloud.client.preferences.SubFolderRule
import com.nextcloud.utils.extensions.shouldSkipFile
import com.owncloud.android.datamodel.MediaFolderType
import com.owncloud.android.datamodel.SyncedFolder
+import com.owncloud.android.datamodel.UploadsStorageManager
import io.mockk.clearAllMocks
import io.mockk.mockk
import org.junit.After
@@ -36,6 +37,7 @@ class AutoUploadHelperTest {
private val mockContext: Context = mockk(relaxed = true)
private lateinit var repo: FileSystemRepository
+ private val mockUploadsStorageManager: UploadsStorageManager = mockk(relaxed = true)
@Before
fun setup() {
@@ -43,7 +45,7 @@ class AutoUploadHelperTest {
tempDir.mkdirs()
assertTrue("Failed to create temp directory", tempDir.exists())
- repo = FileSystemRepository(mockDao, mockContext)
+ repo = FileSystemRepository(mockDao, mockUploadsStorageManager, mockContext)
}
@After
diff --git a/fastlane/metadata/android/en-US/changelogs/30360051.txt b/fastlane/metadata/android/en-US/changelogs/30360051.txt
new file mode 100644
index 000000000000..cec3850930db
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/30360051.txt
@@ -0,0 +1,9 @@
+## 3.36.0 RC1 (February 4, 2026)
+
+- Streamlined sharing flow and refined UI
+- Enhanced Nextcloud Assistant with chat and translation support
+- Bug fixes and performance improvements
+
+Minimum: NC 20 Server, Android 9 Nougat
+
+For a full list, please see https://github.com/nextcloud/android/milestone/120
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/30360051.txt.license b/fastlane/metadata/android/en-US/changelogs/30360051.txt.license
new file mode 100644
index 000000000000..a67e1fec2721
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/30360051.txt.license
@@ -0,0 +1,2 @@
+# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/30360052.txt b/fastlane/metadata/android/en-US/changelogs/30360052.txt
new file mode 100644
index 000000000000..3000245fd9c8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/30360052.txt
@@ -0,0 +1,8 @@
+## 3.36.0 RC2 (February 11, 2026)
+
+- Auto upload fixes
+- Bug fixes and performance improvements
+
+Minimum: NC 20 Server, Android 9 Nougat
+
+For a full list, please see https://github.com/nextcloud/android/milestone/120
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/30360052.txt.license b/fastlane/metadata/android/en-US/changelogs/30360052.txt.license
new file mode 100644
index 000000000000..a67e1fec2721
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/30360052.txt.license
@@ -0,0 +1,2 @@
+# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/330000090.license b/fastlane/metadata/android/en-US/changelogs/330000090.license
new file mode 100644
index 000000000000..a67e1fec2721
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/330000090.license
@@ -0,0 +1,2 @@
+# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/330000090.txt b/fastlane/metadata/android/en-US/changelogs/330000090.txt
new file mode 100644
index 000000000000..0c147eb5431e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/330000090.txt
@@ -0,0 +1,8 @@
+## 33.0.0 (February 18, 2026)
+
+- Auto upload improvements
+- Bug fixes and performance improvements
+
+Minimum: NC 20 Server, Android 9 Nougat
+
+For a full list, please see https://github.com/nextcloud/android/milestone/120
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8618391638e9..17498e0c9bba 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -5,7 +5,7 @@
androidCommonLibraryVersion = "4fc0f29981"
androidGifDrawableVersion = "1.2.30"
androidImageCropperVersion = "4.7.0"
-androidLibraryVersion ="38328f338c00870c6a062188974f8f30a61fb450"
+androidLibraryVersion ="2.23.0"
androidPluginVersion = "9.0.0"
androidsvgVersion = "1.4"
androidxMediaVersion = "1.5.1"
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 37b90dc159d7..99bde9333885 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -20751,6 +20751,14 @@
+
+
+
+
+
+
+
+
@@ -21748,6 +21756,14 @@
+
+
+
+
+
+
+
+