Skip to content

Forms : Attachment size based functionality restrictions #518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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 @@ -302,6 +302,13 @@ internal class FormAttachmentState(
*/
val thumbnail: State<Bitmap?> = _thumbnail

/**
* The maximum attachment size in bytes that can be loaded. If [size] is greater than this limit,
* then the attachment will fail to load with an [AttachmentSizeLimitExceededException] when
* [load] is called.
*/
val maxAttachmentSize = 50_000_000L

/**
* The size of the thumbnail image.
*/
Expand All @@ -327,12 +334,11 @@ internal class FormAttachmentState(
_loadStatus.value = LoadStatus.Loading
var result = Result.success(Unit)
try {
if (formAttachment == null) {
result = Result.failure(Exception("Form attachment is null"))
} else {
formAttachment.retryLoad().onFailure {
result = Result.failure(it)
}.onSuccess {
result = when {
formAttachment == null -> Result.failure(IllegalStateException("Form attachment is null"))
formAttachment.size == 0L -> Result.failure(EmptyAttachmentException())
formAttachment.size > maxAttachmentSize -> Result.failure(AttachmentSizeLimitExceededException(maxAttachmentSize))
else -> formAttachment.retryLoad().onSuccess {
createThumbnail()
}
}
Expand Down Expand Up @@ -403,6 +409,9 @@ internal class FormAttachmentState(
else -> null
}
} catch (ex: Exception) {
if (ex is CancellationException) {
throw ex
}
null
}
}
Expand Down Expand Up @@ -521,3 +530,15 @@ internal fun AttachmentElementState.getNewAttachmentNameForContentType(
}
return "${prefix}$count.$extension"
}

/**
* Exception indicating that the attachment size exceeds the maximum limit.
*
* @param limit The maximum attachment size limit in bytes.
*/
internal class AttachmentSizeLimitExceededException(val limit : Long) : Exception("Attachment size exceeds the maximum limit of $limit MB")

/**
* Exception indicating that the attachment size is 0.
*/
internal class EmptyAttachmentException : Exception("Attachment size is 0")
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ internal fun AttachmentTile(
)
)
}
})
},
enabled = state.size <= state.maxAttachmentSize
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.delete)) },
leadingIcon = {
Expand Down Expand Up @@ -215,8 +217,11 @@ internal fun AttachmentTile(
delay(configuration.longPressTimeoutMillis)
wasALongPress = true
// handle long press
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
showContextMenu = true
if (state.size > 0) {
// show context menu only if the attachment is not empty
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
showContextMenu = true
}
}

is PressInteraction.Release -> {
Expand All @@ -226,11 +231,6 @@ internal fun AttachmentTile(
if (loadStatus is LoadStatus.NotLoaded || loadStatus is LoadStatus.FailedToLoad) {
// load attachment
state.loadWithParentScope()
if (state.size == 0L) {
// show an error toast if the attachment is empty since the load
// will likely fail
Toast.makeText(context, context.getString(R.string.download_empty_file), Toast.LENGTH_SHORT).show()
}
} else if (loadStatus is LoadStatus.Loaded) {
// open attachment
val intent = Intent()
Expand All @@ -257,6 +257,29 @@ internal fun AttachmentTile(
}
}
}
LaunchedEffect(Unit) {
state.loadStatus.collectLatest {
// show an error toast if the attachment failed to load
if (it is LoadStatus.FailedToLoad) {
val message = when(it.error) {
is AttachmentSizeLimitExceededException -> {
val limit = (it.error as AttachmentSizeLimitExceededException).limit
val limitFormatted = Formatter.formatFileSize(context, limit)
context.getString(R.string.attachment_size_limit_exceeded, limitFormatted)
}

is EmptyAttachmentException ->{
context.getString(R.string.download_empty_file)
}

else ->{
it.error.localizedMessage
}
}
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
}
}

@Composable
Expand Down
1 change: 1 addition & 0 deletions toolkit/featureforms/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,5 @@
<string name="attachment_is_empty">Empty files are not supported</string>
<string name="download_empty_file">Empty files cannot be downloaded</string>
<string name="attachment_deleted">%1s was deleted successfully</string>
<string name="attachment_size_limit_exceeded">Attachments larger than %1$s cannot be downloaded</string>
</resources>