Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1452,9 +1452,13 @@
<string name="set_download_limit_failed">Unable to set download limit. Please check capabilities.</string>
<string name="download_limit">Download limit</string>
<plurals name="share_download_limit_description">
<item quantity="zero">No downloads remaining</item>
<item quantity="one">%1$d download remaining</item>
<item quantity="other">%1$d downloads remaining</item>
</plurals>
<string name="share_download_limit_required">Download limit is required</string>
<string name="share_download_limit_invalid">Please enter a valid number</string>
<string name="share_download_limit_must_be_positive">Download limit must be greater than 0</string>
<string name="sign_tos_failed">Please manually check terms of service!</string>
<string name="terms_of_service_title">Terms of service</string>
<string name="terms_of_services_agree">I agree to the above ToS</string>
Expand Down
Loading