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..5409a793d298 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
@@ -98,26 +98,33 @@ internal class LinkShareViewHolder(itemView: View) : RecyclerView.ViewHolder(ite
}
}
- private fun setSubline(binding: FileDetailsShareLinkShareItemBinding?, context: Context?, publicShare: OCShare) {
+ /**
+ * Displays download limit information when available, hides subline otherwise.
+ * Clears text on hide to prevent RecyclerView item reuse issues.
+ */
+ private fun setSubline(
+ binding: FileDetailsShareLinkShareItemBinding?,
+ context: Context?,
+ publicShare: OCShare
+ ) {
if (binding == null || context == null) {
return
}
- val downloadLimit = publicShare.fileDownloadLimit
- if (downloadLimit != null) {
- val remaining = publicShare.remainingDownloadLimit() ?: return
- val text = context.resources.getQuantityString(
+ val remaining = publicShare.remainingDownloadLimit()?.coerceAtLeast(0)
+
+ if (remaining == null) {
+ // No download limit set at all
+ binding.subline.text = ""
+ binding.subline.visibility = View.GONE
+ } else {
+ val binding.subline.text = context.resources.getQuantityString(
R.plurals.share_download_limit_description,
remaining,
remaining
)
-
- binding.subline.text = text
binding.subline.visibility = View.VISIBLE
- return
}
-
- binding.subline.visibility = View.GONE
}
private fun setPermissionName(
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 d97dfc911c40..be099b4e35df 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
@@ -42,6 +42,8 @@ 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 java.text.NumberFormat
+import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -491,22 +493,24 @@ class FileDetailsSharingProcessFragment :
}
}
+ /**
+ * Pre-fills download limit controls with current remaining count for existing share.
+ * Clamps negative values to 0. Returns early if no limit is set.
+ */
private fun updateFileDownloadLimitView() {
if (!canSetDownloadLimit()) {
return
}
- // user can set download limit thus no need to rely on current limit to show download limit
- showFileDownloadLimitInput(true)
+ showFileDownloadLimitInput(true) // only other option
binding.shareProcessSetDownloadLimitSwitch.visibility = View.VISIBLE
binding.shareProcessSetDownloadLimitInput.visibility = View.VISIBLE
- val currentLimit = share?.remainingDownloadLimit() ?: return
- if (currentLimit > 0) {
- binding.shareProcessSetDownloadLimitSwitch.isChecked = true
- binding.shareProcessSetDownloadLimitInput.setText(
- "%d".format(Locale.getDefault(), currentLimit)
- )
+ val currentLimit = share?.remainingDownloadLimit()?.coerceAtLeast(0) ?: return
+
+ binding.shareProcessSetDownloadLimitSwitch.isChecked = true
+ binding.shareProcessSetDownloadLimitInput.setText(
+ NumberFormat.getInstance(Locale.getDefault()).format(currentLimit)
}
}
@@ -844,16 +848,50 @@ class FileDetailsSharingProcessFragment :
}
}
+ /**
+ * Validates and applies the download limit from the input field.
+ *
+ * Parses the user's input using locale-aware number formatting to handle
+ * grouping separators (e.g., "1,000" in US or "1.000" in Germany).
+ * Shows inline error messages for invalid input.
+ *
+ * @see [updateFileDownloadLimitView] for how the field is populated
+ */
private fun setDownloadLimit() {
+ if (!binding.shareProcessSetDownloadLimitSwitch.isChecked) {
+ // User wants to remove the download limit
+ fileOperationsHelper?.updateFilesDownloadLimit(share, 0)
+ return
+ }
+
val downloadLimitInput = binding.shareProcessSetDownloadLimitInput.text.toString().trim()
- val downloadLimit =
- if (binding.shareProcessSetDownloadLimitSwitch.isChecked && downloadLimitInput.isNotEmpty()) {
- downloadLimitInput.toInt()
- } else {
- 0
- }
- fileOperationsHelper?.updateFilesDownloadLimit(share, downloadLimit)
+ if (downloadLimitInput.isEmpty()) {
+ binding.shareProcessSetDownloadLimitInput.error = getString(R.string.share_download_limit_required)
+ return
+ }
+
+ // Parse with locale awareness to match formatting in updateFileDownloadLimitView
+ val downloadLimit = try {
+ NumberFormat.getInstance(Locale.getDefault())
+ .parse(downloadLimitInput)?.toInt()
+ } catch (e: ParseException) {
+ null
+ }
+
+ when {
+ downloadLimit == null -> {
+ binding.shareProcessSetDownloadLimitInput.error = getString(R.string.share_download_limit_invalid)
+ return
+ }
+ downloadLimit <= 0 -> {
+ binding.shareProcessSetDownloadLimitInput.error = getString(R.string.share_download_limit_must_be_positive)
+ return
+ }
+ else -> {
+ fileOperationsHelper?.updateFilesDownloadLimit(share, downloadLimit)
+ }
+ }
}
private fun createShare(noteText: String) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b4cac075161e..656da2b0de7e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1452,9 +1452,13 @@
Unable to set download limit. Please check capabilities.
Download limit
+ - No downloads remaining
- %1$d download remaining
- %1$d downloads remaining
+ Download limit is required
+ Please enter a valid number
+ Download limit must be greater than 0
Please manually check terms of service!
Terms of service
I agree to the above ToS