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 @@ + + + + + + + +