From f10666ff648c6265fa768533153c9b70244a3c7d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 4 Dec 2025 12:09:22 +0100 Subject: [PATCH 1/5] fix: update file download limit Signed-off-by: alperozturk --- .../nextcloud/client/database/dao/ShareDao.kt | 3 ++ .../utils/extensions/OCShareExtensions.kt | 9 ++++ .../GetFilesDownloadLimitOperation.kt | 30 ------------ .../UpdateShareDownloadLimitOperation.kt | 47 +++++++++++++++++++ .../android/ui/adapter/LinkShareViewHolder.kt | 5 +- .../FileDetailsSharingProcessFragment.kt | 45 +++++++++++++++--- 6 files changed, 100 insertions(+), 39 deletions(-) delete mode 100644 app/src/main/java/com/owncloud/android/operations/GetFilesDownloadLimitOperation.kt create mode 100644 app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt diff --git a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt index 61ea4a09c54a..5f44abbc6e8f 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt @@ -11,10 +11,13 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Update import com.nextcloud.client.database.entity.ShareEntity @Dao interface ShareDao { + @Update + suspend fun update(entity: ShareEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(shares: List) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt index dcb43b34dbfd..b72274cb8a6b 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCShareExtensions.kt @@ -10,6 +10,15 @@ package com.nextcloud.utils.extensions import com.nextcloud.client.database.entity.ShareEntity import com.owncloud.android.lib.resources.shares.OCShare +fun OCShare?.remainingDownloadLimit(): Int? { + val downloadLimit = this?.fileDownloadLimit ?: return null + return if (downloadLimit.limit > 0) { + downloadLimit.limit - downloadLimit.count + } else { + null + } +} + fun OCShare.hasFileRequestPermission(): Boolean = (isFolder && shareType?.isPublicOrMail() == true) fun List.mergeDistinctByToken(other: List): List = (this + other).distinctBy { it.token } diff --git a/app/src/main/java/com/owncloud/android/operations/GetFilesDownloadLimitOperation.kt b/app/src/main/java/com/owncloud/android/operations/GetFilesDownloadLimitOperation.kt deleted file mode 100644 index b3b35c541078..000000000000 --- a/app/src/main/java/com/owncloud/android/operations/GetFilesDownloadLimitOperation.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2025 ZetaTom <70907959+zetatom@users.noreply.github.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.operations - -import com.nextcloud.android.lib.resources.files.FileDownloadLimit -import com.nextcloud.android.lib.resources.files.GetFilesDownloadLimitRemoteOperation -import com.nextcloud.common.NextcloudClient -import com.owncloud.android.datamodel.FileDataStorageManager -import com.owncloud.android.lib.common.operations.RemoteOperationResult -import com.owncloud.android.lib.resources.shares.OCShare -import com.owncloud.android.operations.common.SyncOperation - -class GetFilesDownloadLimitOperation(val share: OCShare, storageManager: FileDataStorageManager) : - SyncOperation( - storageManager - ) { - override fun run(client: NextcloudClient): RemoteOperationResult> { - val token = share.token ?: return RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND) - val operation = GetFilesDownloadLimitRemoteOperation(token) - - val result = operation.execute(client) - - return result - } -} diff --git a/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt b/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt new file mode 100644 index 000000000000..9472a458153d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt @@ -0,0 +1,47 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations + +import com.nextcloud.android.lib.resources.files.FileDownloadLimit +import com.nextcloud.android.lib.resources.files.GetFilesDownloadLimitRemoteOperation +import com.nextcloud.utils.extensions.toEntity +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.resources.shares.OCShare +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class UpdateShareDownloadLimitOperation( + private val share: OCShare, + private val client: OwnCloudClient, + private val storageManager: FileDataStorageManager, + private val accountName: String +) { + @Suppress("DEPRECATION") + suspend fun run(): OCShare = withContext(Dispatchers.IO) { + val remotePath = share.path ?: return@withContext share + + val op = GetFilesDownloadLimitRemoteOperation(remotePath) + val result = op.execute(client) + + if (!result.isSuccess) { + return@withContext share + } + + val limits = result.data + ?.filterIsInstance() + ?: return@withContext share + + val newLimit = limits.firstOrNull() ?: return@withContext share + + share.fileDownloadLimit = newLimit + storageManager.shareDao.update(share.toEntity(accountName)) + + return@withContext share + } +} 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 4c6f3aa59af1..dd9a01832ccb 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 @@ -18,6 +18,7 @@ import android.text.TextUtils import android.view.View import androidx.core.content.res.ResourcesCompat import androidx.recyclerview.widget.RecyclerView +import com.nextcloud.utils.extensions.remainingDownloadLimit import com.nextcloud.utils.mdm.MDMConfig import com.owncloud.android.R import com.owncloud.android.databinding.FileDetailsShareLinkShareItemBinding @@ -105,8 +106,8 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite } val downloadLimit = publicShare.fileDownloadLimit - if (downloadLimit != null && downloadLimit.limit > 0) { - val remaining = downloadLimit.limit - downloadLimit.count + if (downloadLimit != null) { + val remaining = publicShare.remainingDownloadLimit() ?: return val text = context.resources.getQuantityString( R.plurals.share_download_limit_description, remaining, diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index d1d2327a51ed..8a01b9e8bdc1 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -16,10 +16,12 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.nextcloud.client.di.Injectable import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument import com.nextcloud.utils.extensions.isPublicOrMail +import com.nextcloud.utils.extensions.remainingDownloadLimit import com.nextcloud.utils.extensions.setVisibilityWithAnimation import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R @@ -33,6 +35,7 @@ import com.owncloud.android.lib.resources.shares.extensions.isAllowDownloadAndSy import com.owncloud.android.lib.resources.shares.extensions.toggleAllowDownloadAndSync import com.owncloud.android.lib.resources.status.NextcloudVersion import com.owncloud.android.lib.resources.status.OCCapability +import com.owncloud.android.operations.UpdateShareDownloadLimitOperation import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment import com.owncloud.android.ui.fragment.util.SharePermissionManager @@ -41,6 +44,9 @@ import com.owncloud.android.utils.ClipboardUtil import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.theme.CapabilityUtils import com.owncloud.android.utils.theme.ViewThemeUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject @@ -490,15 +496,40 @@ class FileDetailsSharingProcessFragment : } } + private suspend fun updateShareDownloadLimit() { + val share = share ?: return + val activity = activity as? FileActivity + val client = activity?.clientRepository?.getOwncloudClient() ?: return + val storageManager = activity.storageManager ?: return + val optionalUser = activity.user + if (optionalUser.isEmpty) { + return + } + + val accountName = optionalUser.get().accountName + val operation = UpdateShareDownloadLimitOperation(share, client, storageManager, accountName) + val newShare = operation.run() + + Log_OC.d(TAG, "share download limit updated") + this@FileDetailsSharingProcessFragment.share = newShare + } + private fun updateFileDownloadLimitView() { - if (canSetDownloadLimit()) { - binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE + if (!canSetDownloadLimit()) { + return + } - val currentDownloadLimit = share?.fileDownloadLimit?.limit ?: capabilities.filesDownloadLimitDefault - if (currentDownloadLimit > 0) { - binding.shareProcessSetDownloadLimitSwitch.isChecked = true - showFileDownloadLimitInput(true) - binding.shareProcessSetDownloadLimitInput.setText("$currentDownloadLimit") + lifecycleScope.launch(Dispatchers.IO) { + updateShareDownloadLimit() + + withContext(Dispatchers.Main) { + val currentLimit = share?.remainingDownloadLimit() ?: return@withContext + binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE + if (currentLimit > 0) { + binding.shareProcessSetDownloadLimitSwitch.isChecked = true + showFileDownloadLimitInput(true) + binding.shareProcessSetDownloadLimitInput.setText(currentLimit.toString()) + } } } } From f4bea54bfd6b501c2c837088f8f963276dc72f90 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 4 Dec 2025 12:13:56 +0100 Subject: [PATCH 2/5] fix: update file download limit Signed-off-by: alperozturk --- .../UpdateShareDownloadLimitOperation.kt | 47 ------------------- .../FileDetailsSharingProcessFragment.kt | 41 +++------------- 2 files changed, 6 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt diff --git a/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt b/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt deleted file mode 100644 index 9472a458153d..000000000000 --- a/app/src/main/java/com/owncloud/android/operations/UpdateShareDownloadLimitOperation.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2025 Alper Ozturk - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.operations - -import com.nextcloud.android.lib.resources.files.FileDownloadLimit -import com.nextcloud.android.lib.resources.files.GetFilesDownloadLimitRemoteOperation -import com.nextcloud.utils.extensions.toEntity -import com.owncloud.android.datamodel.FileDataStorageManager -import com.owncloud.android.lib.common.OwnCloudClient -import com.owncloud.android.lib.resources.shares.OCShare -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class UpdateShareDownloadLimitOperation( - private val share: OCShare, - private val client: OwnCloudClient, - private val storageManager: FileDataStorageManager, - private val accountName: String -) { - @Suppress("DEPRECATION") - suspend fun run(): OCShare = withContext(Dispatchers.IO) { - val remotePath = share.path ?: return@withContext share - - val op = GetFilesDownloadLimitRemoteOperation(remotePath) - val result = op.execute(client) - - if (!result.isSuccess) { - return@withContext share - } - - val limits = result.data - ?.filterIsInstance() - ?: return@withContext share - - val newLimit = limits.firstOrNull() ?: return@withContext share - - share.fileDownloadLimit = newLimit - storageManager.shareDao.update(share.toEntity(accountName)) - - return@withContext share - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index 8a01b9e8bdc1..dd4c1537f038 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -16,7 +16,6 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import com.nextcloud.client.di.Injectable import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSerializableArgument @@ -35,7 +34,6 @@ import com.owncloud.android.lib.resources.shares.extensions.isAllowDownloadAndSy import com.owncloud.android.lib.resources.shares.extensions.toggleAllowDownloadAndSync import com.owncloud.android.lib.resources.status.NextcloudVersion import com.owncloud.android.lib.resources.status.OCCapability -import com.owncloud.android.operations.UpdateShareDownloadLimitOperation import com.owncloud.android.ui.activity.FileActivity import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment import com.owncloud.android.ui.fragment.util.SharePermissionManager @@ -44,9 +42,6 @@ import com.owncloud.android.utils.ClipboardUtil import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.theme.CapabilityUtils import com.owncloud.android.utils.theme.ViewThemeUtils -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.Date import javax.inject.Inject @@ -496,41 +491,17 @@ class FileDetailsSharingProcessFragment : } } - private suspend fun updateShareDownloadLimit() { - val share = share ?: return - val activity = activity as? FileActivity - val client = activity?.clientRepository?.getOwncloudClient() ?: return - val storageManager = activity.storageManager ?: return - val optionalUser = activity.user - if (optionalUser.isEmpty) { - return - } - - val accountName = optionalUser.get().accountName - val operation = UpdateShareDownloadLimitOperation(share, client, storageManager, accountName) - val newShare = operation.run() - - Log_OC.d(TAG, "share download limit updated") - this@FileDetailsSharingProcessFragment.share = newShare - } - private fun updateFileDownloadLimitView() { if (!canSetDownloadLimit()) { return } - lifecycleScope.launch(Dispatchers.IO) { - updateShareDownloadLimit() - - withContext(Dispatchers.Main) { - val currentLimit = share?.remainingDownloadLimit() ?: return@withContext - binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE - if (currentLimit > 0) { - binding.shareProcessSetDownloadLimitSwitch.isChecked = true - showFileDownloadLimitInput(true) - binding.shareProcessSetDownloadLimitInput.setText(currentLimit.toString()) - } - } + val currentLimit = share?.remainingDownloadLimit() ?: return + binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE + if (currentLimit > 0) { + binding.shareProcessSetDownloadLimitSwitch.isChecked = true + showFileDownloadLimitInput(true) + binding.shareProcessSetDownloadLimitInput.setText(currentLimit.toString()) } } From 945ad6f8431b85fe7dc99d4d1aedb16244a126b4 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 4 Dec 2025 12:15:12 +0100 Subject: [PATCH 3/5] fix: update file download limit Signed-off-by: alperozturk --- .../main/java/com/nextcloud/client/database/dao/ShareDao.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt index 5f44abbc6e8f..c83f6a1f3ae5 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt @@ -11,14 +11,10 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query -import androidx.room.Update import com.nextcloud.client.database.entity.ShareEntity @Dao interface ShareDao { - @Update - suspend fun update(entity: ShareEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(shares: List) From c4ccea7f762da1e27b5e892384c45881c9a3afe0 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 4 Dec 2025 12:15:33 +0100 Subject: [PATCH 4/5] fix: update file download limit Signed-off-by: alperozturk --- app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt index c83f6a1f3ae5..61ea4a09c54a 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt @@ -15,6 +15,7 @@ import com.nextcloud.client.database.entity.ShareEntity @Dao interface ShareDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(shares: List) From d76ed5a76904396c20bd56b84b40d9e6b92ddb6c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 9 Dec 2025 11:56:36 +0100 Subject: [PATCH 5/5] ui fixes Signed-off-by: alperozturk --- .../FileDetailsSharingProcessFragment.kt | 25 ++++--------------- app/src/main/res/values/strings.xml | 1 - 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt index dd4c1537f038..6052fa51df6c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailsSharingProcessFragment.kt @@ -198,7 +198,6 @@ class FileDetailsSharingProcessFragment : setCheckboxStates() themeView() setVisibilitiesOfShareOption() - toggleNextButtonAvailability(isAnySharePermissionChecked()) logShareInfo() } @@ -496,11 +495,14 @@ class FileDetailsSharingProcessFragment : return } - val currentLimit = share?.remainingDownloadLimit() ?: return + // user can set download limit thus no need to rely on current limit to show download limit + showFileDownloadLimitInput(true) binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE + binding.shareProcessSetDownloadLimitInput.visibility = View.VISIBLE + + val currentLimit = share?.remainingDownloadLimit() ?: return if (currentLimit > 0) { binding.shareProcessSetDownloadLimitSwitch.isChecked = true - showFileDownloadLimitInput(true) binding.shareProcessSetDownloadLimitInput.setText(currentLimit.toString()) } } @@ -587,7 +589,6 @@ class FileDetailsSharingProcessFragment : val isCustomPermissionSelected = (optionId == R.id.custom_permission_radio_button) customPermissionLayout.setVisibilityWithAnimation(isCustomPermissionSelected) - toggleNextButtonAvailability(true) } // endregion } @@ -612,13 +613,6 @@ class FileDetailsSharingProcessFragment : ) } - private fun toggleNextButtonAvailability(value: Boolean) { - binding.run { - shareProcessBtnNext.isEnabled = value - shareProcessBtnNext.isClickable = value - } - } - @Suppress("NestedBlockDepth") private fun setCheckboxStates() { val currentPermissions = share?.permissions ?: permission @@ -676,7 +670,6 @@ class FileDetailsSharingProcessFragment : private fun togglePermission(isChecked: Boolean, permissionFlag: Int) { permission = SharePermissionManager.togglePermission(isChecked, permission, permissionFlag) - toggleNextButtonAvailability(true) } private fun showExpirationDateDialog(chosenDateInMillis: Long = chosenExpDateInMills) { @@ -781,14 +774,6 @@ class FileDetailsSharingProcessFragment : return } - if (!isSharePermissionChecked() && !isCustomPermissionSelectedAndAnyCustomPermissionTypeChecked()) { - DisplayUtils.showSnackMessage( - binding.root, - R.string.file_details_sharing_fragment_custom_permission_not_selected - ) - return - } - // if modifying existing share information then execute the process if (share != null) { updateShare() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a48e76517e0a..9df94fe5e626 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1448,7 +1448,6 @@ Sync duplication Could not load content The device is likely not connected to the internet - Please select custom permission Unknown Upload Stopped – Storage Permission Required Your files cannot be uploaded without access to local storage. Tap to grant permission.