[PM-25654] Add image attachment preview#5864
Conversation
|
Thank you for your contribution! We've added this to our internal tracking system for review. Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process. |
|
Hi @amrg101! Thank you for submitting this awesome new feature. I've passed it along to the appropriate teams for product review. We'll provide status updates as they become available. |
|
wow this looks great, would love to see that on iOS too |
|
@SaintPatrck are there any new information for this PR? |
SaintPatrck
left a comment
There was a problem hiding this comment.
Hi @amrg101
Apologies for the delay. This feature has been approved. If you are still willing to make the suggested changes, we can move the pull request forward. If you do not have the time, let us know and we can take it from here.
Overall, the changes look good. We will need tests for all of this new functionality. I've also left a few comments that need to be addressed.
| } | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
All files must end in a newline.
| } | |
| } | |
|
|
||
| /** | ||
| * Displays a secure, temporary image attachment in a full-screen dialog with zoom and pan | ||
| * capabilities. The dialog can be dismissed by clicking the close button or pressing the back button. |
There was a problem hiding this comment.
Max line length is 100.
| * capabilities. The dialog can be dismissed by clicking the close button or pressing the back button. | |
| * capabilities. The dialog can be dismissed by clicking the close button or pressing the back | |
| * button. |
| LaunchedEffect(attachmentFile) { | ||
| val loadedPainter = withContext(Dispatchers.IO) { | ||
| try { | ||
| val bitmap = BitmapFactory.decodeFile(attachmentFile.path) |
There was a problem hiding this comment.
🎨 It might be worth downsampling the image to prevent OOM exceptions for large files since we support attachments up to 500MB.
| shouldShowPremiumWarningDialog = true | ||
| return@BitwardenStandardIconButton | ||
| } | ||
| onAttachmentPreviewClick(attachmentItem) |
There was a problem hiding this comment.
We also need a large file warning here, like the download button has.
| ) | ||
| onAttachmentPreviewClick = vaultCommonItemTypeHandlers | ||
| .onAttachmentPreviewClick, | ||
| ) |
There was a problem hiding this comment.
I don't think this indention is correct. Can you run the formatter here?
| AttachmentPreviewDialog( | ||
| attachmentFile = dialog.file, | ||
| onDismissRequest = onDismissRequest, | ||
| onLoaded = onPreviewLoaded |
There was a problem hiding this comment.
Missing a trailing comma
| onLoaded = onPreviewLoaded | |
| onLoaded = onPreviewLoaded, |
| VaultItemAction.Internal.AttachmentDecryptReceive( | ||
| result = result, | ||
| fileName = action.attachment.title, | ||
| isPreview = false |
There was a problem hiding this comment.
Missing trailing comma
| isPreview = false | |
| isPreview = false, |
| VaultItemAction.Internal.AttachmentDecryptReceive( | ||
| result = result, | ||
| fileName = action.attachment.title, | ||
| isPreview = true |
There was a problem hiding this comment.
Missing trailing comma
| isPreview = true | |
| isPreview = true, |
| modifier = modifier | ||
| .defaultMinSize(minHeight = 60.dp) | ||
| .cardStyle(cardStyle = cardStyle, paddingStart = 16.dp) | ||
| .cardStyle(cardStyle = cardStyle, paddingStart = 16.dp, paddingEnd = 8.dp) |
There was a problem hiding this comment.
🤔 The seems off. I think we need to take an approach similar to BitwardenTextRow and use an outer container (Box) and SpaceBetween. @david-livefront thoughts on this?
There was a problem hiding this comment.
@SaintPatrck is correct, we should not be adding the additional paddingEnd as that will break alignment with other rows.
| var showContent by remember { mutableStateOf(false) } | ||
|
|
||
| LaunchedEffect(attachmentFile) { | ||
| val loadedPainter = withContext(Dispatchers.IO) { |
There was a problem hiding this comment.
♻️ Not a huge fan of this explicit Dispatchers.IO use, but considering the alternatives it may be the cleanest way. 😢
Nothing to change here, for now.
| .background(Color.Black.copy(alpha = 0.5f)) | ||
| ) { | ||
| Icon( | ||
| imageVector = Icons.Filled.Close, |
There was a problem hiding this comment.
Please use our icons.
This one should be painter = rememberVectorPainter(id = BitwardenDrawable.ic_close),
| .fillMaxSize() | ||
| .pointerInput(Unit) { | ||
| detectTransformGestures { _, pan, zoom, _ -> | ||
| scale = (scale * zoom).coerceIn(1f, 5f) |
There was a problem hiding this comment.
Can you make some constants for this. 5f is a magic number.
| BitmapPainter(bitmap.asImageBitmap()) | ||
| } else { | ||
| null | ||
| } |
There was a problem hiding this comment.
Can we simplify this too:
BitmapFactory
.decodeFile(attachmentFile.path)
?.let { BitmapPainter(it.asImageBitmap()) }| .align(Alignment.TopEnd) | ||
| .padding(16.dp) | ||
| .clip(CircleShape) | ||
| .background(Color.Black.copy(alpha = 0.5f)) |
There was a problem hiding this comment.
We should be using our Bitwarden components to ensure that adhere to our style guide.
Something like this should do the trick:
BitwardenTonalIconButton(
vectorIconRes = BitwardenDrawable.ic_close,
contentDescription = stringResource(id = BitwardenString.close),
onClick = onDismissRequest,
modifier = Modifier
.padding(top = 8.dp, end = 24.dp)
.align(Alignment.TopEnd),
)| } | ||
| } | ||
|
|
||
| if (showContent && painter != null) { |
There was a problem hiding this comment.
I don't think we need this showContent at all.
Can we just base it on whether the image is loaded:
painter?.let {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Image(
painter = it,
contentDescription = stringResource(id = BitwardenString.preview),| } | ||
| } | ||
|
|
||
| private fun isImageFile(fileName: String?): Boolean { |
There was a problem hiding this comment.
The file name will never be null, right?
|
@SaintPatrck @david-livefront Thanks for the review! I've addressed the feedback in the latest commit. |
🎟️ Tracking
https://community.bitwarden.com/t/view-attachments-without-downloading/52/19
📔 Objective
Viewing image attachments is a hassle since you have to download, save, and open them in another app.
This adds a built-in preview for images, Ideally, we’d have a full file manager that supports all types, but since images are the ones most often needing quick viewing, I think this is okay for now.
The implementation:
📸 Screenshots
Screen_Recording_20250910_170903_Bitwarden.Dev.mp4
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:) or similar for great changes:memo:) or ℹ️ (:information_source:) for notes or general info:question:) for questions:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion:art:) for suggestions / improvements:x:) or:warning:) for more significant problems or concerns needing attention:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt:pick:) for minor or nitpick changes