From 808dc92cd76b406cd0b9656b0903910ba1b11b04 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Sep 2025 12:17:06 +0200 Subject: [PATCH] Added PDF file preview in conversation (message bubble + documents list) --- CHANGELOG.md | 1 + .../linphone/ui/main/chat/model/FileModel.kt | 80 +++++++++++++++---- .../chat_bubble_single_file_content.xml | 15 ++++ 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398ac3042..bab4b102c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Group changes to describe their impact on the project, as follows: ### Added - Added the ability to edit/delete chat messages sent less than 24 hours ago. +- Added PDF preview in conversation (message bubble & documents list) - Added hover effect when using a mouse (useful for tablets or devices with desktop mode) - Support right click on some items to open bottom sheet/menu - Added toggle speaker action in active call notification diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt index 1b4c2d315..2d922731e 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt @@ -19,9 +19,11 @@ */ package org.linphone.ui.main.chat.model +import android.graphics.pdf.PdfRenderer import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION import android.media.ThumbnailUtils +import android.os.ParcelFileDescriptor import android.provider.MediaStore import androidx.annotation.AnyThread import androidx.annotation.UiThread @@ -35,6 +37,9 @@ import org.linphone.core.tools.Log import org.linphone.utils.FileUtils import org.linphone.utils.TimestampUtils import androidx.core.net.toUri +import androidx.core.graphics.createBitmap +import kotlinx.coroutines.withContext +import java.io.File class FileModel @AnyThread @@ -97,6 +102,9 @@ class FileModel if (!isWaitingToBeDownloaded) { val extension = FileUtils.getExtensionFromFileName(path) isPdf = extension == "pdf" + if (isPdf) { + loadPdfPreview() + } val mime = FileUtils.getMimeTypeFromExtension(extension) mimeTypeString = mime @@ -168,21 +176,65 @@ class FileModel } @AnyThread - private fun loadVideoPreview() { - try { - Log.i("$TAG Try to create an image preview of video file [$path]") - val previewBitmap = ThumbnailUtils.createVideoThumbnail( - path, - MediaStore.Images.Thumbnails.MINI_KIND - ) - if (previewBitmap != null) { - val previewPath = FileUtils.storeBitmap(previewBitmap, fileName) - Log.i("$TAG Preview of video file [$path] available at [$previewPath]") - mediaPreview.postValue(previewPath) - mediaPreviewAvailable.postValue(true) + private fun loadPdfPreview() { + scope.launch { + withContext(Dispatchers.IO) { + try { + val pdfFileDescriptor = ParcelFileDescriptor.open( + File(path), + ParcelFileDescriptor.MODE_READ_ONLY + ) + if (pdfFileDescriptor == null) { + Log.e("$TAG Failed to get a file descriptor for PDF at [$path]") + return@withContext + } + + val pdfRenderer = PdfRenderer(pdfFileDescriptor) + val pdfFirstPage = pdfRenderer.openPage(0) + val previewBitmap = createBitmap(pdfFirstPage.width, pdfFirstPage.height) + pdfFirstPage.render( + previewBitmap, + null, + null, + PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY + ) + + val previewPath = FileUtils.storeBitmap(previewBitmap, fileName) + Log.i("$TAG Preview of PDF file [$path] available at [$previewPath]") + mediaPreview.postValue(previewPath) + mediaPreviewAvailable.postValue(true) + + previewBitmap.recycle() + pdfFirstPage.close() + pdfRenderer.close() + pdfFileDescriptor.close() + } catch (e: Exception) { + Log.e("$TAG Failed to get image preview for PDF file [$path]: $e") + } + } + } + } + + @AnyThread + private fun loadVideoPreview() { + scope.launch { + withContext(Dispatchers.IO) { + try { + Log.i("$TAG Try to create an image preview of video file [$path]") + val previewBitmap = ThumbnailUtils.createVideoThumbnail( + path, + MediaStore.Images.Thumbnails.MINI_KIND + ) + if (previewBitmap != null) { + val previewPath = FileUtils.storeBitmap(previewBitmap, fileName) + Log.i("$TAG Preview of video file [$path] available at [$previewPath]") + mediaPreview.postValue(previewPath) + mediaPreviewAvailable.postValue(true) + } + } catch (e: Exception) { + Log.e("$TAG Failed to get image preview for file [$path]: $e") + } } - } catch (e: Exception) { - Log.e("$TAG Failed to get image preview for file [$path]: $e") } } diff --git a/app/src/main/res/layout/chat_bubble_single_file_content.xml b/app/src/main/res/layout/chat_bubble_single_file_content.xml index 3a94ab502..221df8877 100644 --- a/app/src/main/res/layout/chat_bubble_single_file_content.xml +++ b/app/src/main/res/layout/chat_bubble_single_file_content.xml @@ -64,6 +64,21 @@ app:layout_constraintEnd_toEndOf="@id/left_background" app:tint="?attr/color_main2_600" /> + +